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
问题分析与修正
核心错误与原因
entry_handler时机错误
__x64_sys_getdents64的执行逻辑是:接收用户传入的空(或未填充)缓冲区,内核完成目录项读取后将数据写入缓冲区,最后返回实际写入的字节数。你在entry阶段(系统调用刚进入时)就执行copy_from_user,此时缓冲区还未被内核写入数据,自然无法获取有效内容。而返回值为0时,是系统调用已经完成了多次读取,缓冲区残留了之前调用的有效数据,所以你能看到文件列表。寄存器解析错误
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;全局变量线程安全问题
使用全局变量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
相关产品推荐
相关产品推荐

