其實用這個標題隨便baidu、Google出來都是一大堆,大部分都是轉來轉去,代碼無非那么幾種。可是真正編譯通過還是費了不少功夫,我在雙系統的Ubuntu10.04和虛擬機里的Red Hat9里來來回回不知折騰了多少次。所以本文更多的是記錄下自己調試的細節,而不是簡單的粘代碼和轉載。
目的是在不重新編譯內核的前提下添加系統調用,思路倒是很簡單,修改映射在內存中的系統調用表,把一個空閑的系統調用表項指向自己寫的模塊中的函數,如果是已使用的表項,甚至可以實現系統調用劫持。
分配的空閑的系統調用號依然要在源碼的asm/unistd.h中去找,只是不用修改。如果沒有unused的,怕是還是得重新編譯內核了,畢竟系統調用表在編譯后大小就固定了,映射到內存中也是固定的,在內存中這個表的最后新增加表項難免會溢出。
找到系統調用號后,還要找系統調用表在內存中的位置。當前系統可以查看/proc/kallsyms的。對於不同的內核,它放在編譯后的System.map中,同樣可以在/boot/System.map.X.X.XX.XX中查看。我的是c057e110,由於它是十六進制數,這在源代碼中前面還要加0x。而且通過R標志可以看出它是只讀的。
為了修改內存中的表項,還要修改寄存器寫保護位,否則是不能修改的。它位於cr0的16位,其修改和保存由模塊初始化函數實現。
模塊代碼基本是參考http://www.lupaworld.com/home.php?mod=space&uid=401174&do=blog&id=229115,幾乎沒有改動,只是系統調用提供的功能不同,這不是重點。
syscall.c
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/unistd.h> #include <linux/time.h> #include <asm/uaccess.h> #include <linux/sched.h> #define __NR_syscall 223 #define SYS_CALL_TABLE_ADDRESS 0xc057e110 unsigned int clear_and_return_cr0(void); void setback_cr0(unsigned int val); int orig_cr0; unsigned long *sys_call_table = 0; static int (*anything_saved)(void); unsigned int clear_and_return_cr0(void) { unsigned int cr0 = 0; unsigned int ret; asm volatile ("movl %%cr0, %%eax" : "=a"(cr0) ); ret = cr0; cr0 &= 0xfffeffff; asm volatile ("movl %%eax, %%cr0" : : "a"(cr0) ); return ret; } void setback_cr0(unsigned int val) { asm volatile ("movl %%eax, %%cr0" : : "a"(val) ); } asmlinkage long sys_mycall(long arg1, long arg2, char func, long *ret_val) { switch(func) { case '+':*ret_val = arg1 + arg2;return 1; case '-':*ret_val = arg1 - arg2;return 1; case '*':*ret_val = arg1 * arg2;return 1; case '/':if (arg2 == 0) {*ret_val = 0;return 0;} else {*ret_val = arg1 / arg2;return 1;} default: *ret_val = 0;return -1; } } int init_addsyscall(void) { sys_call_table = (unsigned long *)SYS_CALL_TABLE_ADDRESS; anything_saved = (int(*)(void))(sys_call_table[__NR_syscall]); orig_cr0 = clear_and_return_cr0(); sys_call_table[__NR_syscall] = (unsigned long)&sys_mycall; setback_cr0(orig_cr0); return 0; } void exit_addsyscall(void) { orig_cr0 = clear_and_return_cr0(); sys_call_table[__NR_syscall] = (unsigned long)anything_saved; setback_cr0(orig_cr0); printk("call exit....\n"); } module_init(init_addsyscall); module_exit(exit_addsyscall); MODULE_AUTHOR("WY"); MODULE_LICENSE("GPL");
測試代碼
test1.c
#include <stdio.h> #include <stdlib.h> int main() { long x = 0; syscall(223,1,1,'+',&x); printf("syscall result is %ld\n", x); return 0; }
最初用帶參數的gcc編譯時總是出錯,看了一些帖子,照貓畫虎的寫了一個Makefile,現在覺得它是很有必要的:
obj-m := syscall.o KERNELDIR := /lib/modules/2.6.32.22/build PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules modules_install: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
模塊倒是正確生成了syscall.ko,但是insmod時終端直接顯示“段錯誤”,lsmod里看卻是加載了,但是系統調用的功能無法實現,而且這個模塊還卸載不掉,總是顯示in use。編譯的時候也沒有把rmmod的-f功能打開,不能強制卸載,只能重啟,相當郁悶。
沒辦法,只能找問題所在了。Linux下的調試不是很熟悉,打算用printk手動找bug。以前沒用過printk,查詢了一下,使用dmesg就可以看到它的輸出了。於是在模塊里加了一些。首先定位出內聯匯編那里執行不下去了,但是與內聯匯編的代碼比照,反復看都沒有問題。不經意間刪除了一些空格,替換成了Tab(其他位置也有這樣做),再次重啟、make、insmod,居然沒有提示並加載成功了。運行測試函數,輸出了正確結果,這么看來代碼本身是沒有問題的,問題應該出在復制代碼時的空格上。
這里可以看出,這個模塊如果放到別的環境中是不能運行的,移植時,系統調用號和系統調用表地址必須做出相應的修改。
