原文地址: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,如今咱們能夠對系統調用監聽、過濾和審計了。進程