http://www.phrack.org/issues.html?issue=61&id=3

|=-----------------------------------------------------------------------=|
|=-=[ 6 - Finding hidden kernel modules (the extrem way) ]=--------------=|
|=-----------------------------------------------------------------------=|

by madsys <madsys at ercist.iscas.ac.cn>


1 Introduction
2 The technique of module hiding
3 Countermeasure -- brute force
4 Problem of unmapped
5 Greetings
6 References
7 Code


1 Introduction
==============

This paper presents a method for how to find out the hidden modules in
linux system. Generaly speaking, most of the attackers intend to hide
their modules after taking down the victim. They like this way to prevent
the change of kernel from being detected by the administrator. As modules
were linked to a singly linked chain, the original one was unable to be
recovered while some modules have been removed. In this sense, to retrieve
the hidden modules came up to be hard. Essential C skill and primary
knowledge of linux kernel are needed.


2 The technique of module hiding
================================

First of all, the most popular and general technique of module hiding 
and the quomodo of application to get module's list were examined.
An implement of module hiding was shown as below:

    ----snip----
    struct module *p;
    
    for (p=&__this_module; p->next; p=p->next)
        {
            if (strcmp(p->next->name, str))
                continue;
            p->next=p->next->next;        // <-- here it 
removes that module
                break;
        }
    
    ----snip----

As you can see, in order to hide one module, the unidirectional chain was 
modified, and following is a snippet of sys_create_module() system call, 
which might tell why the technique worked:

    ----snip----
    spin_lock_irqsave(&modlist_lock, flags);
    mod->next = module_list;
    module_list = mod;    /* link it in */
    spin_unlock_irqrestore(&modlist_lock, flags);
    ----snip----

A conclusion could be made: modules linked to the end of unidirectional 
chain when they were created.

"lsmod" is an application on linux for listing current loaded modules, 
which uses sys_query_module() system call to get the listing of loaded 
modules, and qm_modules() is the actual function called by it while 
querying modules:


static int qm_modules(char *buf, size_t bufsize, size_t *ret)
{
    struct module *mod;
    size_t nmod, space, len;

    nmod = space = 0;

    for (mod=module_list; mod != &kernel_module; mod=mod->next, 
++nmod) {
        len = strlen(mod->name)+1;
        if (len > bufsize)
            goto calc_space_needed;
        if (copy_to_user(buf, mod->name, len))
            return -EFAULT;
        buf += len;
        bufsize -= len;
        space += len;
    }

    if (put_user(nmod, ret))
        return -EFAULT;
    else
        return 0;

calc_space_needed:
    space += len;
    while ((mod = mod->next) != &kernel_module)
        space += strlen(mod->name)+1;

    if (put_user(space, ret))
        return -EFAULT;
    else
        return -ENOSPC;
}

    note: pointer module_list is always at the head of the singly linked
chain. It clearly showing the technique of hiding module was valid.


3 Countermeasure -- brute force
===============================

According to the technique of hiding module, brute force might be useful.
sys_creat_module() system call was expressed as below. 

    --snip--
    if ((mod = (struct module *)module_map(size)) == NULL) {
        error = -ENOMEM;
        goto err1;
    }
    --snip--

    and the macro module_map in "asm/module.h": 
    #define module_map(x)    vmalloc(x)


You should have noticed that the function calls vmalloc() to allocate the
module struct. So the size limitation of vmalloc zone for brute force is
able to be exploited to determine what modules in our system on earth.
As you know, the vmalloc zone is 128M(2.2, 2.4 kernel, there are many
inanition zones in it), however, any allocated module should be aligned by
4K. Therefor, the theoretical maximum number we were supposed to detect
was 128M/4k=32768.

4 Problem of unmapped
=====================

By far, maybe you think: umm, it's very easy to use brute force to list
those evil modules". But it is not true because of an important
reason: it is possible that the address which you are accessing is
unmapped, thus it can cause a paging fault and the kernel would report:
"Unable to handle kernel paging request at virtual address".

So we must make sure the address we are accessing is mapped. The solution
is to verify the validity of the corresponding entry in kernel
pgd(swapper_pg_dir) and the corresponding entry in page table.Furthermore,
we were supposed to make sure the content of address pointed by "name"
pointer(in struct module) was valid. Because the 768~1024 entries of user
process's pgd were synchronous with kerenl pgd, and that was why such
hardcore address of kernel pgd (0xc0101000) was used.


following is the function for validating those entries in pgd or pgt:

int valid_addr(unsigned long address)
{
    unsigned long page;

    if (!address)
        return 0;

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

After validating those addresses which we would check, the next step would
be easy -- just brute force. As the list of modules including hidden
modules had been created, you could compare it with the output of "lsmod".
Then you can find out those evil modules and get rid of them freely.


5 Greetings
===========

Shout to [EMAIL PROTECTED]


6 Code
======

-----BEGING MODULE_HUNTER.C-----
/* 
 * module_hunter.c: 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.
 *
 * This tool is currently implemented as a module but can be easily ported
 * to a userland application (using /dev/kmem).
 * 
 * Compile with: gcc -c module_hunter.c -I/usr/src/linux/include
 * insmod ./module_hunter.o
 *
 * usage: cat /proc/showmodules && dmesg
 */

#define MODULE
#define __KERNEL__

#include <linux/config.h>

#ifdef CONFIG_SMP
#define __SMP__ 
#endif

#ifdef CONFIG_MODVERSIONS
#define 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 <linux/errno.h>
#include <asm/uaccess.h>


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

static int errno;


int valid_addr(unsigned long address)
{
    unsigned long page;

    if (!address)
        return 0;

    page = ((unsigned long *)0xc0101000)[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;
}

ssize_t
showmodule_read(struct file *unused_file, char *buffer, size_t len, loff_t *off)
{
    struct module *p;

    printk("address                         module\n\n");
    for (p=(struct module *)VMALLOC_START; p<=(struct \
module*)(VMALLOC_START+VMALLOC_RESERVE-PAGE_SIZE); p=(struct module \
*)((unsigned long)p+PAGE_SIZE))
    {
        if (valid_addr((unsigned long)p+ (unsigned long)&((struct \
module *)NULL)->name) && valid_addr(*(unsigned long *)((unsigned long)p+ \
(unsigned long)&((struct module *)NULL)->name)) && strlen(p->name))
            if (*p->name>=0x21 && *p->name<=0x7e && (p->size < 1 <<20))
                printk("0x%p%20s size: 0x%x\n", p, p->name, p->size);
    }

    return 0;
}

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

int init_module(int x)
{
    struct proc_dir_entry *entry;

    entry = create_proc_entry("showmodules", S_IRUSR, &proc_root);
    entry->proc_fops = &showmodules_ops;

    return 0;
}

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

MODULE_LICENSE("GPL");
MODULE_AUTHOR("madsys<at>ercist.iscas.ac.cn");
-----END MODULE-HUNTER.C-----

Reply via email to