原文地址:Kernel Module实战指南(四):系统调用劫持linux
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) ...
经过编写一个劫持open()系统调用的Linux Kernel Module,如今咱们能够对系统调用监听、过滤和审计了。进程