名詞解釋
檔案偏移:靜態反彙編工具看到的PE檔案中某條指令的位置是相對於磁碟檔案的。IDA Pro雖然是靜態反彙編工具,不過出來的是VA。
裝載基址(Image Base):PE檔案裝入記憶體的 基地址。默認情況下,EXE檔案的基址為0x00400000,DLL檔案的基址為0x10000000。
虛擬記憶體地址(VA):PE檔案中的指令被裝入記憶體後的地址,OllyDbg動態反彙編產生。
VA = Image Base + RVA。
PE檔案中的數據按照磁碟數據標準存放,以0x200位元組為基本單位進行組織。 代碼裝入記憶體後,將按照記憶體數據標準存放,以0x1000位元組為基本單位進行組織。
以下內容將十分有助於理解上述抽象名詞:
檔案組織
在Windows系統下,當一個PE應用程式運行時,這個PE檔案在磁碟中的數據結構布局和記憶體中的數據結構布局是一致的。系統在載入一個可執行程式時,首先是Windows裝載器(又稱PE裝載器)把磁碟中的檔案映射到進程的地址空間,它遍歷PE檔案並決定檔案的哪一部分被映射。其方式是將檔案較高的偏移位置映射到較高的記憶體地址中。磁碟檔案一旦被裝入記憶體中,其某項的偏移地址可能與原始的偏移地址有所不同,但所表現的是一種從磁碟檔案偏移到記憶體偏移的轉換,如圖2.2所示。
PE檔案記憶體映射
當PE檔案被載入到記憶體後,記憶體中的版本稱為模組(Module),映射檔案的起始地址稱為模組句柄(hModule),可以通過模組句柄訪問記憶體中的其他數據結構。這個初始記憶體地址也稱為檔案映像基址(ImageBase)。載入一個PE程式的主要步驟如下:
(1)當PE檔案被執行時,PE裝載器首先為進程分配一個4GB的虛擬地址空間,然後把程式所占用的磁碟空間作為虛擬記憶體映射到這個4GB的虛擬地址空間中。一般情況下,會映射到虛擬地址空間中0x400000的位置。裝載一個應用程式的時間比一般人所構想的要少,因為裝載一個PE檔案並不是把這個檔案一次性地從磁碟讀到記憶體中,而是簡單地做一個記憶體映射,映射一個大檔案和映射一個小檔案所花費的時間相差無幾。當然,真正執行檔案中的代碼時,作業系統還是要把存在於磁碟上的虛擬記憶體中的代碼交換到物理記憶體(RAM)中。但是,這種交換也不是把整個檔案所占用的虛擬地址空間一次性地全部從磁碟交換到物理記憶體中,作業系統會根據需要和記憶體占用情況交換一頁或多頁。當然,這種交換是雙向的,即存在於物理記憶體中的一部分當前沒有被使用的頁,也可能被交換到磁碟中。
(2)PE裝載器在核心中創建進程對象和主執行緒對象以及其他內容。
(3)PE裝載器搜尋PE檔案中的Import Table(引入表),裝載應用程式所使用的動態程式庫。對動態程式庫的裝載與對應用程式的裝載方法完全類似。
(4)PE裝載器執行PE檔案首部所指定地址處的代碼,開始執行應用程式主執行緒。
2.2.3 Big-endian和Little-endian
PE Header中IMAGE_FILE_HEADER的成員Machine 中的值,根據winnt.h中的定義,對於Intel CPU應該為0x014c。但是用十六進制編輯器打開PE檔案時,看到這個WORD顯示的卻是4c 01。其實4c 01就是0x014c,只不過由於Intel CPU是Little-endian,所以顯示出來是這樣的。對於Big-endian和Little-endian,請看下面的例子。一個整型int變數,長度為4個位元組。當這個整形變數的值為0x12345678時,對於Big-endian來說,顯示的是{12,34,45,78},而對於Little-endian來說,顯示的卻是{78,45,34,12}。注意Intel使用的是Little-endian。
2.2.4 3種不同的地址
PE檔案的各種結構中,涉及到很多地址、偏移。有些是指在檔案中的偏移,有些 是指在記憶體中的偏移。以下的第一種是指在檔案中的地址,第二、三種是指在記憶體中的地址。
第一種,檔案中的地址。比如用十六進制編輯器打開PE檔案,看到的地址(偏移)就是檔案中的地址,使用某個結構的檔案地址,就可以在檔案中找到該結構。
第二種,當檔案被整個映射到記憶體時,例如某些PE分析軟體,把整個PE檔案映射到記憶體中,這時是記憶體中的虛擬地址(VA)。如果知道在這個檔案中某一個結構的記憶體地址的話,那么它等於這個PE檔案被映射到記憶體的地址加上該結構在檔案中的地址。
第三種,當執行PE時,PE檔案會被載入器載入記憶體,這時經常需要的是RVA。例如知道一個結構的RVA,那么程式載入點加上RVA就可以得到該結構的記憶體地址。比如,如果PE檔案裝入虛擬地址(VA)空間的0x400000處,某一結構的RVA 為0x1000,那么其虛擬地址為0x401000。
PE檔案格式要用到RVA,主要是為了減少PE裝載器的負擔。因為每個模組都有可能被重載到任何虛擬地址空間,如果讓PE裝載器修正每個重定位項,這肯定是個夢魘。相反,如果所有重定位項都使用RVA,那么PE裝載器就不必操心那些東西了,即它只要將整個模組重定位到新的起始VA。這就像相對路徑和絕對路徑的概念:RVA類似相對路徑,VA就像絕對路徑。
注意,RVA和VA是指記憶體中,不是指檔案中。是指相對於載入點的偏移而不是一個記憶體地址,只有RVA加上載入點的地址,才是一個實際的記憶體地址。