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


  其實用這個標題隨便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,居然沒有提示並加載成功了。運行測試函數,輸出了正確結果,這么看來代碼本身是沒有問題的,問題應該出在復制代碼時的空格上。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM