【问题标题】:How can I override a system call table entry with my own function?如何使用自己的函数覆盖系统调用表条目?
【发布时间】:2020-01-19 16:39:26
【问题描述】:

我想更新系统调用表以使用我的自定义打开函数。我写了以下代码:

#include <linux/module.h>
#include <linux/kallsyms.h>

MODULE_LICENSE("GPL");
char *sym_name = "sys_call_table";

typedef asmlinkage long (*sys_call_ptr_t)(const struct pt_regs *);
static sys_call_ptr_t *sys_call_table;
typedef asmlinkage long (*custom_open) (const char __user *filename, int flags, umode_t mode);

custom_open old_open;

static asmlinkage long my_open(const char __user *filename, int flags, umode_t mode)
{
    pr_info("%s\n",__func__);
        return old_open(filename, flags, mode);
}

static int __init hello_init(void)
{
    sys_call_table = (sys_call_ptr_t *)kallsyms_lookup_name(sym_name);
    old_open = (custom_open)sys_call_table[__NR_open];
    sys_call_table[__NR_open] = (sys_call_ptr_t)my_open;

    return 0;
}

static void __exit hello_exit(void)
{
    sys_call_table[__NR_open] = (sys_call_ptr_t)old_open;    
}

module_init(hello_init);
module_exit(hello_exit);

当我加载模块时,我收到以下错误:

[69736.192438] BUG: unable to handle page fault for address: ffffffff98e001d0
[69736.192441] #PF: supervisor write access in kernel mode
[69736.192442] #PF: error_code(0x0003) - permissions violation
[69736.192443] PGD 10460e067 P4D 10460e067 PUD 10460f063 PMD 80000001040000e1 
[69736.192461] Oops: 0003 [#1] SMP PTI
[69736.192463] CPU: 0 PID: 45249 Comm: insmod Tainted: G           OE     5.2.8 #6

我可以更新系统调用表吗?我该如何解决这样的错误?

【问题讨论】:

  • 你在 x86 上吗?
  • X86_64 处理器

标签: c security linux-kernel x86 system-calls


【解决方案1】:

你快到了。在 Intel x86 CPU 中,Control Register CR0 有一个特殊位(称为写保护位),用于控制 CPU 在特权级别 0 下运行时是否可以写入只读页面(内核代码确实在特权级别 0 下运行)。

由于系统调用表位于只读页面内,并且默认情况下设置了“写保护”位,因此您无法对其进行写入。因此,如果您尝试这样做:

sys_call_table[__NR_open] = (sys_call_ptr_t)my_open;

你会让一切崩溃。

为了正确劫持系统调用,您需要在覆盖表条目之前通过将CR0 寄存器的“Write Protect”位设置为0 来禁用写保护,并在完成后重新启用它完毕。两个宏 read_cr0()write_cr0() 正是用于操作所述寄存器。

这是正确的代码:

// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));

// Overwrite the syscall table entry
sys_call_table[__NR_open] = /* whatever */;

// Re-enable write protection
write_cr0(read_cr0() | 0x10000);

上面使用了掩码0x10000,因为“写保护”位是寄存器的第 17 个最低有效位。

请注意,上述步骤需要在模块的initexit 函数中完成。

【讨论】:

  • CR0 的第 0 位是 PE 位(保护模式)。位 0x10000 是写保护 (WP) 位。 Linux 将启用 WP 位,因此即使内核(在 ring 0 处运行)在写入只读页面时也会出错。暂时关闭它可以防止这种情况发生。
  • 在更新系统调用表之前更新位 0x10000 有效。但是我的自定义函数现在没有被调用......我错过了任何其他的东西
  • @md.jamal 对此不确定,你做的那些演员阵容对我来说似乎真的不需要。添加更多printk 看看会发生什么。
  • 已存在一个 printk。添加多个有什么好处
  • @md.jamal 为什么不呢?除非您想用调试支持重新编译内核,否则这是您拥有的唯一工具。另外,请考虑使用pr_alert() 而不是pr_info(),因为系统的日志级别设置得太低,您可能看不到打印。请参阅here 了解更多信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-11-20
  • 1970-01-01
  • 1970-01-01
  • 2010-10-25
  • 2016-08-26
  • 1970-01-01
  • 2017-07-27
相关资源
最近更新 更多