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

kretprobe挂钩__x64_sys_getdents64返回数据异常问题排查

Linux内核kretprobe挂钩__x64_sys_getdents64异常问题排查

问题现象

  • 当__x64_sys_getdents64返回值非零时,从用户空间拷贝的dirp数据中无文件信息;
  • 当返回值为0时,dirp中反而包含文件列表。

疑问点

  • 代码或逻辑是否存在错误?
  • 为何返回值非零时无法获取有效数据?

内核模块代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/unistd.h>
#include <linux/slab.h>

#define ROOTKIT_PATTERN                 "example.txt"
#define GETDENTS_COUNT_DUMMY            9999888221

struct linux_dirent
{
    unsigned long   d_ino;
    unsigned long   d_off;
    unsigned short  d_reclen;
    char            d_name[];
};

struct linux_dirent64
{
    uint64_t        d_ino;
    int64_t         d_off;
    unsigned short  d_reclen;
    unsigned char   d_type;
    char            d_name[];
};

spinlock_t supp_len_lock;

struct linux_dirent64 *g_dirp = NULL;
unsigned long g_count = GETDENTS_COUNT_DUMMY;
char* g_kdirp_buf = NULL;
int g_is_copied = 0;

static int handler_entry(struct kretprobe_instance *ri, struct pt_regs *regs)
{
#if IS_ENABLED(CONFIG_X86_64)
    // int fd = ((struct pt_regs*)regs->di)->di;
    void *dirv = (void *)((struct pt_regs*)regs->di)->si;
    struct linux_dirent64 *dirp = (struct linux_dirent64 *)dirv;
    unsigned long count = ((struct pt_regs*)regs->di)->dx;
#elif IS_ENABLED(CONFIG_ARM64)
    // int fd = ((struct pt_regs*)regs->regs[1])->regs[0];
    void *dirv = (void *)((struct pt_regs*)regs->regs[1])->regs[1];
    struct linux_dirent64 *dirp = (struct linux_dirent64 *)dirv;
    unsigned long count = ((struct pt_regs*)regs[1])->regs[2];
#endif

    g_dirp = dirp;
    g_count = count;
    g_kdirp_buf = kzalloc(count, GFP_KERNEL);
    if (g_kdirp_buf == NULL)
    {
        printk("ERR kmalloc() failed!\n");
        return 0;
    }

    if (copy_from_user(g_kdirp_buf, dirp, count))
    {
        printk("ERR copy_from_user() failed!\n");
        kfree(g_kdirp_buf);
        return 0;
    }

    return 0;
}

static int handler_ret(struct kretprobe_instance *ri, struct pt_regs *regs)
{
    long ret;

    if ((g_dirp == NULL) || (g_kdirp_buf == NULL) || (g_count == GETDENTS_COUNT_DUMMY))
    {
        printk("[handler_ret]\t\tINITIALIZATION FAILED\n");
        return 0;
    }

    ret = regs_return_value(regs);


    if (ret != 0)
    {
        unsigned long offset = 0;
        struct linux_dirent64 *d;
        d = (struct linux_dirent64 *)(g_kdirp_buf + offset);
        printk("%s\n", d->d_name);
    }

    if (g_kdirp_buf != NULL)
    {
        kfree(g_kdirp_buf);
    }

    return 0;
}

static struct kretprobe kretprobe = {
    .handler = handler_ret,
    .entry_handler = handler_entry,
    .kp.symbol_name = "__x64_sys_getdents64"
};


static int __init kretprobe_init(void)
{
    spin_lock_init(&supp_len_lock);

    register_kretprobe(&kretprobe);
    return 0;
}

static void __exit kretprobe_exit(void)
{

    unregister_kretprobe(&kretprobe);
}

module_init(kretprobe_init)
module_exit(kretprobe_exit)
MODULE_LICENSE("GPL");

Makefile

obj-m += hidproc.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

问题分析与修正

核心错误与原因

  1. entry_handler时机错误
    __x64_sys_getdents64的执行逻辑是:接收用户传入的空(或未填充)缓冲区,内核完成目录项读取后将数据写入缓冲区,最后返回实际写入的字节数。你在entry阶段(系统调用刚进入时)就执行copy_from_user,此时缓冲区还未被内核写入数据,自然无法获取有效内容。而返回值为0时,是系统调用已经完成了多次读取,缓冲区残留了之前调用的有效数据,所以你能看到文件列表。

  2. 寄存器解析错误
    x86_64架构下,系统调用的参数直接存储在pt_regs的di、si、dx寄存器中,代码中((struct pt_regs*)regs->di)->di的写法完全错误,正确的参数获取方式应为:

    int fd = regs->di;
    struct linux_dirent64 *dirp = (struct linux_dirent64 *)regs->si;
    unsigned long count = regs->dx;
    
  3. 全局变量线程安全问题
    使用全局变量g_dirp、g_count、g_kdirp_buf存储调用数据,当多个进程同时调用getdents64时会出现数据覆盖,导致逻辑混乱。正确做法是利用kretprobe_instance的data字段存储每个调用的私有数据。

修正后的关键代码片段

首先定义私有数据结构:

struct probe_data {
    struct linux_dirent64 *dirp;
    unsigned long count;
    char *kbuf;
};

修改entry_handler,分配私有数据并存储参数:

static int handler_entry(struct kretprobe_instance *ri, struct pt_regs *regs)
{
    struct probe_data *data;

    data = kzalloc(sizeof(struct probe_data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

#if IS_ENABLED(CONFIG_X86_64)
    data->dirp = (struct linux_dirent64 *)regs->si;
    data->count = regs->dx;
#elif IS_ENABLED(CONFIG_ARM64)
    data->dirp = (struct linux_dirent64 *)regs->regs[1];
    data->count = regs->regs[2];
#endif

    ri->data = data;
    return 0;
}

修改ret_handler,在系统调用完成后拷贝数据并处理:

static int handler_ret(struct kretprobe_instance *ri, struct pt_regs *regs)
{
    long ret = regs_return_value(regs);
    struct probe_data *data = ri->data;

    if (!data)
        return 0;

    if (ret > 0) {
        data->kbuf = kzalloc(ret, GFP_KERNEL);
        if (!data->kbuf)
            goto cleanup;

        if (!copy_from_user(data->kbuf, data->dirp, ret)) {
            unsigned long offset = 0;
            struct linux_dirent64 *d;
            while (offset < ret) {
                d = (struct linux_dirent64 *)(data->kbuf + offset);
                printk(KERN_INFO "Found file: %s\n", d->d_name);
                // 这里可以加入文件隐藏逻辑,过滤指定文件名后写回用户空间
                offset += d->d_reclen;
            }
        } else {
            printk(KERN_ERR "copy_from_user failed\n");
        }
    }

cleanup:
    if (data->kbuf)
        kfree(data->kbuf);
    kfree(data);
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 14:20:18