分享
 
 
 

ways to find 2.6 kernel rootkits

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

|=------------------=[ ways to find 2.6 kernel rootkits ]=------------------=|

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

|=-----------------=[ CoolQ <qufuping@ercist.iscas.ac.cn> ]=----------------=|

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

--[ 内容

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

<" target=_blankhttp://www.phrack.org/phrack/58/p58-0x07>

[2] Hijacking Linux Page Fault Handler

<" target=_blankhttp://www.phrack.org/show.php?p=61&a=7>

[3] Static Kernel Patching

<" target=_blankhttp://www.phrack.org/show.php?p=60&a=8>

[4] Linux kernel rootkits: protecting system's "Ring-zero"

<" target=_blankhttp://www.giac.org/practical/GCUX/Raul_Siles_GCUX.pdf>

[5] 利用异常表处理 Linux 内核态缺页异常

<" target=_blankhttp://www-900.ibm.com/developerWorks/cn/linux/kernel/l-page/index.shtml>

[6] linux kernel source code

</" target=_blankhttp://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;

}

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝網路 版權所有