IAT表的詳解(轉)


IAT表詳解

IAT的全稱是Import Address Table。

IAT表是執行程序或者dll為了實現動態加載和重定位函數地址,用到的一個導入函數地址表。這里面記錄了每個導入函數的名字和所在的dll名稱,在pe加載的時候系統會加載這些dll到用戶的地址空間然后把函數地址覆蓋這個表里的函數地址,然后重構所有用到這個表的代碼,讓其調用直接指向實際函數地址(PE是否覆蓋不確定,驅動會這么做),PE的IAT表會留在內存,驅動的就丟棄了。

 

對於每一個引入的可執行文件(例如dll),有一個鏡像引入描述符(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)
     };
     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;                             // RVA,指向字符串,是這個可執行文件的名字。例如"ACE.dll"
      DWORD FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;

dll的forward不好講,因為很多都是undocumented的.

我們着重關心兩個指針,OriginalFirstThunk和FirstThunk。
Characteristics一詞出於歷史原因,故在此給它了一個別名,OriginalFirstThunk。
OriginalFirstThunk和FirstThunk是兩個DWORD值,存貯着兩個RVA數值,其實它們就是兩個指針。
OriginalFirstThunk和FirstThunk實際上都是指向同一個數組。
前者,我們稱之為INT,而后者,我們稱之為IAT.

 

IAT是一個IMAGE_THUNK_DATA類型的數組。有多少個函數被導入,這個數組就有多少個成員。該數組以0結尾。
typedef struct _IMAGE_THUNK_DATA32 {
     union {
           DWORD ForwarderString;          // 一個RVA地址,指向forwarder string 
           DWORD Function;                       // PDWORD,被導入的函數的入口地址
           DWORD Ordinal;                         // 該函數的序數
           DWORD AddressOfData;           // 一個RVA地址,指向IMAGE_IMPORT_BY_NAME
      } u1;
} IMAGE_THUNK_DATA32;

IMAGE_THUNK_DATA64與IMAGE_THUNK_DATA32的區別,僅僅是把DWORD換成了64位整數。

PIMAGE_IMPORT_BY_NAME是一個非常簡單的結構,就兩個成員。
typedef struct _IMAGE_IMPORT_BY_NAME {
     WORD Hint;            // 該函數的導出序數
     BYTE Name[1];      // 該函數的名字
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

而IMAGE_THUNK_DATA32就是一個非常魔術般的東西了。
struct IMAGE_THUNK_DATA的大小,恰好等於一個指針的大小。(32bit機器下是32bit,64bit機器下是64bit)

每一個IMAGE_THUNK_DATA對應着一個被導入的函數。
對於可執行文件而言,IAT中的IMAGE_THUNK_DATA中存儲的要么是Ordinal,要么是AddressOfData。
怎么判斷IMAGE_THUNK_DATA中存儲的是Ordinal 還是 AddressOfData 呢?
眾所周知,在32bit的機器上,地址空間是00000000-FFFFFFFF,
一般而言,其中00000000-7FFFFFFF是用戶空間,其它是系統空間。
於是,看IMAGE_THUNK_DATA的最高位,如果是1,就是Ordinal,否則就是AddressOfData。

但是這里還存在一個2GB的問題。因為2GB的用戶地址空間對於很多程序不夠用,(主要是數據庫系統),於是微軟就想了一些變通的辦法。例如win 2000的/3GB選項。
在啟動文件,boot.ini中加上這個選項后,用戶空間變成3GB,系統空間減少到1GB。
然后呢?
然后在鏈接該可執行文件的時候必須加上特殊的選項,這樣在PE頭就會有一個特殊的設置。
如果開了3GB選項,如果PE頭不加這個設置,那么用戶空間是2GB,系統空間是1GB.
如果開了3GB選項,且PE頭加了這個設置,那么用戶空間是3GB,系統空間是1GB.


而INT和IAT中存儲的本來應該是同樣的數據。
然后說綁定(binding).
當一個可執行文件被綁定的時候,IAT中的IMAGE_THUNK_DATA被改寫為(被導入的)該函數的實際地址。
這一步也許是交給鏈接器在鏈接的時候執行,也許是在該可執行文件載入的時候執行。
但是,如果,該可執行文件已經和dll綁定。但是這個dll后來又被更改了,這些被導入的函數依然在該dll中存在,但是實際地址已經改變了。還有,我們保留過一個IAT的副本,它就是INT.(這就是為什么我們稱之為Original FirstThunk).根據INT中的內容,我們可以重建IAT表。

 

綜上所述,將exe文件綁定到dll的最佳時機,是在安裝可執行文件的時候。這就是安裝程序,Windows installer,所要做的事情之一。

 

下面說,怎么判斷IAT中的信息是否已經過期。
首先,綁定分兩種類型,新式的和老式的。
前面已經說過IMAGE_IMPORT_DESCRIPTOR中的TimeDateStamp有三種可能性。
1.TimeDateStamp等於0 =〉 尚未綁定
2.TimeDateStamp等於-1 => 新式綁定
3.其它 => 老式綁定,這里存儲的就是上次綁定是在什么時間。

然后我詳細介紹下新式綁定
DataDirectory[ IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT ]指向一個重要的數據結構。
typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {
      DWORD TimeDateStamp;      // a DWORD that contains the time/date stamp of the imported DLL.
      WORD OffsetModuleName;    // a WORD that contains an offset to a string with the name of the imported DLL. 
                                                            // This field is an offset (not an RVA) from the first IMAGE_BOUND_IMPORT_DESCRIPTOR.
       WORD NumberOfModuleForwarderRefs;  // 這個結構體后面還有多少個IMAGE_BOUND_FORWARDER_REF 結構體
                                                                                   // Array of zero or more IMAGE_BOUND_FORWARDER_REF follows
} IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;

來看看IMAGE_BOUND_FORWARDER_REF是一個什么樣的結構體。
typedef struct _IMAGE_BOUND_FORWARDER_REF {
          DWORD TimeDateStamp;
          WORD OffsetModuleName;
         WORD Reserved;
} IMAGE_BOUND_FORWARDER_REF, *PIMAGE_BOUND_FORWARDER_REF;
和IMAGE_BOUND_IMPORT_DESCRIPTOR完全相同,除了最后一個字節,它是被保留的。

然后說這兩個結構體的作用。

IMAGE_BOUND_IMPORT_DESCRIPTOR的作用很顯然。根據TimeDateStamp和OffsetModuleName字段的值我們就可以判斷IAT表中的信息是否已經過期。

但是存在這樣一種情況。一個dll導到另一個dll中。例如USER32.DLL和KERNEL32.DLL。

假如USER32.DLL未更改,但是KERNEL32.DLL更改過了。此時需要重建USER32.DLL的IAT。但是我們的程序只是直接用到了 USER32.DLL,於是導入表中就沒有KERNEL32.DLL的IAT,也沒有KERNEL32.DLL的TimeDateStamp和 OffsetModuleName。
所以也就有, 如果一個dll forward了另一個dll,那么在這個dll的IMAGE_BOUND_IMPORT_DESCRIPTOR結構體后面需要再插入被forward的 dll的IMAGE_BOUND_FORWARDER_REF結構體。之后才是下一個dll的 IMAGE_BOUND_IMPORT_DESCRIPTOR。

IMAGE_BOUND_IMPORT_DESCRIPTOR.NumberOfModuleForwarderRefs的意義就不言而喻了。

每個dll一個IAT表,一般而言,這些IAT表都是統一存儲在一起的。由於每個IAT表是以0結尾。所以很容易分離開來

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM