1. 程式人生 > 其它 >PE檔案格式介紹(一)

PE檔案格式介紹(一)

PE檔案格式介紹(一)

0x00 前言

  PE檔案是portable File Format(可移植檔案)的簡寫,我們比較熟悉的DLL和exe檔案都是PE檔案。瞭解PE檔案格式有助於加深對作業系統的理解,掌握可執行檔案的資料結構機器執行機制,對於逆向破解,加殼等安全方面方面的同學極其重要。接下來我將通過接下來幾篇詳細介紹PE檔案的格式。

0x01 基本概念

PE檔案使用的是一個平面地址空間,所有程式碼和資料都被合併在一起,組成一個很大的組織結構。檔案的內容分割為不同的區塊(Setion,又稱區段,節等),區段中包含程式碼資料,各個區塊按照頁邊界來對齊,區塊沒有限制大小,是一個連續的結構。每塊都有他自己在記憶體中的屬性,比如:這個塊是否可讀可寫,或者只讀等等。

認識PE檔案不是作為單一記憶體對映檔案被裝入記憶體是很重要的,windows載入器(PE載入器)便利PE檔案並決定檔案的哪個部分被對映,這種對映方式是將檔案較高的偏移位置對映到較高的記憶體地址中。當磁碟的資料結構中尋找一些內容,那麼幾乎能在被裝入到記憶體對映檔案中找到相同的資訊。但是資料之間的位置可能改變,其某項的偏移地址可能區別於原始的偏移位置,不管怎麼樣,所表現出來的資訊都允許從磁碟檔案到記憶體偏移的轉換,如下圖:

 PS:PE檔案頭以下的地址無論在記憶體對映中還是在磁碟對映中都是一樣的,當記憶體分頁和磁碟分頁一致時無需進行地址轉換,只有當磁碟分頁和記憶體分頁不一樣時才要進行地址轉化,這點很重要,拿到PE檔案是首先檢視分頁是否一致。前兩天一直沒碰到記憶體和磁碟分頁不一樣的,所以這個點一直沒發現,今天特來補上。

下面要介紹幾個重要概念,分別是基地址(ImageBase),相對虛擬地址(Relative Virtual Address),檔案偏移地址(File Offset)。

1)基地址

定義:當PE檔案通過Windows載入器被裝入記憶體後,記憶體中的版本被稱作模組(Module)。對映檔案的起始地址被稱作模組控制代碼(hMoudule),可以通過模組控制代碼訪問其他的資料結構。這個初始記憶體弟子就是基地址。

記憶體中的模組代表著程序從這個可執行檔案中所需要的程式碼,資料,資源,輸入表,輸出表以及其他有用的資料結構所使用的記憶體都放在一個連續的記憶體塊中,程式設計人員只要知道裝載程式檔案映像到記憶體的基地址即可。在32位系統中可以直接呼叫GetModuleHandle以取得指向DLL的指標,通過指標訪問DLL module的內容,例如:

HMODULE GetmoduleHandle(LPCTSRT lpModuleName);

當呼叫該函式時,傳遞一個可執行檔案或者DLL檔名字字串。如果系統找到該檔案,則返回該可執行檔案的或者DLL檔案映像載入到的基地址。也可以呼叫GetModuleHandle,傳遞NULL引數,則返回呼叫的可執行檔案的基地址。

2)相對虛擬地址

在可執行檔案中,有相當多的地方需要指定記憶體的地址。例如:引用全域性變數時,需要指定它的地址。PE檔案儘管有一個首選的載入地址(基地址),但是他們可以載入到程序空間的任意地方,所以不能依賴與PE的載入點。由於這個原因,必須有一個方法來指定一個地址而不是依賴於PE載入點。

為了在PE檔案中避免有確定的記憶體地址,出現了相對虛擬地址(Relative Virtual Addres,簡稱RVA)的概念。RVA只是記憶體中的一個簡單的相對於PE檔案裝入地址的偏移地址,它是一個“相對”地址,或者稱位“偏移量”地址。例如:假設一個EXE檔案從地址40000h處載入,並且它的程式碼區塊開始於4010000h,程式碼區的RVA將是:

目標地址401000h ——轉入地址400000h則RVA=1000h。

將RVA地址轉換成真實地址,只需簡單的翻轉這個過程:將實際裝入地址加上RVA即可得到實際的記憶體地址。順便一提,在PE用語裡,實際的記憶體地址被稱作虛擬地址(Vritual Address,簡稱VA),另外也可以把虛擬地址想象為加上首選裝入地址的RVA。不要忘了前面提到的裝入地址等同於模組控制代碼,它們之間的關係如下:

虛擬地址(VA)=基地址(ImageBase)+相對虛擬地址(RVA)

3)檔案偏移地址

當PE檔案儲存在磁碟上時,某個資料的位置相對於檔案頭的偏移量也稱檔案偏移地址(FileOffset)或者實體地址(RAW Offset)。檔案偏移地址從PE檔案的第一個位元組開始計數,起始為零。用十六進位制工具比如:winhex,hexworkshop都可以檢視。注意這個實體地址和虛擬地址的區別,實體地址是檔案在磁碟上相對於檔案頭的地址,而虛擬地址是PE可執行程式載入在記憶體中的地址。

0x02 幾個重要頭部資訊介紹

接下來介紹MS-DOS頭部資訊,PE檔案頭資訊及幾個重要欄位。

1)MS-DOS頭部

每個PE檔案是以一個DOS程式開始的,有了它,一旦程式在DOS下執行,DOS就能辨別出這是個有效的執行體,然後執行緊隨MZ header(後面會介紹)之後的DOS stub(DOS塊)。DOS stub實際上是一個有效的EXE,在不支援PE檔案格式的作業系統中,它將簡單顯示一個錯誤提示,類似於字串“This Program cannot be run in MS-DOS”。使用者通常對DOS stub 不感興趣,因為大多數情況下他們由彙編器自動生成。平常把DOS stub和DOS MZ頭部合稱為DOS檔案頭。

PE檔案的第一個位元組起始於一個傳統的MS-DOS頭部,被稱作IMAGE_DOS_HEADER。其IMAGE_DOS_HEADER的結構如下(左邊的數字是到檔案頭的偏移量):

IMAGE_DOS_HEADER STRUCT 

+0h WORD e_magic   // Magic DOS signature MZ(4Dh 5Ah)     DOS可執行檔案標記 

+2h   WORD  e_cblp  // Bytes on last page of file    

+4h WORD  e_cp   // Pages in file 

+6h WORD  e_crlc   // Relocations 

+8h WORD  e_cparhdr   // Size of header in paragraphs 

+0ah WORD  e_minalloc  // Minimun extra paragraphs needs 

+0ch WORD  e_maxalloc  // Maximun extra paragraphs needs 

+0eh WORD  e_ss    // intial(relative)SS value      DOS程式碼的初始化堆疊SS 

+10h WORD  e_sp    // intial SP value                 DOS程式碼的初始化堆疊指標SP 

+12h WORD  e_csum    // Checksum 

+14h WORD  e_ip    //    intial IP value                     DOS程式碼的初始化指令入口[指標IP] 

+16h WORD  e_cs    // intial(relative)CS value                    DOS程式碼的初始堆疊入口 

+18h WORD  e_lfarlc    // File Address of relocation table 

+1ah WORD  e_ovno        //    Overlay number 

+1ch WORD  e_res[4]    // Reserved words 

+24h WORD  e_oemid    //    OEM identifier(for e_oeminfo) 

+26h WORD      e_oeminfo   //    OEM information;e_oemid specific  

+29h WORD  e_res2[10]   //    Reserved words 

+3ch DWORD   e_lfanew     // Offset to start of PE header             指向PE檔案頭 

} IMAGE_DOS_HEADER ENDS

 

這個結構中有兩欄位很重要,一個是e_magic,一個是e_lfanew。e_magic(一個字大小)欄位需要被設定為5A4Dh這個也是PE程式載入的重要標誌,這個值非常有意思,他們對應的字元分別位Z和M,是為了紀念MS-DOS的最初建立者Mark Zbikowski而專門設定的,由於在hex編輯器中顯示是由低位到高位故顯示為4D5Ah,剛好是建立者的名字縮寫。另一個欄位是e_lfanew。這個欄位表示的是真正的PE檔案頭部相對偏移地址(RVA),它指出了真正PE頭部檔案偏移位置。它佔用四個位元組,位於檔案開始偏移的3ch位元組中。

下面我將用hexworkshop開啟一個pe檔案向大家展示一下上面這段話的含義。

 

 

第一張圖說明的就是IMAGE_DOS_HEADER的第一個欄位e_magic的值與地址。第二張圖就是上面所講的第二個關鍵欄位e_fannew欄位的值(注意:不同的PE程式這個值可能不一樣,但原理一樣),這個值就是PE標頭檔案的起始偏移量。

2)PE檔案標頭檔案

相對於MS-DOS標頭檔案,PE標頭檔案PEheader要複雜的多,下面將詳細講解其中的幾個欄位。

緊跟著DOS標頭檔案下面的就是peheader。PEheader是PE相關結構NT映像頭(IMAGE_NT_HEADER)的簡稱,其中包含許多PE裝載器用到的重要欄位。執行體在支援PE檔案結構的作業系統執行時,PE裝載器將IMAGE_DOS_HEADER結構中的e_fanew欄位找到PEheader的起始偏移量,加上基址得到PE檔案頭的指標:

PNTHeader=IMAGBase+dosHeader->e_lfanewr(其實就是去欄位e_lfanew的值)。

下面來討論IMAGE_NT_HEADER的結構,它是由三個欄位組成(左邊的數字是PE檔案頭的偏移量):IMAGE_NT_HEADER STRUCT 

 

+0h Signature  DWORD              //PE檔案標誌

+4h FileHeader IMAGE_FILE_HEADER  //檔案頭初始偏移地址

+18 optionalHeader IMAGE_OPTION_HEADER //另一個重要頭部初始偏移地址

 

} IMAGE_NT_HEADER ENDS

下面對這三個欄位逐個詳細分析:

  1. Signature欄位

這個欄位是PE檔案的標誌欄位,通常設定成00004550h,其ASCII碼為PE00,這個欄位是PE檔案頭的開始,前面的DOS_HEADER結構中的欄位e_lfanew欄位就是指向這裡。

2.IMAGE_FILE_HEADER欄位

這個欄位也是包含幾個欄位結構,它包含了PE檔案的一些基本資訊,最重要的是其中一個域指出了IMAGE_OPTIONAL_HEADER的大小。

typedef struct _IMAGE_FILE_HEADER {

WORD Machine;//執行平臺

WORD NumberOfSections;//檔案的區塊數目

DWORD TimeDateStamp;//檔案建立的用時間戳標識的日期

DWORD PointerToSymbolTable;//指向符號表(用於除錯)

DWORD NumberOfSymbols;//符號表中符號的個數

WORD SizeOfOptionalHeader;//IMAGE_OPTIONAL_HEADER32結構大小

WORD Characteristics;//檔案屬性

} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

上圖標出七個欄位的位置及各自的值。

1)Machine欄位,表示目標CPU 的型別。

幾個常見的及其標識如下:

機器              標識     

Intel I386          14ch

MIPS R3000        162h

Alpha AXP          184h

Power PC           1F0h

MIPS R4000         184h

根據以上資訊我們知道這個PE檔案要執行在Intel I386機器上。

2)NumberOfSection,標識區塊的數目,關於區塊後面會詳細講。

3)TimeDateStamp

這個欄位沒啥好說的,指的就是PE檔案建立的事件,這個時間是指從1970年1月1日到建立該檔案的所有的秒數。

4)PointerToSymbolTable。這個欄位用的比較少,略

5)NumberOfSymbol。這個欄位也用得很少,略

6)SizeOfOptionalHeader:緊跟著IMAGE_FILE_HEADER後面的資料大小,這也是一個數據結構,它叫做IMAGE_OPTIONAL_HEADER,其大小依賴於是64位還是32位檔案。32位檔案值通常是00EOh,對於64位值通常為00F0h。

7)Characteristics:檔案屬性,普通EXE檔案這個欄位值為010fh,DLL檔案這個欄位一般是0210h。

下一篇將從欄位IMAGE_OPTIONAL_HEADER講起。