PE檔案學習筆記(一)---匯入匯出表
最近在看《黑卡免殺攻防》,對講解的PE檔案匯入表、匯出表的作用與原理有了更深刻的理解,特此記錄。 首先,要知道什麼是匯入表? 匯入表機制是PE檔案從其他第三方程式(一般是DLL動態連結庫)中匯入API,以提供本程式呼叫的機制。而在Windows平臺下,PE檔案中的匯入表結構就承擔了完成這一工作的引導者角色。
IMAGE_IMPORT_DESCRIPTOR結構
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;
一般來說,對於匯入表,我們只需要關注它的兩個欄位,分別是OriginalFirstThunk與FirstThunk,這兩個欄位分別指向了包含匯出名稱和匯出地址的IMAGE_THUNK_DATA結構陣列,這個陣列以空的IMAGE_THUNK_DATA結構結尾。
IMAGE_THUNK_DATA結構
typedef struct _IMAGE_THUNK_DATA32 { union { PBYTE ForwarderString; // 轉發字串的RAV PDWORD Function; // 被匯入函式的地址 DWORD Ordinal; PIMAGE_IMPORT_BY_NAME AddressOfData; // 指向輸入名稱表 } u1; } IMAGE_THUNK_DATA32; typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
ForwarderString是轉發用的,暫時不用考慮,Function表示函式地址,如果是按序號匯入Ordinal就有用了,若是按名字匯入AddressOfData便指向名字資訊。可以看出這個結構體就是一個大的union,大家都知道union雖包含多個域但是在不同時刻代表不同的意義那到底應該是名字還是序號,該如何區分呢?可以通過Ordinal判斷,如果Ordinal的最高位是1,就是按序號匯入的,這時候,低16位就是匯入序號,如果最高位是0,則AddressOfData是一個RVA,指向一個IMAGE_IMPORT_BY_NAME結構,用來儲存名字資訊,由於Ordinal和AddressOfData實際上是同一個記憶體空間,所以AddressOfData其實只有低31位可以表示RVA,但是一個PE檔案不可能超過2G,所以最高位永遠為0,這樣設計很合理的利用了空間。實際編寫程式碼的時候微軟提供兩個巨集定義處理序號匯入:IMAGE_SNAP_BY_ORDINAL判斷是否按序號匯入,IMAGE_ORDINAL用來獲取匯入序號。
瞭解一下IMAGE_IMPORT_BY_NAME結構。
typedef struct _IMAGE_THUNK_DATA32 {
union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;
} u1;
} IMAGE_THUNK_DATA32;
Hint: 儲存著需要匯入函式的序號 Name: 儲存著需要匯入函式的名稱
知道了基本的組成,那麼匯入表又是怎樣盡職盡責的工作呢? 記錄一個例項流程。
藉助LordPE工具,快速定位匯入表的位置。
Importtable的RVA地址是0x0000233C;那麼它的檔案OFFSET是多少? 首先檢視各個段的RVA和OFFSET:
顯然,Importtable在.rdata區段 .rdata區段的RVA為0x2000,.rdata區段的起始Offset是0x00001000 RVA到Offset的轉化: 匯出表Offset = 匯出表RVA - 匯出表所在區段的RVA + 匯出表所在區段的 Offset 求得匯出表Offset = 0x0000133c 匯出表地址計算參照值 d = 匯出表RVA - 匯出表Offset = 0x00001000 (便於計算各個欄位的Offset)
得到Offset就可以在winhex裡面找到importtable的內容了:
由於是以空的結構體結尾的,很清楚的看到是有三個IMAGE_IMPORT_DESCRIPTOR結構: TABLE1:
欄位 | OriginalFirstThunk | TimeDataStamp | ForwarderChain | Name | FirstThunk |
值(RVA) | 0x0000238c | 0x00000000 | 0x00000000 | 0x000023dc | 0x00002000 |
轉化為Offset | 0x0000138c | 0x000013dc | 0x00001000 |
TABLE2:
欄位 | OriginalFirstThunk | TimeDataStamp | ForwarderChain | Name | FirstThunk |
值(RVA) | 0x00002450 | 0x00000000 | 0x00000000 | 0x000024f8 | 0x000020c4 |
轉化為Offset | 0x00001450 | 0x000013dc | 0x000010c4 |
TABLE3:
欄位 | OriginalFirstThunk | TimeDataStamp | ForwarderChain | Name | FirstThunk |
值(RVA) | 0x000023cc | 0x00000000 | 0x00000000 | 0x0000252e | 0x00002040 |
轉化為Offset | 0x000013cc | 0x0000152e | 0x00001040 |
由這些資訊(INT、IAT、映像名的起始Offset);就可以找它們的具體結構了
映像名、INT、IAT
NAME | OriginalFirstThunk1 | OriginalFirstThunk2 | FirstThunk1 | FirstThunk2 |
KERNEL32.dll | 0x00002458 | 0x00002466 | 0x00002458 | 0x00002466 |
USER32.dll | 0x000024ea | NULL | 0x000024ea | NULL |
MSVCR110.dll | 0x00002558 | 0x00002568 | 0x00002558 | 0x00002568 |
根據上述的匯入資訊,轉化為Offset;再從檔案中找到匯入函式和倒入序號:
NAME | 序號1 | 函式名1 | 序號2 | 函式名2 |
KERNEL32.dll | 0x016d | ExitProcess | 0x0223 | GetCurrentProcess |
USER32.dll | 0X010a | FindWindowW | NULL | NULL |
MSVCR110.dll | 0x01a4 | __getmainorgs | 0x01e0 | __set_app_type |
總結:
匯入表的工作原理步驟:
1、根據IMANE_IMPORT_DESCRIPTOR的欄位(NAME,OriginalFirstThunk,FirstThunk),來找到映像名、INT、IAT;
2、根據INT表的資訊(OriginalFirstThunk欄位),找到_IMAGE_IMPORT_BY_NAME結構的位置,從而得出函式的函式名、函式序號。