linux-kernel模块中的系统调用拦截(内核3.5)

Posted

技术标签:

【中文标题】linux-kernel模块中的系统调用拦截(内核3.5)【英文标题】:System call interception in linux-kernel module (kernel 3.5) 【发布时间】:2012-12-02 07:38:10 【问题描述】:

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

正如我在一些资料中看到的,包括 *** 上的 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* 然后分配 可能是this和this对你有帮助 没有不打补丁的通用解决方案。 【参考方案1】:

这对我有用。

看 Linux Kernel: System call hooking example 和 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();

【讨论】:

【参考方案2】:

是的,有一个无需修补/重建内核的解决方案。使用Kprobes 基础架构(或 SystemTap)。

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

现在可以防止通过修改 sys_call_table 来做类似的事情(它是只读的)并且被认为是一种肮脏的黑客攻击! Kprobes/Jprobes/etc 是一种“干净”的方式。此外,内核源代码树中提供的文档和samples 非常好(查看内核源代码树下的Documentation/kprobes.txt)。

【讨论】:

kprobes/systemtap 不会让您替换系统调用处理程序,但可以补充它/在它之前。 嘿,kprobes 使用补丁 :) @fche:是的,我同意。关键是效果是相似的。。@IlyaMatveychikov:AFAIK,不,kprobes 是一个内核特性;你不需要应用任何补丁。此外,大多数发行版都启用了 kprobes..【参考方案3】:

问题是由于 sys_call_table 是只读的。为了避免该错误,在操作 sys_call_table 之前,您还必须使其可写。内核提供了一个函数来实现它。该函数以 set_mem_rw() 的形式给出。

只需在操作 sys_call_table 之前添加以下代码 sn-p

set_mem_rw((long unsigned int)sys_call_table,1);

在内核模块的退出函数中,请不要忘记将sys_call_table恢复为只读。可以如下实现。

set_mem_ro((long unsigned int)sys_call_table,1);    

【讨论】:

【参考方案4】:

首先,您需要确定 sys_call_table 的位置。见here。

在写入刚刚定位的系统表之前,您必须使其内存页可写。检查here,如果这不起作用,请尝试this。

【讨论】:

【参考方案5】:

使用 LSM 基础架构。

查看 LSM 挂钩 path_mkdirinode_mkdir 了解详情。需要解决的一个问题是如何在系统不允许的情况下注册自己的 LSM 模块。在此处查看答案以获取详细信息:

How can I implement my own hook function with LSM?

【讨论】:

以上是关于linux-kernel模块中的系统调用拦截(内核3.5)的主要内容,如果未能解决你的问题,请参考以下文章

可加载内核模块编程和系统调用拦截

CentOS 7.0 升级 Linux-kernel

LINUX对shell命令的拦截

通过系统调用,内核断点方法定位用户进程被内核踩内存的问题

ioctl 与 Linux 中的内核模块

模块机制