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

如何用Kprobe获取Linux内核函数参数?X86_64参数异常解惑

内核Kprobe挂钩__x64_sys_openat时参数读取异常问题

问题背景

在X86_64架构的Fedora系统(Linux 6.11.10-200.fc40.x86_64)上,使用Kprobe挂钩内核函数__x64_sys_openat(int dfd, const char *path, struct open_how *how)。按照X86_64调用约定,函数参数应依次存于RDI、RSI、RDX寄存器,但读取到的寄存器值完全不符合预期:

[23374.116347] "pre 1st arg dfd  :" [di]:  0xFFFFAE19081A3F58 (-90052443357352) valid 0
[23374.116351] "pre 2nd arg *path:" [si]:  0x101 (257) valid 0
[23374.116353] "pre 3th arg *how :" [dx]:  0x0 (0) valid 0

预期第一个参数dfd是小正整数,后两个是指针,但实际第一个参数是指针风格的值,第二个是整数,尝试将第二个参数作为字符串读取直接导致内核崩溃。

相关代码

/* 
 * syscall-steal.c 
 * 
 * System call "stealing" sample. 
 * 
 * Disables page protection at a processor level by changing the 16th bit 
 * in the cr0 register (could be Intel specific). 
 */ 
 
#include <linux/delay.h> 
#include <linux/kernel.h> 
#include <linux/module.h> 
#include <linux/moduleparam.h> /* which will have params */ 
#include <linux/unistd.h> /* The list of system calls */ 
#include <linux/cred.h> /* For current_uid() */ 
#include <linux/uidgid.h> /* For __kuid_val() */ 
#include <linux/version.h> 
 
/* For the current (process) structure, we need this to know who the 
 * current user is. 
 */ 
#include <linux/sched.h> 
#include <linux/uaccess.h> 
 
/* If you have tried to use the syscall table to intercept syscalls and it  
* doesn't work, you can try to use Kprobes to intercept syscalls. 
* Set USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL to 1 to register a pre-handler 
* before the syscall. 
*/ 
#include <linux/kprobes.h> 

#define MAX_FILENAME_SIZE  1024

/* UID we want to spy on - will be filled from the command line. */ 
static uid_t uid = -1; 
module_param(uid, int, 0644); 
 
/*
 * rdi: 1st argument (saved in regs->di?)
 * rsi: 2nd argument (saved in regs->si?)
 * rdx: 3th argument (saved in regs->dx?)
 *
 * __x64_sys_openat(int dfd, const char *path, struct open_how *how)
 * dfd  => rdi
 * path => rsi
 * how  => rdx
 * 
 */

#define VALID_ADDRESS(desc, name) pr_info("%s [%s]:  0x%lX (%ld) valid %d\n", #desc, #name, regs->name, regs->name, virt_addr_valid(regs->name)); 

static int openat_pre_handler(struct kprobe *p, struct pt_regs *regs) 
{ 
    char filename[MAX_FILENAME_SIZE];

    if (__kuid_val(current_uid()) != uid) { 
        return 0; 
    }  
    strcpy(filename, "No file"); 
    pr_info("openat called by %d\n", uid); 
    VALID_ADDRESS("pre 1st arg dfd  :", di);
    VALID_ADDRESS("pre 2nd arg *path:", si);
    VALID_ADDRESS("pre 3th arg *how :", dx);
    if(copy_from_user(filename, (void*)regs->si, MAX_FILENAME_SIZE-1)) {
        pr_info("copy user data failed\n");
    }
    pr_info("Opening file: %s\n", filename);
    //dump_stack();
    return 0; 
}


static struct kprobe openat_kprobe = { 
    .symbol_name = "__x64_sys_openat", 
    .pre_handler = openat_pre_handler, 
}; 

static int __init syscall_steal_start(void) 
{ 
    pr_info("Using kprobes\n");

    int err;
    err = register_kprobe(&openat_kprobe);
    if (err) {
        pr_err("register_kprobe() on openat failed: %d\n", err); 
    } 
    return err;
    
    pr_info("Spying on UID:%d\n", uid); 
    
    return err; 
} 
 
static void __exit syscall_steal_end(void) 
{ 
    unregister_kprobe(&openat_kprobe); 
    msleep(500); 
} 
 
module_init(syscall_steal_start); 
module_exit(syscall_steal_end); 
 
MODULE_LICENSE("GPL");

提问

能否从pt_regs结构体的保存寄存器中获取正确参数?为何当前方式无效?


解答

问题根源

你读取的寄存器字段完全错误。在X86_64架构的Linux内核pt_regs结构体中,64位寄存器对应的字段是rdi、rsi、rdx,而你代码中使用的di、si、dx是32位寄存器的字段,仅保留了寄存器的低32位值,这直接导致读取到的参数完全错乱:

  • 原本存在64位寄存器rdi的dfd参数,你读取di只能拿到低32位,结果变成了类似指针的大数值
  • 原本是64位用户空间指针的path参数,读取si只拿到低32位,变成了无意义的小整数,用它做copy_from_user自然会访问非法地址,引发内核崩溃

修复方案

修改代码中的寄存器字段名,将32位寄存器字段替换为对应的64位字段:

  1. 调整地址验证宏,同时修正用户空间地址的类型判断:
#define VALID_ADDRESS(desc, name) pr_info("%s [%s]:  0x%lX (%ld) valid %d\n", #desc, #name, regs->name, regs->name, virt_addr_valid((void __user *)regs->name)); 
  1. 修改handler中的寄存器引用:
VALID_ADDRESS("pre 1st arg dfd  :", rdi);
VALID_ADDRESS("pre 2nd arg *path:", rsi);
VALID_ADDRESS("pre 3th arg *how :", rdx);
  1. 调整copy_from_user的参数类型:
if(copy_from_user(filename, (void __user *)regs->rsi, MAX_FILENAME_SIZE-1)) {
    pr_info("copy user data failed\n");
}

额外优化点

模块初始化代码中有冗余的return err;语句,会导致pr_info("Spying on UID:%d\n", uid);永远不会执行,建议移除第一个return err;。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 06:55:01