PE文件格式详解(一)
PE文件格式介紹(一)
0x00 前言
??PE文件是portable File Format(可移植文件)的簡寫,我們比較熟悉的DLL和exe文件都是PE文件。了解PE文件格式有助于加深對操作系統(tǒng)的理解,掌握可執(zhí)行文件的數(shù)據(jù)結(jié)構(gòu)機器運行機制,對于逆向破解,加殼等安全方面方面的同學(xué)極其重要。接下來我將通過接下來幾篇詳細(xì)介紹PE文件的格式。
0x01 基本概念
PE文件使用的是一個平面地址空間,所有代碼和數(shù)據(jù)都被合并在一起,組成一個很大的組織結(jié)構(gòu)。文件的內(nèi)容分割為不同的區(qū)塊(Setion,又稱區(qū)段,節(jié)等),區(qū)段中包含代碼數(shù)據(jù),各個區(qū)塊按照頁邊界來對齊,區(qū)塊沒有限制大小,是一個連續(xù)的結(jié)構(gòu)。每塊都有他自己在內(nèi)存中的屬性,比如:這個塊是否可讀可寫,或者只讀等等。
認(rèn)識PE文件不是作為單一內(nèi)存映射文件被裝入內(nèi)存是很重要的,windows加載器(PE加載器)便利PE文件并決定文件的哪個部分被映射,這種映射方式是將文件較高的偏移位置映射到較高的內(nèi)存地址中。當(dāng)磁盤的數(shù)據(jù)結(jié)構(gòu)中尋找一些內(nèi)容,那么幾乎能在被裝入到內(nèi)存映射文件中找到相同的信息。但是數(shù)據(jù)之間的位置可能改變,其某項的偏移地址可能區(qū)別于原始的偏移位置,不管怎么樣,所表現(xiàn)出來的信息都允許從磁盤文件到內(nèi)存偏移的轉(zhuǎn)換,如下圖:
?PS:PE文件頭以下的地址無論在內(nèi)存映射中還是在磁盤映射中都是一樣的,當(dāng)內(nèi)存分頁和磁盤分頁一致時無需進(jìn)行地址轉(zhuǎn)換,只有當(dāng)磁盤分頁和內(nèi)存分頁不一樣時才要進(jìn)行地址轉(zhuǎn)化,這點很重要,拿到PE文件是首先查看分頁是否一致。前兩天一直沒碰到內(nèi)存和磁盤分頁不一樣的,所以這個點一直沒發(fā)現(xiàn),今天特來補上。
下面要介紹幾個重要概念,分別是基地址(ImageBase),相對虛擬地址(Relative Virtual Address),文件偏移地址(File Offset)。
1)基地址
定義:當(dāng)PE文件通過Windows加載器被裝入內(nèi)存后,內(nèi)存中的版本被稱作模塊(Module)。映射文件的起始地址被稱作模塊句柄(hMoudule),可以通過模塊句柄訪問其他的數(shù)據(jù)結(jié)構(gòu)。這個初始內(nèi)存弟子就是基地址。
內(nèi)存中的模塊代表著進(jìn)程從這個可執(zhí)行文件中所需要的代碼,數(shù)據(jù),資源,輸入表,輸出表以及其他有用的數(shù)據(jù)結(jié)構(gòu)所使用的內(nèi)存都放在一個連續(xù)的內(nèi)存塊中,編程人員只要知道裝載程序文件映像到內(nèi)存的基地址即可。在32位系統(tǒng)中可以直接調(diào)用GetModuleHandle以取得指向DLL的指針,通過指針訪問DLL module的內(nèi)容,例如:
HMODULE GetmoduleHandle(LPCTSRT lpModuleName);
當(dāng)調(diào)用該函數(shù)時,傳遞一個可執(zhí)行文件或者DLL文件名字字符串。如果系統(tǒng)找到該文件,則返回該可執(zhí)行文件的或者DLL文件映像加載到的基地址。也可以調(diào)用GetModuleHandle,傳遞NULL參數(shù),則返回調(diào)用的可執(zhí)行文件的基地址。
2)相對虛擬地址
在可執(zhí)行文件中,有相當(dāng)多的地方需要指定內(nèi)存的地址。例如:引用全局變量時,需要指定它的地址。PE文件盡管有一個首選的載入地址(基地址),但是他們可以載入到進(jìn)程空間的任意地方,所以不能依賴與PE的載入點。由于這個原因,必須有一個方法來指定一個地址而不是依賴于PE載入點。
為了在PE文件中避免有確定的內(nèi)存地址,出現(xiàn)了相對虛擬地址(Relative Virtual Addres,簡稱RVA)的概念。RVA只是內(nèi)存中的一個簡單的相對于PE文件裝入地址的偏移地址,它是一個“相對”地址,或者稱位“偏移量”地址。例如:假設(shè)一個EXE文件從地址40000h處載入,并且它的代碼區(qū)塊開始于4010000h,代碼區(qū)的RVA將是:
目標(biāo)地址401000h ——轉(zhuǎn)入地址400000h則RVA=1000h。
將RVA地址轉(zhuǎn)換成真實地址,只需簡單的翻轉(zhuǎn)這個過程:將實際裝入地址加上RVA即可得到實際的內(nèi)存地址。順便一提,在PE用語里,實際的內(nèi)存地址被稱作虛擬地址(Vritual Address,簡稱VA),另外也可以把虛擬地址想象為加上首選裝入地址的RVA。不要忘了前面提到的裝入地址等同于模塊句柄,它們之間的關(guān)系如下:
虛擬地址(VA)=基地址(ImageBase)+相對虛擬地址(RVA)
3)文件偏移地址
當(dāng)PE文件存儲在磁盤上時,某個數(shù)據(jù)的位置相對于文件頭的偏移量也稱文件偏移地址(FileOffset)或者物理地址(RAW Offset)。文件偏移地址從PE文件的第一個字節(jié)開始計數(shù),起始為零。用十六進(jìn)制工具比如:winhex,hexworkshop都可以查看。注意這個物理地址和虛擬地址的區(qū)別,物理地址是文件在磁盤上相對于文件頭的地址,而虛擬地址是PE可執(zhí)行程序加載在內(nèi)存中的地址。
0x02 幾個重要頭部信息介紹
接下來介紹MS-DOS頭部信息,PE文件頭信息及幾個重要字段。
1)MS-DOS頭部
每個PE文件是以一個DOS程序開始的,有了它,一旦程序在DOS下執(zhí)行,DOS就能辨別出這是個有效的執(zhí)行體,然后運行緊隨MZ header(后面會介紹)之后的DOS stub(DOS塊)。DOS stub實際上是一個有效的EXE,在不支持PE文件格式的操作系統(tǒng)中,它將簡單顯示一個錯誤提示,類似于字符串“This Program cannot be run in MS-DOS”。用戶通常對DOS stub 不感興趣,因為大多數(shù)情況下他們由匯編器自動生成。平常把DOS stub和DOS MZ頭部合稱為DOS文件頭。
PE文件的第一個字節(jié)起始于一個傳統(tǒng)的MS-DOS頭部,被稱作IMAGE_DOS_HEADER。其IMAGE_DOS_HEADER的結(jié)構(gòu)如下(左邊的數(shù)字是到文件頭的偏移量):
IMAGE_DOS_HEADER STRUCT?
{?
+0h WORD e_magic? ?// Magic DOS signature MZ(4Dh 5Ah) ? ? DOS可執(zhí)行文件標(biāo)記?
+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代碼的初始化指令入口[指針I(yè)P]?
+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
?
這個結(jié)構(gòu)中有兩字段很重要,一個是e_magic,一個是e_lfanew。e_magic(一個字大小)字段需要被設(shè)置為5A4Dh這個也是PE程序載入的重要標(biāo)志,這個值非常有意思,他們對應(yīng)的字符分別位Z和M,是為了紀(jì)念MS-DOS的最初創(chuàng)建者Mark Zbikowski而專門設(shè)置的,由于在hex編輯器中顯示是由低位到高位故顯示為4D5Ah,剛好是創(chuàng)建者的名字縮寫。另一個字段是e_lfanew。這個字段表示的是真正的PE文件頭部相對偏移地址(RVA),它指出了真正PE頭部文件偏移位置。它占用四個字節(jié),位于文件開始偏移的3ch字節(jié)中。
下面我將用hexworkshop打開一個pe文件向大家展示一下上面這段話的含義。
?
?
第一張圖說明的就是IMAGE_DOS_HEADER的第一個字段e_magic的值與地址。第二張圖就是上面所講的第二個關(guān)鍵字段e_fannew字段的值(注意:不同的PE程序這個值可能不一樣,但原理一樣),這個值就是PE頭文件的起始偏移量。
2)PE文件頭文件
相對于MS-DOS頭文件,PE頭文件PEheader要復(fù)雜的多,下面將詳細(xì)講解其中的幾個字段。
緊跟著DOS頭文件下面的就是peheader。PEheader是PE相關(guān)結(jié)構(gòu)NT映像頭(IMAGE_NT_HEADER)的簡稱,其中包含許多PE裝載器用到的重要字段。執(zhí)行體在支持PE文件結(jié)構(gòu)的操作系統(tǒng)執(zhí)行時,PE裝載器將IMAGE_DOS_HEADER結(jié)構(gòu)中的e_fanew字段找到PEheader的起始偏移量,加上基址得到PE文件頭的指針:
PNTHeader=IMAGBase+dosHeader->e_lfanewr(其實就是去字段e_lfanew的值)。
下面來討論IMAGE_NT_HEADER的結(jié)構(gòu),它是由三個字段組成(左邊的數(shù)字是PE文件頭的偏移量):IMAGE_NT_HEADER STRUCT?
{?
?
+0h Signature ?DWORD ?????????????//PE文件標(biāo)志
+4h FileHeader IMAGE_FILE_HEADER ?//文件頭初始偏移地址
+18 optionalHeader IMAGE_OPTION_HEADER //另一個重要頭部初始偏移地址
?
} IMAGE_NT_HEADER ENDS
下面對這三個字段逐個詳細(xì)分析:
這個字段是PE文件的標(biāo)志字段,通常設(shè)置成00004550h,其ASCII碼為PE00,這個字段是PE文件頭的開始,前面的DOS_HEADER結(jié)構(gòu)中的字段e_lfanew字段就是指向這里。
2.IMAGE_FILE_HEADER字段
這個字段也是包含幾個字段結(jié)構(gòu),它包含了PE文件的一些基本信息,最重要的是其中一個域指出了IMAGE_OPTIONAL_HEADER的大小。
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;//運行平臺
WORD NumberOfSections;//文件的區(qū)塊數(shù)目
DWORD TimeDateStamp;//文件創(chuàng)建的用時間戳標(biāo)識的日期
DWORD PointerToSymbolTable;//指向符號表(用于調(diào)試)
DWORD NumberOfSymbols;//符號表中符號的個數(shù)
WORD SizeOfOptionalHeader;//IMAGE_OPTIONAL_HEADER32結(jié)構(gòu)大小
WORD Characteristics;//文件屬性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
上圖標(biāo)出七個字段的位置及各自的值。
1)Machine字段,表示目標(biāo)CPU 的類型。
幾個常見的及其標(biāo)識如下:
機器 ?????????????標(biāo)識 ????
Intel I386 ?????????14ch
MIPS R3000 ???????162h
Alpha AXP ?????????184h
Power PC ??????????1F0h
MIPS R4000 ????????184h
根據(jù)以上信息我們知道這個PE文件要運行在Intel I386機器上。
2)NumberOfSection,標(biāo)識區(qū)塊的數(shù)目,關(guān)于區(qū)塊后面會詳細(xì)講。
3)TimeDateStamp
這個字段沒啥好說的,指的就是PE文件創(chuàng)建的事件,這個時間是指從1970年1月1日到創(chuàng)建該文件的所有的秒數(shù)。
4)PointerToSymbolTable。這個字段用的比較少,略
5)NumberOfSymbol。這個字段也用得很少,略
6)SizeOfOptionalHeader:緊跟著IMAGE_FILE_HEADER后面的數(shù)據(jù)大小,這也是一個數(shù)據(jù)結(jié)構(gòu),它叫做IMAGE_OPTIONAL_HEADER,其大小依賴于是64位還是32位文件。32位文件值通常是00EOh,對于64位值通常為00F0h。
7)Characteristics:文件屬性,普通EXE文件這個字段值為010fh,DLL文件這個字段一般是0210h。
下一篇將從字段IMAGE_OPTIONAL_HEADER講起。
?
轉(zhuǎn)載于:https://www.cnblogs.com/2f28/p/9800992.html
總結(jié)
以上是生活随笔為你收集整理的PE文件格式详解(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(750):作用域导读
- 下一篇: 逗你玩学脱壳(一)