網絡安全編程:PE結構的地址與地址的轉換
用OD調試器調試程序時看到的地址與使用C32Asm以十六進制形式查看程序時的地址形式有所差異。程序在內存中與在文件中有著不同的地址形式,而且PE相關的地址不只有這兩種形式。與PE結構相關的地址形式有3種,且這3種地址形式可以進行轉換。
1. 與PE結構相關的3種地址
與PE結構相關的3種地址是VA(虛擬地址)、RVA(相對虛擬地址)和FileOffset(文件偏移地址)。
VA(虛擬地址):PE 文件映射到內存后的地址。
RVA(相對虛擬地址):內存地址相對于映射基地址的偏移地址。
FileOffset(文件偏移地址):相對 PE 文件在磁盤上的文件開頭的偏移地址。
PE文件在磁盤上和在內存中的結構是一樣的。所不同的是,在磁盤上,文件是按照IMAGE_OPTIONAL_HEADER的FileAlignment值進行對齊的。而在內存中,映像文件是按照IMAGE_OPTIONAL_HEADER的SectionAlignment進行對齊的。FileAlignment是以磁盤上的扇區為單位的,也就是說,FileAlignment最小為512字節,十六進制的0x200字節。而SectionAlignment是以內存分頁為單位來對齊的,通常Win32平臺一個內存分頁為4KB,也就是十六進制的0x1000字節。一般情況下,FileAlignment的值會與SectionAlignment的值相同,這樣磁盤文件和內存映像的結構是完全一樣的。當FileAlignment的值和SectionAlignment的值不相同的時候,就存在一些細微的差異了,其主要區別在于,根據對齊的實際情況而多填充了很多0值。PE文件映射如圖1所示。
圖1 PE文件映射圖
除了文件對齊與內存對齊的差異以外,文件的起始地址從0地址開始,用C32Asm的十六進制模式查看PE文件時起始位置是0x00000000。而在內存中,它的起始地址為IMAGE_OPTIONAL_HEADER結構體的ImageBase字段(該說法只針對EXE文件,DLL文件的映射地址不一定固定,但是絕對不會是0x00000000地址)。
2. 3種地址的轉換
當FileAlignment和SectionAlignment的值不相同時,磁盤文件與內存映像的同一節表數據在磁盤和內存中的偏移也不相同,這樣兩個偏移就發生了一個需要轉換的問題。當知道某數據的RVA,想要在文件中讀取同樣的數據的時候,就必須將RVA轉換為FileOffset。反之,也是同樣的情況。
下面用一個例子來介紹如何進行轉換。一個用MessageBox()輸出“Hello World”的例子程序,用PEID打開它,查看它的節表情況,如圖2所示。
圖2 PEID顯示的節表內容
從圖2的標題欄可以看到,這里不叫“節表”,而叫“區段”。還有別的資料上稱之為“區塊”或“節區”,只是叫法不同,內容都是一樣的。
從圖2中可以看到,節表的第一個節區的節名稱為“.text”。通常情況下,第一個節表項都是代碼區,入口點也通常落在這個節表項。在早期殼不流行時,通過判斷入口點是否在第一個節區就可以判斷該程序是否被病毒感。如今,由于殼的流行,這種判斷方法就不可靠了。關鍵要看的是“R.偏移”,表明了該節區在文件中的起始位置。PE頭部包括DOS頭、PE頭和節表,通常不會超過512字節,也就是說,不會超過0x200的大小。如果這個“R.偏移”為0x00001000,那么通常情況下可以確定該文件的磁盤對齊大小為0x1000。測試驗證一下這個程序,看到“V.偏移”與“R.偏移”相同,則說明磁盤對齊與內存對齊是一樣的,這樣就沒辦法完成演示轉換的工作了。不過,可以人為地修改文件對齊大小。也可以通過工具來修改文件對齊的大小。這里借助LordPE來修改其文件對齊大小。修改方法很簡單,先將要修改的測試文件復制一份,以與修改后的文件做對比。打開LordPE,單擊“重建PE”按鈕,然后選擇剛才復制的那個測試文件,如圖3和圖4所示。
圖3 LordPE界面
圖4 重建PE功能結果
PE重建功能中有壓縮文件大小的功能,這里的壓縮也就是修改磁盤文件的對齊值,避免過多地因對齊而進行補0,使其少占用磁盤空間。用PEID查看這個進行重建的PE文件的節表,如圖5所示。
圖5 重建PE文件后的節表
現在可以看到“V.偏移”與“R.偏移”的值不相同了,它們的對齊值也不相同了,大家可以自己驗證一下FileAlignment和SectionAlignment的值是否相同。
現在有兩個功能完全一樣,而且PE結構也一樣的兩個文件了,唯一的不同就是其磁盤對齊大小不同。現在在這兩個程序中分別尋找一個節表中的數據,學習不同地址之間的轉換。
先用OD打開未進行重建PE結構的測試程序,找到反匯編中調用MessageBox()處要彈出對話框的兩個字符串參數的地址,如圖6和圖7所示。
圖6 MessageBox()函數中使用的字符串地址
圖7 兩個字符串的地址在數據窗口的顯示
從圖6和圖7中可以看到,字符串“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處的內容,如圖8所示。
圖8 文件偏移0x00006030處的內容為“hello world!”字符串
從這個例子中可以看出,當SectionAlignment和FileAlignment相同時,同一節表項中數據的RVA(相對虛擬地址)和FileOffset(文件偏移地址)是相同的。RVA的值是用VA – ImageBase計算得到的。
再用OD打開“重建PE”后的測試程序,同樣找到反匯編中調用MessageBox()函數使用的那個字符串“hello world !”,看其虛擬地址是多少。它的虛擬地址仍然是0x00406030。同樣,用虛擬地址減去裝載地址,相對虛擬地址的值仍然為0x00006030。不過用C32Asm打開該文件查看的話會有所不同。用C32Asm看一下0x00006030地址處的內容,如圖9所示。
圖9 文件偏移0x00006030處沒有“hello world!”字符串
從圖9中可以看到,用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地址處的內容,如圖10所示。
圖10 0x00004030文件偏移處的內容
從圖10中可以看出,該文件偏移處保存著“hello world !”字符串,也就是說,將RVA轉換為FileOffset是正確的。通過LordPE工具來驗證一下,如圖11所示。
圖11 用LordPE計算RVA為0x00006030的文件偏移
再來回顧一下這個過程。
某數據的文件偏移 = 該數據所在節的起始文件偏移 + (某數據的RVA –該數據所在節的起始RVA)。
除了上面的計算方法以外,還有一種計算方法,即用節的起始RVA值減去節的起始文件偏移值,得到一個差值,再用RVA減去這個得到的差值,就可以得到其所對應的FileOffset。可以使用例子程序進行手工計算,然后通過LordPE進行驗證。
知道如何通過RVA轉換為文件偏移,那么通過文件偏移轉換為RVA的方法也就不難了。這3種地址相互的轉換方法就介紹完了。如果沒有理解,就可以反復地按照公式進行學習和計算。只要在頭腦中建立關于磁盤文件和內存映像的結構,那么理解起來就不會太吃力。