原文地址 :http://www.blogfshare.com/pe-relocate.htmlhtml
1、什麼是重定位?算法
重定位就是你原本這個程序理論上要佔據這個地址,可是因爲某種緣由,這個地址如今不能讓你霸佔,你必須轉移到別的地址,這就須要基址重定位。數組
2、爲何須要重定位?工具
這個和上面的問題的解釋是同樣的。不是說過每一個進程都有本身獨立的虛擬地址空間嗎?既然都是本身的,怎麼會被佔據呢?對於EXE應用程序來講,是這樣的。可是動態連接庫就不同了,咱們說過動態連接庫都是寄居在別的應用程序的空間的,因此出現要載入的基地址被應用程序佔據了也是很正常的,這時它就不得不進行重定位了。優化
3、重定位表的結構spa
1.重定位所需的數據指針
在開始分析重定位表的結構以前須要瞭解兩個問題:第一,對一條指令進行重定位須要哪些信息?第二,這些信息中哪些應該被保存在重定位表中?htm
下面舉例來講明這兩個問題,請看下面的這段代碼:
:00400FFC 0000 ;dwVar變量
: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,1234blog
其中地址爲00401006h處的mov eax,dword ptr [00400ffc]就是一句須要重定位的指令,當整個程序的起始地址位於00400000h處的時候,這句代碼是正確的,假如將它移到00500000h處的時候,這句指令必須變成mov eax,dword ptr [00500ffc]纔是正確的。這就意味着它須要重定位。進程
讓咱們看看須要改變的是什麼,重定位前的指令機器碼是A1 FC0F 40 00,而重定位後將是A1 FC0F 50 00,也就是說00401007h開始的雙字00400ffch變成了00500ffch,改變的正是起始地址的差值(00500000h-00400000h)=00100000h。
因此,重定位的算法能夠描述爲:將直接尋址指令中的雙字地址加上模塊實際裝入地址與模塊建議裝入地址之差。爲了進行這個運算,須要有3個數據,
首先是須要修正的機器碼地址;
其次是模塊的建議裝入地址;
最後是模塊的實際裝入地址。這就是第一個問題的答案。
在這3個數據中,模塊的建議裝入地址已經在PE文件頭中定義了,而模塊的實際裝入地址是Windows裝載器肯定的,到裝載文件的時候天然會知道,因此第二個問題的答案很簡單,那就是應該被保存在重定位表中的僅僅是須要修正的代碼的地址。
事實上正是如此,PE文件的重定位表中保存的就是一大堆須要修正的代碼的地址。
2.重定位表的位置
重定位表通常會被單獨存放在一個可丟棄的以「.reloc」命名的節中,可是和資源同樣,這並非必然的,由於重定位表放在其餘節中也是合法的,唯一能夠確定的是,若是重定位表存在的話,它的地址確定能夠在PE文件頭中的數據目錄中找到。
3.重定位表的結構
雖然重定位表中的有用數據是那些須要重定位機器碼的地址指針,但爲了節省空間,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),結構的定義是這樣的:
IMAGE_BASE_RELOCATION STRUCT
VirtualAddress dd ? ;重定位內存頁的起始RVA
SizeOfBlock dd ? ;重定位塊的長度
IMAGE_BASE_RELOCATION ENDS
VirtualAddress字段是當前頁面起始地址的RVA值,本塊中全部重定位項中的12位地址加上這個起始地址後就獲得了真正的RVA值。SizeOfBlock字段定義的是當前重定位塊的大小,從這個字段的值能夠算出塊中重定位項的數量,因爲SizeOfBlock=4+4+2×n,也就是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,這就和重定位塊的結束方式衝突了。
4、實例分析
咱們來分析一個實例,簡單分析一個DLL的重定位表,先反彙編:
根據上面的理論講解,咱們須要重定位的有兩處:00402000和00403030
下面咱們就來實例分析一下,是否是?首先找到重定位表的指針:
若是還不知道重定位表的RVA是怎麼找的,前參照我前面加的內容。
從圖中能夠看出數據目錄表指向重定位表的指針是5000h,換算成文件偏移地址就是0E00h,(也不要問我怎麼來的,我前面已經說明三個步驟)咱們在定位到File Offset爲0E00處,能夠獲得IMAGE_BASE_RELOCATION結構以下圖所示:
從圖中能夠看出:
VirtualAddress:00001000h
SizeOfBlock:00000010h(有四個重定位數據,(10h-8h)/2h=4h)
重定位數據1:300Fh
重定位數據2:3023h
重定位數據3:0000h(用於對齊)
重定位數據4:0000h(用於對齊)
重定位數據計算過程以下表所示:
用十六進制工具查看實例文件,其中060Fh和623h分別指向402000h和403030h,以下圖所示:
和咱們上面假設的徹底同樣!
執行PE文件前,加載程序在進行重定位的時候,會將PE文件在內存中的實際映像地址減去PE文件所要求的映像地址,獲得一個差值,再將這一差值根據重定位類型的不一樣添加到地址數組中。