1. 程式人生 > >PE結構之重定位表

PE結構之重定位表

  •  什麼是重定位:

 重定位就是你本來這個程式理論上要佔據這個地址,但是由於某種原因,這個地址現在不能讓你佔用,你必須轉移到別的地址,這就需要基址重定位。你可能會問,不是說過每個程序都有自己獨立的虛擬地址空間嗎?既然都是自己的,怎麼會被佔據呢?對於EXE應用程式來說,是這樣的。但是動態連結庫就不一樣了,我們說過動態連結庫都是寄居在別的應用程式的空間的,所以出現要載入的基地址被應用程式佔據了或者被其它的DLL佔據了,也是很正常的,這時它就不得不進行重定位了。

  • 哪些資料需要重定位:

:00401000 55    push ebp
:00401001 8BEC    mov ebp, esp
:0040100383C4FC    add esp, FFFFFFFC
:00401006 A1FC0F4000   mov eax, dword ptr [00400FFC

] ;mov eax,dwVar
:0040100B 8B45FC    mov eax, dword ptr [ebp-04] ;mov eax,@dwLocal
:0040100E 8B4508    mov eax, dword ptr [ebp+08] ;mov eax,_dwParam
:00401011 C9    leave
:00401012 C20400    ret 0004
:00401015 68D2040000   push 000004D2
:0040101A E8E1FFFFFF   call 00401000 ;invoke Proc1,1234

其中地址為00401006h處的mov eax,dword ptr [00400ffc]就是一句需要重定位的指令,當整個程式的起始地址位於00400000h處的時候,這句程式碼是正確的,但00400000h只是它自己期望的起始地址,也許exe希望把這個DLL載入到00500000h處,那麼句指令必須變成mov eax,dword ptr [00500ffc]才是正確的。這就意味著它需要重定位。即地址變成了00400ffch+(00500000h-00400000h)。這些資料被表示成[00400FFC],其實FFC只是資料相對於程式自身首地址的一個偏移量,程式假設自己就是從400000處開始載入然後從偏移FFC處可以取到資料,但是,實際載入之後,有時候並不是從400000開始。

所以,重定位的演算法可以描述為:將直接定址指令中的雙字地址加上模組實際裝入地址與模組建議裝入地址之差。為了進行這個運算,需要有3個數據,首先是需要修正的機器碼地址;其次是模組的建議裝入地址;最後是模組的實際裝入地址。在這3個數據中,模組的建議裝入地址已經在PE檔案頭中定義了,即OptionalHeader中的ImageBase,

而模組的實際裝入地址是Windows裝載器確定的,到裝載檔案的時候自然會知道。

事實上,PE檔案的重定位表中儲存的就是一大堆需要修正的程式碼的地址。

  • 重定位表的位置:

重定位表一般會被單獨存放在一個可丟棄的以“.reloc”命名的節中,但是和資源一樣,這並不是必然的,因為重定位表放在其他節中也是合法的,惟一可以肯定的是,如果重定位表存在的話,它的地址肯定可以在PE檔案頭中的資料目錄中找到。

我們可以這樣得到一個重定位表:

即利用OptionalHeader中的DataDirectory。

  • 重定位表的結構:

pNewOptionalHead.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress + pbBase指向的就是IMAGE_BASE_RELOCATION陣列的第一個元素。

每個IMAGE_BASE_RELOCATION元素包含了VirtualAddress、SizeOfBlock,後邊跟著數目不定的重定位項。如下圖所示:

雖然重定位表中的有用資料是那些需要重定位機器碼的地址指標,但為了節省空間,PE檔案對存放的方式做了一些優化。

在正常的情況下,每個32位的指標佔用4個位元組,如果有n個重定位項,那麼重定位表的總大小是4×n位元組大小。

直接定址指令在程式中還是比較多的,在比較靠近的重定位表項中,32位指標的高位地址總是相同的,如果把這些相近表項的高位地址統一表示,那麼就可以省略一部分的空間,當按照一個記憶體頁來分割時,在一個頁面中定址需要的指標位數是12位(一頁等於4096位元組,等於2的12次方),假如將這12位湊齊16位放入一個字型別的資料中,並用一個附加的雙字來表示頁的起始指標,另一個雙字來表示本頁中重定位項數的話,那麼佔用的總空間會是4+4+2×n位元組大小,計算一下就可以發現,當某個記憶體頁中的重定位項多於4項的時候,後一種方法的佔用空間就會比前面的方法要小。
PE檔案中重定位表的組織方法就是採用類似的按頁分割的方法,從PE檔案頭的資料目錄中得到重定位表的地址後,這個地址指向的就是順序排列在一起的很多重定位塊,每一塊用來描述一個記憶體頁中的所有重定位項。

每個重定位塊以一個IMAGE_BASE_RELOCATION結構開頭,後面跟著在本頁面中使用的所有重定位項,每個重定位項佔用16位的地址(也就是一個word),結構的定義是這樣的:

VirtualAddress欄位是當前頁面起始地址的RVA值,本塊中所有重定位項中的12位地址加上這個起始地址後就得到了真正的RVA值。SizeOfBlock欄位定義的是當前重定位塊的大小,從這個欄位的值可以算出塊中重定位項的數量,由於SizeOfBlock=4+4+2×n,(4位元組VritualAddress,4位元組SizeOfBlock,每個重定位項2位元組),也就是sizeof IMAGE_BASE_RELOCATION+2×n,所以重定位項的數量n就等於(SizeOfBlock-sizeof IMAGE_BASE_RELOCATION)÷2
IMAGE_BASE_RELOCATION結構後面跟著的n個就是重定位項,每個重定位項的16位資料位中的低12位就是需要重定位的資料在頁面中的地址,剩下的高4位也沒有被浪費,它們被用來描述當前重定位項的種類

雖然高4位定義了多種重定位項的屬性,但實際上在PE檔案中只能看到0和3這兩種情況。

所有的重定位塊最終以一個VirtualAddress欄位為0的IMAGE_BASE_RELOCATION結構作為結束,讀者現在一定明白了為什麼可執行檔案的程式碼總是從裝入地址的1000h處開始定義的了(比如裝入00400000h處的.exe檔案的程式碼總是從00401000h開始,而裝入10000000h處的.dll檔案的程式碼總是從10001000h處開始),要是程式碼從裝入地址處開始定義,那麼第一頁程式碼的重定位塊的VirtualAddress欄位就會是0,這就和重定位塊的結束方式衝突了。

但凡涉及到直接定址的指令都需要進行重定位處理

把記憶體中需要重定位的資料按頁的大小0x1000分為若干個塊,而這個VirtualAddress就是每個的起始RVA。如上圖第一個重定位項是33f2,去掉高位的3,得到3f2,再加上這個重定位塊的VirtualAddress,00011000,就得到000113f2。只知道塊的RVA當然還不行,我們要知道每一個需要重定位資料的具體地址。在程式沒有被真正載入(得到真實的起始地址)之前,就用ImageBase作為基址(這時的ImageBase是00400000),相加得到004113f2。下邊例子中的004112CB就是這樣得到的

PE檔案的重定位表中儲存的就是一大堆需要修正的程式碼。

 

  • 舉例:

寫一個小程式testRelHello.exe,裡邊呼叫了HelloWorld()函式:

用IDA檢視:

這裡沒表示成[0004XXXX],而是用 [email protected]@YAXXZ代替了,但是雙擊跟進發現:

 

實際就是[004112CB]。

下面我們檢視testRelHello的PE結構中的OptionalHeader.ImageBase的值:

即每次testRelHello都希望自身被載入到00400000處。

現在,我們利用GetModuleHandle()函式,執行testRelHello,看看實際上會被載入到哪裡:

我在執行之前先設好斷點:

現在在VS中檢視其彙編程式碼:

對HelloWorld()的呼叫就變成了call [0B312CB],而不再是[004112CB]。這就是因為進行了重定位。

現在我們計算:004112CB+B20000-400000,結果等於:

這就是對HelloWorld()函式進行重定位的一個過程。

參考:

http://www.blogfshare.com/pe-relocate.html

http://www.51testing.com/html/87/300987-823223.html

http://blog.sina.com.cn/s/blog_a9303fd90101bwxj.html

http://blog.csdn.net/cosmoslife/article/details/17270475

轉載請宣告出處:http://www.cnblogs.com/predator-wang/p/4962775.html