前面兩篇 pe檔案結構詳解(四)pe匯入表 和 pe檔案結構詳解(五)延遲匯入表 介紹了pe檔案中比較常用的兩種匯入方式,不知道大家有沒有注意到,在呼叫匯入函式時系統生成的**是像下面這樣的:
在這裡,ie的iexplorer.exe匯入了kernel32.dll的getcommandlinea函式,可以看到這是個間接call,00401004這個位址的記憶體裡儲存了目的位址,根據圖中顯示的符號資訊可知,00401004這個位址是存在於iexplorer.exe模組中的,實際上也就是一項iat的位址。這個是ie6的exe中的例子,當然在dll中如果匯入其他dll中的函式,結果也是一樣的。這樣就有乙個問題,**裡call的位址是乙個模組內的位址,而且是乙個va,那麼如果模組基位址發生了變化,這個位址豈不是就無效了?這個問題如何解決?
答案是:windows使用重定位機制保證以上**無論模組載入到哪個基址都能正確被呼叫。聽起來很神奇,是怎麼做到的呢?其實原理並不很複雜,這個過程分三步:
1.編譯的時候由編譯器識別出哪些項使用了模組內的直接va,比如push乙個全域性變數、函式位址,這些指令的運算元在模組載入的時候就需要被重定位。
2.鏈結器生成pe檔案的時候將編譯器識別的重定位的項紀錄在一張表裡,這張表就是重定位表,儲存在datadirectory中,序號是 image_directory_entry_basereloc。
3.pe檔案載入時,pe 載入器分析重定位表,將其中每一項按照現在的模組基址進行重定位。
在檢視重定位表的定義前,我們先了解一下他的儲存方式,有助於後面的理解。按照常規思路,每個重定位項應該是乙個dword,裡面儲存需要重定位的rva,這樣只需要簡單操作便能找到需要重定位的項。然而,windows並沒有這樣設計,原因是這樣存放太占用空間了,試想一下,加入乙個檔案有n個重定位項,那麼就需要占用4*n個位元組。所以windows採用了分組的方式,按照重定位項所在的頁面分組,每組儲存乙個頁面其實位址的rva,頁內的每項重定位項使用乙個word儲存重定位項在頁內的偏移,這樣就大大縮小了重定位表的大小。
有了上面的概念,我們現在可以來看一下基址重定位表的定義了:
typedef struct _image_base_relocation image_base_relocation;
typedef image_base_relocation unaligned * pimage_base_relocation;
virtualaddress:頁起始位址rva。
sizeofblock:表示該分組儲存了幾項重定位項。
typeoffset:這個域有兩個含義,大家都知道,頁內偏移用12位就可以表示,剩下的高4位用來表示重定位的型別。而事實上,windows只用了一種型別image_rel_based_highlow 數值是 3。
好了,有了以上知識,相信大家可以很容易的寫出自己修正重定位表的**,不如自己做個練習驗證一下吧。
最後,還是總結一下,哪些專案需要被重定位呢?
1.**中使用全域性變數的指令,因為全域性變數一定是模組內的位址,而且使用全域性變數的語句在編譯後會產生一條引用全域性變數基位址的指令。
2.將模組函式指標賦值給變數或作為引數傳遞,因為賦值或傳遞引數是會產生mov和push指令,這些指令需要直接位址。
PE檔案結構詳解(六)重定位
前面兩篇 pe檔案結構詳解 四 pe匯入表 和 pe檔案結構詳解 五 延遲匯入表 在這裡,ie的iexplorer.exe匯入了kernel32.dll的getcommandlinea函式,可以看到這是個間接call,00401004這個位址的記憶體裡儲存了目的位址,根據圖中顯示的符號資訊可知,00...
PE 重定位表
參考文章 重定位表 relocation table 用於在程式載入到記憶體中時,進行記憶體位址的修正。為什麼要進行記憶體位址的修正?我們舉個例子來說 test.exe可執行程式需要三個動態鏈結庫dll a.dll,b.dll,c.dll 假設test.exe的imagebase為400000h,而...
PE重定位表解析
在模組被載入到記憶體中,如果該模組沒有裝載到期待的位置,裡面以固定形式而不是以偏移形式硬編碼的位址就需要修正,這樣程式才能被正確載入。例如 call 401203。這個在編譯器編譯的時候將call後面的函式位址以硬編碼的形式固定住,那麼一旦模組不是被載入到40000的基址,而是被載入到100000,...