linuxrootkit:在Linux下用gdb检测内核rootkit

本文涉及技术原理都不是新对研究人员没什么特别大价值不过对工程人员应急相应来说不失为种新思路方法 理解攻击向量 内核rookit通常以系统为攻击目标主要出于两个原因: a.在内核态劫持系统能以较小代价控制整个系统不必修太多东西; b.应用层大多数个或多个系统区别形式封装更改系统意味着其上层所有都会被欺骗; 在kernel-2.4.27中大约有230多个系统而kernel-2.6.9中大约有290多个系统系统个数取决于内核版本完整系统列表可以在 /usr//asm/unistd.h头文件中获得 另外需要注意是入侵者并不更改所有系统而只是替换其中些比较有用这些系统如表所示他们可以被系统管理员及入侵检测系统(OS kernel级IDS)监视可以用man命令得到每个系统完整描述
call name Short description ID --------------------------------------------------------------------------------------- sys_read used for reading from files 3 sys_write used for writing to files 4 sys_open used to create or open files 5 sys_getdents/sys_getdents64 used to list a content of directories(also /proc) 141/220 sys_call used for managing s 102 sys_query_module used for querying loaded modules 167 sys_uid/sys_getuid used for managing UIDs 23/24 sys_execve used for executing binary files 11 sys_chdir used to change the directory 12 sys_fork/sys_clone used to create a child process 2/120 sys_ioctl used to control devices 54 sys_kill used to send signal to processes 37



我们注意上表系统这些ID都是针对kernel-2.4.18-3 本文所有例子都在Redhat7.3 kernel-2.4.18-3上通过测试我们也可以在其他版本包括最新2.6.x上用相似步骤研究区别的处可能在于2.6些内部结构比如系统地址原来内含在系统处理例程system_call中现在改成在syscall_call 更改系统 当前系统地址保存在系统表中位于操作系统为内核保留内存空间(虚拟地址最高1GB)系统入口地址存放顺序同/usr//asm/unistd.h中排列顺序按系统号递增 在0x80软中断发生的前对应系统号被压入eax寄存器例如sys_write被其对应系统ID:4会被压入eax 入侵者使用种思路方法是:更改系统表中系统地址这样系统发生时会跳转到攻击者自己编写去执行通过观察系统表中系统入口地址使用gdb我们可以比较容易检测到这种攻击行为 原始系统地址在内核编译阶段被指定不会更改通过比较原始系统地址和当前内核态中系统地址我们就可以发现系统有没有被更改原始系统地址在编译阶段被写入两个文件: a..map该文件包含所有符号地址系统也包含在内; b.系统化时首先被读入内存内核映像文件vmlinux-2.4.x; vmlinux-2.4.x文件通常以压缩格式存放在/boot目录下所以在比较的前必须解压这个文件个问题是:我们比较前提是假设system.map及vmlinuz image都没有被入侵者更改所以更安全做法是在系统干净时已经创建这两个文件可信任拷贝并创建文件md5 hash 原文中也列举了个内核模块[gcc -c scpr.c -I/usr/src/`uname -r`// ]使用该模块打印系统地址并自动写入syslog这样可以进行实时比较 在大多数被装载内核后门情况中内核在系统化的后才被更改更改发生在加载了rootkitmodule或者被植入直接读写/dev/kmemon-the-fly kernel patch的后而通常情况下rootkit并不更改vmlinuz和system.map 这两个文件所以打印这两个文件中符号地址就可以知道系统原始系统地址系统当前运行中系统地址(可能被更改)可以同过/proc下kcore文件得到比较两者就知道结果 1.首先找出系统表地址:
[root@rh8 boot]# cat .map-2.4.18-13 | grep sys_call_table c0302c30 D sys_call_table



2.使用nm命令可以打印出未被strip过image文件中所有符号地址:
[root@rh8 boot]# nm vmlinux-2.4.18-13 | grep sys_call_table c0302c30 D sys_call_table



使用gdb可以打印出所有系统入口地址这些对应地址在内核源代码entry.S文件中定义例如:
entry 0 (0xc01261a0)是sys_ni_syscall系统 entry 1 (0xc011e1d0)是sys_exit系统 entry 2 (0xc01078a0)是sys_fork系统 #gdb /boot/vmlinux-2.4.* (gdb) x/255 0xc0302c30 0xc0302c30 <sys_call_table>: 0xc01261a0 0xc011e1d0 0xc01078a0 0xc013fb70 0xc0302c40 <sys_call_table+16>: 0xc013fcb0 0xc013f0e0 0xc013f230 0xc011e5b0 0xc0302c50 <sys_call_table+32>: 0xc013f180 0xc014cb10 0xc014c670 0xc0107940 0xc0302c60 <sys_call_table+48>: 0xc013e620 0xc011f020 0xc014bcd0 0xc013e9a0 ...



我们也可以通过系统名打印出系统地址:
(gdb) x/x sys_ni_syscall 0xc01261a0 <sys_ni_syscall>: 0xffffdab8 ((gdb) x/x sys_fork 0xc01078a0 <sys_fork>: 0x8b10ec83



要打印出当前运行系统中系统地址我们必须给gdb加两个参数: a.第个参数是内核映像文件vmliux-2.4.x b.第 2个参数是/proc/kcore 2进制文件
#gdb /boot/vmlinux-2.4.* /proc/kcore (gdb) x/255x 0xc0302c30 0xc0302c30 <sys_call_table>: 0xc01261a0 0xc011e1d0 0xc01078a0 0xc88ab11a <<-- 0xc0302c40 <sys_call_table+16>: 0xc013fcb0 0xc013f0e0 0xc013f230 0xc011e5b0 0xc0302c50 <sys_call_table+32>: 0xc013f180 0xc014cb10 0xc014c670 0xc0107940 0xc0302c60 <sys_call_table+48>: 0xc013e620 0xc011f020 0xc014bcd0 0xc013e9a0 ...



我们注意到第行最后0xc88ab11a这个地址明显不正常这是系统号为3系统即sys_read (系统从0开始) 我们说它不正常显著标志是它地址高于0xc8xxxxxxLinux默认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对其做了些修改并且该代码不断完善中

Tags:  深入理解linux内核 linux内核 linuxgdb linuxrootkit

延伸阅读

最新评论

发表评论