Linux環(huán)境下的高級隱藏技術(shù)
添加時間:2016-3-8 17:26:22
添加:
思海網(wǎng)絡(luò)
隱藏技術(shù)在計算機系統(tǒng)安全中應(yīng)用十分廣泛,尤其是在網(wǎng)絡(luò)攻擊中,當攻擊者成功侵入一個系統(tǒng)后,有效隱藏攻擊者的文件、進程及其加載的模塊變得尤為重要。本文將討論Linux系統(tǒng)中文件、進程及模塊的高級隱藏技術(shù),這些技術(shù)有的已經(jīng)被廣泛應(yīng)用到各種后門或安全檢測程序之中,而有一些則剛剛起步,仍然處在討論階段,應(yīng)用很少。
1.隱藏技術(shù)
1.1.Linux下的中斷控制及系統(tǒng)調(diào)用
Intel x86系列微機支持256種中斷,為了使處理器比較容易地識別每種中斷源,把它們從0~256編號,即賦予一個中斷類型碼n,Intel把它稱作中斷向量。
Linux用一個中斷向量(128或者0x80)來實現(xiàn)系統(tǒng)調(diào)用,所有的系統(tǒng)調(diào)用都通過唯一的入口system_call來進入內(nèi)核,當用戶動態(tài)進程執(zhí)行一條int 0x80匯編指令時,CPU就切換到內(nèi)核態(tài),并開始執(zhí)行system_call函數(shù),system_call函數(shù)再通過系統(tǒng)調(diào)用表 sys_call_table來取得相應(yīng)系統(tǒng)調(diào)用的地址進行執(zhí)行。系統(tǒng)調(diào)用表sys_call_table中存放所有系統(tǒng)調(diào)用函數(shù)的地址,每個地址可以用系統(tǒng)調(diào)用號來進行索引,例如sys_call_table[NR_fork]索引到的就是系統(tǒng)調(diào)用sys_fork()的地址。
Linux用中斷描述符(8字節(jié))來表示每個中斷的相關(guān)信息,其格式如下:
偏移量31….16 一些標志、類型碼及保留位
段選擇符 偏移量15….0
所有的中斷描述符存放在一片連續(xù)的地址空間中,這個連續(xù)的地址空間稱作中斷描述符表(IDT),其起始地址存放在中斷描述符表寄存器(IDTR)中,其格式如下:
32位基址值 界限
其中各個結(jié)構(gòu)的相應(yīng)聯(lián)系可以如下表示:
通過上面的說明可以得出通過IDTR寄存器來找到system_call函數(shù)地址的方法:根據(jù)IDTR寄存器找到中斷描述符表,中斷描述符表的第0x80項即是system_call函數(shù)的地址,這個地址將在后面的討論中應(yīng)用到。
1.2.Linux 的LKM(可裝載內(nèi)核模塊)技術(shù)
為了使內(nèi)核保持較小的體積并能夠方便的進行功能擴展,Linux系統(tǒng)提供了模塊機制。模塊是內(nèi)核的一部分,但并沒有被編譯進內(nèi)核,它們被編譯成目標文件,在運行過程中根據(jù)需要動態(tài)的插入內(nèi)核或者從內(nèi)核中移除。由于模塊在插入后是作為Linux內(nèi)核的一部分來運行的,所以模塊編程實際上就是內(nèi)核編程,因此可以在模塊中使用一些由內(nèi)核導(dǎo)出的資源,例如Linux2.4.18版以前的內(nèi)核導(dǎo)出系統(tǒng)調(diào)用表(sys_call_table)的地址,這樣就可以根據(jù)該地址直接修改系統(tǒng)調(diào)用的入口,從而改變系統(tǒng)調(diào)用。在模塊編程中必須存在初始化函數(shù)及清除函數(shù),一般情況下,這兩個函數(shù)默認為 init_module()以及clearup_module(),從2.3.13內(nèi)核版本開始,用戶也可以給這兩個函數(shù)重新命名,初始化函數(shù)在模塊被插入系統(tǒng)時調(diào)用,在其中可以進行一些函數(shù)及符號的注冊工作,清除函數(shù)則在模塊移除系統(tǒng)時進行調(diào)用,一些恢復(fù)工作通常在該函數(shù)中完成。
1.3.Linux下的內(nèi)存映像
/dev/kmem是一個字符設(shè)備,是計算機主存的映像,通過它可以測試甚至修改系統(tǒng),當內(nèi)核不導(dǎo)出sys_call_table地址或者不允許插入模塊時可以通過該映像修改系統(tǒng)調(diào)用,從而實現(xiàn)隱藏文件、進程或者模塊的目的。
1.4.proc 文件系統(tǒng)
proc文件系統(tǒng)是一個虛擬的文件系統(tǒng),它通過文件系統(tǒng)的接口實現(xiàn),用于輸出系統(tǒng)運行狀態(tài)。它以文件系統(tǒng)的形式,為操作系統(tǒng)本身和應(yīng)用進程之間的通信提供了一個界面,使應(yīng)用程序能夠安全、方便地獲得系統(tǒng)當前的運行狀況何內(nèi)核的內(nèi)部數(shù)據(jù)信息,并可以修改某些系統(tǒng)的配置信息。由于proc以文件系統(tǒng)的接口實現(xiàn),因此可以象訪問普通文件一樣訪問它,但它只存在于內(nèi)存之中。
2.技術(shù)分析
2.1 隱藏文件
Linux系統(tǒng)中用來查詢文件信息的系統(tǒng)調(diào)用是sys_getdents,這一點可以通過strace來觀察到,例如strace ls 將列出命令ls用到的系統(tǒng)調(diào)用,從中可以發(fā)現(xiàn)ls是通過sys_getedents來執(zhí)行操作的。當查詢文件或者目錄的相關(guān)信息時,Linux系統(tǒng)用 sys_getedents來執(zhí)行相應(yīng)的查詢操作,并把得到的信息傳遞給用戶空間運行的程序,所以如果修改該系統(tǒng)調(diào)用,去掉結(jié)果中與某些特定文件的相關(guān)信息,那么所有利用該系統(tǒng)調(diào)用的程序?qū)⒖床灰娫撐募,從而達到了隱藏的目的。首先介紹一下原來的系統(tǒng)調(diào)用,其原型為:
int sys_getdents(unsigned int fd, strUCt dirent *dirp,unsigned int count)
其中fd為指向目錄文件的文件描述符,該函數(shù)根據(jù)fd所指向的目錄文件讀取相應(yīng)dirent結(jié)構(gòu),并放入dirp中,其中count為 dirp中返回的數(shù)據(jù)量,正確時該函數(shù)返回值為填充到dirp的字節(jié)數(shù)。下圖是修改后的系統(tǒng)調(diào)用hacked_getdents執(zhí)行流程。
圖中的hacked_getdents函數(shù)實際上就是先調(diào)用原來的系統(tǒng)調(diào)用,然后從得到的dirent結(jié)構(gòu)中去除與特定文件名相關(guān)的文件信息,從而應(yīng)用程序從該系統(tǒng)調(diào)用返回后將看不到該文件的存在。
應(yīng)該注意的是,一些較新的版本中是通過sys_getdents64來查詢文件信息的,但其實現(xiàn)原理與sys_getdents基本相同,所以在這些版本中仍然可以用與上面類似的方法來修改該系統(tǒng)調(diào)用,隱藏文件。
2.2 隱藏模塊
上面分析了如何修改系統(tǒng)調(diào)用以隱藏特定名字的文件,在實際的處理中,經(jīng)常會用模塊來達到修改系統(tǒng)調(diào)用的目的,但是當插入一個模塊時,若不采取任何隱藏措施,很容易被對方發(fā)現(xiàn),一旦對方發(fā)現(xiàn)并卸載了所插入的模塊,那么所有利用該模塊來隱藏的文件就暴露了,所以應(yīng)繼續(xù)分析如何來隱藏特定名字的模塊。Linux中用來查詢模塊信息的系統(tǒng)調(diào)用是sys_query_module,所以可以通過修改該系統(tǒng)調(diào)用達到隱藏特定模塊的目的。首先解釋一下原來的系統(tǒng)調(diào)用,原來系統(tǒng)調(diào)用的原型為:
int sys_query_module(const char *name, int which, void *buf, size_t bufsize , size_t *ret)
如果參數(shù)name不空,則訪問特定的模塊,否則訪問的是內(nèi)核模塊,參數(shù)which說明查詢的類型,當which=QM_MODULES時,返回所有當前已插入的模塊名稱,存入buff, 并且在ret中存放模塊的個數(shù),buffsize是buf緩沖區(qū)的大小。在模塊隱藏的過程中只需要對which=QM_MODULES的情況進行處理就可以達到目的。修改后的系統(tǒng)調(diào)用工作過程如下:
1)調(diào)用原來的系統(tǒng)調(diào)用,出錯則返回錯誤代碼;
2)如果which不等于QM_MODULES,則不需要處理,直接返回。
3)從buf的開始位置進行處理,如果存在特定的名字,則將后面的模塊名稱向前覆蓋該名字。
4)重復(fù)3),直到處理處理完所有的名字,正確返回。
2.3 隱藏進程
在Linux中不存在直接查詢進程信息的系統(tǒng)調(diào)用,類似于ps這樣查詢進程信息的命令是通過查詢proc文件系統(tǒng)來實現(xiàn)的,在背景知識中已經(jīng)介紹過proc文件系統(tǒng),由于它應(yīng)用文件系統(tǒng)的接口實現(xiàn),因此同樣可以用隱藏文件的方法來隱藏proc文件系統(tǒng)中的文件,只需要在上面的 hacked_getdents中加入對于proc文件系統(tǒng)的判斷即可。由于proc是特殊的文件系統(tǒng),只存在于內(nèi)存之中,不存在于任何實際設(shè)備之上,所以Linux內(nèi)核分配給它一個特定的主設(shè)備號0以及一個特定的次設(shè)備號1,除此之外,由于在外存上沒有與之對應(yīng)的i節(jié)點,所以系統(tǒng)也分配給它一個特殊的節(jié)點號PROC_ROOT_INO(值為1),而設(shè)備上的1號索引節(jié)點是保留不用的。通過上面的分析,可以得出判斷一個文件是否屬于proc文件系統(tǒng)的方法:
1)得到該文件對應(yīng)的inode結(jié)構(gòu)dinode;
2)if (dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i _dev) == 1)
通過上面的分析,給出隱藏特定進程的偽代碼表示:
hacket_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
{
調(diào)用原來的系統(tǒng)調(diào)用;
得到fd所對應(yīng)的節(jié)點;
if(該文件屬于proc文件系統(tǒng)&&該文件名需要隱藏)
}
2.4 修改系統(tǒng)調(diào)用的方法
現(xiàn)在已經(jīng)解決了如何修改系統(tǒng)調(diào)用來達到隱藏的目的,那么如何用修改后的系統(tǒng)調(diào)用來替換原來的呢?這個問題在實際應(yīng)用中往往是最關(guān)鍵的,下面將討論在不同的情況下如何做到這一點。
(1)當系統(tǒng)導(dǎo)出sys_call_table,并且支持動態(tài)的插入模塊的情況下:
在Linux內(nèi)核2.4.18版以前,這種內(nèi)核配置是非常普遍的。這種情況下修改系統(tǒng)調(diào)用非常容易,只需要修改相應(yīng)的sys_call_table表項,使其指向新的系統(tǒng)調(diào)用即可。下面是相應(yīng)的代碼:
int orig_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
int init_module(void)
/*初始化模塊*/
{
orig_getdents=sys_call_table[SYS_getdents]; //保存原來的系統(tǒng)調(diào)用
orig_query_module=sys_call_table[SYS_query_module]
sys_call_table[SYS_getdents]=hacked_getdents; //設(shè)置新的系統(tǒng)調(diào)用
sys_call_table[SYS_query_module]=hacked_query_module;
return 0; //返回0表示成功
}
void cleanup_module(void)
/*卸載模塊*/
{
sys_call_table[SYS_getdents]=orig_getdents; //恢復(fù)原來的系統(tǒng)調(diào)用
sys_call_table[SYS_query_module]=orig_query_module;
}
(2)在系統(tǒng)并不導(dǎo)出sys_call_table的情況下:
linux內(nèi)核在2.4.18以后為了安全起見不再導(dǎo)出sys_call_table符號,從而無法直接獲得系統(tǒng)調(diào)用表的地址,那么就必須找到其他的辦法來得到這個地址。在背景知識中提到了/dev/kmem是系統(tǒng)主存的映像,可以通過查詢該文件來找到sys_call_table的地址,并對其進行修改,來使用新的系統(tǒng)調(diào)用。那么如何在系統(tǒng)映像中找到sys_call_table的地址呢?讓我們先看看system_call的源代碼是如何來實現(xiàn)系統(tǒng)調(diào)用的(代碼見/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)
這段源代碼首先保存相應(yīng)的寄存器的值,然后判斷系統(tǒng)調(diào)用號(在eax寄存器中)是否合法,繼而對設(shè)置調(diào)試的情況進行處理,在所有這些進行完后,利用call *SYMBOL_NAME(sys_call_table)(,%eax,4) 來轉(zhuǎn)入相應(yīng)的系統(tǒng)調(diào)用進行處理,其中的SYMBOL_NAME(sys_call_table)得出的就是sys_call_table的地址。從上面的分析可以看出,當找到system_call函數(shù)之后,利用字符匹配來尋找相應(yīng)call語句就可以確定sys_call_table的位置,因為call something(,%eax,4)的機器指令碼是0xff 0x14 0x85。所以匹配這個指令碼就行了。至于如何確定system_call的地址在背景知識中已經(jīng)介紹了,下面給出相應(yīng)的偽代碼:
struct{ //各字段含義可以參考背景知識中關(guān)于IDTR寄存器的介紹
unsigned short limit;
unsigned int base;
}__attribute__((packed))idtr;
struct{ //各字段含義可以參考背景知識中關(guān)于中斷描述符的介紹
unsigned short off1;
unsigned short sel;
unsigned char none,flags;
unsigned short off2;
}__attribute__((packed))idt;
int kmem;
/ *下面函數(shù)用于從kemem對應(yīng)的文件中偏移量為off處讀取sz個字節(jié)至內(nèi)存m處*/
void readkmem(void *m,unsigned off,int sz)
/*下面函數(shù)用于從src讀取count個字節(jié)至dest處*/
void weitekmem(void *src,void *dest,unsigned int count)
unsigned sct; //用來存放sys_call_table地址
char buff[100]; //用于存放system_call函數(shù)的前100個字節(jié)。
char *p;
if((kmem=open(“/dev/kmem”,O_RDONLY))<0)
return 1;
asm(“sidt %0” “:=m” (idtr)); //讀取idtr寄存器的值至idtr結(jié)構(gòu)中
readkmem(&idt,idtr.base+8*0x80,sizeof(idt)) //將0x80描述符讀至idt結(jié)構(gòu)中
sys_ call_off=(idt.off2<<16)|idt.off1; //得到system_call函數(shù)的地址。
readkmem(buff,sys_call_off,100) //讀取system_call函數(shù)的前100字節(jié)至buff
p=(char *)memmem(buff,100,”xffx14x85”,3); //得到call語句對應(yīng)機器碼的地址
sct=(unsigned *)(p+3) //得到sys_call_table的地址。
至此已經(jīng)得到了sys_call_table在內(nèi)存中的位置,這樣在根據(jù)系統(tǒng)調(diào)用號就能夠找到相應(yīng)的系統(tǒng)調(diào)用對應(yīng)的地址,修改該地址就可以使用新的系統(tǒng)調(diào)函數(shù),具體的做法如下:
readkmem(&orig_getdents,sct+ SYS_getdents*4,4)//保存原來的系統(tǒng)調(diào)用
readkmem(&orig_query_module,sct+SYS_query_module*4,4);
writekmem(hacked_getdents,sct+SYS_getdents*4,4);//設(shè)置新的系統(tǒng)調(diào)用
writekmem(hacket_query_module,sct+SYS_query_module*4,4);
2.5 其他的相關(guān)技術(shù)
上面已經(jīng)完全解決了隱藏的相關(guān)技術(shù)問題,在實際應(yīng)用中,可以把啟動模塊或者進程的代碼做成腳本加入到相應(yīng)的啟動目錄中,假設(shè)你的Linux運行級別為3,則可以加到目錄rc3.d中(該目錄常存在于/etc/rc.d或者/etc目錄下),然后把該腳本的名字改為可以隱藏的名字。另一種方法就是在一些啟動腳本中加入啟動你的模塊或者進程的代碼,但這樣比較容易被發(fā)現(xiàn),一個解決思路就是進程或模塊啟動以后馬上恢復(fù)正常的腳本,由于系統(tǒng)關(guān)機時會向所有進程發(fā)送SIGHUP信號,可以在進程或模塊中處理該信號,使該信號發(fā)生時修改啟動腳本,重新加入啟動模塊的代碼,這樣當系統(tǒng)下次啟動時又可以加載這個的模塊了,而且管理員察看啟動腳本時也不會發(fā)現(xiàn)異常。
3.結(jié)束語
本文對Linux環(huán)境下的一些高級隱藏技術(shù)進行了分析研究,其中所涉及的技術(shù)不僅可以用在系統(tǒng)安全方面,在其他方面也有重要的借鑒意義。由于Linux的開放特性,使得攻擊者一旦獲得了root權(quán)限就能夠?qū)ο到y(tǒng)進行較多的修改,所以避免第一次被入侵是至關(guān)重要的
readkmem(&orig_getdents,sct+ SYS_getdents*4,4)//保存原來的系統(tǒng)調(diào)用
readkmem(&orig_query_module,sct+SYS_query_module*4,4);
writekmem(hacked_getdents,sct+SYS_getdents*4,4);//設(shè)置新的系統(tǒng)調(diào)用
writekmem(hacket_query_module,sct+SYS_query_module*4,4);
2.5 其他的相關(guān)技術(shù)
上面已經(jīng)完全解決了隱藏的相關(guān)技術(shù)問題,在實際應(yīng)用中,可以把啟動模塊或者進程的代碼做成腳本加入到相應(yīng)的啟動目錄中,假設(shè)你的Linux運行級別為3,則可以加到目錄rc3.d中(該目錄常存在于/etc/rc.d或者/etc目錄下),然后把該腳本的名字改為可以隱藏的名字。另一種方法就是在一些啟動腳本中加入啟動你的模塊或者進程的代碼,但這樣比較容易被發(fā)現(xiàn),一個解決思路就是進程或模塊啟動以后馬上恢復(fù)正常的腳本,由于系統(tǒng)關(guān)機時會向所有進程發(fā)送SIGHUP信號,可以在進程或模塊中處理該信號,使該信號發(fā)生時修改啟動腳本,重新加入啟動模塊的代碼,這樣當系統(tǒng)下次啟動時又可以加載這個的模塊了,而且管理員察看啟動腳本時也不會發(fā)現(xiàn)異常。
3.結(jié)束語
本文對Linux環(huán)境下的一些高級隱藏技術(shù)進行了分析研究,其中所涉及的技術(shù)不僅可以用在系統(tǒng)安全方面,在其他方面也有重要的借鑒意義。由于Linux的開放特性,使得攻擊者一旦獲得了root權(quán)限就能夠?qū)ο到y(tǒng)進行較多的修改,所以避免第一次被入侵是至關(guān)重要的。
關(guān)鍵字:Linux、環(huán)境、內(nèi)核編程
新文章:
- CentOS7下圖形配置網(wǎng)絡(luò)的方法
- CentOS 7如何添加刪除用戶
- 如何解決centos7雙系統(tǒng)后丟失windows啟動項
- CentOS單網(wǎng)卡如何批量添加不同IP段
- CentOS下iconv命令的介紹
- Centos7 SSH密鑰登陸及密碼密鑰雙重驗證詳解
- CentOS 7.1添加刪除用戶的方法
- CentOS查找/掃描局域網(wǎng)打印機IP講解
- CentOS7使用hostapd實現(xiàn)無AP模式的詳解
- su命令不能切換root的解決方法
- 解決VMware下CentOS7網(wǎng)絡(luò)重啟出錯
- 解決Centos7雙系統(tǒng)后丟失windows啟動項
- CentOS下如何避免文件覆蓋
- CentOS7和CentOS6系統(tǒng)有什么不同呢
- Centos 6.6默認iptable規(guī)則詳解