1.關於IAT(import address table)表
當exe程序中調用dll中的函數時,反匯編可以看到,call后面並不是跟的實際函數的地址,而是給了一個地址;

這些連起來就是一張表,就是IAT表;
1)內存鏡像中的dll中函數的調用;
例如:一個exe中調用系統提供的dll中的MessageBox函數時:

可以看到call的地址是42d2c4;
查看42d2c4中的內容:

可以看到:42d2c4中保存的是762b0026;
762b0026是dll的領空;也就是dll中MessageBox函數的地址;
總結:
exe程序調用dll中的函數時,會使用FF15call;
call的並不是實際的函數地址,而是該函數對應的IAT表的地址;
通過IAT表來找到實際的函數地址;
2)文件鏡像中的IAT相關
接下來取文件中找42d2c4中保存的內容;
需要將RVA轉成FOA;



42d2c4-400000 = 2d2c4;在.idata節;
foa = 2d2c4 - 2d000 + 2b000 = 2b2c4;
可以看到:文件鏡像中保存的值為2d2f4;


繼續追到2d2f4,對應的foa為2b2f4

看到並不是MessageBox的地址,而是一MessageBox先關的描述;
結論:IAT表在文件和內存中是不一樣的;
3)原因分析
對於一個exe程序,在運行時會加載到獨立的4gb空間;
exe程序是可以按ImageBase來加載的;
但該exe引用的dll並不能保證,因為ImageBase可能被占用;
因此對於dll來說有重定位表來記錄dll中的可能需要修改絕對地址;
對於引用dll的exe程序來說,因為無法確定dll中引用的函數的地址,所以不會再文件鏡像中將引用dll的函數的地址寫死;
而是保存的是這些函數相關的信息;
當程序運行后,並且所有dll都加載到該程序的4gb空間后,此時dll中函數的地址才確定;
IAT表中將保存dll中函數在內存中的真實地址;
因此IAT表中在文件和內存中不同;
4)IAT表總結
IAT表在程序執行前和程序執行后是不一樣的;
程序在編譯時給分了一個空間,保存一個偏移,這個偏移指向的是引用dll中的函數的函數名,而不是真正的函數地址;
當程序加載完畢,所有的dll都貼到程序的4gb空間后,會把IAT表中的值改為真正要用的函數的地址;因為dll的地址因不是按ImageBase加載而不一樣;
2.導入表
比如說當去餐館吃飯時,餐館會提供一個菜單,告訴誰提供了哪些菜;
自己在點餐時也需要給餐館提供一個單,來告訴自己點了哪些菜;
對於程序來說dll提供導出表,導出表中保存的是導出函數的清單以及地址;
程序也需要提供一個導入表,來說明使用了哪些函數;
1)導入表的結構
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; //RVA 指向IMAGE_THUNK_DATA結構數組 }; DWORD TimeDateStamp; //時間戳 DWORD ForwarderChain; DWORD Name; //RVA,指向dll名字,該名字已0結尾 DWORD FirstThunk; //RVA,指向IMAGE_THUNK_DATA結構數組 } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
數據目錄的第二項就是導入表;
導入表結構可能有多個,一個dll對應一個導入表結構;
其中比較重要的兩個屬性:
OriginalFirstThunk ->指向INT表(import name table);
表中存的是一些IMAGE_THUNK_DATA結構;
這個表以0結尾;
FirstThunk ->指向IAT表(import address table);
IAT表有兩種方式可以找到:1】通過數據目錄的第13個結構;2】導入表結構的這個屬性;
2)導入表在文件加載前和文件加載后有區別
1】pe文件加載前:

IAT表和INT表中保存的結構都是IMAGE_THUNK_DATA,且都是以0結尾;
在文件加載前兩張表里的內容完全一樣;
IMAGE_THUNK_DATA用來保存函數名以及函數序號等信息;
IMAGE_THUNK_DATA結構中只有一個聯合,占4字節的空間;
IMAGE_THUNK_DATA結構:
typedef struct _IMAGE_THUNK_DATA32 { union { PBYTE ForwarderString; PDWORD Function; DWORD Ordinal; //序號 PIMAGE_IMPORT_BY_NAME AddressOfData; //指向IMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32; typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
IMAGE_IMPORT_BY_NAME結構:
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; //可能為空,編譯器決定 如果不為空 是函數在導出表中的索引 BYTE Name[1]; //函數名稱,以0結尾 } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
2】pe文件加載后:

文件加載后,系統會調用通過函數名或函數序號找函數地址的函數GetProcAddr() ;
得到函數地址后,用函數地址替換IAT表中的數據;
使用兩張表INT、IAT的原因:
如果只有IAT表,在程序加載完后,IAT表中的函數名將替換成函數地址,就無法知道函數名,也就是留一個函數名的備份;
3.解析導入表
主要步驟:
1】定位導入表:
數據目錄的第二個結構為導入表;
通過該結構,可以找到導入表rva,轉成foa可以在文件中找到導入表;
每一個引用的dll都對應一個導入表,需要循環解析,直到遇到全0的導入表結構為止;
也就是sizeOf(IMAGE_IMPORT_DESCRIPTOR) 個 0 代表導入表結束;
2】解析dll名:
導入表中儲存了引用的dll的名字信息;
在導入表結構的Name屬性中;
3】解析OriginalFirstThunk:
OriginalFirstThunk指向INT表;
INT表中保存了IMAGE_THUNK_DATA結構,該結構只有一個聯合,占4字節;
判斷最高位是否為1,如果是那么除去最高位的值就是函數的導出序號;有些函數匿名導出的,不能用函數名找地址;
如果不是,那么這個值是一個RVA 指向IMAGE_IMPORT_BY_NAME ;
IMAGE_IMPORT_BY_NAME結構中有2個屬性;
Hint ->是函數在導出表中的索引,並不是函數的序號,沒有實際作用;
Name ->函數名,只有1個字節,也就是函數名的開始,因為字符串長度不固定,從字符串開始到0結束為一個函數名;
調用 GetProcAddr(m,函數的名字或者導出序號) 函數可以找到函數地址;
當IMAGE_THUNK_DATA結構為0時,表示該INT表結束;
4】解析FirstThunk:
FirstThunk指向IAT表,IAT表在程序加載前和INT表是一樣的;
輸出導入表信息:




代碼:
#include "stdafx.h" #include "PeTool.h" #include "string.h" #define SRC "C:\\Users\\Administrator\\Desktop\\Hello.exe" //解析導入表 void printImport(){ //定義pe頭結構指針 PIMAGE_DOS_HEADER dosHeader = NULL; //dos頭指針 PIMAGE_FILE_HEADER peHeader = NULL; //pe頭指針 PIMAGE_OPTIONAL_HEADER32 opHeader = NULL; //可選pe頭指針 PIMAGE_DATA_DIRECTORY dataDir = NULL; //數據目錄指針 PIMAGE_IMPORT_DESCRIPTOR importDir= NULL; //導入表指針 //1.將文件讀入內存 LPVOID pFileBuffer = NULL; DWORD fileSize = ReadPEFile(SRC, &pFileBuffer); if(!pFileBuffer){ printf("讀取dll文件失敗\n"); return; } //2.初始化頭結構指針 dosHeader = (PIMAGE_DOS_HEADER) pFileBuffer; peHeader = (PIMAGE_FILE_HEADER) ((DWORD)pFileBuffer + dosHeader->e_lfanew + 4); opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER); dataDir = opHeader ->DataDirectory; importDir = (PIMAGE_IMPORT_DESCRIPTOR) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, (dataDir + 1)->VirtualAddress )); //3.輸出導入表信息 while(importDir->OriginalFirstThunk){ printf("按enter鍵顯示下一個dll導入表信息:\n"); getchar(); LPSTR name = (LPSTR)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, importDir->Name )); printf("================%s================\n", name); //解析INT表 PDWORD thunk =(PDWORD) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, (DWORD)importDir->OriginalFirstThunk)) ; while(*thunk){ if((*thunk & 0x80000000) >> 31 == 1){ //如果第一位為1,表示為序號導入 DWORD ord = *thunk & 0x7fffffff; printf("按序號導入:%d\n", ord); }else{ PIMAGE_IMPORT_BY_NAME pName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, *thunk)); printf("按函數名導入:%s\n",pName->Name); } //下一個INT表項 thunk++; } //下一張導入表 importDir++; printf("\n"); } } int main(int argc, char* argv[]) { //輸出導入表信息 printImport(); getchar(); }
結果:
