PE文件結構詳解(四)PE導入表&nbs…

本文轉自:http://blog.csdn.net/evileagle/article/details/12357155 感謝作者的原創

PE文件結構詳解(二)可執行文件頭的最後展示了一個數組,PE文件結構詳解(三)PE導出表中解釋了其中第一項的格式,本篇文章來揭示這個數組中的第二項:IMAGE_DIRECTORY_ENTRY_IMPORT,即導入表。

也許大家注意到過,在IMAGE_DATA_DIRECTORY中,有幾項的名字都和導入表有關係,其中包括:IMAGE_DIRECTORY_ENTRY_IMPORT,IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT,IMAGE_DIRECTORY_ENTRY_IAT和IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT這幾個導入都是用來幹什麼的,他們之間又是什麼關係呢?聽我慢慢道來。

 

  • IMAGE_DIRECTORY_ENTRY_IMPORT就是我們通常所知道的導入表,在PE文件加載時,會根據這個表裏的內容加載依賴的DLL,並填充所需函數的地址。
  • IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT叫做綁定導入表,在第一種導入表導入地址的修正是在PE加載時完成,如果一個PE文件導入的DLL或者函數多那麼加載起來就會略顯的慢一些,所以出現了綁定導入,在加載以前就修正了導入表,這樣就會快一些。
  • IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT叫做延遲導入表,一個PE文件也許提供了很多功能,也導入了很多其他DLL,但是並非每次加載都會用到它提供的所有功能,也不一定會用到它需要導入的所有DLL,因此延遲導入就出現了,只有在一個PE文件真正用到需要的DLL,這個DLL纔會被加載,甚至於只有真正使用某個導入函數,這個函數地址纔會被修正。
  • IMAGE_DIRECTORY_ENTRY_IAT是導入地址表,前面的三個表其實是導入函數的描述,真正的函數地址是被填充在導入地址表中的。
舉個實際的例子,看一下下面這張圖:

 


這個代碼調用了一個RegOpenKeyW的導入函數,我們看到其opcode是FF 15 0000 19 30氣質FF 15表示這是一個間接調用,即call dwordptr [30190000];這表示要調用的地址存放在30190000這個地址中,而30190000這個地址在導入地址表的範圍內,當模塊加載時,PE加載器會根據導入表中描述的信息修正30190000這個內存中的內容。

那麼導入表裏到底記錄了那些信息,如何根據這些信息修正IAT呢?我們一起來看一下導入表的定義:

 

[cpp] viewplaincopy
  1. typedef struct _IMAGE_IMPORT_DESCRIPTOR {  
  2.     union {  
  3.         DWORD   Characteristics;            // 0 for terminating null import descriptor  
  4.         DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)  
  5.     } DUMMYUNIONNAME;  
  6.     DWORD   TimeDateStamp;                  // 0 if not bound,  
  7.                                             // -1 if bound, and real date\time stamp  
  8.                                             //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)  
  9.                                             // O.W. date/time stamp of DLL bound to (Old BIND)  
  10.   
  11.     DWORD   ForwarderChain;                 // -1 if no forwarders  
  12.     DWORD   Name;  
  13.     DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)  
  14. } IMAGE_IMPORT_DESCRIPTOR;  
  15. typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;  
使用RtlImageDirectoryEntryToData並將索引號傳1,會得到一個如上結構的指針,實際上指向一個上述結構的數組,每個導入的DLL都會成爲數組中的一項,也就是說,一個這樣的結構對應一個導入的DLL。

 

Characteristics和OriginalFirstThunk:一個聯合體,如果是數組的最後一項Characteristics爲0,否則OriginalFirstThunk保存一個RVA,指向一個IMAGE_THUNK_DATA的數組,這個數組中的每一項表示一個導入函數。

TimeDateStamp:映象綁定前,這個值是0,綁定後是導入模塊的時間戳。

ForwarderChain:轉發鏈,如果沒有轉發器,這個值是-1。

Name:一個RVA,指向導入模塊的名字,所以一個IMAGE_IMPORT_DESCRIPTOR描述一個導入的DLL。

FirstThunk:也是一個RVA,也指向一個IMAGE_THUNK_DATA數組
既然OriginalFirstThunk與FirstThunk都指向一個IMAGE_THUNK_DATA數組,而且這兩個域的名字都長得很像,他倆有什麼區別呢?爲了解答這個問題,先來認識一下IMAGE_THUNK_DATA結構:

 

[cpp] viewplaincopy
  1. typedef struct _IMAGE_THUNK_DATA32 {  
  2.     union {  
  3.         DWORD ForwarderString;      // PBYTE   
  4.         DWORD Function;             // PDWORD  
  5.         DWORD Ordinal;  
  6.         DWORD AddressOfData;        // PIMAGE_IMPORT_BY_NAME  
  7.     } u1;  
  8. } IMAGE_THUNK_DATA32;  
  9. typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;  
ForwarderString是轉發用的,暫時不用考慮,Function表示函數地址,如果是按序號導入Ordinal就有用了,若是按名字導入AddressOfData便指向名字信息。可以看出這個結構體就是一個大的union,大家都知道union雖包含多個域但是在不同時刻代表不同的意義那到底應該是名字還是序號,該如何區分呢?可以通過Ordinal判斷,如果Ordinal的最高位是1,就是按序號導入的,這時候,低16位就是導入序號,如果最高位是0,則AddressOfData是一個RVA,指向一個IMAGE_IMPORT_BY_NAME結構,用來保存名字信息,由於Ordinal和AddressOfData實際上是同一個內存空間,所以AddressOfData其實只有低31位可以表示RVA,但是一個PE文件不可能超過2G,所以最高位永遠爲0,這樣設計很合理的利用了空間。實際編寫代碼的時候微軟提供兩個宏定義處理序號導入:IMAGE_SNAP_BY_ORDINAL判斷是否按序號導入,IMAGE_ORDINAL用來獲取導入序號。

 

這時我們可以回頭看看OriginalFirstThunk與FirstThunk,OriginalFirstThunk指向的IMAGE_THUNK_DATA數組包含導入信息,在這個數組中只有Ordinal和AddressOfData是有用的,因此可以通過OriginalFirstThunk查找到函數的地址。FirstThunk則略有不同,在PE文件加載以前或者說在導入表未處理以前,他所指向的數組與OriginalFirstThunk中的數組雖不是同一個,但是內容卻是相同的,都包含了導入信息,而在加載之後,FirstThunk中的Function開始生效,他指向實際的函數地址,因爲FirstThunk實際上指向IAT中的一個位置,IAT就充當了IMAGE_THUNK_DATA數組,加載完成後,這些IAT項就變成了實際的函數地址,即Function的意義。還是上個圖對比一下:


上圖是加載前。


上圖是加載後。


最後總結一下:

 

  1. 導入表其實是一個IMAGE_IMPORT_DESCRIPTOR的數組,每個導入的DLL對應一個IMAGE_IMPORT_DESCRIPTOR。
  2. IMAGE_IMPORT_DESCRIPTOR包含兩個IMAGE_THUNK_DATA數組,數組中的每一項對應一個導入函數。
  3. 加載前OriginalFirstThunk與FirstThunk的數組都指向名字信息,加載後FirstThunk數組指向實際的函數地址。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章