by evil.eagle 轉(zhuǎn)載請注明出處。
http://blog.csdn.net/evileagle/article/details/11693499
PE(Portable Execute)文件是Windows下可執(zhí)行文件的總稱,常見的有DLL,EXE,OCX,SYS等,事實(shí)上,一個文件是否是PE文件與其擴(kuò)展名無關(guān),PE文件可以是任何擴(kuò)展名。那Windows是怎么區(qū)分可執(zhí)行文件和非可執(zhí)行文件的呢?我們調(diào)用LoadLibrary傳遞了一個文件名,系統(tǒng)是如何判斷這個文件是一個合法的動態(tài)庫呢?這就涉及到PE文件結(jié)構(gòu)了。
PE文件的結(jié)構(gòu)一般來說如下圖所示:從起始位置開始依次是DOS頭,NT頭,節(jié)表以及具體的節(jié)。
當(dāng)一個PE文件被加載到內(nèi)存中以后,我們稱之為“映象”(image),一般來說,PE文件在硬盤上和在內(nèi)存里是不完全一樣的,被加載到內(nèi)存以后其占用的虛擬地址空間要比在硬盤上占用的空間大一些,這是因?yàn)楦鱾€節(jié)在硬盤上是連續(xù)的,而在內(nèi)存中是按頁對齊的,所以加載到內(nèi)存以后節(jié)之間會出現(xiàn)一些“空洞”。
因?yàn)榇嬖谶@種對齊,所以在PE結(jié)構(gòu)內(nèi)部,表示某個位置的地址采用了兩種方式,針對在硬盤上存儲文件中的地址,稱為原始存儲地址或物理地址表示距離文件頭的偏移;另外一種是針對加載到內(nèi)存以后映象中的地址,稱為相對虛擬地址(RVA),表示相對內(nèi)存映象頭的偏移。
然而CPU的某些指令是需要使用絕對地址的,比如取全局變量的地址,傳遞函數(shù)的地址編譯以后的匯編指令中肯定需要用到絕對地址而不是相對映象頭的偏移,因此PE文件會建議操作系統(tǒng)將其加載到某個內(nèi)存地址(這個叫基地址),編譯器便根據(jù)這個地址求出代碼中一些全局變量和函數(shù)的地址,并將這些地址用到對應(yīng)的指令中。例如在IDA里看上去是這個樣子:
這種表示方式叫做虛擬地址(VA)。
也許有人要問,既然有VA這么簡單的表示方式為什么還要有前面的RVA呢?因?yàn)殡m然PE文件為自己指定加載的基地址,但是windows有茫茫多的DLL,而且每個軟件也有自己的DLL,如果指定的地址已經(jīng)被別的DLL占了怎么辦?如果PE文件無法加載到預(yù)期的地址,那么系統(tǒng)會幫他重新選擇一個合適的基地址將他加載到此處,這時原有的VA就全部失效了,NT頭保存了PE文件加載所需的信息,在不知道PE會加載到哪個基地址之前,VA是無效的,所以在PE文件頭中大部分是使用RVA來表示地址的,而在代碼中是用VA表示全局變量和函數(shù)地址的。那又有人要問了,既然加載基址變了以后VA都失效了,那存在于代碼中的那些VA怎么辦呢?答案是:重定位。系統(tǒng)有自己的辦法修正這些值,到后續(xù)重定位表的文章中會詳細(xì)描述。既然有重定位,為什么NT頭不能依靠重定位采用VA表示地址呢(十萬個為什么)?因?yàn)椴皇撬械腜E都有重定位,早期的EXE就是沒有重定位的。
我們都知道PE文件可以導(dǎo)出函數(shù)讓其他的PE文件使用,也可以從其他PE文件導(dǎo)入函數(shù),這些是如何做到的?PE文件通過導(dǎo)出表指明自己導(dǎo)出那些函數(shù),通過導(dǎo)入表指明需要從哪些模塊導(dǎo)入哪些函數(shù)。導(dǎo)入和導(dǎo)出表的具體結(jié)構(gòu)會在單獨(dú)的文章中詳細(xì)解釋。
好了,看了這篇文章,相信大家應(yīng)該對PE文件有個整體的了解了,以后的篇章,我會將整個PE文件常見部分進(jìn)行“拆解“,敬請期待。