PE知識復習之PE的重定位表
一丶何為重定位
重定位的意思就是修正偏移的意思. 如一個地址位 0x401234 ,Imagebase = 0x400000 . 那么RVA就是 1234. 如果Imagebase 變了成了0x300000, 那么修正之后就是 ImageBase + RVA = 0X300000+1234 = 0x301234.
首先我們知道.一個EXE文件.會調用很多DLL(PE) 有多個PE文件組成.
exe文件啟動的基址 (ImageBase) 是0x40000. 假設我們調用三個DLL A B C.
A DLL 在EXE展開的基址位置是0x10000000
那么恰巧 B DLL 展開的位置也是 0x1000000 兩個DLL位置展開地方是一樣的.那么就出現問題了.
如下圖:
這時候操作系統就會給我們進行修正. 將B DLL 換個內存位置. 進行展開. 這也是為什么很多游戲外掛.等等.都選擇DLL注入. 因為系統幫你重定位了各種信息. 代碼寫在DLL中即可.
如下圖: B DLL 從0x20.... 展開了.規避了使用相同地址
雖然這樣解決了入口基址不一樣.內存展開不一樣. 但是我們知道.PE文件中有很多RVA .RVA 是相對於ImageBase的偏移進行存放的. 如果PE文件中都是 RVA 那就好辦了.
但是不一定呀.
如一下代碼所示:
#include <stdio.h> #include <stdlib.h> #include <Windows.h> int g_Value; int main() { g_Value = 10; }
查看其反匯編
我們發現,在給全局變量賦值的的時候.地址是一個固定的.他不是 RVA
再次運行截圖:
我們發現地址變了. 而且硬編碼 是一個固定的. 0x012c813c ,他直接編譯到二進制文件中了.
他把 ImageBase + RVA的值. 直接編譯到二進制當中. 就是說.這個全局變量的地址. 是一個 RVA + IMAGEBASE 的地址. 但是他是直接編譯到二進制中的.
問題所在:
假設A編譯的全局變量的地址是 RVA + iMAGEbase 假設是 0x1012345,那么A展開的位置是 0x10..... 那么全局變量地址是正確的. 但是如果B編譯的時候.地址也是1012345. 但是模塊基址加載不一樣.那么就會出問題了.如下圖:
根據上面我們發現了問題所在.所以現在我們需要一張表.記錄那個地方需要進行重定位. 我們把這個地方的值改一下即可.
也就是要記錄我們修改需要重定位的位置.以上圖的代碼進行反匯編查看.
也就是記錄需要重定位的地方即可.
重定位表就是記錄所有需要修正的地址.只要有了重定位表.我們就不用擔心我們的ImageBase 沒有占住位置.
非常重要的一張表.
二丶重定位表的定位以及結構
重定位表.的定位在擴展頭中的數據目錄中. 數據目錄的第6項就是重定位表的 RVA偏移.以及重定位表的大小.
定位到重定位表,那么有額外的結構體來描述重定位表.
typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; //存儲的值以字節為單位.字節多大.表示了一個重定位快有多大. // WORD TypeOffset[1]; } IMAGE_BASE_RELOCATION; typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
看着重定位表就兩個成員. 其實非常復雜. 我們設 VirtualAddress 為 x 設 SizeofBlock為Y
如下圖所示,一個格子為1個字節.
第一行四個字節為x.也就是 Virtualaddress.. y則是第二個成員
SizeOfBlock 成員你的意思. 以字節為單位.代表重定位的快由多大. 我們知道.一個PE文件需要很多地方進行重定位的.比如這個記錄的
大小為16. 也就是兩個重定位塊,那么我們的重定位表的大小就是如下圖所示:

下面則是新的重定位表.結構就是重定位表的結構,如果SzieofBlock大小為20個字節.那么重定位表大小就是20個自己.
第三個依次類推,重定位表的結束是不停的往下找,知道最后一個重定位表的結構成員你都為0
這樣設計的原因:
算術題解惑.
比如我們有地址 101234 101235 101236 這種修正的地址有10000個.
那么每個地址有4個字節的. 那么 4 * 10000 = 4萬個字節. 也就是我們要准備一張4萬個字節的表來保存重定位的.
但是我們發現一個規律.我們要修正的表的偏移都很近, 1234 1235 ....
那么我們可不可以這樣那. 我們把 100000取出來. 兩個字節存儲1234 另外兩個地址存儲1235,不用准備四個字節了.小的偏移我們兩個字節存儲.這樣的話我們的表的字節就會縮小一半.
VirtuallAddress 就是存儲了 100000這個值,也就是需要 ""基址"" 公用的地址.
SizeofBlock 就是下面的偏移由多大, 我們要修正的偏移是 VirtualAddress + sizeofBlock下面的值.
如下圖:
我們的重定位表,需要修正的基址是 0x11000,大小是54. 那么需要修正的偏移是 "36b0" "36bc" "36e0"....
我們基址 + 偏移就是要修正的位置.
偏移的概念:
重定位表,是按照一個物理頁(4kb)進行存儲的. 也就是一個4kb內存,有一個重定位塊, 一個重定位表只管自己當前的物理頁的重定位.
一個重定位表的記錄偏移的大小是2個字節,也就是16位. 而記錄偏移的大小. 是由 SizeofBlock決定的.
但是我們記錄偏移的位置,12位就夠了. 高4位.挪作他用. 並不是記錄的才會修正偏移.只有高4位為3的時候.才會進行重定位(基址 + 偏移)
真正修復的位置 virtualaddress + (高四位為3 ? 低12位偏移 : 無所謂的值.)
也就是高四位為3 Vir + 低12位偏移就等於真正要修復的RVA 例如 36b0 高位為3 低12位就是6b0 要修復的RVA = vir + 6b0 ,如果加上當前DLL的ImagebASE 才是真正要修復的虛擬地址 (VA) 我們計算出的是RVA
如果高位不為3,那么這個值是無所謂了.因為內存對齊的原因.
例如上圖重定位表. 0x11000代表了當前要進行修復的塊位置. 要修復偏移的地址第一個是36b0 . 高位為3是要進行修復.
所以低位為6B0. 所以修復的位置是 0x116b0的位置. 0x116b0 + 當前PE文件的ImageBase就是要進行重定位的位置
當前PE的Imagebase為0x400000 重定位地方為 0x4116b0位置.
我們第一個修正的位置是4116b0位置,從內存中.反匯編查看. 4116b0的位置的值是0x0041813c. 也就是說.這個位置的值.是我們需要重定位的. 也就是我們上面寫的程序.為全局變量賦值的時候.全局變量的地址.需要進行更改.
而需要重定位的值. 則是 全局變量的RVA值 + Imagebase 填寫到這里面了. 全局變量是在內存中的data節存儲着.所以觀看前幾篇博客.能知道如何定位全局變量在文件的位置.
三丶總結重定位
重定位表有兩個成員. VirtuallAddress sizeofBlock
1.virtualladdress 記錄了當前物理頁需要進行重定位的起始地址.
2.sizeofBlock 記錄了重定位表多大.去掉8個字節(重定位表大小) 下面都是記錄了重定位表需要重定位的偏移.
3.偏移是2個字節存儲. 12位存儲偏移. 高4位存儲是否進行重定位. 高4位為3則需要進行重定位. virtuall + 低12位 就是要修正的 RVA偏移.