在 PE文件頭的 IMAGE_OPTIONAL_HEADER 結構中的 DataDirectory(數據目錄表) 的第二個成員就是指向輸入表的。每個被鏈接進來的 DLL文件都分別對應一個 IMAGE_IMPORT_DESCRIPTOR (簡稱IID) 數組結構。 typedef struct _IM... ...
在 PE文件頭的 IMAGE_OPTIONAL_HEADER 結構中的 DataDirectory(數據目錄表) 的第二個成員就是指向輸入表的。每個被鏈接進來的 DLL文件都分別對應一個 IMAGE_IMPORT_DESCRIPTOR (簡稱IID) 數組結構。
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) } DUMMYUNIONNAME; DWORD TimeDateStamp; // 0 if not bound, // -1 if bound, and real date\time stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) // O.W. date/time stamp of DLL bound to (Old BIND) DWORD ForwarderChain; // -1 if no forwarders DWORD Name; DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
在這個 IID數組中,並沒有指出有多少個項(就是沒有明確指明有多少個鏈接文件),但它最後是以一個全為NULL(0) 的 IID 作為結束的標誌。
下麵只摘錄比較重要的欄位:
OriginalFirstThunk
它指向first thunk,IMAGE_THUNK_DATA,該 thunk 擁有 Hint 和 Function name 的地址。
Name
它表示DLL 名稱的相對虛地址(譯註:相對一個用null作為結束符的ASCII字元串的一個RVA,該字元串是該導入DLL文件的名稱。如:KERNEL32.DLL)。
FirstThunk
它包含由IMAGE_THUNK_DATA定義的 first thunk數組的虛地址,通過loader用函數虛地址初始化thunk。
在Orignal First Thunk缺席下,它指向first thunk:Hints和The Function names的thunks。
下麵來解釋下OriginalFirstThunk和FirstThunk。就個人理解而言:
1. 在文件中時,他們都分別指向一個RVA地址。這個地址轉換到文件中,分別對應兩個以 IMAGE_THUNK_DATA 為元素的的數組,這兩個數組是以一個填充為 0 的IMAGE_THUNK_DATA作為結束標識符。雖然他們這兩個表位置不同,但實際內容是一模一樣的。此時,每個 IMAGE_THUNK_DATA 元素指向的是一個記錄了函數名和相對應的DLL文件名的 IMAGE_IMPORT_BY_NAME結構體。
2. 為什麼會有兩個一模一樣的數組呢?是有原因的:
OriginalFirstThunk 指向的數組通常叫做 hint-name table,即 HNT ,他在 PE 載入到記憶體中時被保留了下來且永遠不會被修改。但是在 Windows 載入過 PE 到記憶體之後,Windows 會重寫 FirstThunk 所指向的數組元素中的內容,使得數組中每個 IMAGE_THUNK_DATA 不再表示指向帶有函數描述的 IMAGE_THUNK_DATA 元素,而是直接指向了函數地址。此時,FirstThunk 所指向的數組就稱之為輸入地址表(Import Address Table ,即經常說的 IAT)。
typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE 指向一個轉向者字元串的RVA DWORD Function; // PDWORD 被輸入的函數的記憶體地址 DWORD Ordinal; // 被輸入的 API 的序數值 DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME 指向 IMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32; typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
根據 _IMAGE_THUNK_DATA32 所指虛擬地址轉到文件地址可以得到實際的 _IMAGE_IMPORT_BY_NAME 數據
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; // 序號
CHAR Name[1]; // 實際上是一個可變長的以0為結尾的字元串
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
例如有程式:
文字版:
#include <windows.h> int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd) { MessageBoxA(0, "hello", "my message", MB_OK); SetWindowTextA(0, "Si Wang"); return 0; }
此程式使用了兩個 Windows API : MessageBoxA 和 SetWindowTextA
編譯得到程式(為簡化說明,區段位置由軟體計算出):
我們試著找出 MessageBoxA。首先分析 PE 頭文件,找到導出表在文件中的位置:
輸入表位置在 .rdata 區段內, 0x2264 – 0x2000 = 0x0264 得到偏移量。加上文件地址 0x0E00 得到實際文件偏移量(0x0E00 + 0x264 = 0x1064):0x1064。
接下來查看 0x1064 處:
可以得到三個 DLL 的描述,最後一個_IMAGE_IMPORT_DESCRIPTOR 以0填充表示結束:
那麼只要一個個查看每個DLL對應的數據就能找到,不過之前我把所有的數據都看了下,在第一個DLL中
根據第一個DLL描述的 OriginalFirstThunk 的 0x2350 轉換可以知道,_IMAGE_THUNK_DATA32 在文件的 0x1150處,FirstThunk 指向的數據相同:
於是就得到了文件中的 MessageBoxA 的信息。
最後,在記憶體中 FirstThunk 所指位置上的_IMAGE_THUNK_DATA32 數組被 Windows 載入後被重寫後就成了傳說中的 IAT ,Import Address Table,輸入地址表。使用 OllyDbg 查看運行時情況: