1. 程式人生 > 其它 >PE檔案詳解七

PE檔案詳解七

前面好幾篇在講輸入表,今天要講的是輸出表和地址的是地址重定位。有了前面的基礎,其實對於怎麼找輸出表地址重定位的表已經非常熟悉了。

  0x01 輸出表結構

  當建立一個DLL檔案時,實際上建立了一組能讓EXE或者其他DLL呼叫的一組函式,PE裝載器根據DLL檔案中輸出資訊修正正在執行檔案的IAT。當一個DLL函式能被EXE或者DLL檔案使用時,它被稱為輸出了。其中輸出資訊被儲存在輸出表中,DLL檔案通過輸出表向系統提供輸出函式名,序號和入口資訊。

  對於EXE檔案一般不存在輸出表,而大部分DLL檔案都有輸出表。輸出表是由一個叫做IMAGE_EXPORT_DIRECTORY(簡稱IED)組成。IED存放著輸出函式名,輸出序數等資訊,他的結構如下:

typedef struct _IMAGE_EXPORT_DIRECTORY {

    DWORD   Characteristics;    // 未使用,總為0 

    DWORD   TimeDateStamp;      // 檔案建立時間戳

    WORD    MajorVersion;       // 未使用,總為0

    WORD    MinorVersion;       // 未使用,總為0

    DWORD   Name;               // 指向一個代表此 DLL名字的 ASCII字串的 RVA

    DWORD   Base;               // 函式的起始序號

    DWORD   NumberOfFunctions;  // 匯出函式的總數

    DWORD   NumberOfNames;      // 以名稱方式匯出的函式的總數

    DWORD   AddressOfFunctions;     // 指向輸出函式地址的RVA

    DWORD   AddressOfNames;         // 指向輸出函式名字的RVA

    DWORD   AddressOfNameOrdinals;  // 指向輸出函式序號的RVA

} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

下面介紹幾個重要欄位:

Name:指向的是的ASCII字串的RVA

NumberOfFunctions:輸出表EAT表中的資料條目數。

NumberOfName:輸出表ENT表中的資料條目數,ENT也是一個RVA地址,不過每項指向的是函式名字的ascii碼。

AddressOfFunctions:是指向EAT的RVA值EAT是RVA一個數組,每一項指向了被輸出的函式的地址。

AddressOfNames:是指向ENT的RVA值,ENT也是一個數組,每項指向被輸出函式的名字。

下圖是他們之間的關係圖:

 

 

  0x02例項分析輸出表結構

  1)用hexworkshop開啟DLLDemo.DLL檔案,找到資料目錄表的第一項,它在檔案頭偏移78h處。如下圖:

值為RVA=4000h,轉化為FileOffset=800h。此處即為輸出表結構所在地址。

2)跳往800h,依次讀出幾個重要欄位的值如下圖:

 

由上圖可知各個欄位的RVA值,Name=0000 4032,NumberOfFunctions=0000 0010 NumberOfNames:0000 0010

AddressOfFunctions=0000 4028 ,AddressOfNames=0000 402c 。兩個Address欄位全都轉化為FileOffset值。

Name=832h,AddressOfFunctions=828h,AddressOfNames=82ch。跳往832h如下圖:

 可知輸出的DLL名字叫做DLLDemo.DLL,跳往828h即可找到輸出函式的EAT表,跳往82ch即可找到輸出函式的ENT表。

  0x03 輸出實現過程總結

  PE裝載器呼叫GetProcAddress來查詢DLLDemo.DLL裡的api函式,系統通過定位DLLDemo.DLL的IMAGE_EXPORT_DIRECTORY(出書目錄表)結構開始工作,從這個結構中他獲得輸出函式名稱表(ENTb表)的起始地址,進而知道這個陣列只有一個元素,他對名字進行二進位制查詢直到發現字串“MegBox”。PE裝載器發現MsgBox是陣列的第一個元素,載入器然後從輸出序數表讀取相應的第一個值,這個值是MsgBox的輸出序數。使用輸出序數作為進入EAT的索引,他得到MsgBox的值是1008h,1008h加上DllDemo.DLL的裝入地址得到MsgBox的實際地址。

0x04 基址重定位概念

當聯結器生成一個PE檔案時,他假設這個檔案執行時會被裝入預設的基址處,並且把code和data的相關地址寫入PE檔案中。如果裝入是按照預設的值作為基址裝入,則不需要基址重定位,但是如果可執行檔案被裝在到記憶體中的另一個地址,連結器所登記的地址就是錯的這時就需要用重定位表來調整。在PE檔案中,它往往單獨分為一塊,用“.reloc”來表示。PE檔案重定位過程。

  0x05 詳細解讀基址重定位

  基址重定位表放在一個位於.relo的區域中,但是找到它需要通過資料目錄表的第五項成員Base reloction Table,這項所指向重定位的基本結構IMAGE_BASE_RELOCATION。

IMAGE_BASE_RELOCATION的基本定義如下:

 struct IMAGE_BASE_RELOCATION {

    DWORD   VirtualAddress;//重定位資料開始RVA

DWORD   SizeOfBlock;//重定位塊的長度

WORD    TypeOffset;//重定位項位陣列

 

}

 IMAGE_BASE_RELOCATION ENDS

下面分別解釋一下這幾個欄位:

VirtualAddress:是這一組重定位資料的開始的RVA地址。其實就是原來的地址加上這個值就完成了重定位。

SizeOfBlock:當前重定位結構的大小,因為VirtualAddress和SizeOfBlock都是固定的四個位元組,所以SizeOfBlock的值減去8就得到了重定位塊的大小。

TypeOffset:這個欄位很有意思,一個兩個位元組16位,高四位代表重定義型別,低12位裝入的是我們需要重定位的地址,即這個地址加上前面的欄位VirtualAddress的值完成重定位。注意Typeoffset是一個數組他的值有SizeOfBlock-8決定。

下圖位重定位示意圖:

 

 0x06 例項講解地址重定位

 1)hexWrokShop開啟PE檔案DllDemo.DLL。通過資料目錄表的第五項找到重定位結構IMAGE_BASE_RELOCATION,即在PE檔案頭偏移地址為A0h處,跳往a00h處,下圖示黑地方即為結構IMAGE_BASE_RELOCATION資料。

 我們注意到第一個欄位VritualAddress=0000 1000h,SizeOfBlock=0000 0010h,由此可得陣列TypeOffset共八個個位元組。每個元素兩個位元組則共四組資料。整理得出下表:

專案

重定位資料1

重定位資料2

重定位資料3

重定位資料4

原始資料

0F 30

23 30

00 00

00 00

TypeOffset值

30 0F

30 23

00 00

00 00

TypeOffset高四位

3

3

 

 

TypeOffset低12位

00F

023

 

 

低12位加上VritualAddress

100F(RVA值)

1023(RVA值)

 

 

轉換成FileOffset

20F

223

 

 

 

重定位後的地址如下圖: