Linux 5.19下LKM+IOCTL实现open系统调用Hook失效求助
问题描述
我在Linux 5.19内核中尝试通过可加载内核模块(LKM)结合IOCTL实现open系统调用的Hook。模块代码本身可正常运行,所有调试打印都能通过dmesg查看,但运行调用open的测试程序时,却无法看到Hook的打印信息,求解决建议。
我通过cat /proc/kallsyms | grep sys_call命令获取系统调用表的地址。
内核模块代码
#include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/kdev_t.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/slab.h> //kmalloc() #include <linux/uaccess.h> //copy_to/from_user() #include <linux/ioctl.h> #include <linux/err.h> #include <linux/kallsyms.h> #include <asm/special_insns.h> #define WR_VALUE _IOW('a','a',int32_t*) #define RD_VALUE _IOR('a','b',int32_t*) #define IOCTL_PATCH_TABLE 0x00000001 #define IOCTL_FIX_TABLE 0x00000004 int32_t value = 0; dev_t dev = 0; static struct class *dev_class; static struct cdev etx_cdev; /* ** Function Prototypes */ static int __init etx_driver_init(void); static void __exit etx_driver_exit(void); static int etx_open(struct inode *inode, struct file *file); static int etx_release(struct inode *inode, struct file *file); static ssize_t etx_read(struct file *filp, char __user *buf, size_t len,loff_t * off); static ssize_t etx_write(struct file *filp, const char *buf, size_t len, loff_t * off); static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg); unsigned long *sys_call_table = (unsigned long*)0xffffffffa70004c0; //hard coded address of sys_call_table from /boot/System.map asmlinkage int (*real_open)(const char* __user, int, int); asmlinkage int (*real_openat)(int, const char*, int, umode_t); asmlinkage int (*real_openat_)(const struct pt_regs *); asmlinkage int (*real_openat2)(int, const char*, struct open_how*, size_t); asmlinkage int custom_open(const char* __user file_name, int flags, int mode){ pr_info("interceptor: open(\"%s\", %X, %X)\n", file_name,flags,mode); //return open(file_name,flags,mode); return (*real_open)(file_name, flags, mode); } asmlinkage long custom_openat_(const struct pt_regs *regs){ pr_info(KERN_INFO "%s: interceptor: openat_\n", __func__); return real_openat_(regs); } asmlinkage long custom_openat(int dirfd, const char __user* pathname, int flags, umode_t mode) { pr_info(KERN_INFO "%s: interceptor: openat\n", __func__); return real_openat(dirfd, pathname, flags, mode); } asmlinkage long custom_openat2(int dirfd, const char __user* pathname, struct open_how *how, size_t size) { pr_info(KERN_INFO "%s: interceptor: openat\n", __func__); return real_openat2(dirfd, pathname, how, size); } static struct file_operations fops = { .owner = THIS_MODULE, .read = etx_read, .write = etx_write, .open = etx_open, .unlocked_ioctl = etx_ioctl, .release = etx_release, }; int make_rw(unsigned long address){ unsigned int level; pte_t *pte = lookup_address(address, &level); //printk(_PAGE_RW, pte->pte &~ _PAGE_RW); if(pte->pte &~ _PAGE_RW) //bitwise and -> if the page is pte->pte |= _PAGE_RW; //bitwise or -> put the result inside pte->pte in the pte_t struct return 0; } //Make the page write protected int make_ro(unsigned long address){ unsigned int level; pte_t *pte = lookup_address(address, &level); pte->pte = pte->pte &~ _PAGE_RW; return 0; } static int etx_open(struct inode *inode, struct file *file) { pr_info("Device File Opened...!!!\n"); return 0; } static int etx_release(struct inode *inode, struct file *file) { pr_info("Device File Closed...!!!\n"); return 0; } static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { pr_info("Read Function\n"); return 0; } static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off) { pr_info("Write function\n"); return len; } static inline void _write_cr0(unsigned long val) { asm volatile("mov %0,%%cr0" : "+r"(val) : : "memory"); } static inline void _write_cr4(unsigned long val) { asm volatile("mov %0,%%cr4" : "+r"(val) : : "memory"); } static inline void wp_disable(void) { _write_cr0(read_cr0() & (~0x10000)); } static inline void wp_enable(void) { _write_cr0(read_cr0() | 0x10000); } static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch(cmd) { case WR_VALUE: if( copy_from_user(&value ,(int32_t*) arg, sizeof(value)) ) { pr_err("Data Write : Err!\n"); } pr_info("Value = %d\n", value); break; case RD_VALUE: if( copy_to_user((int32_t*) arg, &value, sizeof(value)) ) { pr_err("Data Read : Err!\n"); } break; case IOCTL_PATCH_TABLE: //disable_write_protection(); //pr_info("address of real open before WP edit: %p",sys_call_table[__NR_open]); wp_disable(); pr_info("syscalltable edited\n"); real_open = (void*)(sys_call_table[__NR_open]); real_openat = (void*)(sys_call_table[__NR_openat]); real_openat2 = (void*)(sys_call_table[__NR_openat2]); pr_info("address of real open: %p", *real_open); pr_info("address of real openat: %p", *real_openat_); pr_info("address of real openat: %p", *real_openat2); pr_info("address of custom open: %p", custom_open); pr_info("address of custom openat: %p", custom_openat_); pr_info("address of custom openat2: %p", custom_openat2); sys_call_table[__NR_open] = custom_open; sys_call_table[__NR_openat] = custom_openat_; sys_call_table[__NR_openat2] = custom_openat2; pr_info("syscalltable open address edited\n"); pr_info("new address of open is: %p\n", sys_call_table[__NR_open]); pr_info("new address of openat is: %p\n", sys_call_table[__NR_openat]); pr_info("new address of openat2 is: %p\n", sys_call_table[__NR_openat2]); wp_enable(); pr_info("syscalltable restored\n"); //enable_write_protection(); break; case IOCTL_FIX_TABLE: //disable_write_protection(); wp_disable(); sys_call_table[__NR_open] = real_open; sys_call_table[__NR_openat] = real_openat_; sys_call_table[__NR_openat2] = real_openat2; wp_enable(); //enable_write_protection(); break; default: pr_info("Default\n"); break; } return 0; } static int __init etx_driver_init(void) { /*Allocating Major number*/ if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){ pr_err("Cannot allocate major number\n"); return -1; } pr_info("Major = %d Minor = %d \n", MAJOR(dev), MINOR(dev)); /*Creating cdev structure*/ cdev_init(&etx_cdev,&fops); /*Adding character device to the system*/ if((cdev_add(&etx_cdev,dev,1)) < 0){ pr_err("Cannot add the device to the system\n"); goto r_class; } /*Creating struct class*/ if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class"))){ pr_err("Cannot create the struct class\n"); goto r_class; } /*Creating device*/ if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){ pr_err("Cannot create the Device 1\n"); goto r_device; } pr_info("Device Driver Insert...Done!!!\n"); return 0; r_device: class_destroy(dev_class); r_class: unregister_chrdev_region(dev,1); return -1; } static void __exit etx_driver_exit(void) { device_destroy(dev_class,dev); class_destroy(dev_class); cdev_del(&etx_cdev); unregister_chrdev_region(dev, 1); pr_info("Device Driver Remove...Done!!!\n"); } module_init(etx_driver_init); module_exit(etx_driver_exit); MODULE_LICENSE("GPL"); MODULE_VERSION("1.5");
测试程序代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #define WR_VALUE _IOW('a','a',int32_t*) #define RD_VALUE _IOR('a','b',int32_t*) #define IOCTL_PATCH_TABLE 0x00000001 #define IOCTL_FIX_TABLE 0x00000004 int main() { int fd; int fptr; int32_t value, number; printf("\nOpening Driver\n"); fd = open("/dev/etx_device", O_RDWR); if(fd < 0) { printf("Cannot open device file...\n"); return 0; } printf("Enter the Value to send\n"); scanf("%d",&number); printf("Writing Value to Driver\n"); ioctl(fd, WR_VALUE, (int32_t*) &number); printf("Reading Value from Driver\n"); ioctl(fd, RD_VALUE, (int32_t*) &value); printf("Value is %d\n", value); printf("Patching Syscall table\n"); ioctl(fd, IOCTL_PATCH_TABLE, NULL); fptr = open("/home/osboxes/test.txt", O_RDONLY); printf("fptr: %d\n",fptr); printf("Fixing Syscall table\n"); ioctl(fd, IOCTL_FIX_TABLE, NULL); printf("Closing Driver\n"); close(fd); }
解决建议
1. 修正系统调用入口类型(x86_64架构适配)
Linux 5.19的x86_64架构下,系统调用统一使用struct pt_regs传递参数,而非传统的直接参数列表。你的代码存在以下问题:
custom_open使用了过时的参数形式,无法匹配内核实际的系统调用入口real_openat_变量未被正确赋值,导致sys_call_table[__NR_openat]替换后调用异常
修正方案:
所有Hook函数统一使用pt_regs参数格式,示例:
asmlinkage long custom_open(const struct pt_regs *regs) { // 从regs中解析参数:x86_64寄存器对应rdi=filename, si=flags, dx=mode const char __user *filename = (const char __user *)regs->di; int flags = regs->si; umode_t mode = regs->dx; pr_info("interceptor: open(\"%s\", %X, %X)\n", filename, flags, mode); return ((asmlinkage long (*)(const struct pt_regs *))real_open)(regs); } // 同步修正openat/openat2的Hook函数 asmlinkage long custom_openat(const struct pt_regs *regs) { pr_info(KERN_INFO "%s: interceptor: openat\n", __func__); return ((asmlinkage long (*)(const struct pt_regs *))real_openat)(regs); }
同时在IOCTL_PATCH_TABLE分支中,正确赋值所有函数指针:
// 删除未使用的real_openat_变量 real_open = (void*)sys_call_table[__NR_open]; real_openat = (void*)sys_call_table[__NR_openat]; real_openat2 = (void*)sys_call_table[__NR_openat2]; // 替换系统调用表时使用对应Hook函数 sys_call_table[__NR_open] = (unsigned long)custom_open; sys_call_table[__NR_openat] = (unsigned long)custom_openat; sys_call_table[__NR_openat2] = (unsigned long)custom_openat2;
2. 完善系统调用表写保护处理
仅修改CR0的WP位不足以完全解除系统调用表的写保护,需同时修改页表权限:
static void disable_write_protection(void) { wp_disable(); make_rw((unsigned long)sys_call_table); } static void enable_write_protection(void) { make_ro((unsigned long)sys_call_table); wp_enable(); }
在patch和fix系统调用表时,替换原有的wp_disable()和wp_enable()调用。
3. 针对用户态实际调用路径Hook
用户态glibc的open函数会封装为openat系统调用,因此优先确保__NR_openat和__NR_openat2的Hook正确生效,这是测试程序实际触发的系统调用。
4. 调试验证步骤
- 执行
dmesg -w实时监控内核日志,确认patch后系统调用表地址已替换为Hook函数地址 - 用
strace ./test_program查看测试程序实际调用的系统调用,确认是否命中Hook目标 - 检查内核日志是否有Oops信息,若存在说明Hook函数的参数/返回值类型不匹配
5. 检查内核配置限制
确保内核未开启以下阻止系统调用表修改的配置:
CONFIG_STRICT_KERNEL_RWX=y:需确保页表权限修改逻辑生效CONFIG_SECURITY=y:部分安全模块会拦截系统调用表修改
内容的提问来源于stack exchange,提问作者isf3t
相关产品推荐
相关产品推荐

