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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 10:47:02