PE文件结构详解(六)重定位
前面兩篇?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保存重定位項在頁內的偏移,這樣就大大縮小了重定位表的大小。
有了上面的概念,我們現在可以來看一下基址重定位表的定義了:
[cpp]?view plain?copy
SizeOfBlock:表示該分組保存了幾項重定位項。
TypeOffset:這個域有兩個含義,大家都知道,頁內偏移用12位就可以表示,剩下的高4位用來表示重定位的類型。而事實上,Windows只用了一種類型IMAGE_REL_BASED_HIGHLOW ?數值是 3。
好了,有了以上知識,相信大家可以很容易的寫出自己修正重定位表的代碼,不如自己做個練習驗證一下吧。
本文?by evil.eagle 轉載的時候請注明出處。http://blog.csdn.net/evileagle/article/details/12886949
最后,還是總結一下,哪些項目需要被重定位呢?
1.代碼中使用全局變量的指令,因為全局變量一定是模塊內的地址,而且使用全局變量的語句在編譯后會產生一條引用全局變量基地址的指令。
2.將模塊函數指針賦值給變量或作為參數傳遞,因為賦值或傳遞參數是會產生mov和push指令,這些指令需要直接地址。
3.C++中的構造函數和析構函數賦值虛函數表指針,虛函數表中的每一項本身就是重定位項,為什么呢?大家自己考慮一下吧,不難哦~
總結
以上是生活随笔為你收集整理的PE文件结构详解(六)重定位的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PE文件结构详解(五)延迟导入表
- 下一篇: 判断程序是否运行在虚拟机中的代码