lkm和ebpf rootkit分析的简要记录
背景
再次捕获云上在野容器攻击,TeamTNT黑产攻击方法揭秘 文章中提到一个内核rootkit-Diamorphine
新型eBPF后门boopkit的原理分析与演示 也提到基于ebpf的rootkit
本文简要分析这两个rootkit在”进程隐藏”上实现的区别。
特别因为是eBPF只能通过“helper functions”调用内核能力,而不能直接调用内核函数、修改内核数据结构,所以我好奇eBPF后门怎么实现”进程隐藏”。
lkm rootkit是怎么隐藏进程的
通过修改__sys_call_table
,hook了kill、getdents64系统调用。
用户在调用kill系统调用时,rootkit在收到信号”SIGMODINVIS”(头文件中可以看到是31)后,就会执行两步:
- for_each_process找到进程task_struct
- 修改task_struct的flags
1 | // diamorphine.c |
用户在getdents64系统调用查看目录信息时,rootkit会修改返回给用户的目录信息。
1 | hacked_getdents64(unsigned int fd, struct linux_dirent64 __user *dirent, |
测试后,就会发现rootkit会影响/proc
目录中是否可以看到进程目录。
实际上是rootkit影响了getdents64系统调用的结果
ps命令也是读/proc
目录来获取进程信息的,所以rootkit影响ps命令结果
所以这个lkm rootkit是通过修改task_struct的flags字段来给进程打个标记,等getdents64时会根据标记判断是不是要修改目录信息。
eBPF程序不能直接修改内核数据,那eBPF后门是怎么做”进程隐藏”的呢?
ebpf rootkit是怎么隐藏进程的
getdents64系统调用可以用来获取目录信息,man 2 getdents
可以知道第二个指针参数指向”目录条目”buffer
1 | int getdents64(unsigned int fd, struct linux_dirent64 *dirp, |
“目录条目”数据结构如下,因为有”柔性数组”,所以用d_reclen记录了大小,这样就可以在”目录条目”buffer中定位到下一个”目录条目”。
1 | struct linux_dirent64 { |
pr0be.safe.c 注释写得很清楚,通过增大”目标进程所属的目录条目的前一个目录条目”的d_reclen值,使得用户程序在遍历*dirp
结果时,就会跳过”目标进程所属的目录条目”。
1 | SEC("tp/syscalls/sys_exit_getdents64") |
因为&dirp_previous->d_reclen
是用户空间地址,而不是内核空间地址,所以ebpf可以用bpf_probe_write_user
helper functions 修改dirp
地址中的数据。
总结
案例中的lkm rootkit和ebpf rootkit都是通过修改getdents64系统调用中dirp
地址指向的内容,使得查看/proc
目录信息时,看不到进程信息。
根据帖子知道,想利用ebpf修改系统调用的参数值或返回值有很大的限制。因为dirp是一个用户空间地址,所以ebpf程序可以用bpf_probe_write_user
修改此地址的内容。