/* 
 * module_hunter.c v0.2.0 - Search for patterns in the kernel address space 
 * that look like module structures. This tools find hidden modules that
 * unlinked themself from the chained list of loaded modules.
 *
 * insmod ./module_hunter.ko
 *
 * usage: cat /proc/showmodules && dmesg
 *
 * Originally listed in http://www.phrack.org/phrack/61/p61-0x03_Linenoise.txt
 *
 * Updated 2006/1/22
 *  - Now works with 2.6.x Fedora-based kernels
 *  - Will list only hidden modules
 *
 * TODO:
 *   - Properly use /proc files instead of dmesg for output
 *
 * This hunter is not foolproof and can easily be circumvented.
 */

#include <linux/config.h>

#ifdef CONFIG_SMP
#define __SMP__ 
#endif

#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>

#include <linux/unistd.h>
#include <linux/string.h>


#include <linux/proc_fs.h>

#include <asm/uaccess.h>


#include <asm/pgtable.h>
#include <asm/fixmap.h>
#include <asm/page.h>
#include <linux/mm.h>

// FIXME: This is a variable in later kernels.. The kernel code doesn't
// ever seem to change it, but it is possible they may make it dynamic
// in the future (possibly for > 32bit arch or PAE, but then this whole
// module would probably break)
#define MY_VMALLOC_RESERVE  (128 << 20)

/* Black magic voodoo. Unlikely to work on non-x86 or even PAE */
int valid_addr(unsigned long address)
{
    unsigned long page;

    if (!address)
        return 0;

    page = ((unsigned long *)init_mm.pgd)[address >> 22];        

    if (page & 1)
    {
        page &= PAGE_MASK;
        address &= 0x003ff000;
        page = ((unsigned long *) __va(page))[address >> PAGE_SHIFT];  //pte
        if (page)
            return 1;
    }
    
    return 0;
}

int
is_print(char *str)
{
    while(*str != 0) {
        if (*str < 0x21 || *str > 0x7e)
            return 0;
        str++;
    }

    return 1;
}

int
is_in_list(struct module *mod) {
    struct list_head *list = &__this_module.list;
    
    do {
        if (list == &mod->list) {
            return 1;
        }
        list = list->next;
    } while(list != &__this_module.list);
    return 0;
}

ssize_t
showmodule_read(struct file *unused_file, char *buffer, size_t len, loff_t *off)
{
    struct module *mod = NULL;
    unsigned long p;
    int found_one = 0;

    printk("The following modules (if any) are not listed in lsmod:\n\n");
    for (p=VMALLOC_START; 
            p<VMALLOC_START+MY_VMALLOC_RESERVE-sizeof(struct module); 
            p+=0x80) /* 0x80 is the align of the this_module section 
                        according to 'objdump -h' (2**7) */
    {
        if(valid_addr(p) && valid_addr(p+sizeof(struct module)-1)) {
            mod = (struct module*)p;
            if(valid_addr((unsigned long)mod->module_core)
#ifdef CONFIG_MODULE_UNLOAD
                    && (!mod->exit || valid_addr((unsigned long)mod->exit))
#endif
                    && (!mod->module_init 
                        || valid_addr((unsigned long)mod->module_init))
                    && mod->core_size < 2<<20 && mod->init_size < 2<<20
                    && mod->state == 0
                    && is_print(mod->name) && strlen(mod->name)) {
                if (!is_in_list(mod)) {
                    found_one = 1;
                    printk("0x%p%20s core_size: %ld init_size %ld\n", 
                            mod, mod->name, mod->core_size, mod->init_size);
                }
            }
        }
    }

    if (!found_one) {
        printk("No hidden modules found! (This does not guarantee your system is clean)\n");
    }
    
    return 0;
}

static struct file_operations showmodules_ops = {
    read:    showmodule_read,
};

int init_module(void)
{
    struct proc_dir_entry *entry;

    printk("\nModule Hunter v0.2\n");
    entry = create_proc_entry("showmodules", S_IRUSR, &proc_root);
    entry->proc_fops = &showmodules_ops;
    printk("Installed /proc/showmodules\n");

    return 0;
}

void cleanup_module()
{
    remove_proc_entry("showmodules", &proc_root);
}

MODULE_LICENSE("GPL");

