PE文件格式詳解(七)
Ox00 前言
前面好幾篇在講輸入表,今天要講的是輸出表和地址的是地址重定位。有了前面的基礎,其實對於怎么找輸出表地址重定位的表已經非常熟悉了。
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 |
|
|
重定位后的地址如下圖: