0x00 前言
前一篇了解了區塊虛擬地址和文件地址轉換的相關知識,這一篇該把我們所學拿出來用用了。這篇我們將了解更為重要的一個知識點——輸入表和輸出表的知識。
0x01 輸入表
首先我們有疑問。這個輸入表是啥?為啥有輸入表?其實輸入表就是記錄PE輸入函數相關信息的一張表。那為什么要有這張表?答:原來PE文件運行過程中並不是獨立運行的,它必須要借助window系統的需對函數才能完成其功能。常見的如USER32,KERNEL32等DLL。
輸入表所起的作用就是幫助載入的PE找到所需調用的函數。
在PE文件中,有個專門的數組,他們分別對每個被輸入的DLL程序。每個這樣的結構都給出了被輸入DLL的名稱並且指向一組函數指針,這組函數指針就是輸入地址表(Import Address Table 簡稱ITA)。
在之前講過的PE文件頭的IMAGE_OPTIONAL_HAEDER結構中的數據目錄表即DataDirectory[16]中第二個成員Imaport Table指向輸入表。輸入表是一個由IMAGE_IMPORT_DESCRIPORT(簡稱IID)的結構組成的數組。IID的結構如下:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
DWORD OriginalFirstThunk;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
下面介紹幾個重要字段:
OriginalFirstThunk:這個字段包含指向輸入名稱表(簡稱INT)的RVA,INT是一個IMAGE_THUNK_DATA結構的數組,數組中的每個IMAGE_THUNK_DATA結構指向IMAGE_IMPORT_BY_NAME結構,數組最后以一個內容為0的IMAGE_THUNK_DATA結構結束。
直接看這個描肯定感覺很繞,下面看看這幾個結構的關系圖,希望能夠幫助理解:
其實就是1指向2再指向3
Name:輸入的DLL的名字指針,它是一個以00結尾的ASCII字符的RVA地址,該字符串包含輸入的DLL名。例如:KERNEL32.DLL,或者USER32.DLL。
FirstThunk:包含指向輸入地址表(IAT)的RVA。IAT也是指向IAMGE_THUNK_DATA結構。
這里的FristThunk和OringinalFristThunk極為相似,作用也很類似,但是作用的先后有不同,后面將會做詳細講解。
下面來重點看看這個起着巨大作用的IMAGE_THUNK_DATA結構。
typedef struct _IMAGE_THUNK_DATA32 {
union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;
} u1;
} IMAGE_THUNK_DATA32;
ForwarderString 指向一個轉向者字符串的RVA;
Function 被輸入的函數的內存地址;
Ordinal 被輸入的API的序數值
AddressOfData 指向IMAGE_IMPORT_BY_NAME
標識位黃色的這幾個字段都很重要,是的這個結構的字段全都很重要!
下面來看看AddressOfData字段指向的IMAGE_IMPORT_BY_NAME結構
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME;
這個結構很簡單,只有兩個字段:
Hint字段:指示本函數在其所駐留的輸出表的中序號該域被PE裝載器用來在DLL的輸出表里快速查詢。該值不是必須的,一些鏈接器將此值設為0;
NAME字段:這個字段比較重要。它含有輸入函數的函數名,函數名是一個ASCII碼字符串,並以NULL結尾。注意,這里雖然將NAME的大小定義為字節,其實他是可變的。
0x02 輸入地址表
接下來要講的才是本文里最為關鍵的部分。通過上面的了解大概我們都會疑惑為啥這兩個數組都要指向IMAGR_IMPORT_BY_NAME結構?原因如下:
第一,第一個由OriginalFrist通過IMAGE_THUNK_DATA結構所指向的IMAGE_IMPORT_BY_NAME是單獨的一項,而且IMAGE_THUNK_DATA的值不可以更改,這個IMAGE_THUNK_DATA組成的數組就是INT,其實它是為FristThunk做為提示用的。
第二,第二個由FristThunk所指向的IMAGE_THUNK_DATA的值是由PE裝載器填寫的,他們的值構成了IAT。PE裝載器首先搜索OringinalFristThunk,通過它所指向的INT結構中的每個IMAGE_IMPORT_BY_NAME所指向的每個被載入函數的地址。然后通過加載器將值填充到FristThunk指向的IAT表中。
接下來對比一下加載前后的INT表和IAN表值變化:
加載前
加載后,顯示IAT的值已經填充了函數地址
我們最后要用到的即使PE加載后的ITA表。
0x03 實例講解如何找到輸入表的FileOffset
前面我們已經詳細講解了怎么找到數據目錄表,這里就不再綴述。我們直接找到數據目錄表的第二項,它的位置在180h處如下圖:
由此可知輸入表的RVA值為3000h,大小為52h。
其實我們也可以可以直接在lordPE的區段表中找到FileOffset的值如下圖:
值為A00h。
用hexwrokshop直接跳轉到該位置(大小為52h)。如下圖:
由於IMAGE_THUNK_DATA的五個字段都是雙字,因此按照八個字節依次讀出數組中第一個結構的每個字段:第一個OriginnalFristThunk為:0000 3028 第一個TimeDateStamp為00000000。第一個ForwardChain為:00000000。第一個Name值為:00003038,第一個FristThunk值為:00003030
也可以用LordPE查看IMAGE_THUNK_DATA的字段值及名字如下圖:
可以知道調用了的是USE32.DLL。
數組中的其余值也可以依次讀寫出來