Linux 2.6 中導出sys_call_table表修改系統調用函數

         Linux中實現系統調用時是利用了i386體系結構中的軟中斷,通過產生0x80中斷,使程序由用戶態進入內核態執行系統調用函數。當系統調用發生時,產生0x80中斷,CPU被切換到內核態執行中斷向量表IDT對應的0x80中斷處理函數,即跳轉到了system_call()的入口,system_call()函數檢查系統調用號,到系統調用表sys_call_table中找到該系統調用(號)對應的內核函數入口,接着調用這個內核函數,然後返回。

                                                

     

   那麼要修改系統調,只需要在sys_call_table中將原有的系統調用替換爲自己寫的新系統調用就可以了。但是這對用戶來說無疑是個巨大的安全隱患。在2.4內核中sys_call_table是直接導出的,使改變系統調用函數變得很容易,出現了很多安全問題(後門程序)。在2.6內核中已經沒有將sys_call_table導出了。所以我們第一件要做的事情就要想辦法“重新”導出sys_call_table,找到其地址。

(一) 導出sys_call_table

 1、首先我們在Sysmap中查看sys_call_table 地址。

[root@sun ~]# grep sys_call_table /boot/System.map-2.6.35.6-45.fc14.i686 
c07ae328 R sys_call_table
其中 R 直接顯示了這段內存是隻讀的性質,這就引出了第二步驟“取消頁讀寫保護”。


2、可以直接修改內核源碼,自己導出sys_call_table,然後再編譯內核。這個辦法耗時,不可移植。

修改過程分爲三步:

1、修改arch/x86/kernel/entry_32.S
將語句
-.section .rodata,"a"
替換爲
+.section .data,"aw"
將sys_call_table設置爲可讀可寫

2、修改kernel/kallsyms.c,到處sys_call_table符號
+extern void *sys_call_table;
+EXPORT_SYMBOL(sys_call_table);

3、在自己的模塊程序中聲明sys_call_table並使用
extern void *sys_call_table[];
old_entry = sys_call_table[285];
sys_call_table[285] = sys_storeint;

3、從中斷向量表獲取系統調用符號表sys_call_table。(採用)

unsigned long* find_sys_call_table(void)
{
		// 中斷描述符表寄存器結構
        struct {
                unsigned short  limit;
                unsigned int    base;
        } __attribute__ ( ( packed ) ) idtr;
 
       // 中斷描述符表結構
        struct {
                unsigned short  offset_low;
                unsigned short  segment_select;
                unsigned char   reserved,   flags;
                unsigned short  offset_high;
        } __attribute__ ( ( packed ) ) * idt;
 
        unsigned long system_call = 0;        // x80中斷處理程序system_call 地址
        char *call_hex = "\xff\x14\x85";        // call 指令
        char *code_ptr = NULL;
        char *p = NULL;
        unsigned long sct = 0x0;
        int i = 0;
	
        //通過sidt指令獲得中斷描述表寄存器內容放入idtr,通過idtr.base即可得到idt的基地址
        __asm__ ( "sidt %0": "=m" ( idtr ) );
        idt = ( void * ) ( idtr.base + 8 * 0x80 );
        system_call = ( idt->offset_high << 16 ) | idt->offset_low;
 
        code_ptr = (char *)system_call;
        for(i = 0;i < ( 100 - 2 ); i++) {
                if(code_ptr[i] == call_hex[0]
                                && code_ptr[i+1] == call_hex[1]
                                && code_ptr[i+2] == call_hex[2] ) {
                        p = &code_ptr[i] + 3;
                        break;
                }
        }
        if ( p ){
                sct = *(unsigned long*)p;
        }
        return (unsigned long*)sct;
}

實現原理:

      在linux中使用0x80異常實現系統調用,因此,主要通過先獲取中斷向量表,然後或許0x80中斷處理函數(系統調用處理函數system_call())地址,最後根據system_call()編碼特點找到 sys_call_table。

(1)中斷向量表的獲取

   在x86中,idtr寄存器使得中斷向量表可以存放在內存的任何位置,idtr寄存器有一個基地址和一個段限地址組成,高4字節爲基地址,低2字節爲段限地址。可以通過sidt指令獲得idtr的內容。

// 中斷描述符表寄存器結構
struct {
unsigned short limit;
unsigned int base;
} __attribute__ ( ( packed ) ) idtr;

__asm__ ( "sidt %0": "=m" ( idtr ) );

通過sidt指令獲得中斷描述表寄存器內容放入idtr,通過idtr.base即可得到idt的基地址

(2)系統調用處理函數地址的獲取

    IDT基地址存放的是中斷門,每個門8個字節,門描述符的格式參考Intel開發手冊,其中,中斷門是最低兩個字節和最高兩個字節構成了中斷處理程序的地址。

   // 中斷描述符表結構
        struct {
                unsigned short  offset_low;
                unsigned short  segment_select;
                unsigned char   reserved,   flags;
                unsigned short  offset_high;
        } __attribute__ ( ( packed ) ) * idt;

獲取系統調用中斷處理程序sys_call()的地址:

idt = ( void * ) ( idtr.base + 8 * 0x80 );

system_call = ( idt->offset_high << 16 ) | idt->offset_low;

(3)獲取系統調用表sys_call_table

        system_call是所有系統調用的處理程序,在進行必要的處理後,統一調用 call sys_call_table(,eax,4)來調用sys_call_table表中的系統調用服務,eax存放的即時系統調用號,因此,獲取sys_call_table的地址即可以達到目的。
        通過反彙編sys_call函數,可以得知,只有在調用系統調用處使用了call指令,x86 call指令的二進制格式爲\xff\x14\x85,因此,我們可以從sys_call函數開始進行搜索,當出現\xff\x14\x85指令的時候,即爲call的地址,從而能得到存放sys_call_table的地址即當前地址+3,而系統調用表即地址的內容,因此,獲取系統調用表地址的實現過程就簡單了。

code_ptr = (char *)system_call;
        for(i = 0;i < ( 100 - 2 ); i++) {
                if(code_ptr[i] == call_hex[0]
                                && code_ptr[i+1] == call_hex[1]
                                && code_ptr[i+2] == call_hex[2] ) {
                        p = &code_ptr[i] + 3;
                        break;
                }
        }
        if ( p ){
                sct = *(unsigned long*)p;
        }

這是通過簡單的搜索的方式來找到call 指令,從而得到sys_call_table的地址的。

(二)取消sys_call_table頁表的寫保護

我們在上述看到sys_call_table只是可讀的,那麼我們就沒法修改系統調用符號表,用自己寫的系統調用函數替換原有函數。所有我們需要取消頁的讀寫保護。

  1、使用change_page_attr()函數。但是經查證在2.6.25以後就取消掉了該函數,故以下方法在本系統(2.6.35.6)中已經失效了,不能編譯通過

      static 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);
      }
     
      static 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);
      }

2、設置CR0 的WP位,來取消寫保護。(採用)

原理:對於Intel 80486或以上的CPU,CR0的位16是寫保護(Write Proctect)標誌。當設置該標誌時,處理器會禁止超級用戶程序(例如特權級0的程序)向用戶級只讀頁面執行寫操作;當該位復位時則反之。該標誌有利於UNIX類操作系統在創建進程時實現寫時複製(Copy on Write)技術。

/* FUNCTION TO DISABLE WRITE PROTECT BIT IN CPU */
static void disable_wp(void)
{
        unsigned int cr0_value;
        
        asm volatile ("movl %%cr0, %0" : "=r" (cr0_value));
        
        /* Disable WP */
        cr0_value &= ~(1 << 16);
        
        asm volatile ("movl %0, %%cr0" :: "r" (cr0_value));

}
        
/* FUNCTION TO RE-ENABLE WRITE PROTECT BIT IN CPU */
static void enable_wp(void)
{
        unsigned int cr0_value;

        asm volatile ("movl %%cr0, %0" : "=r" (cr0_value));

        /* Enable WP */
        cr0_value |= (1 << 16);

        asm volatile ("movl %0, %%cr0" :: "r" (cr0_value));

}


(三)修改系統調用open的實例

        在前面的基礎上,寫一個自己的open系統調用。自己的系統調用中加入對打開文件的記錄,並打印記錄,最後在調用原始系統調用open(注意:如果最後不調用原始open函數,那麼會帶來極大的災難,例如mkdir等很多命令不能使用)。本模塊在2.6.35.6內核中編譯、安裝通過。

syscall.c

#include <linux/kernel.h>   
#include <linux/module.h>   
#include <linux/moduleparam.h>  
#include <linux/unistd.h>  
#include <linux/init.h>
/* 
 * For the current (process) structure, we need
 * this to know who the current user is. 
 */
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <asm/cacheflush.h>



static  unsigned long **sys_call_table;


unsigned long* find_sys_call_table(void)
{
        struct {
                unsigned short  limit;
                unsigned int    base;
        } __attribute__ ( ( packed ) ) idtr;
 
        struct {
                unsigned short  offset_low;
                unsigned short  segment_select;
                unsigned char   reserved,   flags;
                unsigned short  offset_high;
        } __attribute__ ( ( packed ) ) * idt;
 
        unsigned long system_call = 0;        // x80中斷處理程序system_call 地址
        char *call_hex = "\xff\x14\x85";        // call 指令
        char *code_ptr = NULL;
        char *p = NULL;
        unsigned long sct = 0x0;
        int i = 0;
 
        __asm__ ( "sidt %0": "=m" ( idtr ) );
        idt = ( void * ) ( idtr.base + 8 * 0x80 );
        system_call = ( idt->offset_high << 16 ) | idt->offset_low;
 
        code_ptr = (char *)system_call;
        for(i = 0;i < ( 100 - 2 ); i++) {
                if(code_ptr[i] == call_hex[0]
                                && code_ptr[i+1] == call_hex[1]
                                && code_ptr[i+2] == call_hex[2] ) {
                        p = &code_ptr[i] + 3;
                        break;
                }
        }
        if ( p ){
                sct = *(unsigned long*)p;
        }
        return (unsigned long*)sct;
}



/* FUNCTION TO DISABLE WRITE PROTECT BIT IN CPU */
static void disable_wp(void)
{
        unsigned int cr0_value;
        
        asm volatile ("movl %%cr0, %0" : "=r" (cr0_value));
        
        /* Disable WP */
        cr0_value &= ~(1 << 16);
        
        asm volatile ("movl %0, %%cr0" :: "r" (cr0_value));

}
        
/* FUNCTION TO RE-ENABLE WRITE PROTECT BIT IN CPU */
static void enable_wp(void)
{
        unsigned int cr0_value;

        asm volatile ("movl %%cr0, %0" : "=r" (cr0_value));

        /* Enable WP */
        cr0_value |= (1 << 16);

        asm volatile ("movl %0, %%cr0" :: "r" (cr0_value));

}



/* 
 * UID we want to spy on - will be filled from the
 * command line 
 */
static int uid;
module_param(uid, int, 0644);

asmlinkage int (*original_call) (const char *, int, int);


asmlinkage int our_sys_open(const char *filename, int flags, int mode)
{
    int i = 0;
    char ch;

    /* 
     * Check if this is the user we're spying on 
     */
    if (uid == current->cred->uid) {     //2.6.35 中current->cred->uid
        printk("Opened file by %d: ", uid);
        do {
            get_user(ch, filename + i);
            i++;
            printk("%c", ch);
        } while (ch != 0);
        printk("\n");
    }

    /* 
     * Call the original sys_open - otherwise, we lose
     * the ability to open files 
     */
    return original_call(filename, flags, mode);
}



/* 
 * Initialize the module - replace the system call 
 */


unsigned int cr0;

int init_module()
{
    
    sys_call_table=find_sys_call_table();
    disable_wp();
    
    original_call=sys_call_table[__NR_open];
    sys_call_table[__NR_open] = (long*)our_sys_open;
   

    printk("Spying on UID:%d\n", uid);

    return 0;
}

/* 
 * Cleanup - unregister the appropriate file from /proc 
 */
void cleanup_module()
{
    sys_call_table[__NR_open] = (long *)original_call;
    enable_wp();
}

MODULE_LICENSE("GPL");

Makefile

obj-m:=syscall.o

all:
        make -C /usr/src/kernels/$(shell uname -r)  M=$(shell pwd) modules 
clean:
        make -C /usr/src/kernels/$(shell uname -r) M=$(shell pwd) clean

學習參考:

linux 系統調用中斷劫持實現—原理和代碼。

http://blog.sina.com.cn/s/blog_596d00a70100jpa7.html

The Linux Kernel Module Programming Guide(chapter 8)

http://www.tldp.org/LDP/lkmpg/2.6/html/

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