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
下面對這三個字段逐個詳細分析:
這個字段是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講起。
?
轉載于:https://www.cnblogs.com/2f28/p/9800992.html
總結
以上是生活随笔為你收集整理的PE文件格式详解(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(750):作用域导读
- 下一篇: 逗你玩学脱壳(一)