在Linux内核模块中拦截系统调用(内核3.5版)

9

我需要用自己的实现替换标准系统调用(例如SYS_mkdir)。

根据一些来源,包括Stackoverflow上的this question,自内核版本2.6以来,sys_call_table未被导出为符号。

我尝试了以下代码:

    #include <linux/module.h> 
    #include <linux/kernel.h> 
    #include <linux/unistd.h> 
    #include <asm/syscall.h> 

    int (*orig_mkdir)(const char *path); 

    ....

    int init_module(void) 
    { 
            orig_mkdir=sys_call_table[__NR_mkdir]; 
            sys_call_table[__NR_mkdir]=own_mkdir;  
            printk("sys_mkdir replaced\n"); 
            return(0); 
    } 

    ....

不幸的是,我收到编译器错误:

 error: assignment of read-only location ‘sys_call_table[83]’

我该如何替换系统调用?
编辑:有没有不需要内核补丁的解决方案?

尝试使用类型转换为 char*,然后进行赋值。 - Grijesh Chauhan
2
也许这个链接(http://www.linuxforums.org/forum/kernel/133982-cannot-modify-sys_call_table.html)和这个链接(https://dev59.com/nXI95IYBdhLWcg3w5SSh)对你有帮助。 - Grijesh Chauhan
没有补丁,就没有通用解决方案。 - Ilya Matveychikov
5个回答

8
这对我有用。
请参考以下链接了解更多信息: Linux内核:系统调用挂钩示例https://bbs.archlinux.org/viewtopic.php?id=139406
asmlinkage long (*ref_sys_open)(const char __user *filename, int flags, umode_t mode);
asmlinkage long new_sys_open(const char __user *filename, int flags, umode_t mode)
{
  return ref_sys_open(filename, flags, mode);
}

static unsigned long **aquire_sys_call_table(void)
{
  unsigned long int offset = PAGE_OFFSET;
  unsigned long **sct;

  while (offset < ULLONG_MAX) {
    sct = (unsigned long **)offset;

    if (sct[__NR_close] == (unsigned long *) sys_close) 
      return sct;

    offset += sizeof(void *);
  }
  print("Getting syscall table failed. :(");
  return NULL;
}


// Crazy copypasted asm stuff. Could use linux function as well...
// but this works and will work in the future they say.
static void disable_page_protection(void) 
{
  unsigned long value;
  asm volatile("mov %%cr0, %0" : "=r" (value));

  if(!(value & 0x00010000))
    return;

  asm volatile("mov %0, %%cr0" : : "r" (value & ~0x00010000));
}

static void enable_page_protection(void) 
{
  unsigned long value;
  asm volatile("mov %%cr0, %0" : "=r" (value));

  if((value & 0x00010000))
    return;

  asm volatile("mov %0, %%cr0" : : "r" (value | 0x00010000));
}


static int __init rootkit_start(void) 
{

  //Hide me

  print("loaded");

  if(!(sys_call_table = aquire_sys_call_table()))
    return -1;

  disable_page_protection(); 
  {
    ref_sys_open = (void *)sys_call_table[__NR_open];
    sys_call_table[__NR_open] = (unsigned long *)new_sys_open;
  }
  enable_page_protection();
  return 0;
}

static void __exit rootkit_end(void) 
{
  print("exiting");

  if(!sys_call_table) {
    return;
  }

  disable_page_protection();
  {
    sys_call_table[__NR_open] = (unsigned long *)ref_sys_open;
  }
  enable_page_protection();
}

7

是的,有一种解决方案而不需要打补丁/重建内核。使用Kprobes基础设施(或SystemTap)。

这将允许您使用内核模块在内核中的任何点上放置“探针”(函数)。

通过修改sys_call_table进行类似的操作现在已被阻止(它是只读的)并被认为是一种肮脏的黑客! Kprobes/Jprobes等是一种“干净”的方法。此外,内核源树中提供的文档和样例非常好(请查看内核src树下的Documentation/kprobes.txt)。


1
kprobes / systemtap不允许您替换系统调用处理程序,但可以对其进行补充/先于它。 - fche
嘿,kprobes使用修补程序 :) - Ilya Matveychikov
@fche:是的,我同意。重点是效果类似。@IlyaMatveychikov:据我所知,kprobes不需要应用任何补丁,它是一个内核特性。此外,大多数发行版都启用了kprobes。 - kaiwan

1
问题的原因是sys_call_table是只读的。为了避免错误,在操作sys_call_table之前,您必须将其设置为可写。内核提供了一个函数来实现这一点。该函数名为set_mem_rw()
在操作sys_call_table之前,只需添加以下代码片段即可:
set_mem_rw((long unsigned int)sys_call_table,1);

在内核模块的退出函数中,请不要忘记将sys_call_table恢复为只读状态。可以通过以下方法实现。
set_mem_ro((long unsigned int)sys_call_table,1);    

0
使用LSM架构。
查看LSM钩子path_mkdirinode_mkdir以获取详细信息。需要解决的一个问题是如何在系统不允许明确的情况下注册自己的LSM模块。有关详细信息,请参见此处的答案:

如何使用LSM实现自己的钩子函数?


0
首先,您需要确定sys_call_table的位置。请参见这里
在对刚定位的系统表进行写入之前,您需要将其内存页面设置为可写。有关此操作,请查看这里,如果不起作用,请尝试这个

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接