利用模塊添加系統調用(不重新編譯內核)

其實用這個標題隨便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");

  測試代碼

#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,居然沒有提示並加載成功了。運行測試函數,輸出了正確結果,這麼看來代碼本身是沒有問題的,問題應該出在複製代碼時的空格上。

  這裏可以看出,這個模塊如果放到別的環境中是不能運行的,移植時,系統調用號和系統調用表地址必須做出相應的修改。

作者:五嶽 
出處:http://www.cnblogs.com/wuyuegb2312 
對於標題未標註爲“轉載”的文章均爲原創,其版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。


編譯了一個模塊,在內核空間啓動一個實時任務。
insmod 後直接提示“已殺死”。
lsmod 後發現這個模塊正在被使用(used 一欄顯示 1),但是後面沒有顯示是被哪個模塊使用...(by一欄是空的),所以也沒辦法卸載,rmmod -f 也不好用...
請問這種情況一般是什麼原因造成的?

 

 

 

從彙編看有可能是gcc優化導致在訪問0xc05fd5dc時,cr0還沒有被修改.把gcc的編譯優化關掉,即在makefile中加一行:
EXTRA_CFLAGS= -O0,再編譯試下.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章