PE檔案格式詳細解析(六)-- 基址重定位表(Base Relocation Table)
一、PE重定位
向程式的虛擬記憶體載入PE檔案時,檔案會被載入到PE頭的ImageBase所指的地址處。如果是載入的DLL(SYS)檔案,且在ImageBase位置處已經載入了DLL(SYS)檔案,那麼PE裝載器就會將其載入到其他未被佔用的空間。此時就會發生基址重定位。
使用SDK或VC++建立PE檔案,EXE預設的ImageBase為00400000,DLL預設的ImageBase為10000000,使用DDK建立的SYS檔案預設的ImageBase為10000。
建立好程式後,因為EXE檔案會首先載入進記憶體,所以EXE檔案中無需考慮基址重定位問題。但是需要考慮ASLR(地址隨機化)。對於各OS的主要系統DLL,微軟會根據不同版本分別賦予不同的ImageBase地址,例如同一系統的kernel32.dll和user32.dll等會被載入到自身固有的ImageBase,所以系統的DLL實際上也不會發生重定位問題。
二、PE重定位時發生了什麼
以下以書上程式為例(書上是以exe檔案舉例,純粹是舉例,實際環境中基址重定位多發生在DLL檔案中)。
基本資訊:
如下圖所示,其ImageBase為01000000
使用OD執行,觀察記憶體:
下圖是程式的EP程式碼部分,因為ASLR的原因,程式被載入到00270000處。
從圖中可以看出,紅框內程式的記憶體地址是以硬編碼的方式存在的,地址2710fc、271100是.text節區的IAT區域,地址27c0a4是.data節區的全域性變數。因為ASLR的存在,每次在OD中重啟程式,地址值就會隨載入地址的不同而發生變化,這種使硬編碼在程式中的記憶體地址隨當前載入地址變化而改變的處理過程就是PE重定位。
將以上兩個圖進行對比整理,資料如下表所示:
檔案(ImageBase:01000000) |
程式記憶體(載入地址:00270000) |
0100010fc |
002710fc |
01001100 |
00271100 |
0100c0a4 |
0028c0a4 |
即:因為程式無法預測會被載入到哪個地址,所以記錄硬編碼地址時以ImageBase為準;在程式執行書簡,經過PE重定位,這些地址全部以載入地址為基準進行變換,從而保證程式的正常執行。
三、PE重定位操作原理
1. 基本操作原理
- 在應用程式中查詢硬編碼的地址位置
- 讀取數值後,減去ImageBase(VA->RVA)
- 加上實際載入地址(RVA->VA)
上面三個步驟即可完成PE重定位,其中最關鍵的是查詢硬編碼地址的位置,查詢過程中會使用到PE檔案內部的Relocation Tables(重定位表),它記錄了硬編碼地址便宜,是在PE檔案構建中的編譯/連結階段提供的。通過重定位表查詢,本質上就是根據PE頭的“基址重定位表”項進行的查詢。
如上圖所示,紅框內的硬編碼的地址都需要經過重定位再載入到記憶體中。
2. 基址重定位表
位於PE頭的DataDirectory陣列的第六個元素,索引為5.如下圖所示:
上圖中的基址重定位表的RVA為2f000,檢視該地址處內容:
3. IMAGE_BASE_RELOCATION結構體
上圖中詳細羅列了硬編碼地址的偏移,讀取該表就可以獲得準確的硬編碼地址偏移。基址重定位表是IMAGE_BASE_RELOCATION結構體陣列。
其定義如下:
typedefine struct _IMAGE_BASE_RELOCATION{
DWORD VirtualAddress; //RVA值
DOWRD SizeOfBlock; //重定位塊的大小
//WORD TypeOffset[1]; //以註釋形式存在,非結構體成員,表示在該結構體下會出現WORD型別的陣列,並且該陣列元素的值就是硬編碼在程式中的地址偏移。
}IMAGE_BASE_RELOCATION;
tydefine IMAGE_BASE_RELOCATION UNALIGEND * PIMAGE_BASE_RELOCATION;
4. 基地址重定位表的分析方法
下表列出上圖中基址重定位表的部分內容:
RVA |
資料 |
註釋 |
2f000 |
00001000 |
VirtualAddress |
2f004 |
00000150 |
SizeOfBlock |
2f008 |
3420 |
TypeOffset |
2f00a |
342d |
TypeOffset |
2f00c |
3436 |
TypeOffset |
以VirtualAddress=00001000,SizeOfBlock=00000150,TypeOffset=3420為例。
TypeOffset值為2個位元組,由4位的Type與12位的Offset合成:
高4位指定Type,PE檔案中常見的值為3(IMAGE_REL_BASED_HIGHLOW),64位的PE檔案中常見值為A(IMAGE_REL_BASED_DIR64)。低12位位真正位移(最大地址為1000),改位移是基於VirtualAddress的位移,所以程式中硬編碼地址的偏移使用以下公式進行計算:
VirtualAddress(1000) + Offset(420) = 1420(RVA)
下面我們在OD中看一下RVA 1420處是否實際存在要執行PE重定位操作的硬編碼地址:
程式載入的基地址為270000,所以在271420處可以看到IAT的地址(VA,2710c4)。
5. 總結流程
查詢程式中硬編碼地址的位置(通過基址重定位表查詢)
可以看到,RVA 1420處存在著程式的硬編碼地址010010c4
讀取數值後,減去ImageBase值:
010010c4 - 01000000 = 000010c4
加上實際載入地址
000010c4 + 00270000=002710c4
對於程式內硬編碼的地址,PE裝載器都做如上的處理,根據實際載入的記憶體地址修正後,將得到的值覆蓋到同一位置上。對一個IMAGE_BASE_RELOCATION結構體的所有TypeOffset都做如上處理,且對RVA 1000~2000地址區域對應的所有硬編碼地址都要進行PE重定位處理。如果TypeOffset值為0,說明一個IMAGE_BASE_RELOCATION結構體結束。至此,完成重定位流程。
四、參考
《逆向工程核心原理》