1. 程式人生 > >PE檔案格式詳解(五)

PE檔案格式詳解(五)

0x00 前言

  前一篇瞭解了區塊虛擬地址和檔案地址轉換的相關知識,這一篇該把我們所學拿出來用用了。這篇我們將瞭解更為重要的一個知識點——輸入表和輸出表的知識。

0x01 輸入表

  首先我們有疑問。這個輸入表是啥?為啥有輸入表?其實輸入表就是記錄PE輸入函式相關資訊的一張表。那為什麼要有這張表?答:原來PE檔案執行過程中並不是獨立執行的,它必須要藉助window系統的需對函式才能完成其功能。常見的如USER32KERNEL32DLL

輸入表所起的作用就是幫助載入的PE找到所需呼叫的函式。

  PE檔案中,有個專門的陣列,他們分別對每個被輸入的

DLL程式。每個這樣的結構都給出了被輸入DLL的名稱並且指向一組函式指標,這組函式指標就是輸入地址表(Import Address Table 簡稱ITA)

  在之前講過的PE檔案頭的IMAGE_OPTIONAL_HAEDER結構中的資料目錄表即DataDirectory[16]中第二個成員Imaport Table指向輸入表。輸入表是一個由IMAGE_IMPORT_DESCRIPORT(簡稱IID)的結構組成的陣列。IID的結構如下:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

 

DWORD    OriginalFirstThunk;

 

DWORD    TimeDateStamp;

 

DWORD     ForwarderChain;

 

DWORD     Name;

 

DWORD      FirstThunk;

 

} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;

下面介紹幾個重要欄位:

OriginalFirstThunk:這個欄位包含指向輸入名稱表(簡稱

INT)的RVAINT是一個IMAGE_THUNK_DATA結構的陣列,陣列中的每個IMAGE_THUNK_DATA結構指向IMAGE_IMPORT_BY_NAME結構,陣列最後以一個內容為0IMAGE_THUNK_DATA結構結束。

直接看這個描肯定感覺很繞,下面看看這幾個結構的關係圖,希望能夠幫助理解:

 其實就是1指向2再指向3

Name:輸入的DLL的名字指標,它是一個以00結尾的ASCII字元的RVA地址,該字串包含輸入的DLL名。例如:KERNEL32.DLL,或者USER32.DLL

FirstThunk:包含指向輸入地址表(IAT)的RVAIAT也是指向IAMGE_THUNK_DATA結構。

這裡的FristThunkOringinalFristThunk極為相似,作用也很類似,但是作用的先後有不同,後面將會做詳細講解。

下面來重點看看這個起著巨大作用的IMAGE_THUNK_DATA結構。

typedef struct _IMAGE_THUNK_DATA32 {
union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;
} u1;
} IMAGE_THUNK_DATA32;

ForwarderString 指向一個轉向者字串的RVA


Function 被輸入的函式的記憶體地址

 

Ordinal 被輸入的API的序數值


AddressOfData 指向IMAGE_IMPORT_BY_NAME

標識位黃色的這幾個欄位都很重要,是的這個結構的欄位全都很重要!

下面來看看AddressOfData欄位指向的IMAGE_IMPORT_BY_NAME結構

  typedef struct _IMAGE_IMPORT_BY_NAME {

       WORD Hint;

       BYTE Name[1];

  } IMAGE_IMPORT_BY_NAME;

這個結構很簡單,只有兩個欄位:

Hint欄位:指示本函式在其所駐留的輸出表的中序號該域被PE裝載器用來在DLL的輸出表裡快速查詢。該值不是必須的,一些連結器將此值設為0;

NAME欄位:這個欄位比較重要。它含有輸入函式的函式名,函式名是一個ASCII碼字串,並以NULL結尾。注意,這裡雖然將NAME的大小定義為位元組,其實他是可變的。

   0x02 輸入地址表

   接下來要講的才是本文裡最為關鍵的部分。通過上面的瞭解大概我們都會疑惑為啥這兩個陣列都要指向IMAGR_IMPORT_BY_NAME結構?原因如下:

第一,第一個由OriginalFrist通過IMAGE_THUNK_DATA結構所指向的IMAGE_IMPORT_BY_NAME是單獨的一項,而且IMAGE_THUNK_DATA的值不可以更改,這個IMAGE_THUNK_DATA組成的陣列就是INT,其實它是為FristThunk做為提示用的。

第二,第二個由FristThunk所指向的IMAGE_THUNK_DATA的值是由PE裝載器填寫的,他們的值構成了IAT。PE裝載器首先搜尋OringinalFristThunk,通過它所指向的INT結構中的每個IMAGE_IMPORT_BY_NAME所指向的每個被載入函式的地址。然後通過載入器將值填充到FristThunk指向的IAT表中。

接下來對比一下載入前後的INT表和IAN表值變化:

 

                                                                                                                    載入前

 

                                                                                     載入後,顯示IAT的值已經填充了函式地址

我們最後要用到的即使PE載入後的ITA表。

  0x03 例項講解如何找到輸入表的FileOffset

  前面我們已經詳細講解了怎麼找到資料目錄表,這裡就不再綴述。我們直接找到資料目錄表的第二項,它的位置在180h處如下圖:

 

由此可知輸入表的RVA值為3000h,大小為52h

其實我們也可以可以直接在lordPE的區段表中找到FileOffset的值如下圖:

 

值為A00h

hexwrokshop直接跳轉到該位置(大小為52h)。如下圖:

 由於IMAGE_THUNK_DATA的五個欄位都是雙字,因此按照八個位元組依次讀出陣列中第一個結構的每個欄位:第一個OriginnalFristThunk為:0000 3028 第一個TimeDateStamp00000000。第一個ForwardChain為:00000000。第一個Name值為:00003038,第一個FristThunk值為:00003030

也可以用LordPE檢視IMAGE_THUNK_DATA的欄位值及名字如下圖:

 

可以知道呼叫了的是USE32.DLL

陣列中的其餘值也可以依次讀寫出來