在Linux下用gdb检测内核rootkit
我们注意到第一行最后的0xc88ab11a这个地址明显不正常,这是系统调用号为3的系统调用,即sys_read (系统调用从0开始) 。
我们说它不正常的显著标志是它的地址高于0xc8xxxxxx,Linux默认4GB线性地址,其中最高1GB0x00000000-0xffffffff为内核保留,当一个模块被插入内核时,vmalloc函数为其分配一段地址空间,这个地址通常从0xc8800000开始...到这里已经很明显了吧?
系统调用劫持
劫持系统调用与上一种方法不同之处在于:它并不直接修改系统调用表中的入口地址,即指向每个系统调用的跳转指针,而是在想要hook的系统调用之前加一段跳转代码,使执行流重定向到入侵者自己的内核态函数,这些被hook的系统调用前部通常有call,jmp之类的汇编指令。
要检测这种攻击,同样使用gdb加vmlinux-2.4.*及/proc/kcore两个参数,然后反汇编系统调用:
#gdb /boot/vmlinux-2.4.* /proc/kcore (gdb) disass sys_read Dump of assembler code for function sys_read: 0xc013fb70 <sys_read>: mov $0xc88ab0a6,%ecx 0xc013fb73 <sys_read+3>: jmp *%ecx <<-- 0xc013fb77 <sys_read+7>: mov %esi,0x1c(%esp,1) 0xc013fb7b <sys_read+11>: mov %edi,0x20(%esp,1) 0xc013fb7f <sys_read+15>: mov $0xfffffff7,%edi ...
我们注意"mov $0xc88ab0a6,%ecx -- jmp *%ecx"这两条指令,他跳转到了其他的地方去执行了。
然后再来看一下被hook之前的系统调用指令:
#gdb /boot/vmlinx-2.4.* (gdb) disass sys_read Dump of assembler code for function sys_read: 0xc013fb70 <sys_read>: sub $0x28,%esp 0xc013fb73 <sys_read+3>: mov 0x2c(%esp,1),%eax 0xc013fb77 <sys_read+7>: mov %esi,0x1c(%esp,1) 0xc013fb7b <sys_read+11>: mov %edi,0x20(%esp,1) 0xc013fb7f <sys_read+15>: mov $0xfffffff7,%edi ...
看到了吧,不一样的。
更改系统调用处理例程
入侵者可能修改一些重要的内核函数,比如系统调用处理例程system_call函数,顾名思义,这个函数对用户请求的系统调用作出响应,在系统调用表中寻找对应的入口地址,然后跳转到那里执行,这个函数中保存了系统调用表的地址。攻击者能做什么呢?另辟一块内存空间,在那里攻击者伪造自己的系统调用表,然后修改system_call函数中的系统调用表地址指向那里就可以了。
通过反汇编system_call函数可以找出系统调用表的地址:
(gdb) disass system_call Dump of assembler code for function system_call: 0xc01090dc <system_call>: push %eax 0xc01090dd <system_call+1>: cld 0xc01090de <system_call+2>: push %es 0xc01090df <system_call+3>: push %ds 0xc01090e0 <system_call+4>: push %eax 0xc01090e1 <system_call+5>: push %ebp 0xc01090e2 <system_call+6>: push %edi 0xc01090e3 <system_call+7>: push %esi 0xc01090e4 <system_call+8>: push %edx 0xc01090e5 <system_call+9>: push %ecx 0xc01090e6 <system_call+10>: push %ebx 0xc01090e7 <system_call+11>: mov $0x18,%edx 0xc01090ec <system_call+16>: mov %edx,%ds 0xc01090ee <system_call+18>: mov %edx,%es 0xc01090f0 <system_call+20>: mov $0xffffe000,%ebx 0xc01090f5 <system_call+25>: and %esp,%ebx 0xc01090f7 <system_call+27>: testb $0x2,0x18(%ebx) 0xc01090fb <system_call+31>: jne 0xc010915c <tracesys> 0xc01090fd <system_call+33>: cmp $0x100,%eax 0xc0109102 <system_call+38>: jae 0xc0109189 <badsys> 0xc0109108 <system_call+44>: call *0xc0302c30 (,%eax,4) <<--系统调用表地址 0xc010910f <system_call+51>: mov %eax,0x18(%esp,1) 0xc0109113 <system_call+55>: nop End of assembler dump.
注意:上面的输出中显示的是一个正常的系统调用表地址。
实用工具
一种方法是使用基于主机的入侵检测系统HIDS实时监控重要的内核结构,比如使用Samhain工具,可以监视系统调用表、IDT等,在“Host Integrity Monitoring: Best Practices for Deployment”一文中有相关描述。
译者注
本文提及的方法在kstat2.4版中都有代码的实现,可以参阅kstat/2.4/src/syscall.c,使用gdb是一种手工检测方法,它能解决的问题是检测系统是否被更改,至于如何找出内核rootkit还需要一些工具,比如madsys在phrack60上的module_hunter.c,有2.4和2.6的版本,grip2、coolq对其做了一些修改,并且该代码不断完善中。