?? 病毒編程技術.txt
字號:
and ebx,0ffe00000h ;該地址為Kernel32.dll模塊下方的某個地址
;先減去0x100000確保該地址處于Kernel32.dll的下方
;向高地址搜索如果將來Windows的發行版本中Kernel32.dll
;大小和代碼結構發生變化,該方法可能無效
ebx中現在已經是Kernel32.DLL基址之前某個地址了,后續代碼可以向高地址搜索其基址。該方法有一個缺點,就是必須明確知道程序入口的堆棧指針值,或間接可計算出該值,對于那些在程序入口獲取控制權的病毒代碼而言,是可以的,但對于采用EPO技術的病毒而言,該方法則不適用。事實上還有另外一種更加通用的方法,我們知道在Win32程序執行過程中fs段寄存器的基址總是指向進程的TEB,TEB的第一個成員指向SEH鏈表,該鏈表每個節點都是一個EXCEPTION_REGISTRATION結構,該結構定義如下:
struct EXCEPTION_REGISTRATION{
struct EXCEPTION_REGISTRATION *prev;
void* handler;
};
在Windows下SEH鏈表最后一個成員的handler指向Kernel32.DLL中函數UnhandledExceptionFilter的起始地址,利用這一特性我們可以寫出更通用的代碼:
xor esi,esi
lods dword [fs:esi];取得SEH鏈表的頭指針
@@:
inc eax ;是否是最后一個SEH節點,檢查prev是否為0xFFFFFFFF
je @F
dec eax
xchg esi,eax
LODSD ;下一個SEH節點
jmp near @B
@@:
LODSD ;取得Kernel32.dll中UnhandledExceptionFilter的地址
在有的病毒直接以0x7FFDE000作為TEB的指針值,其原因在于在Windows 2003 SP1、Windows XP SP2以前的NT類系統上,該值是固定的,這樣的確可以節省一兩個字節。但是在Windows 2003 SP1、Windows XP SP2中,情況已經發生了變化,出于安全性的考慮,Windows系統開始動態映射TEB了,也就是說,指向TEB的指針值不再固定,因此這種硬編碼的方法也就走到了盡頭。此時可以按照前面的方法向低地址搜索判斷直到找到Kernel32.dll的基址為止。Elkern中判斷是否找到了Kernel32.dll基址的相關代碼如下:
search_api_addr_@1:
add ebx,10000h
jz short search_api_addr_seh_restore
cmp word ptr [ebx],'ZM' ;是否是MZ標志
jnz short search_api_addr_@1
mov eax,[ebx+3ch]
add eax,ebx
cmp word ptr [eax],'EP' ;是否具有PE標志
jnz short search_api_addr_@1
;找到了kernel32.dll的基址
方法2:搜索PEB的相關結構獲取Kernel32.DLL的基址
前述TEB偏移0x30處,亦即FS:[0x30]地址處保存著一個重要的指針,該指針指向PEB(進程環境塊),PEB成員很多,這里并不介紹PEB的詳細結構。我們只需要知道PEB結構的偏移0xC處保存著另外一個重要指針ldr,該指針指向PEB_LDR_DATA結構:
typedef struct _PEB_LDR_DATA
{
ULONG Length; // +0x00
BOOLEAN Initialized; // +0x04
PVOID SsHandle; // +0x08
LIST_ENTRY InLoadOrderModuleList; // +0x0c
LIST_ENTRY InMemoryOrderModuleList; // +0x14
LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
該結構的后三個成員是指向LDR_MODULE鏈表結構中相應三條雙向鏈表頭的指針,分別是按照加載順序、在內存中的地址順序和初始化順序排列的模塊信息結構的指針。LDR_MODULE結構如下所示:
typedef struct _LDR_MODULE
{
LIST_ENTRY InLoadOrderModuleList; // +0x00
LIST_ENTRY InMemoryOrderModuleList; // +0x08
LIST_ENTRY InInitializationOrderModuleList; // +0x10
PVOID BaseAddress; // +0x18
PVOID EntryPoint; // +0x1c
ULONG SizeOfImage; // +0x20
UNICODE_STRING FullDllName; // +0x24
UNICODE_STRING BaseDllName; // +0x2c
ULONG Flags; // +0x34
SHORT LoadCount; // +0x38
SHORT TlsIndex; // +0x3a
LIST_ENTRY HashTableEntry; // +0x3c
ULONG TimeDateStamp; // +0x44
// +0x48
} LDR_MODULE, *PLDR_MODULE;
Peb->Ldr->InInitializationOrderModuleList指向按照初始化順序排序的第一個LDR_MODULE節點的InInitializationOrderModuleList成員的指針,在WinNT平臺(不包含Win9X)下,該鏈表頭節點的LDR_MODULE結構包含的是NTDLL.DLL的相關信息,而鏈表的下一個節點所包含的就是Kernel32.dll相關的信息了,該節點LDR_MODULE結構中的BaseAddress不正是我們所苦苦尋找的嗎。注意InInitializationOrderModuleList是LDR_MODULE的第3個成員,因此要獲取BaseAddress的地址,只需將其指針加8再derefrence即可。因此下面的匯編代碼即可獲取Kernel32.DLL的基址:
mov eax, dword ptr fs:[30h] ;獲取PEB基址
mov eax, dword ptr [eax+0ch] ;獲取PEB_LDR_DATA結構指針
mov esi, dword ptr [eax+1ch]
;獲取InInitializationOrderModuleList鏈表頭第一個LDR_MODULE節點
InInitializationOrderModuleList成員的指針
lodsd ;獲取雙向鏈表當前節點后繼的指針
mov ebx, dword ptr [eax+08h] ;取其基地址,該結構當前包含的是
;kernel32.dll相關的信息
該方法在所有的Windows NT(包括Windows 2003 SP1和Windows XP SP2)操作系統上都是有效的,唯一的缺憾是由于PEB結構不同,該方法在Win9X系統上無效。聽起來可能比較費解,還是用一張圖更加清晰一些:
圖6 利用PEB搜索kernel32.dll基地址的過程
* 解析PE文件的導出函數表
PE文件的函數導出機制是進行模塊間動態調用的重要機制,對于正常的程序,相關操作是由系統加載器在程序加載前自動完成的,對用戶程序是透明的。但要想在病毒代碼中實現函數地址的動態解析以取代加載器,那就有必要了解函數導出表的結構了。在圖1中可以看到在PE頭結構IMAGE_OPTIONAL_HEADER32結構中包含一個DataDirectory數組結構,該結構包含16個成員,每個成員都是一個IMAGE_DATA_DIRECTORY結構:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
DataDirectory數組的每個結構都指向一個重要的數據結構,第一個成員指向導出函數表(索引0),第2個成員指向PE文件的引入函數表(索引1)。DataDirectory中的第一個成員指向導出函數表的IMAGE_EXPORT_DIRECTORY結構:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
AddressOfFunctions是一個雙字數組,包含了所有導出函數的RVA,另外兩個成員AddressOfNames也是一個雙字數組,包含了指向導出函數名字的字符串的RVA,AddressOfNameOrdinals是一個字數組(16bit),和AddressOfNames數組是并行的,和AddressOfNames數組一起確定了相應引出函數的序號,該序號可直接用于索引AddressOfFunctions數組獲取導出函數的地址。因此病毒搜索指定的API就包含了如下步驟:
a)獲取NumberOfNames的值以及AddressOfNames、AddressOfNameOrdinals和AddressOfFunctions的數組的地址。
b)搜索AddressOfNames數組,按字符串對比,若找到相應的API,轉d
c)若NumberOfNames名字尚未全部搜索完畢,轉b繼續搜索,若搜索完畢,則表明未找到進行錯誤處理,這一步通常可以省略,因為我們已經知道相應的DLL中肯定導出了相應的函數。
d)獲取當前函數名字指針在AddressOfNames數組中的索引,在AddressOfNameOrdinals數組中取出以該值索引的函數序號,以該序號值作為AddressOfFunctions數組的索引,在AddressOfFunctions數組中取出導出函數的RVA值,加上基址就得到了運行時導出函數的地址。
看起來似乎比較羅嗦,實際上這是PE設計時為考慮靈活性而做出的犧牲。不過實現起來還是比較簡單的,通常匯編代碼編譯后不到100字節。以下是在Kernel32搜索GetProcAddress的完整代碼:
push esi
;esi=VA Kernel32.BASE
;edi=RVA K32.pehdr
mov ebp,esi
mov edi,[ebp+edi+peh.DataDirectory]
push edi esi
mov eax,[ebp+edi+peexc.AddressOfNames]
mov edx,[ebp+edi+peexc.AddressOfNameOrdinals]
call @F
db "GetProcAddress",0
@@:
pop edi
mov ecx,15
sub eax,4
next_:
add eax,4
add edi,ecx
sub edi,15
mov esi,[ebp+eax]
add esi,ebp
mov ecx,15
repz cmpsb ;進行字符串比較,判斷是否為要查找的函數
jnz next_
pop esi edi
sub eax,[ebp+edi+peexc.AddressOfNames]
shr eax,1
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -