Linux | LKM 型 Rootkit 取证浅析

admin 2022年6月9日10:07:54评论108 views字数 4471阅读14分54秒阅读模式

LKM(Loadable Kernel Module)全称可加载内核模块,主要用来扩展Linux的内核功能。其优点在于可以动态地加载到内存中,无须重新编译内核,常用于Rootkit技术。

列举几种LKM型Rootkit技术:

  • sys_call_table替换

  • 函数蹦床

  • 中断处理器patch

  • Kprobe技术

  • 调试寄存器Rootkit

  • VFS Rootkit

        本文主要通过对内核镜像文件LKM Rootkit进行取证思路,参考O'Neill(ELFmaster)。

1. 重构vmlinux符号表


       vmlinux是一个ELF可执行文件,当对内核重新进行编译,才会访问到vmlinux文件。在/boot目录下存在一个vmlinuz-<kernel_version>的压缩文件。解压后的内核镜像文件缺少符号表。带符号的内核镜像对于调试和取证分析来说都非常重要,几乎所有的Linux 内核取证分析都可以使用 GDB 和/proc/kcore 完成。

Linux | LKM 型 Rootkit 取证浅析

        kdress工具可以通过System.map文件中获取符号信息,重构vmlinux符号表。

./kdress /boot/vmlinuz-`uname -r` vmlinux /boot/System.map-`uname -r`


Linux | LKM 型 Rootkit 取证浅析

        在vmlinux内核中定位 sys_call_table,其值与System.map是一致的:Linux | LKM 型 Rootkit 取证浅析

2. /proc/kcore


     /proc/kcore是用于访问内核内存的接口,采用的是ELF核心文件的形式。利用GDB分析/proc/kcore进行取证。

通过vmlinux镜像可以定位sys_call_table[0]存放的第一个指针,即sys_read地址。然后打印该系统调用的前五个指令。如果安装了一个内核 rootkit,使用函数蹦床对 sys_read 进行了劫持,那么显示的前几条指令就会是跳转指令,或者是返回到另一个恶意函数的指令。Linux | LKM 型 Rootkit 取证浅析

3. 分析sys_call_table替换


        基于sys_call_table的Hook,一般通过直接替换sys_call_table的函数地址来达到劫持目的。

        分析是否存在基于sys_call_table的Hook,可以通过查看System.map或者/proc/kallsyms中每个系统调用的内存地址,来对比vmlinux内核镜像地址是否一致。例如,如果要检测sys_write系统调用是否被替换,需要先找到System.map或者/proc/kallsyms的内存地址,再利用GDB和/proc/kcore验证内存中实际存放的地址是否正确。

(gdb) x/gx &sys_call_table + 1grep sys_write /proc/kallsyms

        sys_call_table[1]中的值与/proc/kallsym 中查找到的 sys_write 的正确地址相同。因此,我们已成功验证 sys_call_table 中的 sys_write 条目未被篡改。

Linux | LKM 型 Rootkit 取证浅析

        替换sys_open,实现文件监控功能。利用上述方法进行取证。安装恶意内核模块后,&sys_call_table+3被替换为0xfffffffa02ec000。

Linux | LKM 型 Rootkit 取证浅析

        跟进0xfffffffa02ec000地址的函数,发现不是正常的sys_open函数。

Linux | LKM 型 Rootkit 取证浅析

        卸载内核模块后可以看到,&sys_call_table+3还原成正常的sys_open地址,与/proc/kallsyms文件的地址一致。

Linux | LKM 型 Rootkit 取证浅析


4. 分析内核函数蹦床


        函数蹦床最初是由 Silvio Cesare 于 1998 年引入的。最初的想法是在不改变 sys_call_table 的情况下修改系统调用。实际上函数蹦床可以对任意内核函数添加钩子(Hook)。之后增加的一些限制,但是可以通过禁用寄存器cr0的写保护或者修改PTE,来对内核text段修改。

一般情况下,函数蹦床修改函数的过程序言的前5至7个字节。因此,需要分析函数过程序言,判断是否有跳转或返回其他地址的代码。

蹦床指令的形式有很多,下面举例:

1)使用ret指令

将目标地址压栈并返回。使用32位的目标地址时,会占用6个字节的机器码。

 push $address;68 00 00 00 00 ret;c3

2)使用间接跳转指令

将目标地址移入用于间接跳转的寄存器。使用32位的目标地址时,占用7个字节的机器码。

 movl $addr, %eax; jmp *%eax;

3)使用相对跳转指令

计算偏移量,然后根据偏移量进行相对跳转。使用32位的偏移量时,占用5个字节的机器码。

 jmp offset;

检测上述三种蹦床方式,可以使用相同的方式。

        前5个字节实际上是作为对齐的NOP指令,或者是ftrace探测的空间,内核会使用特定的字节序列(0x66 0x66 0x66 0x66 0x90),过程序言紧跟前5个NOP字节之后。比如sys_open未劫持的情况下:

Linux | LKM 型 Rootkit 取证浅析

如果想验证vfs_write系统调用是否本函数蹦床劫持,可以检查其代码,查看过程序言是否还在原位。

gdb -q vmlinux /proc/kcore(gdb)x/5i 0xffffffff811de3a0

       vfs_read前五个字节被替换为jmp指令,发生了函数蹦床指令劫持。


Linux | LKM 型 Rootkit 取证浅析


5. 分析中断处理器patch


中断处理器patch——int 0x80和syscall

中断处理器patch技术的其中一种利用方式是将一个伪造的系统调用表插入到内核内存中,并修改负责调用系统待用的中断处理器的前半部分。

在x86体系中,中断0x80已经弃用了,已被替换为一个特殊的用于系统调用的syscall/sysenter指令。syscall/sysenter与int 0x80最终都调用相同的名为system_call函数,system_call函数从而调用sys_call_table中相应的系统调用。

在x86_64系统中,前面的调用指令发生在system_call函数中的swapgs指令之后。下面是entry.S中的代码。

call *sys_call_table(,%rax,8)

        (r/e)ax寄存器中保存了系统调用编号与sizeof(long)相乘的结果,以获取正确的系统调用指针的索引。攻击者可以利用kmalloc()将伪造的系统调用表分配到内存中,然后修改调用指令使得程序访问伪系统调用表。因为不会修改原本的系统调用表,所以这项技术非常隐蔽。


Linux | LKM 型 Rootkit 取证浅析


        先查看system_call_fastpath后10条指令,找到callq指令。

x/10i system_call_fastpathx/i system_call_fastpath+15

Linux | LKM 型 Rootkit 取证浅析
        再检查callq的调用偏移量是否指向sys_call_table的地址。正确的sys_call_table地址可以在System.map或者/proc/kallsyms中找到。下图说明没有被劫持。

Linux | LKM 型 Rootkit 取证浅析

        当加载了恶意模块,将一个伪系统调用表插入到内核内存中,并修改负责调用系统待用的中断处理器的前半部分。通过内存分析可以发现,伪造的地址,为0xffffffffa03ea260,此地址指向系统调用表中第一个系统调用sys_read函数地址。

Linux | LKM 型 Rootkit 取证浅析

Linux | LKM 型 Rootkit 取证浅析


6. 分析Kprobe Rootkit


通过分析内存来检测Kprobe是否存在是非常容易的。如果设置了常规的Kprobe,就会在函数入口点或者任意指令上设置断点(int3)。通过扫描整个代码段的断点就非常容易检测到。因为如果不是使用Kprobe,一般不会在内核代码上设置断点。要检测经过优化的Kprobe,需要使用一条jmp指令,而不是断点指令(int3)。

在/sys/kernel/debug/kprobes/list有一个实时Kprobe列表,存放了正在使用中的Kprobe,但是任何的rootkit都会从文件中将对应的Kprobe隐藏掉。一个好的 rootkit 还能阻止/sys/kernel/debug/kprobes/enabled 中的 Kprobe 被禁用。

7. 分析被劫持的LKM内核模块


利用符号劫持感染LKM文件

        LKM(可加载内核模块)是一种ET_REL类型的ELF目标文件。

       LKM 实际上只是重定位代码,使用函数劫持方式感染 LKM 会受限制。但是,在 ELF 内核目标文件加载期间, 一些特定的内核机制, 如 LKM 内部的重定位函数过程, 会使得感染 LKM非常容易。

        在phrack中《Infecting loadable kernel modules: kernel versions 2.6.x/3.0.x》有对其工作原理的方法和原因的详细描述。

        1.寄生代码注入或链接到内核模块。

        2.将 init_module()的符号值修改为恶意的替代函数的偏移量值。

        这是在 Linux 系统(内核为 2.6~3.x)上普遍使用的方法。

利用函数劫持感染LKM文件

        LKM是重定位文件,因此容易在LKM中插入代码。可以编写寄生代码,在链接之前编译为可重定位文件。寄生代码中可能包含了一个或多个新的函数。链接了新的寄生代码之后,攻击者可以使用函数蹦床劫持LKM中的任意一个函数。

        攻击者也可以使用指向新函数跳转指令替换目标函数的前几个字节。新函数会使用memecpy将原始的字节复制到原始的函数中,然后调用原始函数,最后使用memcpy将函数蹦床复制到原位,等待下次调用。

分析利用符号劫持感染的LKM

    针对符号劫持方法,可以查看具有相同值的两个符号。codeinj.ko的init_module函数被fshid模块的fshid_init劫持。通过objdump或者readelf查看,如果有两个符号具有相同值,则可能发生了劫持。

    下图是劫持前后codeinj模块的符号表情况,劫持发生后出现了两组符号具有相同值的情况。

Linux | LKM 型 Rootkit 取证浅析分析利用函数劫持感染的LKM

针对函数劫持感染LKM,检测方式与上述分析内核函数蹦床的思路一致,只不过是分析对象是LKM文件,可以使用 objdump 工具进行反编译。

reference

《Learning Binary Analysis》elfmaster-ONeill

https://github.com/elfmaster/kdress

http://phrack.org/issues/68/11.html

https://bitlackeys.org/

https://wohin.me/linux-rootkit-shi-yan-0003-rootkit-gan-ran-guan-jian-nei-he-mo-kuai-shi-xian-chi-jiu-hua/

原文始发于微信公众号(TahirSec):Linux | LKM 型 Rootkit 取证浅析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月9日10:07:54
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Linux | LKM 型 Rootkit 取证浅析https://cn-sec.com/archives/1099872.html

发表评论

匿名网友 填写信息