天天看點

《C++ 黑客程式設計揭秘與防範(第2版)》——6.3 PE結構的3種位址

本節書摘來自異步社群出版社《c++ 黑客程式設計揭秘與防範(第2版)》一書中的第6章,第6.3節,作者:冀雲,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

c++ 黑客程式設計揭秘與防範(第2版)

在上一章中用od調試器調試程式時看到的位址與本章使用c32asm以十六進制形式檢視程式時的位址形式有所差異。程式在記憶體中與在檔案中有着不同的位址形式,而且pe相關的位址不隻有這兩種形式。與pe結構相關的位址形式有3種,且這3種位址形式可以進行轉換。

與pe結構相關的3種位址是va(虛拟位址)、rva(相對虛拟位址)和fileoffset(檔案偏移位址)。

va(虛拟位址):pe檔案映射到記憶體後的位址。

rva(相對虛拟位址):記憶體位址相對于映射基位址的偏移位址。

fileoffset(檔案偏移位址):相對pe檔案在磁盤上的檔案開頭的偏移位址。

這3種位址都是和pe檔案結構密切相關的,前面簡單地引用過這幾個位址,但是前面隻是個概念。從了解節表開始,這3種位址的概念就非常重要了,否則後面的很多内容都将無法了解。

這3個概念之是以重要,是因為後面要不斷地使用它們,而且三者之間的關系也很重要。每個位址之間的轉換也很重要,尤其是va和fileoffset的轉換、rva和fileoffset之間的轉換。這兩個轉換不能說複雜,但是需要一定的公式。va和rva的轉換就非常簡單了。

pe檔案在磁盤上和在記憶體中的結構是一樣的。所不同的是,在磁盤上,檔案是按照image_optional_header的filealignment值進行對齊的。而在記憶體中,映像檔案是按照image_optional_header的sectionalignment進行對齊的。這兩個值前面已經介紹過了,這裡再進行簡單的回顧。filealignment是以磁盤上的扇區為機關的,也就是說,filealignment最小為512位元組,十六進制的0x200位元組。而sectionalignment是以記憶體分頁為機關來對齊的,通常win32平台一個記憶體分頁為4k,也就是十六進制的0x1000位元組。一般情況下,filealignment的值會與sectionalignment的值相同,這樣磁盤檔案和記憶體映像的結構是完全一樣的。當filealig-nment的值和sectionalignment的值不相同的時候,就存在一些細微的差異了,其主要差別在于,根據對齊的實際情況而多填充了很多0值。pe檔案映射如圖6-15所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.3 PE結構的3種位址

圖6-15 pe檔案映射圖

除了檔案對齊與記憶體對齊的差異以外,檔案的起始位址從0位址開始,用c32asm的十六進制模式檢視pe檔案時起始位置是0x00000000。而在記憶體中,它的起始位址為image_optional_header結構體的imagebase字段(該說法隻針對exe檔案,dll檔案的映射位址不一定固定,但是絕對不會是0x00000000位址)。

當filealignment和sectionalignment的值不相同時,磁盤檔案與記憶體映像的同一節表資料在磁盤和記憶體中的偏移也不相同,這樣兩個偏移就發生了一個需要轉換的問題。當知道某資料的rva,想要在檔案中讀取同樣的資料的時候,就必須将rva轉換為fileoffset。反之,也是同樣的情況。

下面用一個例子來介紹如何進行轉換。還記得前面為了分析pe檔案結構而寫的那個用messagebox()輸出“hello world”的例子程式嗎?用peid打開它,檢視它的節表情況,如圖6-16所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.3 PE結構的3種位址

圖6-16 peid顯示的節表内容

從圖6-16的标題欄可以看到,這裡不叫“節表”,而叫“區段”。還有别的資料上稱之為“區塊”或“節區”,隻是叫法不同,内容都是一樣的。

從圖6-16中可以看到,節表的第一個節區的節名稱為“.text”。通常情況下,第一個節表項都是代碼區,入口點也通常落在這個節表項。在早期殼不流行時,通過判斷入口點是否在第一個節區就可以判斷該程式是否被病毒感。如今,由于殼的流行,這種判斷方法就不可靠了。關鍵要看的是“r.偏移”,表明了該節區在檔案中的起始位置。pe頭部包括dos頭、pe頭和節表,通常不會超過512位元組,也就是說,不會超過0x200的大小。如果這個“r.偏移”為0x00001000,那麼通常情況下可以确定該檔案的磁盤對齊大小為0x1000(注意:這個測試程式是筆者自己寫的,是以比較熟悉程式的pe結構。而且這也是一種經驗的判斷。嚴格來講,還是要去檢視image_optional_header的sectionalignment和filealignment兩個成員變量的值)。測試驗證一下這個程式,看到“v.偏移”與“r.偏移”相同,則說明磁盤對齊與記憶體對齊是一樣的,這樣就沒辦法完成示範轉換的工作了。不過,可以人為地修改檔案對齊大小。也可以通過工具來修改檔案對齊的大小。這裡借助lordpe來修改其檔案對齊大小。修改方法很簡單,先将要修改的測試檔案複制一份,以與修改後的檔案做對比。打開lordpe,單擊“重建pe”按鈕,然後選擇剛才複制的那個測試檔案,如圖6-17和圖6-18所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.3 PE結構的3種位址

圖6-17 lordpe界面

《C++ 黑客程式設計揭秘與防範(第2版)》——6.3 PE結構的3種位址

圖6-18 重建pe功能結果

pe重建功能中有壓縮檔案大小的功能,這裡的壓縮也就是修改磁盤檔案的對齊值,避免過多地因對齊而進行補0,使其少占用磁盤空間。用peid檢視這個進行重建的pe檔案的節表,如圖6-19所示。

現在可以看到“v.偏移”與“r.偏移”的值不相同了,它們的對齊值也不相同了,大家可以自己驗證一下filealignment和sectionalignment的值是否相同。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.3 PE結構的3種位址

圖6-19 重建pe檔案後的節表

現在有兩個功能完全一樣,而且pe結構也一樣的兩個檔案了,唯一的不同就是其磁盤對齊大小不同。現在在這兩個程式中分别尋找一個節表中的資料,學習不同位址之間的轉換。

先用od打開未進行重建pe結構的測試程式,找到反彙編中調用messagebox()處要彈出對話框的兩個字元串參數的位址,如圖6-20和圖6-21所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.3 PE結構的3種位址

從圖6-20和圖6-21中可以看到,字元串“hello world !”的位址為0x00406030,字元串“hello”的位址為0x00406040。這兩個位址都是虛拟位址,也就是va。

将va(虛拟位址)轉換為rva(相對虛拟位址)是很容易的,rva(相對虛拟位址)為va(虛拟位址)減去image_optional_header結構體中的imagebase(映像檔案的裝載虛拟位址)字段的值,即rva = va – imagebase = 0x00406030 – 0x00400000 = 0x0000 6030。由于image_optional_header中的sectionalignment和filealignment的值相同,是以其fileoffset的值也為0x00006030。用c32asm打開該檔案檢視檔案偏移位址0x00006030處的内容,如圖6-22所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.3 PE結構的3種位址

圖6-22 檔案偏移0x00006030處的内容為“hello world!”字元串

從這個例子中可以看出,當sectionalignment和filealignment相同時,同一節表項中資料的rva(相對虛拟位址)和fileoffset(檔案偏移位址)是相同的。rva的值是用va – imagebase計算得到的。

再用od打開“重建pe”後的測試程式,同樣找到反彙編中調用messagebox()函數使用的那個字元串“hello world !”,看其虛拟位址是多少。它的虛拟位址仍然是0x00406030。同樣,用虛拟位址減去裝載位址,相對虛拟位址的值仍然為0x00006030。不過用c32asm打開該檔案檢視的話會有所不同。用c32asm看一下0x00006030位址處的内容,如圖6-23所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.3 PE結構的3種位址

圖6-23 檔案偏移0x00006030處沒有“hello world!”字元串

從圖6-23中可以看到,用c32asm打開該檔案後,檔案偏移0x00006030處并沒有“hello world!”和“hello”字元串。這就是由檔案對齊與記憶體對齊的差異所引起的。這時就要通過一些簡單的計算把rva轉換為fileoffset。

把rva轉換為fileoffset的方法很簡單,首先看一下目前的rva或者是fileoffset屬于哪個節。0x00006030這個rva屬于.data節。0x00006030這個rva相對于該節的起始rva位址0x00006000來說偏移0x30位元組。再看.data節在檔案中的起始位置為0x00004000,以.data節的檔案起始偏移0x00004000加上0x30位元組的值為0x00004030。用c32asm看一下0x00004030位址處的内容,如圖6-24所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.3 PE結構的3種位址

圖6-24 0x00004030檔案偏移處的内容

從圖6-24中可以看出,該檔案偏移處儲存着“hello world !”字元串,也就是說,将rva轉換為fileoffset是正确的。通過lordpe工具來驗證一下,如圖6-25所示。

《C++ 黑客程式設計揭秘與防範(第2版)》——6.3 PE結構的3種位址

圖6-25 用lordpe計算rva為0x00006030的檔案偏移

再來回顧一下這個過程。

某資料的檔案偏移 = 該資料所在節的起始檔案偏移 + (某資料的rva –該資料所在節的起始rva)。

除了上面的計算方法以外,還有一種計算方法,即用節的起始rva值減去節的起始檔案偏移值,得到一個內插補點,再用rva減去這個得到的內插補點,就可以得到其所對應的fileoffset。讀者可以使用例子程式進行手工計算,然後通過lordpe進行驗證。

知道如何通過rva轉換為檔案偏移,那麼通過檔案偏移轉換為rva的方法也就不難了。這3種位址互相的轉換方法就介紹完了。讀者如果沒有了解,就可以反複地按照公式進行學習和計算。隻要在頭腦中建立關于磁盤檔案和記憶體映像的結構,那麼了解起來就不會太吃力。在後面的例子中,将會寫一個類似lordpe中轉換3種位址的程式,以幫助讀者加強了解。

繼續閱讀