UPDATE:
- 在文章的末尾更新了一張圖,在網上找的,有助於理解導入表的結構
1.概述
導入表是逆向和病毒分析中比較重要的一個表,在分析病毒時幾乎第一時間都要看一下程序的導入表的內容,判斷程序大概用了哪些功能。
導入表是數據目錄表中的第2個元素,排在導出表的后面。
2.導入表解析
先來了解一下導入表在PE文件中的結構體:
struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
在說明字段之前,需要說明的是,導入表是一個數組,每個元素都是上面的結構體,長度是14h,並且以14h字節的“0”作為數組結尾,如果程序導入了多個模塊,那么這個數組的長度應該是14h*(n+1)。
以下是字段的說明。
DWORD TimeDateStamp:一般為空,對應上圖的0x00000000
DWORD ForwarderChain:對應上圖的0x00000000
DWORD Name:導入模塊名的RVA,對應上圖的0x0000230c,
DWORD Characteristics:跟OrginalFirstThunk是聯合體,對應上圖的0x0000227c
DWORD OrginalFirstThunk:跟Characteristics是聯合體,一般情況下是OrginalFirstThunk生效,它是IMAGE_THUNK_DATA數組的RVA,對應上圖的0x0000227c
我們來看看IMAGE_THUNK_DATA結構體
struct _IMAGE_THUNK_DATA32{
union {
DWORD ForwarderString
DWORD Function ; 被輸入的函數的內存地址
DWORD Ordinal ; 被輸入的API的序數值
DWORD AddressOfData ; 高位為0則指向IMAGE_IMPORT_BY_NAME 結構體二
}u1;
}IMAGE_THUNK_DATA32;
我們知道指向IMAGE_THUNK_DATA的RVA是0x0000227c,轉為offset是107ch,從107ch開始到0x00000000結束是一個完整的導入函數數組,如下圖:
我們對照圖來一一講解IMAGE_THUNK_DATA的字段。
ForwarderString:只有當IMAGE_IMPORT_DESCRIPTOR中的ForwarderChain有值時,它才有效
Function:導入函數的實際內存地址,只有在載入內存中時才有效
Ordinal:導入函數的導出序號,導出表中允許使用序號的方式導出函數,導入表也要有相應的機制。只有當IMAGE_THUNK_DATA的最高位是1的時候才有效
AddressOfData:當IMAGE_THUNK_DATA的最高位為0的時候有效,指向一個IMAGE_IMPORT_BY_NAME結構體數組,有沒有一種可能是最高位為1,並且是以函數名的方式導入函數的呢?我們知道最高位為1的最小數也是0x80000000,這個位置是系統空間,程序不可訪問,因此是不可能的。
IMAGE_IMPORT_BY_NAME結構體如下:
struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
Hint:指出函數在所在的dll的輸出表中的序號
Name:指出要輸入的函數的函數名
接下來是IMAGE_IMPORT_DESCRIPTOR的最后一個字段。
DWORD FirstThunk:對應的是IMAGE_IMPORT_DESCRIPTOR那張圖中的0x00002000,在磁盤中它指向的位置跟OriginalFirstThunk是一樣的,同樣是指向IMAGE_THUNK_DATA數組。如果PE文件被加載進內存,FirstThunk指向的IMAGE_THUNK_DATA數組中的Function保存的就是真實內存中的函數地址,OriginalFirstThunk指向的同樣是IMAGE_THUNK_DATA數組,但是保存的並不是真實內存中的函數地址,而是IMAGE_IMPORT_BY_NAME數組的RVA,保存的是導入函數的文件名。
3.總結
1.重點需要分清在磁盤中和在內存中OriginalFirstThunk和FirstThunk的區別,在磁盤中二者都一樣,在內存中前者的IMAGE_THUNK_DATA結構生效的是AddressOfData字段,因此指向的是IMAGE_IMPORT_BY_NAME數組的RVA,所以它也被稱為INT,后者保存的是導入函數在內存中的真實地址,所以它被稱為IAT。
2.需要厘清一點概念就是,PE中導入表,也就是IMAGE_IMPORT_DESCRIPTOR結構在一個數組中,意味着一個PE文件中可能有多個導入表,每個導入表中只有一個OriginalFirstThunk和FirstThunk,但是他們指向的IMAGE_THUNK_DATA是一個數組,數組的元素個數代表函數的個數,如果是IMAGE_THUNK_DATA中的AddressOfData字段生效,它指向的是一個IMAGE_IMPORT_BY_NAME數組,這個數組中的元素個數跟IMAGE_THUNK_DATA中的可能不一樣,因為有的函數沒有名字。
3.凡是數組的最后一定是以0填充,長度是數組元素的大小,字符串以00作為結束。