Kernel Module实战指南(四):系统调用劫持

原文地址:Kernel Module实战指南(四):系统调用劫持linux

Introduction

Kernel Module还能够作一些比较cool的事情,好比劫持系统调用,增长咱们本身的逻辑,在系统调用监听、过滤和审计的场景使用。安全

系统调用概述

劫持系统调用是一件比较危险的事情,例如劫持open()系统调用,而且阻止一切open()的操做,那么计算机将不可以打开任何文件,甚至没法关闭计算机,惟一能作的事情只有冷重启计算机。
一般来说,用户进程不容许直接访问内核,不能访问内核内存,也不能使用内核函数,这由CPU架构来保证,没法改变。
为何是一般来说?由于有一个例外,那就是系统调用。发生系统调用时,用户进程将所需的值(系统调用号、系统调用参数)压入寄存器中,而后调用一条特殊的CPU指令使程序从用户态切换到内核态继续执行。这个特殊的CPU指令,在Intel X86架构下,就是所谓的interrupt 0x80,即80中断。当系统调用结束后,经过一样的方式,从内核态切换到用户态,继续执行程序。
程序切入到系统态后,会根据寄存器中的系统调用号,在系统调用表(sys_call_table)中,执行相应的系统调用处理函数。bash

系统调用劫持代码

劫持系统调用是一个高危操做,在2.6.24内核以前,能够简单的替换sys_call_table中的系统调用函数地址。
下面给出2.6.24内核以前的劫持系统调用open()/__NR_open的代码:架构

#include <linux/kernel.h>
#include <linux/module.h>
...
extern void *sys_call_table[];
asmlinkage int (*original_open)(const char *, int, int);
asmlinkage int hijack_open(const char *filename, int flags, int mode) {
  // do hijack logic, just print the parameter
  printk(KERN_INFO "hijack: open(%s, %d, %d)\n", filename, flags, mode);
  return original_open(filename, flags, mode);
}
int init_module() {
  original_open = sys_call_table[__NR_open];
  sys_call_table[__NR_open] = hijack_open;
  return 0;
}
void cleanup_module() {
  sys_call_table[__NR_open] = original_open;
}

不幸的是,因为劫持系统调用产生的安全性问题(如监听、窃取),Linux Kernel在2.6.24将sys_call_table的内存地址变为只读,用上述的方法写地址时会失败。
稍后,有大神给出了一段代码,能够将只读页变成可读,这样就能够修改系统调用表了。函数

int set_page_ro(long unsigned int _addr) {
  struct page *pg;
  pgprot_t prot;
  pg = virt_to_page(_addr);
  prot.pgprot = VM_READ;
  return change_page_attr(pg, 1, prot);
}
int set_page_rw(long unsigned int _addr) {
  struct page *pg;
  pgprot_t prot;
  pg = virt_to_page(_addr);
  prot.pgprot = VM_READ | VM_WRITE;
  return change_page_attr(pg, 1, prot);
}

过了一段时间后,change_page_attr函数也被禁用了,不过仍是有别的方法能够绕过,下面给出一个在3.19.0内核下仍然可用的代码:日志

#include <asm/unistd.h>
#include <linux/module.h>
#include <linux/highmem.h>
// 因为sys_call_table符号再也不被导出,须要hardcode地址,
// 地址须要在bash下键入下面命令进行查找:
// $ grep sys_call_table /boot/System.map-3.19.0-25-generic
unsigned long *sys_call_table = (unsigned long*) 0xffffffff81600480;
asmlinkage int (*original_open)(const char *, int, int);
asmlinkage int hijack_open(const char *filename, int flags, int mode) {
  // do hijack logic, just print the parameter
  printk(KERN_INFO "hijack: open(%s, %d, %d)\n", filename, flags, mode);
  return original_open(filename, flags, mode);
}
int make_ro(unsigned long address) {
  unsigned int level;
  pte_t *pte = lookup_address(address, &level);
  pte->pte = pte->pte &~ _PAGE_RW;
  return 0;
}
int make_rw(unsigned long address) {
   unsigned int level;
   pte_t *pte = lookup_address(address, &level);
   if(pte->pte &~ _PAGE_RW) {
      pte->pte |= _PAGE_RW;
   }
   return 0;
}
int init_module(void) {
  make_rw((unsigned long)sys_call_table);
  original_open = (void*)*(sys_call_table + __NR_open);
  *(sys_call_table + __NR_open) = (unsigned long)hijack_open;
  make_ro((unsigned long)sys_call_table);
  return 0;
}
void cleanup_module(void) {
  make_rw((unsigned long)sys_call_table);
  *(sys_call_table + __NR_open) = (unsigned long)original_open;
  make_ro((unsigned long)sys_call_table);
}

加载模块后,经过dmesg查看日志:code

$ dmesg
...
[116465.803107] 'hijack: open("/proc/33162/status", 80000, 0)
[116465.803107] 'hijack: open("/etc/passwd", 0, 1B6)
...

Summary

经过编写一个劫持open()系统调用的Linux Kernel Module,如今咱们能够对系统调用监听、过滤和审计了。进程

相关文章
相关标签/搜索