複製鏈接
請複製以下鏈接發送給好友

輸入表

鎖定
輸入表是PE(Portable Executable File Format)文件結構中不可或缺的部分,輸入表也被稱之為“導入表”。可執行文件使用來自於其他 Dll 的代碼或數據時,稱為輸入。輸入表就相當於 EXE文件與 DLL文件溝通的鑰匙,形象的可以比喻成兩個城市之間交流的高速公路,所有的導入函數信息都會寫入輸入表中,在 PE 文件映射到內存後,Windows 將相應的 DLL文件裝入,EXE 文件通過“輸入表”找到相應的 DLL 中的導入函數,從而完成程序的正常運行,這一動態連接的過程都是由“輸入表”參與的。 [1] 
中文名
輸入表
外文名
Import Table
包    含
輸入函數、Data Directory
學    科
計算機
作    用
映射
應    用
EXE文件與DLL文件之間

輸入表簡介

要想了解輸入表,首先還得先從DLL文件入手。日常生活中我們會看見一些大型軟件有很多的DLL格式的文件,它們是“動態鏈接庫文件”,這些文件中有很多的導入函數,這些函數不會直接被執行,當一個程序(EXE)運行時,導入函數是被程序調用執行的,其執行的代碼是不在主程序(EXE)中的一小部分函數,其真正的代碼卻在DLL文件中。這時我們就會想,那麼EXE主程序是如何找到這些需要導入的函數呢,這就要歸結於“輸入表”了,輸入表就相當於EXE文件與DLL文件溝通的鑰匙,形象的可以比喻成兩個城市之間交流的高速公路,所有的導入函數信息都會寫入輸入表中,在PE文件映射到內存後,windows將相應的DLL文件裝入,EXE文件通過“輸入表”找到相應的DLL中的導入函數,從而完成程序的正常運行,這一動態連接的過程都是由“輸入表”參與的。

輸入表PE文件

PE(Portable Executable)文件格式是微軟制定的一種文件標準, 它是從普遍運用於 UNIX 操作系統的 COFF(Common ObjectFile Format)發展而來, 在 Windows 操作系統中扮演着非常重要的角色, 其格式中的數據結構通常定義在 WINNT.H 中。 本文試圖對 PE 的格式及其最新擴展進行分析 , 以便能較好地去理解Windows 操作系統以及目前微軟的最新的開發平台.Net。 [2] 
PE 文件格式是一種文件組織的方式,裏面除了程序運行的代碼和數據外,還有一些文件相關的重要信息,如文件由磁盤裝載到內存中的起始地址、程序執行的入口地址、調用 DLL 動態鏈接庫函數地址等。PE 文件主要由 DOS M Z H eader(DOS頭)、DOS Stub (DOS 塊)、PE H eader (PE 頭)、SectionTable(節表)、Sections(各個節)五部分組成。DOS M Z header 和 DOS stub 稱為 DOS 部分。緊隨 DOS stub 的是 PE 頭,其中包含 PE 裝載器用到的許多重要數據。PE 頭後面是節表,包含了指向各個節的數據信息。各個節存放了 PE 文件的代碼、數據和資源等內容。 [3] 

輸入表結構

輸入表輸入函數

輸入函數,表示被程序調用但是它的代碼不在程序代碼中的,而在dll中的函數。對於這些函數,磁盤上的可執行文件只是保留相關的函數信息,如函數名,dll文件名等。在程序運行前,程序是沒有保存這些函數在內存中的地址。當程序運行起來時,windows加載器會把相關的dll裝入內存,並且將輸入函數的指令與函數真在內存中正的地址聯繫起來。輸入表(導入表)就是用來保存這些函數的信息的。 [4] 

輸入表Data Directory

在IMAGE_OPTIONAL_HEADER 中的 DataDirectory[16] 數組保存了 輸入表的RVA跟大小。通過RVA可以在OD中加載程序通過 ImageBase+RVA 找到 輸入表,或者通過RVA計算出文件偏移地址,查看磁盤中的可執行文件,通過文件偏移地址找到輸入表。
輸入表是以一個IMAGE_IMPORT_DESCRIPTOR(IID)數組 開始的,每一個被PE文件隱式的鏈接進來的dll都有一個IID,IID數組的最後一個單元用NULL表示。 [4] 
IMAGE_IMPORT_DESCRIPTOR 結構:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {  
    _ANONYMOUS_UNION union {              //00h  
        DWORD Characteristics;  
        DWORD OriginalFirstThunk;   
    } DUMMYUNIONNAME;  
    DWORD TimeDateStamp;                  //04h  
    DWORD ForwarderChain;                 //08h  
    DWORD Name;                           //0Ch  
    DWORD FirstThunk;                     //10h  
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR; 
其中Name是dll名字的指針。OriginalFirstThunk指向一個IMAGE_THUNK_DATA數組叫做輸入名稱表Import Name Table(INT),用來保存函數,FirstThunk也指向IMAGE_THUNK_DATA數組叫做輸入地址表Import Address Table(IAT)。
IMAGE_THUNK_DATA 結構:
typedef struct _IMAGE_THUNK_DATA32 {  
    union {  
        DWORD ForwarderString;  
        DWORD Function;  
        DWORD Ordinal;  
        DWORD AddressOfData;  
    } u1;  
} IMAGE_THUNK_DATA32,*PIMAGE_THUNK_DATA32;
當IMAGE_THUNK_DATA 的值最高位為1時,表示函數是以序號方式輸入,這時低31為被當作函數序號。當最高位是0時,表示函數是以字符串類型的函數名方式輸入的,這時,IMAGE_THUNK_DATA 的值為指向IMAGE_IMPORT_BY_NAME 的結構的RVA。
typedef struct _IMAGE_IMPORT_BY_NAME {  
    WORD Hint;  
    BYTE Name[1];  
} IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;
Hint 表示這個函數在其所駐留dll的輸出表的序號,不是必須的。
Name 表示 函數名,是一個ASCII字符串以0結尾,大小不固定。
INT保存的是這個程序導入這個dll中函數信息,它是固定的不會被修改。但是IAT會在程序加載時被重寫,當程序加載時,它會被PE加載器重寫成 這些函數的在內存中的真正地址。即把它原來指向的IMAGE_IMPORT_BY_NAME 改成 函數真正的地址。

輸入表要兩個 IMAGE_THUNK_DATA

數組的原因
當程序加載時,IAT 會被PE加載器重寫,PE加載器先搜索INT,PE加載器迭代搜索INT數組中的每個指針,找出 INT所指向的IMAGE_IMPORT_BY_NAME結構中的函數在內存中的真正的地址,並把它替代原來IAT中的值。當完成後,INT就沒有用了,程序只需要IAT就可以正常運行了。
看下面圖,這個是可執行程序在磁盤中的時候:
這個是當程序被加載的是後:

輸入表實例分析

先找到輸入表RVA,通過IMAGE_OPTIONAL_HEADER 中的最後一個項 IMAGE_DATA_DIRECTORY 可以知道 輸入表相對與PE文件頭的偏移量為80h可以找到輸入表達RVA。
圖片1
但這個是RVA不是文件偏移地址。通過轉換可以知道,輸入表的文件偏移地址為850h,
查看850h,即IID,可以看到這個程序有兩個IID,即鏈接了 兩個dll,看到一個IID,可以知道它的OriginalFirstThunk 是 2098h FirstThunk 為200Ch,它們轉換後的文件偏移地址分別為898h 和 80Ch
圖片2 IID數組
查看OriginalFirstThunk跟FirstThunk ,可以發現這裏INT跟IAT的內容是一樣的,先看看第一個函數的信息,因為第一位為1,所以這裏00002122h 表示 IMAGE_IMPORT_BY_NAME 的RVA,轉化為文件偏移值為922h
圖片3 INT跟IAT
查看922h 可以看函數名。
圖片4:
我們可以從上面看到程序在磁盤中時 INT 與IAT內容一樣,都是指向 IMAGE_IMPORT_BY_NAME 。用OD加載程序,查看INT與IAT的內容
圖片5 INT
圖片6 IAT
可以發現INT沒有發生變化,IAT變成了例如77D3C702h,IAT中的RVA被改成了函數的真正的地址。
查看77D3C702h,就可以看到這個函數
圖片7
參考資料