栈溢出笔记1.7 地址问题(2)
1.6節中找到了kernel32.dll的基地址,這一節,來解決第二個重要問題,即解析kernel32.dll的導出表,找到LoadLibraryA和GetProcAddress的地址。
DLL導出的函數信息位于導出表中,因此,首先,要在PE文件中找到導出表(IMAGE_EXPORT_DIRECTORY)的地址,這位于數據目錄(IMAGE_DATA_DIRECTORY)中。而數據目錄位于PE擴展頭(IMAGE_OPTIONAL_HEADER)的最后。
下圖為PE文件的結構:
?
圖44
PE文件開頭為一個DOS頭(IMAGE_DOS_HEADER),大小固定為64個字節,其中,最后4個字節指定PE頭的偏移量(IMAGE_NT_HEADER),即PE頭標識(Signture)的偏移量,中間有一段大小不固定的DOS Stub。PE頭標識之后是標準PE頭(IMAGE_FILE_HEADER),大小固定為20字節,之后是擴展PE頭(IMAGE_OPTIONAL_HEADER32),數據目錄就位于擴展PE頭的末尾。
數據目錄包含16種(導出表,導入表,資源表等),每種占8個字節,導出表項位于第一項。圖44中已經標出了數據目錄距離DOS頭的偏移,為120(0x78)個字節。(注:引用的原圖有誤,中間少算了8個字節)。這個偏移也就是導出表項的偏移,因為導出表項是第一項。
導出表項的偏移并不是導出表的偏移,每個數據目錄項8個字節,共兩個字段,如下:
/*****************************************************************************/ typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size;} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY; /*****************************************************************************/- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
第一個字段VirtualAddress表示導出表距離PE文件開頭的偏移,第二個字段Size為導出表的大小,根據這兩個字段,可以定位導出表。
下面來看導出表的具體結構,如下:
/*****************************************************************************/ typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; // +0x10 函數起始需要 DWORD NumberOfFunctions; // +0x14 導出函數個數 DWORD NumberOfNames; // +0x18以函數名導出的函數個數 DWORD AddressOfFunctions; // +0x1c 導出函數地址表 DWORD AddressOfNames; // +0x20 函數名稱地址表 DWORD AddressOfNameOrdinals; // +0x24函數序號地址表 } IMAGE_EXPORT_DIRECTORYM, *pIMAGE_EXPORT_DIRECTORY; /*****************************************************************************/- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
標紅的字段為重要的字段。其中,AddressOfFunctions所指的區域依次保存了所有導出函數的地址,個數由NumberOfFunctions決定。AddressOfNames所指的區域依次保存了對應函數的函數名,個數由NumberOfNames決定。AddressOfNameOrdinals與AddressOfNames一一對應,指定函數在AddressOfFunctions的索引值,即將函數名與地址聯系起來。需要注意的是AddressOfNameOrdinals中的序號為一個字(兩個字節),而不是像地址為4個字節。
?
圖45
AddressOfFunctions的個數可能會大于AddressOfNames,因為有的函數可能沒有導出函數名,此時,沒有AddressOfNameOrdinals,就找不到函數地址了。
記住,我們要找的兩個函數LoadLibraryA和GetProcAddress,我們知道的只有函數名,因此,查找從AddressOfNames開始,比較函數名,然后根據索引(第幾個),在AddressOfNameOrdinals查找到函數在AddressOfFunctions的索引值(前面說了,AddressOfNames和AddressOfNameOrdinals一一對應),然后,從AddressOfFunctions找到對應索引處的地址即可。注意,PE文件中的地址均為相對地址(RVA),因此,實際使用時要加上模塊(exe,dll)實際加載基地址,換為實際地址。
現在,又到了寫代碼實現的時候了,比較麻煩的地方是字符串的比較,查找指定名稱函數的代碼可以實現為一個函數,輸入為函數的名稱,輸出為函數的地址。我們還要用到上一節的內容,即獲取kernel32.dll的基地址。
/*****************************************************************************/ // example_9 從kernel32.dll中查找函數地址 #include <stdio.h>// 獲取kernel32.dll的基地址 int get_kernel32_base() {__asm{mov eax, fs:[0x30] // PEBmov eax, [eax+0x0c] // PEB->Ldrmov eax, [eax+0x1c] // PEB->Ldr.InInitializationOrderModuleList.Flink(指向第一個元素)mov eax, [eax] // 指向第二個元素mov eax, [eax+0x08] // kernel32.dll基地址} }int compare_string( char* symbol1, char* symbol2 ) {__asm{mov esi, [ebp+8]; // symbol1mov edi, [ebp+12]; // symbol2xor eax, eaxcompare_loop:mov al, [esi] // 取下一個字符mov bl, [edi]cmp al, bl // 比較是否相等jnz equal_notest al, al // symbol2是否結尾jz equal_yestest bl, bl // symbol1是否結尾jz equal_yesinc esi inc edijmp compare_loopequal_no:sub al, blequal_yes:} }int get_func_addr( char* symbol ) {__asm{call get_kernel32_base // 獲取kernel32.dll基地址mov ebx, [eax+0x3c] // PE頭的偏移mov ebx, [eax+ebx+0x78] // 數據目錄導出表項的偏移add ebx, eax // 導出表地址mov ecx, [ebx+0x18] // NumberOfNamesmov edx, [ebx+0x20] // AddressOfNames的RVAadd edx, eax // 轉換為VAsearch_loop:jecxz error_donedec ecxpushadmov esi, [edx+ecx*4]add esi, eax // 函數名push esimov edi, [ebp+8]; // 參數: char* symbolpush edicall compare_string // 比較函數名add esp, 8test eax, eax // eax為則相等popadjnz search_loopmov edx, [ebx+0x24] // 函數序號表RVAadd edx, eax // 函數序號表VAmov cx, [edx+ecx*2] // 函數序號mov edx, [ebx+0x1c] // 函數地址表RVAadd edx, eax // 函數地址表VAmov edx, [edx+ecx*4] // 函數地址RVAadd edx, eax // 函數地址VAjmp doneerror_done:xor eax, eaxdone:mov eax, edx} }int main() {printf("0x%x\n", get_func_addr("LoadLibraryA"));return 0; } /*****************************************************************************/- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
總結
以上是生活随笔為你收集整理的栈溢出笔记1.7 地址问题(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ACM学习历程—Hihocoder [O
- 下一篇: java selenium (六) XP