|
|
ways to find 2.6 kernel rootkits |
|
|
作者:未知 来源:月光软件站 加入时间:2005-2-28 月光软件站 |
|=------------------=[ ways to find 2.6 kernel rootkits ]=------------------=| |=--------------------------------------------------------------------------=| |=-----------------=[ CoolQ <[email protected]> ]=----------------=| |=--------------------------------------------------------------------------=|
--[ 内容
0 - 前言
1 - Kernel Rootkit的分类 1.1 系统调用相关 1.2 异常相关 1.3 内核静态补丁 1.4 处理指针相关
2 - vmlinux/vmlinuz布局
3 - 对策 3.1 总述 3.2 系统调用相关 3.3 异常相关 3.4 内核静态补丁 3.5 处理指针相关
4 - 需要注意的问题 4.1 SMP和CPU优化带来的麻烦 4.2 2.4内核的差异 4.3 Fedora Core 2的4G补丁
5 - 总结
6 - 参考
7 - 程序 7.1 dump.c(kernel module) 7.2 rkchecker.c(usermode app)
--[ 0 - 前言
内核Rootkit由于隐蔽性好,逐渐成为了木马的主流. 目前内核木马的检测工具,有kstat, chkrooktit, rkhunter 等等。这些工具或多或少都需要对System.map的支持,而且往往 只能对付某种类型的内核木马,能不能找一些比较通用的方法,对付大部分的内核木马, 并且尽量少的利用系统文件呢(例如不使用System.map)? 在现实的生活中,被入侵的主机往往没有做太多的防卫措施,因此,你或许没有用St. Michael确保内核没被修改,你也可能事先没有将正常的系统调用表保存下来,事后没有 比较的标准,这样我们是不是就无能为力了呢? 本文试图找到一种方法,利用磁盘上的内核文件,对内存中的内核进行验证,查找内核 木马的蛛丝马迹。主要针对的是Linux 2.6内核,实验环境是Redhat Fedora Core 2, 内核为2.6.8.1/2.6.5-1.358(4G patch, redhat custom). 至于2.4内核,有一定的差异, 但是思想还是不变的。
--[ 1 - Kernel Rootkit的分类
首先了解一下内核木马的种类,大体上分为四种
--[ 1.1 系统调用相关 在2.2,2.4内核,这种方法是最多的,2.6内核,由于取消了对syscall_table符号的输出, 这种方法的使用用了一定的限制,但是仍然可以通过[1]的方法获得系统调用表的位置。 攻击者可以从5个位置做手脚,后面还会详细介绍。
--[ 1.2 异常相关 这种方法比较独特,在[2]中提出,利用了Linux独特的异常处理机制,修改了__ex_table 中的内容,目前很少有工具对这种方法进行检查。
--[ 1.3 内核静态补丁 这种方法在[3]中由jbtzhm提出,主要的思想是将一个模块静态附着在启动文件的后面,然 后修改系统调用,使得程序有机会执行,对付这种方法,主要是对文件进行完整性检测, 前题是要有内核文件的校验值。
--[ 1.4 处理指针相关 这种方法目标广泛,各种处理函数的指针都可能成为对象,例如binfmt的处理函数、vfs 的处理函数,TCP/IP协议栈的处理函数……,攻击者在修改这些函数时,需要能够得到 这些函数指针的符号:要么是内核导出的,要么需要借助System.map文件。对这种方法的 检测,往往也要借助System.map。
对于内核木马的介绍,GIAC上有一篇写的非常好的Assignment[4],大家可以看看.
--[ 2 - vmlinux/vmlinuz布局
自己编译过Linux内核的人应该都知道这两个文件,[3]中分析了vmlinuz的结构,这里再 简单回顾一下。
vmlinux是ELF格式的内核文件,主要用于调试,而vmlinuz是将vmlinux中Section为A的 内容保存下来,去除了ELF文件头、Section Header、不必要的Section,并用Gzip压缩, 在最前面附加了一个装载和解压的头。
vmlinus.lds.S 拷贝必须的节,压缩并加头 *.o -----------------> vmlinux --------------------------->vmlinuz vmlinuz: [bootsect][setup][[head][misc][compressed_kernel]]
下面再来看看vmlinux的ELF结构,我们先看一下生成vmlinux的连接脚本 13 SECTIONS 14 { 15 . = __PAGE_OFFSET + 0x100000; 16 /* read-only */ 17 _text = .; /* Text and read-only data */ 18 .text : { 19 *(.text) 20 SCHED_TEXT 21 LOCK_TEXT 22 *(.fixup) 23 *(.gnu.warning) 24 } = 0x9090 25 26 _etext = .; /* End of text section */ 27 28 . = ALIGN(16); /* Exception table */ 29 __start___ex_table = .; 30 __ex_table : { *(__ex_table) } 31 __stop___ex_table = .; 32 33 RODATA 34 35 /* writeable */ 36 .data : { /* Data */ 37 *(.data) 38 CONSTRUCTORS 39 } 40 41 . = ALIGN(4096);
# readelf -S vmlinux (感谢Madsys提供) Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS c0100000 001000 31c1e9 00 AX 0 0 4096 [ 2] __ex_table PROGBITS c041c1f0 31d1f0 001188 00 A 0 0 4 [ 3] .rodata PROGBITS c041d380 31e380 03c6d9 00 A 0 0 32 [ 4] .pci_fixup PROGBITS c0459a5c 35aa5c 000398 00 WA 0 0 4 ... [10] __param PROGBITS c0469e2c 36ae2c 0005dc 00 A 0 0 4 [11] .data PROGBITS c046b000 36c000 08886c 00 WA 0 0 4096 ... [16] .init.text PROGBITS c04fb000 3fc000 01fd1a 00 AX 0 0 64 ... [23] .altinstr_replace PROGBITS c05237eb 4247eb 00087f 00 AX 0 0 1 [24] .exit.text PROGBITS c0524080 425080 001595 00 AX 0 0 64 [25] .init.ramfs PROGBITS c0526000 427000 000086 00 A 0 0 1 [26] .bss NOBITS c0527000 428000 02f4dc 00 WA 0 0 4096 ...
根据上边的连接脚本和对vmlinux的格式分析,我们发现,vmlinux中的.text是多个.o 文件中.text、.sched.text、.lock.text、.fixup、.gnu.warning的综合,接下来就是 __ex_table节,这个节其实是一个表,表中的元素是多个异常指针对。每对指针中,第 一个指针是有可能发生异常的地址,第二个指针是发生异常时,处理程序的地址。有关 __ex_table的分析,请参考[5] 至于其它的可执行节,例如.init.text、.exit.text,在系统启动后可能用作它用,一般 不会成为内核木马的目标,我们这里就不分析了。
--[ 3 - 对策
--[ 3.1 总述
内核木马主要的目的,就是偷偷的执行自己的程序,因此就需要在不同的地方hook正常的 系统执行,将程序流导向自己。那么内核木马自己的程序会放在什么位置呢?如果内核木 马是以模块形式装载,那么程序应该位于vmalloc的范围,如果是jbtzhm的内核静态补丁, 程序会附加在.bss之后,可见,内核木马不太可能对vmlinz/vmlinux的.text进行伤筋动 骨的修改,充其量就是在.text程序的入口处跳转出来而已,否则修改的代码大小不好控 制,很容易破坏其它部分的代码. 因此我们只要确定.text的起始范围,并能保证.text 部分的代码没有受到破坏,调用.text的部分没有修改,那么,内核基本上就是安全的。
如果没有System.map,我们如何判定.text的范围呢?(有的System.map,也没有_text和 _etext符号,例如Redhat FC2的System.map)。让我们一步一步来:
o 预处理 Step1:打开内核的启动文件,例如/boot/vmlinuz-2.6.8.1 Step2:获取该文件中Gzip Magic Number的起始位置 Step3:从该起始位置开始,将剩余的内容保存为kernel.gz Step4:用gzip -d解压为kernel文件
o 利用模式匹配寻找_etext,__stop___ex_table 根据上边的连接脚本,我们可以发现,.text是纯代码,中间可能有个数不定的0x90,接下 来一个ALIGN(16)对齐之后,就是__ex_table,而__ex_table中全是指向.text的函数指针 ,我们先来看一下__ex_table有什么规律: # hexdump -C -s 0x31d1f0 -n 500 vmlinux-2.6.10 0031d1f0 c7 11 10 c0 ca 11 10 c0 b9 1a 10 c0 57 af 41 c0 |............W.A.| 0031d200 bc 1a 10 c0 60 af 41 c0 fa 1f 10 c0 69 af 41 c0 |....`.A.....i.A.| 0031d210 c2 27 10 c0 73 af 41 c0 d1 27 10 c0 7f af 41 c0 |.'..s.A..'....A.| 0031d220 dd 27 10 c0 8b af 41 c0 e3 27 10 c0 97 af 41 c0 |.'....A..'....A.| 0031d230 3e 28 10 c0 a3 af 41 c0 49 28 10 c0 ad af 41 c0 |>(....A.I(....A.| 0031d240 62 28 10 c0 b7 af 41 c0 6a 28 10 c0 c1 af 41 c0 |b(....A.j(....A.| 0031d250 d1 28 10 c0 cb af 41 c0 da 28 10 c0 d8 af 41 c0 |.(....A..(....A.| 0031d260 df 28 10 c0 e1 af 41 c0 e9 28 10 c0 ee af 41 c0 |.(....A..(....A.| 0031d270 ee 28 10 c0 f7 af 41 c0 fc 28 10 c0 04 b0 41 c0 |.(....A..(....A.| 0031d280 0a 29 10 c0 11 b0 41 c0 14 29 10 c0 1d b0 41 c0 |.)....A..)....A.| 0031d290 1e 29 10 c0 29 b0 41 c0 28 29 10 c0 35 b0 41 c0 |.)..).A.()..5.A.| 0031d2a0 32 29 10 c0 41 b0 41 c0 3b 29 10 c0 4d b0 41 c0 |2)..A.A.;)..M.A.| 0031d2b0 45 29 10 c0 59 b0 41 c0 |E)..Y.A.| 0031d2b8 由于指针是指向_text到_etext之间的地址,而.text的大小一般都小于0x800000(8M),因此 指针的第一个字节总是等于0xc0(PAGE_OFFSET + 0x100000的第一个字节),根据这一特性 我们就能很容易的找到.text的结尾_etext。 那么__ex_table的结尾__stop___ex_table又该怎么找呢? 注意到.rodata的对齐32,以及.rodata的内容 # hexdump -C -s 0x31e380 -n 50 vmlinux-2.6.10 0031e380 10 00 00 00 11 00 00 00 12 00 00 00 00 00 00 00 |................| 0031e390 08 00 00 00 07 00 00 00 09 00 00 00 06 00 00 00 |................| 0031e3a0 0a 00 00 00 05 00 00 00 |........| 0031e3a8 我们只需要从_etext 16字节对齐后的地址,开始查找每一个整型,如果该整数的第一字 节不是PAGE_OFFSET + 0x100000的第一个字节,该地址就是__stop___ex_table. 具体的步骤是: Step1:打开刚才生成的kernel文件 Step2:从开头按顺序往后查找0xc0(标准内核,0xc0代表(PAGE_OFFSET+0x100000)>>24) 开头的整数,如果连续若干个(例如100个)整数都满足条件,那么第一个整数的 地址就是_etext对齐后的结果(需要加上PAGE_OFFSET+0x100000, 即_text) Step3:继续想后查找第一个字节不是0xc0的整数,该整数的地址就是__stop___ex_table 地址。对齐后就是.rodata的地址(需要加上PAGE_OFFSET+0x100000, 即_text)
既然已经找到了_etext和__stop___ex_table,我们来分情况讨论。
--[ 3.2 系统调用相关
前面说到,基于系统调用的木马,有5处可以做手脚,分别代表了系统调用执行的每一个 阶段,分别是:idtr, 0x80的中断门system_call, system_call中调用的sys_call_table 地址,sys_call_table中每一个函数指针,每一个系统调用函数的内部代码。2.2/2.4的 内核,最常见的目标就是sys_call_table中的函数指针。为了躲避注入kstat的检测,攻 击者有可打其它的四个地方的主意(例如将system_call中的sys_call_table指向别的位 置,并在该位置放置一份系统调用表的拷贝)。
采取的对策如下: Step 1: 确定_etext的值. Step 2: 将内存中_text到_etext的内容与vmlinuz解压的kernel文件进行逐字节的比较 ,以确定内存中的.text没有发生改变,如有不同,说明有内核木马。 Step 3: 从idtr开始,利用[1]中的方法,获得sys_call_table的指针列表 Step 4: 对每一个指针,判断该指针是 否指向_text至_etext之间,如果不是,说 明有内核木马
--[ 3.3 异常相关
这种木马,实际上就是修改了__ex_table中的指针对,我们只需要将磁盘上的__ex_table 与内存中的__ex_table想比较就可以了: Step1: 确定_etext和__stop___ex_table的值 Step2: 将内存中_etext到__stop___ex_table的值进行逐字节比较,如果有不同,说明 有内核木马 注意,在查找__stop___ex_table的时候,正常的__ex_table应该是0xC0XXYYZZ,..,0,.., T,....。其中0只是为了对齐而出现的,如果不满足这个条件,十有八九是有此种类型的 木马,而且还使用了静态补丁(虽然目前还没见到过这种情况)。
--[ 3.4 内核静态补丁
这种情况比较棘手,因为磁盘上的vmlinuz就不可信,但即使使用这个不可信的文件,我 们也能找出一部分方法。jbtzhm将某些系统调用表的入口修改,使的一开始调用的是木马 程序,我们只要将整个的.text反汇编,看是否有调用.text之外的情况(当然这个范围最 好越靠后越好, 如果是指向内核bss之后,就属于这种情况)。至于其它的代码段,例如 .init.text,应该不受影响,因为这些程序段不是显式调用的。 当然更好的方法是直接检查vmlinuz的校验和。当然如果你使用的是分发版的内核,可靠 的vmlinuz文件还是很容易得到的。
--[ 3.5 处理指针相关
这种情况是最麻烦的,因为需要考虑的种类特别多,而且都是非常specific的,不过有 一点,攻击者如果想要修改某个处理函数指针,他必须能够解析该符号,一般是内核导出 或者从System.map中查找。这样我们可以有针对性的检查某些特定的处理函数,看这些函 数指针是不是指向_text到_etext。当然,这个时候还得考虑模块的.text,我们可以遍历 模块列表,获得每个模块的module_core和core_text_size。如果函数指针不属于这些范 围,那你就要小心了,可能有隐藏的模块了。
--[ 4 - 需要注意的问题
--[ 4.1 SMP和CPU优化带来的麻烦
对于vmlinuz和内存中的.text进行逐字节比较,如果你的内核有SMP,或者使用了CPU的某 些优化(默认情况下有很多),即使内核是没有问题的,也会有内容不相同的情况!
先来看对SMP的支持,注意以下这个宏 #define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM) 经测试,这一语句装载到内存中,内容会发生改变,具体的原因还不得而知 0xf0 0x83 0x44 0x24 0x00 -> 0x0f 0xae (0xe8|0xf0) 0x8d 0x76 如果有谁知道原因,请mail我。
另外,CPU优化,也造成磁盘和内存中的内容不一致 一个例子就是list_for_each_entry调用的prefetch() 634 extern inline void prefetch(const void *x) 635 { 636 alternative_input(ASM_NOP4, 637 "prefetchnta (%1)", 638 X86_FEATURE_XMM, 639 "r" (x)); 640 }
#define GENERIC_NOP4 ".byte 0x8d,0x74,0x26,0x00\n" #define K8_NOP4 ".byte 0x66,0x66,0x66,0x90\n" #define K7_NOP4 ".byte 0x8d,0x44,0x20,0x00\n"
#ifdef CONFIG_MK8 #define ASM_NOP4 K8_NOP4 #elif defined(CONFIG_MK7) #define ASM_NOP4 K7_NOP4 #else #define ASM_NOP4 GENERIC_NOP4 出现不一致的情况就是上边的几种NOP4, 由于我对这一块不熟悉,不知有谁能指点一二?
--[ 4.2 2.4内核的差异
2.4与2.6相比,__ex_table的位置靠后了 17 _etext = .; /* End of text section */ 18 19 .rodata : { *(.rodata) *(.rodata.*) } 20 .kstrtab : { *(.kstrtab) } 21 22 . = ALIGN(16); /* Exception table */ 23 __start___ex_table = .; 24 __ex_table : { *(__ex_table) } 25 __stop___ex_table = .; 由于.rodata和.kstrtab都不会发生变化,因此,不影响本文的方法。
--[ 4.3 Fedora Core 2的4G补丁
Redhat的内核改动的地方很多,比较讨厌,4G补丁就是其中之一,这里需要注意的是, 打了4G补丁的内核,PAGE_OFFSET已经不是0xc000000,而是0x02000000,内核有自己单 独的4G空间了。 另外的一个影响是__ex_table中的异常处理指针,有一些发生异常的地方,指针是以 0xffff开头的,估计4G在处理异常的时候,有些特别的地方. 00181000 60 41 10 02 63 41 10 02 30 48 10 02 40 f3 27 02 |`A..cA..0H..@.'.| 00181010 33 48 10 02 49 f3 27 02 35 55 10 02 52 f3 27 02 |3H..I.'.5U..R.'.| 00181020 41 55 10 02 5b f3 27 02 66 32 ff ff 64 f3 27 02 |AU..[.'.f2..d.'.| <---------> 00181030 67 32 ff ff 70 f3 27 02 6b 32 ff ff 7c f3 27 02 |g2..p.'.k2..|.'.| <---------> <---------> 00181040 73 32 ff ff 8d f3 27 02 74 32 ff ff 99 f3 27 02 |s2....'.t2....'.| <---------> <---------> 00181050 78 32 ff ff a5 f3 27 02 92 62 10 02 b6 f3 27 02 |x2....'..b....'. <--------->
--[ 5 - 总结
本文介绍的方法,对于检查基于系统调用和异常表的木马是非常有效的,对于内核静态补丁, 也是管用的. 对于处理函数指针的情况,思想适用,但是需要对每种情况写一种扩展(利用 符号,判断函数指针是否在正常的.text范围内,包括内核与模块的.text)。 本文的方法,对于一种非常简单的静态补丁,是无效的: 木马将磁盘vmlinuz中的指令 修改,例如简单的将jz->jnz, jne->je。但如果系统使用的是发行版带的标准内核,我们 就能保证vmlinuz的完整性(很容易找到)。
--[ 6 - 参考
[1] Linux on-the-fly kernel patching without LKM <http://www.phrack.org/phrack/58/p58-0x07> [2] Hijacking Linux Page Fault Handler <http://www.phrack.org/show.php?p=61&a=7> [3] Static Kernel Patching <http://www.phrack.org/show.php?p=60&a=8> [4] Linux kernel rootkits: protecting system's "Ring-zero" <http://www.giac.org/practical/GCUX/Raul_Siles_GCUX.pdf> [5] 利用异常表处理 Linux 内核态缺页异常 <http://www-900.ibm.com/developerWorks/cn/linux/kernel/l-page/index.shtml> [6] linux kernel source code <http://www.kernel.org> [7] Intel用户手册
--[ 7 - 程序
下面的程序只考虑的前面的两种情况, 至于后面的两种, 由于比较特殊, 需要根据自己的 需要,自己添加功能,当然思想前面已经陈述,很简单. --[ 7.1 dump.c(kernel module)
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/file.h> #include <linux/moduleparam.h> #include <asm/page.h> #include <asm/uaccess.h> #include <asm/string.h> #include <asm/unistd.h>
#define EOF (-1) #define SEEK_SET 0 #define SEEK_CUR 1 #define SEEK_END 2
struct { unsigned short limit; unsigned int base; } __attribute__ ((packed)) idtr;
struct { unsigned short off1; unsigned short sel; unsigned char none,flags; unsigned short off2; } __attribute__ ((packed)) idt;
static unsigned int len = 0x800000; module_param(len, uint, 0); static char buffer[256];
struct file *klib_fopen(const char *filename, int flags, int mode); void klib_fclose(struct file *filp); int klib_fwrite(char *buf, int len, struct file *filp); void *memmem(void *start, unsigned int s_len, void *find, unsigned int f_len);
struct file *klib_fopen(const char *filename, int flags, int mode) { struct file *filp = filp_open(filename, flags, mode); return (IS_ERR(filp)) ? NULL : filp; }
void klib_fclose(struct file *filp) { if (filp) fput(filp); }
int klib_fwrite(char *buf, int len, struct file *filp)
{
int writelen; mm_segment_t oldfs;
if (filp == NULL) return -ENOENT; if (filp->f_op->write == NULL) return -ENOSYS; if (((filp->f_flags & O_ACCMODE) & (O_WRONLY | O_RDWR)) == 0) return -EACCES; oldfs = get_fs(); set_fs(KERNEL_DS); writelen = filp->f_op->write(filp, buf, len, &filp->f_pos); set_fs(oldfs);
return writelen;
}
void *memmem(void *start, unsigned int s_len, void *find, unsigned int f_len) { char *p, *q; unsigned int len; p = start, q = find; len = 0; while((p - (char *)start + f_len) <= s_len){ while(*p++ == *q++){ len++; if(len == f_len) return(p - f_len); }; q = find; len = 0; }; return(NULL); }
static int dump_init(void) { unsigned int addr_start = PAGE_OFFSET + 0x100000; struct file *filep; unsigned int sys_call_off, sct; char *p;
/* Step 1: dump kernel memory */ filep = klib_fopen("./kernel.dat", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if(filep == NULL){ printk("Error create kernel.dat.\n"); return 0; } klib_fwrite((char*)addr_start, len, filep); klib_fclose(filep); printk("dump file to ./kernel.dat - OK.\n");
/* Step 2: Get syscall info */ filep = klib_fopen("./kernel.info", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if(filep == NULL){ printk("Error create kernel.info.\n"); return 0; } __asm__ ("sidt %0" : "=m" (idtr)); // printk("idtr base at 0x%X\n",(int)idtr.base);
memcpy(&idt,(void*)(idtr.base+8*0x80),sizeof(idt)); sys_call_off = (idt.off2 << 16) | idt.off1;
// printk("sys_call_off is at 0x%x\n", sys_call_off); p = (char*)memmem ((void *)sys_call_off, 100, "\xff\x14\x85", 3); if(p){ sct = *(unsigned*)(p+3); // printk("syscall table at addr 0x%x, rel off 0x%x\n", // sct, sct - (unsigned int)addr_start); }else ; // printk("syscall table not find?\n"); sprintf(buffer, ".text = 0x%x\n" "idtr = 0x%x\n" "sys_call_off = 0x%x\n" "sys_call_table = 0x%x\n" "sys_call_nr = %d\n", addr_start, idtr.base, sys_call_off, p ? sct : 0, NR_syscalls); klib_fwrite(buffer, strlen(buffer), filep); klib_fclose(filep);
return 0;
}
static void dump_exit(void) {
return; }
module_init(dump_init); module_exit(dump_exit);
MODULE_LICENSE("GPL");
--[ 7.2 rkchecker.c(usermode app)
/* * Name: rkchecker.c * Author: CoolQ * License: GPL * Intro: try to find some kernel rootkits * Usage: # insmod dump.ko * # cat kernel.info * .text = 0xc0100000 * idtr = 0xaaaaaaaa * sys_call_off = 0xbbbbbbbb * sys_call_table = 0xcccccccc * sys_call_nr = dd * # ./rkchecker -m ./kernel.dat -d /boot/vmlinuz -s 0xcccccccc -n dd * if you use 4G/4G patch, use * # ./rkchecker -4 -m ./kernel.dat -d /boot/vmlinuz -s 0xcccccccc -n dd */
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <getopt.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h>
#define KERNEL_DISC_FILE "/boot/vmlinuz" #define KERNEL_TMP_GZ_FILE "/tmp/kernel.gz" #define KERNEL_TMP_FILE "/tmp/kernel" #define KERNEL_DUMP_FILE "./kernel.dat"
#define MAGIC_GZ 0x00088b1f #define TEXT_START 0xc0100000 #define TEXT_START_4G 0x02100000 #define THRESHOLD 100 #define SYS_CALL_NR 240
#define ERROR(str) \ do{ \ perror(str); \ return -1; \ }while(0)
int extract_file(const char *file); unsigned int find_text_end(const char *file); int check(const char *file_mem, const char *file_store, unsigned int offset); int check_text(void *start_mem, void *start_store, unsigned int len); int check_exceptiontbl(void *start_mem, void *start_store); int check_syscalltbl(void *start_mem, unsigned int sys_call_off, unsigned int nr); void usage(const char *prog);
static char *g_krnl_mem = KERNEL_DUMP_FILE; static char *g_krnl_store = KERNEL_DISC_FILE; static int g_threshold = THRESHOLD; static unsigned int g_text_start = TEXT_START; static unsigned int g_sys_call_off; static unsigned int g_sys_call_nr = SYS_CALL_NR; static unsigned int g_text_end_off;
int main(int argc, char *argv[]) { int ret; char *tmp;
if(argc < 4) usage(argv[0]); while((ret = getopt(argc, argv, "m:d:t:s:n:4")) != -1){ switch(ret){ case 'm': g_krnl_mem = strdup(optarg); break; case 'd': g_krnl_store = strdup(optarg); break; case 't': g_threshold = atoi(optarg); break; case 's': g_sys_call_off = strtoul(optarg, &tmp, 16) -g_text_start; break; case '4': g_text_start = TEXT_START_4G; break; case 'n': g_sys_call_nr = atoi(optarg); break; default: usage(argv[0]); break; } }; ret = extract_file(g_krnl_store); if(ret == -1) exit(EXIT_FAILURE); unlink(KERNEL_TMP_FILE); ret = system("gzip -d " KERNEL_TMP_GZ_FILE); if(ret == -1) ERROR("ungzip error.\n");
g_text_end_off = find_text_end(KERNEL_TMP_FILE); printf("[i] .text end at 0x%x\n", g_text_end_off);
ret = check(KERNEL_TMP_FILE, g_krnl_mem, g_text_end_off); if(ret == -1){ fprintf(stdout, "! your kernel maybe hacked.\n"); return -1; } unlink(KERNEL_TMP_FILE); return 0; } int extract_file(const char *file) { struct stat st; int fd_read, fd_write; int mag = MAGIC_GZ; int len_gz; char *addr_read, *addr_write, *addr_gz;
if(stat(file, &st) == -1) ERROR("stat error.\n"); fd_read = open(file, O_RDONLY); fd_write = open(KERNEL_TMP_GZ_FILE, O_RDWR | O_TRUNC | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH); if(fd_read == -1 || fd_write == -1) ERROR("open error.\n");
addr_read = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd_read, 0); if(addr_read == MAP_FAILED) ERROR("map failed.\n"); addr_gz = memmem(addr_read, st.st_size, &mag, 4); if(addr_gz == NULL) ERROR("not a bzImage\n");
len_gz = st.st_size - (int)(addr_gz - addr_read); if(lseek(fd_write, (off_t)(len_gz - 1), SEEK_SET) == (off_t)-1) ERROR("lseek error.\n"); write(fd_write, "a", 1); addr_write = mmap(0, len_gz, PROT_WRITE, MAP_SHARED, fd_write, 0); if(addr_write == MAP_FAILED) ERROR("map failed.\n");
memcpy(addr_write, addr_gz, len_gz); munmap(addr_read, st.st_size); munmap(addr_write, len_gz); close(fd_read); close(fd_write); return 0; }
unsigned int find_text_end(const char *file) { int fd, count; struct stat st; void *addr; unsigned int *p, *tmp; stat(file, &st); fd = open(file, O_RDONLY); if(fd == -1) ERROR("open error.\n"); addr = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if(addr == MAP_FAILED) ERROR("map error.\n");
p = addr; while((char *)p - (char *)addr < st.st_size){ if((*p >> 24) == (g_text_start >> 24)){ tmp = p; for(count = 0; count < g_threshold; count++, p++) if((*p >> 24) != (g_text_start >> 24)&& !((*p >> 16) == 0xffff && (g_text_start >> 24)==(TEXT_START_4G>>24) ) ) break; }else p++; if(count == g_threshold){ munmap(addr, st.st_size); close(fd); return((char *)tmp - (char *)addr); } } munmap(addr, st.st_size); close(fd); return 0; } int check(const char *file_mem, const char *file_store, unsigned int offset) {
int ret, ret2, ret3; int fd_mem, fd_store; void *addr_mem, *addr_store; struct stat st_mem, st_store;
ret = 0; ret = stat(file_mem, &st_mem); ret |= stat(file_store, &st_store); if(ret) ERROR("stat error.\n"); fd_mem = open(file_mem, O_RDONLY); fd_store = open(file_store, O_RDONLY); if(fd_mem == -1 || fd_store == -1) ERROR("open file error.\n");
addr_mem = mmap(0, st_mem.st_size, PROT_READ, MAP_SHARED, fd_mem, 0); addr_store = mmap(0, st_store.st_size, PROT_READ, MAP_SHARED, fd_store, 0); if(addr_mem == MAP_FAILED || addr_store == MAP_FAILED) ERROR("mmap error.\n");
ret = check_text(addr_mem, addr_store, offset); if(!ret) fprintf(stdout, " - .text check passed.\n");
ret2 = check_exceptiontbl(addr_mem + offset, addr_store + offset); if(!ret2) fprintf(stdout, " - exception table check passed.\n");
ret3 = check_syscalltbl(addr_mem, g_sys_call_off, g_sys_call_nr); if(!ret3) fprintf(stdout, " - sys_call_table check passed.\n"); munmap(addr_mem, st_mem.st_size); munmap(addr_store, st_store.st_size); close(fd_mem); close(fd_store); return(ret | ret2 | ret3); } int check_text(void *start_mem, void *start_store, unsigned int len) { unsigned char *p_mem, *p_store; int count, ret; fprintf(stdout, "[+] Start checking .text section.\n"); ret = 0; for( p_mem = start_mem, p_store = start_store, count = 0; count < 10000 && ((char *)p_mem - (char *)start_mem) <len; p_mem++, p_store++ ) if(*p_mem != *p_store){ if(*p_mem == 0xf0 && *p_store == 0x0f){ if( *(p_mem + 1) == 0x83 && *(p_store + 1) == 0xae && *(p_mem + 2) == 0x44 && (*(p_store + 2) == 0xe8 || *(p_store + 2) == 0xf0) && *(p_mem + 3) == 0x24 && *(p_store + 3) == 0x8d && *(p_mem + 4) == 0x00 && *(p_store + 4) == 0x76 ){ p_mem += 5; p_store += 5; continue; } }else if(*p_mem == 0x8d && *p_store == 0x0f){ if( (*(p_mem + 1) == 0x74 && *(p_store + 1) == 0x18 && *(p_mem + 2) == 0x26 && /* *(p_store + 2) == 0x01 && */ *(p_mem + 3) == 0x00 /* && *(p_store + 3) == 0x90*/) || (*(p_mem + 1) == 0x44 && /* *(p_store + 1) == 0x18 && */ *(p_mem + 2) == 0x20 && /* *(p_store + 2) == 0x08 && */ *(p_mem + 3) == 0x00 && *(p_store + 3) == 0x90) ){ p_mem += 4; p_store += 4; continue; } }else if(*p_mem == 0x66 /*&& *p_store == 0x0f*/){ if( (*(p_mem + 1) == 0x66 && /**(p_store + 1) == 0x18 &&*/ *(p_mem + 2) == 0x66 && /**(p_store + 2) == 0x01 &&*/ *(p_mem + 3) == 0x90 /* && *(p_store + 3) == 0x90)*/ ) ){ p_mem += 4; p_store += 4; continue; } } ret = -1; count++; fprintf(stdout, "mismatch no. %d at offset 0x%x", count, (char *)p_mem - (char *)start_mem); fprintf(stdout, "\tstore = %02X, mem=%02X\n", *p_mem, *p_store); } return ret; } int check_exceptiontbl(void *start_mem, void *start_store) { unsigned int *p_mem, *p_store; int ret; ret = 0;
fprintf(stdout, "[+] Start checking exception table.\n"); if(((int)start_mem & 0x0000000f) != 0x0) fprintf(stdout, " - the offset is weird, not aligned.\n"); for( p_mem = start_mem, p_store = start_mem; ((*p_store >> 24) == (g_text_start >> 24) || ((*p_store >> 16) == 0xffff && (g_text_start == TEXT_START_4G) )); p_mem++, p_store++ ) if(*p_mem != *p_store){ fprintf(stdout, " - suspect except handler at 0x%x", (unsigned int)p_mem); ret = -1; } if(((int)p_store & 0x0000000f) != 0x0) if(*p_store != 0){ fprintf(stdout, " - seems weired at 0x%x, " "kernel file unreliable.\n", p_store); ret = -1; }
fprintf(stdout, " [i] exception tabled end at __ex_table + 0x%x\n", (unsigned int)((char *)p_mem - (char *)start_mem)); return ret; }
int check_syscalltbl(void *start_mem, unsigned int sys_call_off, unsigned int nr) { unsigned int *p; int i, ret; fprintf(stdout, "[+] Start checking sys_call_table.\n"); fprintf(stdout, " [i] checknig %d items.\n", nr); ret = 0; p = (int *)((char *)start_mem + sys_call_off); for(i = 0; i < nr; i++, p++) if(*p < g_text_start || *p > g_text_start + g_text_end_off){ fprintf(stdout, " - sys_call no. %d suspicious." "point to value %x\n", i, *p); ret = -1; } return ret; }
void usage(const char *prog) { fprintf(stderr, "Usage: %s [-4] [-t num] [-n num] -m file_1 -d file_2" "-s addr\n", prog); fprintf(stderr, "Params:\n"); fprintf(stderr, " -4: The kernel uses 4G/4G patch.\n"); fprintf(stderr, " -t num: Set threshold to num.\n"); fprintf(stderr, " -n num: Set sys_call_numbers to num.\n"); fprintf(stderr, " -m file: Set memory dump file to file_1\n"); fprintf(stderr, " -d file: Set disc kernel file to file_2\n"); fprintf(stderr, " -s addr: Set the sys_call_table to addr\n");
exit(EXIT_FAILURE); return; }

|
|
相关文章:相关软件: |
|