PE结构导入表
PE體系
PE結構&整體敘述
PE結構&導入表
PE結構&導出表
PE結構&基址重定位表
PE結構&綁定導入實現
PE結構&延遲加載導入表
導入表簡介
當我們源文件里面需要去如何畫窗口,如何顯示指定字符串這樣功能的代碼,只需要簡單調用Windows API函數。這些調用的函數在源文件中并不存在。這些代碼存儲在DLL文件中,即動態鏈接庫中。在動態鏈接庫里存放的不是函數的源代碼,而是編譯鏈接后生成的字節碼。
當源程序調用了動態鏈接庫的相關函數,在進行編譯和鏈接的時候,編譯程序和鏈接程序就會把調用的相關信息寫入最終生成的PE文件中,從而來告訴操作系統這些函數的執行字節碼能從哪里獲取,這些信息就是導入表所要描述的內容。
DLL加載方式有兩種:顯式鏈接(Explicit Linking) 和 隱式鏈接(Implicit Linking)
IAT提供的機制與DLL的隱式鏈接有關。
優點:
導入函數
當我們調用一個API時,這個API我們不可能自己手寫,基礎源碼這些東西都不知道,所以直接導入頭文件然后使用即可。
#include<iostream> #include<Windows.h> using namespace std; int main() {LPCSTR text = "hello world";LPCSTR title = "第一個MessageBoxA";MessageBoxA(NULL, text, title, MB_OK);}關于API調用的匯編代碼如下:
.text:00401516 mov [ebp+text], offset aHelloWorld ; "hello world" .text:0040151D mov [ebp+title], offset unk_48800C .text:00401524 mov dword ptr [esp+0Ch], 0 ; uType .text:0040152C mov eax, [ebp+title] .text:0040152F mov [esp+8], eax ; lpCaption .text:00401533 mov eax, [ebp+text] .text:00401536 mov [esp+4], eax ; lpText .text:0040153A mov dword ptr [esp], 0 ; hWnd .text:00401541 mov eax, ds:__imp__MessageBoxA@16 ; MessageBoxA(x,x,x,x) .text:00401546 call eax ; MessageBoxA(x,x,x,x) ; MessageBoxA(x,x,關于導入函數就兩行:
.text:00401541 mov eax, ds:__imp__MessageBoxA@16 ; MessageBoxA(x,x,x,x) .text:00401546 call eax ; MessageBoxA(x,x,x,x) ; MessageBoxA(x,x,也就是把一個內存單元里面存的地址放在eax里面去,然后call它。即MessageBoxA的實際地址被放在一個偏移處,查看這個偏移
地址0x004924C0存放著0x77211930(MessageBoxA的實際地址)
從0x004924C0內存單元開始取dd長度的內存數據放在eax里面,
然后直接 call 0x77211930
查看一下文件中0x004924C0中存放的數據:
它存放的是0x61562100
查看一下內存中0x004924C0中存放的數據:
它存放的是0x77211930
這意味著,文件被裝載到內存后,這里的值發生了變化,真正的API函數地址的應該是內存中此處的值。那文件中的值0x61562100和內存映像中的值0x77211930它倆有什么關系呢?
注意
dump程序時,
錯誤示范:
這樣操作后,你得到的文件是一個
dmp文件,然后我找了三個小時。。啥也沒找到。。。
正確示范:
這樣dump后你得到的才是一個exe程序
文件偏移和虛擬地址轉換才用得上。。。以前dump的時候都是直接用的是PEtools,今晚不知道腦子哪里抽筋,偏偏亂搞了三小時。。。
觀察一下文件數據,導入表中數據
RVA數值為0x92000,大小為0xEE8
核心內容
導入表數據所在地址RVA=0x92000
導入表數據大小=0xEE8
導入函數數據地址表所在地址RVA=0x9228C
導入地址表表數據大小=0x23C
轉換成文件偏移后是:
導入表數據所在地址0x8F200,導入表數據大小=0xEE8
導入地址表數據所在地址0x8F48C,導入地址表表數據大小=0x23C
導入表描述符IMAGE_IMPORT_DESCRIPTOR
導入表數據的起始是一組導入表描述符結構。每組為20個字節,該結構定義如下:
IMAGE_IMPORT_DESCRIPTOR STRUCT unionCharacteristics ddOriginalFirstThunk dd ;000h - 橋1 ends TimeDateStamp dd ;0004h - 時間表 ForwarderChain dd ;0008h - 鏈表的前一個結構 Name1 dd ;000ch - 指向鏈接庫名字的指針 FirstThunk dd ;0010 - 橋2以下作出解釋:
IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk:
+0000h,雙字。因為它是指向另外數據結構的通路,因此簡稱橋1。該字段指向一個包含了一系列結構的數據。
指向的數組中每個結構定義了一個導入函數的信息,最后一個內容為全0的結構作為結束。指向的數據中每一項為一個結構,此結構的名稱是IMAGE_THUNK_DATA。該結構實際上只是一個雙字,但在不同時刻卻擁有不同的解釋。該字段有兩種解釋:
IMAGE_IMPORT_DESCRIPTOR.TimeDateStamp:
+0004h,雙字。時間戳,一般不用。如果該導入表被綁定,那么綁定后的這個時間戳就被設置為對應DLL文件的時間戳。操作系統在加載時,可以通過這個時間戳來判斷綁定的信息是否過時
IMAGE_IMPORT_DESCRIPTOR.ForwarderChain:
+0008h,雙字。鏈表的前一個結構
IMAGE_IMPORT_DESCRIPTOR.Name1:
+000ch,雙字。這個字段的含義和名稱并不一致,這里的Name1是一個RVA,它指向該結構對應的DLL文件的名稱,而這個名稱是以“\0”結尾的Ansi字符串。
IMAGE_IMPORT_DESCRIPTOR.FirstThunk :
+0010h,雙字。與OriginalFirstThunk相同,它指向的鏈表定義了針對Name1這個動態鏈接庫引入的所有導入函數,簡稱橋2
導入表雙橋結構
重新找個程序舉例,上面那個數據太大了,頭疼。。。
導入表數據所在地址RVA=0x1B1C4
導入表數據大小=0x50
導入函數數據地址表所在地址RVA=0x1B000
導入地址表表數據大小=0x1C4
轉換成文件偏移后是:
導入表數據所在地址0x83C4,導入表數據大小=0x50
導入地址表數據所在地址0x8200,導入地址表表數據大小=0x1C4
橋1和橋2最終都通向了一個目的地,都指向了引入函數的“編號-名稱”(Hint/Name)描述部分。而從橋2到目的地的過程中,還經過了另外一個很重要的結構IAT(Import Address Table)
橋1,最高位為0,這是一個RVA,表明函數是以字符串類型的函數名導入的。RVA轉換為FOA,值為
0x84AC,從文件的該位置開始取雙字,直到取出的雙字為"0"結束,每一個雙字都是結構IMAGE_THUNK_DATA。該結構的詳細定義如下
0x0001B450轉換成文件偏移為0x8650
這些值組成的數據結構就是IMAGE_IMPORT_BY_NAME
IMAGE_IMPORT_BY_NAME STRUCTHint dw ?;0000h -函數編號Name1 db ?;0004h -表示函數名的字符串IMAGE_IMPORT_BY_NAME ENDS以下是對每個字段的具體解釋:
IMAGE_IMPORT_BY_NAME.Hint:
+0000h,雙字。函數的編號,在DLL中對每個函數進行了編號,訪問函數時可以通過名稱訪問,也可以通過編號訪問
IMAGE_IMPORT_BY_NAME.Name1:
+0004h,大小不確定。函數名字字符串的具體內容,以“\0”作為字符串結束標志。
其中002E標識該函數在動態鏈接庫的編號,后面緊跟著函數名“GetModuleFileNameW”。
時間戳,這里為0
00 00 00 00鏈表的前一個結構,這里為0
9E B4 01 00RVA,指向動態鏈接庫RUNTIME140D.dll的名字字符串,轉換為文件偏移是0x869E
橋二,轉換成文件偏移是0x8298,
根據上面可知IAT是0x8200~0x83C4,橋2指向的地方在IAT范圍內,所以指向IAT
和橋1指向的數據值相同。。但是存儲的位置是不同的。橋1指向的INT與橋2指向的IAT內容完全一樣,但INT和IAT卻存儲在文件的不同位置。
每一個結構IMAGE_IMPORT_DESCRIPORT都對應一個唯一的動態鏈接庫,以及引用了該動態鏈接庫的多個函數,每個函數的最終“值-名稱”描述均可以沿橋1或者橋2找到。這種導入表結構被稱為雙外結構。
雙橋結構的導入表在文件中存在兩份內容完全相同的地址列表。一般情況下,橋2指向的地址列表被定義為IAT,而橋1指向的地址列表則被定義為INT(import name table)。
注意:
有的鏈接程序只只為導入表存儲一個橋,如Borland公司的Tlink只保留橋2,這樣的導入表我們稱之為單橋結構的導入表(單橋結構的導入表是無法指向綁定導入操作的。)
文件中的導入表:
在文件中,橋1指向INT,橋2指向IAT,內容一致。
內存中的導入表:
在內存中,橋1可以找到調用的函數名稱或函數的索引編號,橋2卻可以幫你找到該函數指令代碼在內存空間中的地址。
當PE被加載進虛擬地址空間以后,IAT 的內容會被操作系統更改為函數的VA。這個修改最終會導致通向“值-名稱”描述的橋2發生斷裂,如上圖。
當橋2發送斷裂以后,如果沒有橋1作為參照(因為橋1和橋2維護了兩個一一對應的函數RVA),我們就無法重新找到該地址到底是調用了那個函數。這就是為什么會存在兩個橋的原因。也是為什么單橋導入表無法實施綁定的原因。
實際操作
內存中的導入表:
橋1的值為01B2AC
橋2的IAT 值為0x01B098
兩個函數實際地址值和函數名(函數編號)可以一一對應。。。
PE裝載器把導入函數輸入至IAT的順序
LoadLibrary(“kernel32.dll”)
同一個DLL文件的多個函數的導入表
這是文件中的導入表,上圖中FirstThunk和OriganalFirstThunk一樣也有一份函數指針表,即橋2.只是沒畫而已下面是裝載入內存后的導入表:
定位導入函數地址表的方法有兩種:
每個動態鏈接庫都維護了自己的IAT內容,不同的 鏈接庫維護的這些內容可以是不連續的。
導入表的平面解析
解釋如下:
總結
- 上一篇: HWS计划 decryption
- 下一篇: PE结构导出表