You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux 6.8.11内核模块无法Hook系统调用的问题排查求助

内核模块无法Hook系统调用的问题排查与解决

问题描述

使用Linux 6.8.11-amd64系统(OracleVM VirtualBox虚拟机、X86_64处理器),编写了拦截chmod系统调用的内核模块,通过sudo insmod lkm_chmod.ko加载模块后,执行sudo chmod 755 testfile命令,模块未输出预期的拦截日志。日志无报错信息,将__NR_chmod替换为__NR_open或__NR_close后问题依旧。

内核模块代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kprobes.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include <linux/fs.h>

MODULE_LICENSE("GPL");

static void my_write_cr0(long value) {
    __asm__ volatile("mov %0, %%cr0" :: "r"(value) : "memory");
}

#define disable_write_protection() my_write_cr0(read_cr0() & (~0x10000))
#define enable_write_protection() my_write_cr0(read_cr0() | 0x10000)

unsigned long* sys_call_table_address;
asmlinkage int (*original_chmod)(const char __user*, umode_t);

static struct kprobe kp = {
    .symbol_name = "kallsyms_lookup_name"
};

typedef unsigned long (*kallsyms_lookup_name_t)(const char* name);

unsigned long* get_sys_call_table_address(void) {
    kallsyms_lookup_name_t kallsyms_lookup_name;
    int ret = register_kprobe(&kp);
    if (ret < 0) {
        printk(KERN_ERR "InterceptChmod: register_kprobe failed, returned %d\n", ret);
        return NULL;
    }
    kallsyms_lookup_name = (kallsyms_lookup_name_t)kp.addr;
    unregister_kprobe(&kp);
    if (!kallsyms_lookup_name) {
        printk(KERN_ERR "InterceptChmod: kallsyms_lookup_name not found\n");
        return NULL;
    }
    unsigned long* address = (unsigned long*)kallsyms_lookup_name("sys_call_table");
    if (!address) {
        printk(KERN_ERR "InterceptChmod: sys_call_table not found\n");
    }
    else {
        printk(KERN_INFO "InterceptChmod: sys_call_table address: %p\n", address);
    }
    return address;
}

asmlinkage int custom_chmod(const char __user* filename, umode_t mode) {
    char fname[256];

    // Copy the filename from user space to kernel space
    if (strncpy_from_user(fname, filename, sizeof(fname)) > 0) {
        fname[sizeof(fname) - 1] = '\0'; // Ensure null termination
        printk(KERN_INFO "InterceptChmod: chmod intercepted for file: %s, mode: %o\n", fname, mode);
    }
    else {
        printk(KERN_INFO "InterceptChmod: chmod intercepted, failed to get filename, mode: %o\n", mode);
    }

    // Call the original chmod system call
    return original_chmod(filename, mode);
}

static int __init intercept_chmod_init(void) {
    printk(KERN_INFO "InterceptChmod: Loading module\n");

    sys_call_table_address = get_sys_call_table_address();
    if (!sys_call_table_address) {
        printk(KERN_ERR "InterceptChmod: Failed to get sys_call_table_address\n");
        return -EFAULT;
    }

    printk(KERN_INFO "InterceptChmod: sys_call_table address: %p\n", sys_call_table_address);

    original_chmod = (void*)sys_call_table_address[__NR_chmod];
    printk(KERN_INFO "InterceptChmod: Original chmod address: %p\n", original_chmod);

    disable_write_protection();
    sys_call_table_address[__NR_chmod] = (unsigned long)custom_chmod;
    enable_write_protection();

    printk(KERN_INFO "InterceptChmod: Hooked chmod system call, new address: %p\n", (void*)sys_call_table_address[__NR_chmod]);
    return 0;
}

static void __exit intercept_chmod_exit(void) {
    if (sys_call_table_address) {
        disable_write_protection();
        sys_call_table_address[__NR_chmod] = (unsigned long)original_chmod;
        enable_write_protection();
        printk(KERN_INFO "InterceptChmod: Restored original chmod system call, address: %p\n", (void*)sys_call_table_address[__NR_chmod]);
    }

    printk(KERN_INFO "InterceptChmod: Module unloaded\n");
}

module_init(intercept_chmod_init);
module_exit(intercept_chmod_exit);

系统日志内容

[ 9245.540511] InterceptChmod: Loading module
[ 9245.561100] InterceptChmod: sys_call_table address: 00000000d760dd16
[ 9245.561105] InterceptChmod: sys_call_table address: 00000000d760dd16
[ 9245.561106] InterceptChmod: Original chmod address: 0000000014a7fad1
[ 9245.561110] InterceptChmod: Hooked chmod system call, new address: 00000000ce1988d5
[ 9330.452750] InterceptChmod: Restored original chmod system call, address: 0000000014a7fad1
[ 9330.452766] InterceptChmod: Module unloaded

解决方法

Linux 6.x版本内核对系统调用表的保护机制大幅增强,直接修改sys_call_table的方式兼容性极差,以下是针对性的修复步骤:

1. 替换写保护处理逻辑

原代码中修改cr0寄存器的方式在Linux 6.x中已无法绕过页表级的写保护,需改用内核提供的页属性修改函数:

#include <linux/mm.h>

// 替换原有的disable/enable_write_protection宏
static void make_sys_call_table_writable(void) {
    set_memory_rw((unsigned long)sys_call_table_address, 1);
}

static void make_sys_call_table_readonly(void) {
    set_memory_ro((unsigned long)sys_call_table_address, 1);
}

调用时直接替换原有的disable_write_protection()和enable_write_protection()即可。

2. 改用Kretprobe直接Hook系统调用函数

直接修改系统调用表的方式受KPTI(内核页表隔离)、影子系统调用表等机制影响,推荐使用kretprobe直接Hook系统调用的实现函数,兼容性更好:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kprobes.h>
#include <linux/uaccess.h>

MODULE_LICENSE("GPL");

static struct kretprobe chmod_kretprobe = {
    .kp.symbol_name = "sys_chmod",
};

static int chmod_handler(struct kretprobe_instance *ri, struct pt_regs *regs) {
    // X86_64下,系统调用参数存在rdi、si寄存器中
    const char __user *filename = (const char __user *)regs->di;
    umode_t mode = (umode_t)regs->si;
    char fname[256];

    if (strncpy_from_user(fname, filename, sizeof(fname)) > 0) {
        fname[sizeof(fname)-1] = '\0';
        printk(KERN_INFO "InterceptChmod: chmod intercepted for file: %s, mode: %o\n", fname, mode);
    } else {
        printk(KERN_INFO "InterceptChmod: chmod intercepted, failed to get filename, mode: %o\n", mode);
    }
    return 0;
}

static int __init intercept_chmod_init(void) {
    int ret;
    chmod_kretprobe.handler = chmod_handler;
    ret = register_kretprobe(&chmod_kretprobe);
    if (ret < 0) {
        printk(KERN_ERR "InterceptChmod: register kretprobe failed: %d\n", ret);
        return ret;
    }
    printk(KERN_INFO "InterceptChmod: kretprobe registered successfully\n");
    return 0;
}

static void __exit intercept_chmod_exit(void) {
    unregister_kretprobe(&chmod_kretprobe);
    printk(KERN_INFO "InterceptChmod: kretprobe unregistered\n");
}

module_init(intercept_chmod_init);
module_exit(intercept_chmod_exit);

该方式无需修改系统调用表,直接Hook内核中的sys_chmod函数,不受保护机制影响。

3. 验证系统调用号正确性

确保使用的是X86_64架构的64位系统调用号,执行以下命令查看:

grep __NR_chmod /usr/include/asm/unistd_64.h

若使用32位系统调用号(来自unistd_32.h),会导致Hook失效。

4. 检查内核模块签名配置

如果内核开启了模块签名验证,未签名的模块无法正常工作,查看配置:

zcat /proc/config.gz | grep CONFIG_MODULE_SIG

若开启,需生成签名密钥并为模块签名,或临时关闭签名验证(仅测试环境)。


内容的提问来源于stack exchange,提问作者regens wesali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 13:57:03