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

高版本Linux内核系统调用Hook失效,寻求替代实现方案

内核系统调用Hook失效问题咨询

以下代码可在5.15.0-73版本内核中实现系统函数Hook,但在5.15.0-113版本中失效。现咨询以下问题:

  1. 在5.15.0-113、5.15.0-119内核中,替换系统调用表实现Hook后无法生效,还有哪些可行实现方式?
  2. 在6.8.x及5.15.100以上版本中,除替换系统调用表外,还有哪些方法能实现系统调用函数替换?(最新版本中替换系统调用表的方式已无法生效)

附上相关代码

unsigned long * sys_call_table;
unsigned int clear_and_return_cr0(void);
void setback_cr0(unsigned int val);
static int sys_mycall(void);

int orig_cr0;   
unsigned long *sys_call_table = 0;
static int (*anything_saved)(void); 
unsigned int clear_and_return_cr0(void) 
{
    unsigned int cr0 = 0;
    unsigned int ret;
    asm volatile ("movq %%cr0, %%rax" : "=a"(cr0)); 
        ret = cr0;
    cr0 &= 0xfffeffff;  
    //asm volatile ("movl %%eax, %%cr0" :: "a"(cr0));
    asm volatile ("movq %%rax, %%cr0" :: "a"(cr0)); 
    return ret;
}

void setback_cr0(unsigned int val)
{   

    //asm volatile ("movl %%eax, %%cr0" :: "a"(val));
    asm volatile ("movq %%rax, %%cr0" :: "a"(val));
}

static int sys_mycall(void)
{
    int ret = 12345;
    printk("My syscall is successful!\n");
    return ret;
}

static int __init init_addsyscall(void)
{
    printk("My syscall is starting。。。\n");
    sys_call_table = (unsigned long *)kallsyms_lookup_name("sys_call_table");
    printk("sys_call_table: 0x%p\n", sys_call_table);
    anything_saved = (int(*)(void))(sys_call_table[__NR_syscall]);  
    orig_cr0 = clear_and_return_cr0();  
    sys_call_table[__NR_syscall] = (unsigned long)&sys_mycall;  
    setback_cr0(orig_cr0);  
    return 0;
}

static void __exit exit_addsyscall(void)
{
    orig_cr0 = clear_and_return_cr0();  
        sys_call_table[__NR_syscall] = (unsigned long)anything_saved;   
        setback_cr0(orig_cr0);  
    printk("My syscall exit....\n");    
}

问题解答

针对5.15.0-113、5.15.0-119内核的Hook实现方式

  1. 修复系统调用表写保护绕过逻辑
    从5.15.0-91版本开始,内核增强了写保护机制,仅修改CR0的WP位已无法修改系统调用表,需要额外处理页表权限:

    • 通过lookup_address获取sys_call_table对应的页表项(PTE),将其设置为可写
    • 修改完成后恢复页表只读权限
      示例代码片段:
    #include <asm/pgtable.h>
    
    pte_t *pte = lookup_address((unsigned long)sys_call_table, &level);
    if (pte) {
        set_pte_atomic(pte, pte_mkwrite(*pte));
        // 执行sys_call_table的修改操作
        set_pte_atomic(pte, pte_clear_flags(*pte, _PAGE_RW));
    }
    
  2. 使用kprobe探针实现Hook
    利用内核探针机制直接挂钩目标系统调用入口,无需修改系统调用表:

    • 注册kprobe到目标系统调用的内核符号,在pre_handler中插入自定义逻辑或跳转至自定义函数
      示例:
    #include <linux/kprobes.h>
    
    static struct kprobe kp = {
        .symbol_name = "__x64_sys_xxx", // 替换为目标系统调用的内核符号名
        .pre_handler = my_pre_handler,
    };
    
    static int my_pre_handler(struct kprobe *p, struct pt_regs *regs) {
        // 执行自定义逻辑,或修改regs->rip跳转到自定义函数
        return 0;
    }
    
    // 初始化时注册
    register_kprobe(&kp);
    

针对6.8.x及5.15.100+版本的系统调用替换方案

  1. ftrace fentry探针
    内核ftrace框架的fentry探针可在函数入口插入钩子,是官方推荐的兼容性方案:

    • 定义fentry处理函数,注册到目标系统调用符号
      示例:
    #include <linux/ftrace.h>
    
    static void my_syscall_hook(struct ftrace_ops *ops, struct pt_regs *regs, unsigned long func) {
        // 自定义逻辑,可修改regs参数或返回值,也可跳转至自定义函数
    }
    
    static struct ftrace_ops my_ftrace_ops = {
        .func = my_syscall_hook,
        .flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_IPMODIFY,
    };
    
    // 注册流程
    ftrace_set_filter_ip(&my_ftrace_ops, (unsigned long)__x64_sys_xxx, 0, 0);
    register_ftrace_function(&my_ftrace_ops);
    
  2. eBPF跟踪点
    利用eBPF的系统调用跟踪点实现拦截和修改,无需修改内核,安全性和兼容性更高:

    • 通过tracepoint:syscalls:sys_enter_xxx和sys_exit_xxx跟踪点,在系统调用前后插入逻辑,修改参数或返回值
      示例eBPF代码片段:
    SEC("tracepoint/syscalls/sys_enter_openat")
    int trace_sys_enter_openat(struct trace_event_raw_sys_enter *ctx) {
        // 处理逻辑,例如替换输入参数
        return 0;
    }
    
  3. 直接修改函数入口(结合页表权限)
    若目标系统调用未被标记为__ro_after_init,可通过kallsyms_lookup_name获取函数地址,修改页表权限后直接覆盖函数入口指令。但该方法受内核编译配置限制,通用性不如前两种方案。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 18:55:15