複製鏈接
請複製以下鏈接發送給好友

pe文件

鎖定
PE文件的全稱是Portable Executable,意為可移植的可執行的文件,常見的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微軟Windows操作系統上的程序文件(可能是間接被執行,如DLL)
中文名
pe文件
外文名
Portable Executable
全    稱
Portable Executable
意    為
可移植的執行體
操作系統
Windows

pe文件定義

一個操作系統的可執行文件格式在很多方面是這個系統的一面鏡子。雖然學習一個可執行文件格式通常不是一個程序員的首要任務,但是你可以從這其中學到大量的知識。在這篇文章中,我會給出 Microsoft 的所有基於win32系統(如winnt,win9x)的可移植可執行(PE)文件格式的詳細介紹。在可預知的未來,包括Windows2000, PE文件格式在 MicroSoft 的操作系統中扮演一個重要的角色。如果你在使用 Win32 或 Winnt ,那麼你已經在使用 PE 文件了。甚至你只是在 Windows3.1 下使用 Visual C++編程,你使用的仍然是 PE 文件(Visual C++ 的 32 位MS-DOS擴展組件用這個格式)。簡而言之,PE 格式已經普遍應用,並且在不短的將來仍是不可避免的。
線程局部變量
我最後不會讓你盯住無窮無盡的十六進制Dump,也不會詳細討論頁面的每一個單獨的位的重要性。代替的,我會向你介紹包含在 PE 文件中的概念,並且將他們和你每天都遇到的東西聯繫起來。比如,線程局部變量的概念,如下所述:
declspec(thread) int i;
除了一個不同的可執行文件格式, MicroSoft 還引入了一個用它的編譯器彙編器生成的新的目標模塊格式。這個新的 OBJ 文件格式有許多和PE 文件共同的東東。我做了許多無用功去查找這個新的OBJ 文件格式的文檔。所以我以自己的理解對它進行解析,並且,在這裏,除了 PE 文件,我會描述它的一部分。
微軟系統工具
大家都知道,Windows NT繼承了 VAX? VMS? 和 UNIX? 的傳統。許多 Windows NT 的創始人在進入微軟前都在這些平台上進行設計和編碼。當他們開始設計 Windows NT 時,很自然的,為了最小化項目啓動時間,他們會使用以前寫好的並且已經測試過的工具。用這些工具生成的並且工作的可執行和 OBJ 文件格式叫做 COFF (Common Object File Format 的首字母縮寫)。COFF 的相對年齡可以用八進制的域來指定。COFF 本身是一個好的起點,但是需要擴展到一個現代操作系統如 Windows 95 和 Windows NT 的需要。這個更新的結果就是(PE格式)可移植可執行文件格式。它被稱為"可移植的"是因為在所有平台(如x86,Alpha,MIPS等等)上實現的WindowsNT 都使用相同的可執行文件格式。當然了,也有許多不同的東西如二進制代碼CPU指令。重要的是操作系統的裝入器和程序設計工具不需要為任何一種CPU完全重寫就能達到目的。
MicroSoft 拋棄現存的32位工具和可執行文件格式的事實證實了他們想讓 WindowsNT 升級並且運行的更快的決心。為16位Windows編寫的虛擬設備驅動程序用一種不同的32位文件佈局--LE文件格式--WindowsNT出現很早以前就存在了。比這更重要的是對 OBJ 文件的替換。在 WindowsNT 的 C編譯器以前,所有的微軟編譯器都用 Intel 的 OMF ( Object Module Format ) 規範。就像前面提到的,MicroSoft 的 Win32編譯器生成 COFF 格式的 OBJ 文件。一些微軟的競爭者,如 Borland 和 Symentec ,選擇放棄了 COFF 格式並堅持 Intel 的 OMF文件格式。這樣的結果是製作 OBJ 和 LIB 的公司為了使用多個不同的編譯器,不得不為每個不同的編譯器分發這些庫的不同版本(如果他們不這麼做)。
PE 文件格式在 winnt.h 頭文件中文檔化了(用最不精確的語言)!大約在 winnt.h 的中間部分標題為"Image Format"的一個塊。在把 MS-DOS 的 MZ文件頭和 NE 文件頭移入新的PE文件頭之前,這個塊就開始於一個小欄。WINNT.H提供PE文件用到的生鮮數據結構的定義,但只有很少有助於理解這些數據結構和標誌變量的註釋。不管誰為PE文件格式寫出這樣的頭文件都肯定是一個信徒無疑(突然持續地冒出Michael J. O'Leary的名字來)。描述名字,連同深嵌的結構體和宏。當你配套winnt.h進行編碼時,類似下面這樣的表達式並不鮮見:
pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]
.VirtualAddress;
為了有助於邏輯的理解這些winnt.h中的信息,閲讀可移植可執行和公共對象文件格式的規格説明,這些在MSDN既看光盤中是可用的,一直包括到2001年8月。
現在讓我們轉換到COFF格式的OBJ文件的主體上來,WINNT.H包括COFF OBJ和LIB的結構化定義和類型定義。不幸的是,我還沒有找到上面提到的可執行文件格式的類似文檔。既然PE文件和COFF OBJ文件是如此的相似,我決定是時間把這些文件帶到重點上來,並且把它們也文檔化。僅僅讀過了關於PE文件的組成,你自己也想Dump一些PE文件來看這些概念。如果你用微軟基於32位WINDOWS的開發工具,DUMPBIN 程序可以將PE文件和COFF OBJ/LIB文件轉化為可讀的形式。在所有的PEDump器中,DUMPBIN是最容易理解的。它恰好有一些很好的選項來反彙編它正解析的文件的代碼塊,Borland用户可以使用tdump來瀏覽PE文件,但tdump不能解析 COFF OBJ/LIB 文件。這不是一個重要的東西因為Borland的編譯器首先就不生成 COFF 格式的OBJ文件。
我寫了一個PE和COFF OBJ 文件的Dump程序--PEDUMP,我想提供一些比DUMPBIN更加可理解的輸出。雖然它沒有反彙編器以及和LIB庫文件一起工作,它在其他方面和DUMPBIN是一樣的,並且加入了一些新的特性來使它值得被認同。它的源代碼在任何一個MSJ電子公報版上都可以找到,所有我不打算在這裏把他全部列出。作為代替,我展示一些從PEDUMP得到的示例輸出來闡明我為它們描述的概念。
程序代碼
表 1 PEDUMP.C
file://--------------------/
// PROGRAM: PEDUMP
// FILE: PEDUMP.C
// AUTHOR: Matt Pietrek - 1993
file://--------------------/
#include <windows.h>
#include <stdio.h>
#include "objdump.h"
#include "exedump.h"
#include "extrnvar.h"
// Global variables set here, and used in EXEDUMP.C and OBJDUMP.C
BOOL fShowRelocations = FALSE;
BOOL fShowRawSectionData = FALSE;
BOOL fShowSymbolTable = FALSE;
BOOL fShowLineNumbers = FALSE;
char HelpText[] =
"PEDUMP - Win32/COFF .EXE/.OBJ file dumper - 1993 Matt Pietrek\n\n"
"Syntax: PEDUMP [switches] filename\n\n"
" /A include everything in dump\n"
" /H include hex dump of sections\n"
" /L include line number information\n"
" /R show base relocations\n"
" /S show symbol table\n";
// Open up a file, memory map it, and call the appropriate dumping routine
void DumpFile(LPSTR filename)
HANDLE hFile;
HANDLE hFileMapping;
LPVOID lpFileBase;
PIMAGE_DOS_HEADER dosHeader;
hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if ( hFile = = INVALID_HANDLE_VALUE )
{ printf("Couldn't open file with CreateFile()\n");
return; }
hFileMapping = CreateFileMapping(hFile, NULL,
PAGE_READONLY, 0, 0, NULL);
if ( hFileMapping = = 0 )
{
CloseHandle(hFile);
printf("Couldn't open file mapping with CreateFileMapping()\n");
return;
lpFileBase = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
if ( lpFileBase = = 0 )
CloseHandle(hFileMapping);
CloseHandle(hFile);
printf("Couldn't map view of file with MapViewOfFile()\n");
return;
printf("Dump of file %s\n\n", filename);
dosHeader = (PIMAGE_DOS_HEADER)lpFileBase;
if ( dosHeader->e_magic = = IMAGE_DOS_SIGNATURE )
{ DumpExeFile( dosHeader ); }
else if ( (dosHeader->e_magic = = 0x014C) // Does it look like a i386
&& (dosHeader->e_sp = = 0) ) // COFF OBJ file???
// The two tests above aren't what they look like. They're
// really checking for IMAGE_FILE_HEADER.Machine = = i386 (0x14C)
// and IMAGE_FILE_HEADER.SizeOfOptionalHeader = = 0;
DumpObjFile( (PIMAGE_FILE_HEADER)lpFileBase );
else
printf("unrecognized file format\n");
UnmapViewOfFile(lpFileBase);
CloseHandle(hFileMapping);
CloseHandle(hFile);
// process all the command line arguments and return a pointer to
// the filename argument.
PSTR ProcessCommandLine(int argc, char *argv[])
int i;
for ( i=1; i < argc; i++ )
strupr(argv);
// Is it a switch character?
if ( (argv[0] = = '-') || (argv[0] = = '/') )
if ( argv[1] = = 'A' )
{ fShowRelocations = TRUE;
fShowRawSectionData = TRUE;
fShowSymbolTable = TRUE;
fShowLineNumbers = TRUE; }
else if ( argv[1] = = 'H' )
fShowRawSectionData = TRUE;
else if ( argv[1] = = 'L' )
fShowLineNumbers = TRUE;
else if ( argv[1] = = 'R' )
fShowRelocations = TRUE;
else if ( argv[1] = = 'S' )
fShowSymbolTable = TRUE;
else // Not a switch character. Must be the filename
{ return argv; }
int main(int argc, char *argv[])
PSTR filename;
if ( argc = = 1 )
{ printf( HelpText );
return 1; }
filename = ProcessCommandLine(argc, argv);
if ( filename )
DumpFile( filename );
return 0;
}

pe文件相關概念

MODULE標識模塊
讓我們複習一下幾個透過PE文件的設計瞭解到的基本概念。我用術語"MODULE"來表示一個可執行文件或一個DLL載入內存的代碼(CODE)、數據(DATA)、資源(RESOURCES),除了代碼和數據是你的程序直接使用的,一個模塊還可以由WINDOWS用來確定數據和代碼載入的位置的支撐數據結構組成。在16位WINDOWS中,這些支撐數據結構在模塊數據庫(用一個HMODULE來指示的段)中。在WIN32裏面,這些數據結構在PE文件頭中,這些我將會簡要地解釋一下。
PE文件的兩因素
關於PE文件最重要的是,磁盤上的可執行文件和它被WINDOWS調入內存之後是非常相像的。WINDOWS載入器不必為從磁盤上載入一個文件而辛辛苦苦創建一個進程。載入器使用內存映射文件機制來把文件中相似的塊映射到虛擬空間中。用一個構造式的分析模型,一個PE文件類似一個預製的屋子。它本質上開始於這樣一個空間,這個空間後面有幾個把它連到其餘空間的機件(就是説,把它聯繫到它的DLL上,等等)。這對PE格式的DLL是一樣容易應用的。一旦這個模塊被載入,Windows 就可以有效的把它和其它內存映射文件同等對待。
和16位Windows不同的是。16位NE文件的載入器讀取文件的一部分並且創建完全不同的數據結構在內存中表示模塊。當數據段或者代碼段需要載入時,載入器必須從全局堆中新申請一個段,從可執行文件中找出生鮮數據,轉到這個位置,讀入這些生鮮數據,並且要進行適當的修正。除此而外,每個16位模塊都有責任記住當前它使用的所有段選擇器,而不管這個段是否被丟棄了,如此等等。
對Win32來講,模塊所使用的所有代碼,數據,資源,導入表,和其它需要的模塊數據結構都在一個連續的內存塊中。在這種形勢下,你只需要知道載入器把可執行文件映射到了什麼地方。通過作為映像的一部分的指針,你可以很容易的找到這個模塊所有不同的塊。
另一個你需要知道的概念是相對虛擬地址(RVA)。PE文件中的許多域都用術語RVA來指定。一個RVA只是一些項目相對於文件映射到內存的偏移。比如説,載入器把一個文件映射到虛擬地址0x10000開始的內存塊。如果一個映像中的實際的表的首址是0x10464,那麼它的RVA就是0x464。
虛擬地址 0x10464)-(基地址 0x10000)=RVA 0x00464
為了把一個RVA轉化成一個有用的指針,只需要把RVA值加到模塊的基地址上即可。基地址是內存映射EXE和DLL文件的首址,在Win32中這是一個很重要的概念。為了方便起見,WindowsNT 和 Windows9x用模塊的基地址作為這個模塊的實例句柄(HINSTANCE)。在Win32中,把模塊的基地址叫做HINSTANCE可能導致混淆,因為術語"實例句柄"來自16位Windows。一個程序在16位Windows中的每個拷貝得到它自己分開的數據段(和一個聯繫起來的全局句柄)來把它和這個程序其它的拷貝分別開來,就形成了術語"實例句柄"。在Win32中,每個程序不必和其它程序區別開來,因為他們不共享相同的地址空間。術語INSTANCE仍然保持16位windows和32位Windows之間的連續性。在Win32中重要的是你可以對任何DLL調用(GetModuleHandle)得到一個指針去訪問它的組件(譯註)。
譯註
如果 dllname 為 NULL,則得到執行體自己的模塊句柄。這是非常有用的,如通常編譯器產生的啓動代碼將取得這個句柄並將它作為一個參數hInstance傳給WinMain
你最終需要理解的PE文件的概念是"塊(Section)"。PE文件中的一個塊和NE文件中的一個段或者資源等價。塊可以包含代碼或者數據。和段不同的是,塊是內存中連續的空間,而沒有尺寸限制。當你的連接器和庫為你建立,並且包含對操作系統非常重要的信息的其它的數據塊時,這些塊包含你的程序直接聲明和使用的代碼或數據。在一些PE格式的描述中,塊也叫做對象。術語對象有如此多的涵義,以至於只能把代碼和數據叫做"塊"。

pe文件PE首部

和其它可執行文件格式一樣,PE文件在眾所周知的地方有一些定義文件其餘部分面貌的域。首部就包含這樣像代碼和數據的位置和尺寸的地方,操作系統要對它進行干預,比如初始堆棧大小,和其它重要的塊的信息,我將要簡短的介紹一下。和微軟其它可執行格式相比,主要的首部不是在文件的最開始。典型的PE文件最開始的數百個字節被DOS殘留部分佔用。這個殘留部分是一個可以打印如"這個程序不能在DOS下運行!"這類信息的小程序。所以,你在一個不支持Win32的系統中運行這個程序,便可以得到這類錯誤信息。當載入器把一個Win32程序映射到內存,這個映射文件的第一個字節對應於DOS殘留部分的第一個字節。那是無疑的。和你啓動的任一個基於Win32 的程序一起,都有一個基於DOS的程序連帶被載入。
和微軟的其它可執行格式一樣,你可以通過查找它的起始偏移來得到真實首部,這個偏移放在DOS殘留首部中。WINNT.H頭文件包含了DOS殘留程序的數據結構定義,使得很容易找到PE首部的起始位置。e_lfanew 域是PE真實首部的偏移。為了得到PE首部在內存中的指針,只需要把這個值加到映像的基址上即可。
file://忽/略類型轉化和指針轉化 ...
pNTHeader = dosHeader + dosHeader->e_lfanew;
一旦你有了PE主首部的指針,遊戲就可以開始了!PE主首部是一個IMAGE_NT_HEADERS的結構,在WINNT.H中定義。這個結構由一個雙字(DWORD)和兩個子結構組成,佈局如下:
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader;
標誌域用ASCII表示就是"PE"。如果在DOS首部中用了e_lfanew域,你得到一個NE標誌而不是PE,那麼這是16位NE文件。同樣的,在標誌域中的LE表示這是一個Windows3.x 的虛擬設備驅動程序(VxD)。LX表示這個文件是OS/2 2.0文件。
PE DWORD標誌後的是結構 IMAGE_FILE_HEADER 。這個域只包含這個文件最基本的信息。這個結構表現為並未從它的原始COFF實現更改過。除了是PE首部的一部分,它還表現在微軟Win32編譯器生成的COFF OBJ 文件的最開始部分。IMAGE_FILE_HEADER的這個域顯示在下面:
表2 IMAGE_FILE_HEADER Fields
WORD Machine
表示CPU的類型,下面定義了一些CPU的ID
0x14d Intel i860
0x14c Intel I386 (same ID used for 486 and 586)
0x162 MIPS R3000
0x166 MIPS R4000
WORD NumberOfSections
這個文件中的塊數目。
DWORD TimeDateStamp
連接器產生這個文件的日期(對OBJ文件是編譯器),這個域保存的數是從1969年12月下午4:00開始到現在經過的秒數。
DWORD PointerToSymbolTable
COFF符號表的文件偏移量。這個域只用於有COFF調試信息的OBJ文件和PE文件,PE文件支持多種調試信息格式,所以調試器應該指向數據目錄的IMAGE_DIRECTORY_ENTRY_DEBUG條目。
DWORD NumberOfSymbols
COFF符號表的符號數目。見上面。
WORD SizeOfOptionalHeader
這個結構後面的可選首部的尺寸。在OBJ文件中,這個域是0。在可執行文件中,這是跟在這個結構後的IMAGE_OPTIONAL_HEADER結構的尺寸。
WORD Characteristics
關於這個文件信息的標誌。一些重要的域如下:
0x0001 這個文件中沒有重定位信息
0x0002 可執行文件映像(不是OBJ或LIB文件)
0x2000 文件是動態連接庫,而非程序
其它域定義在WINNT.H中。
PE首部的第三個組成部分是一個IMAGE_OPTIONAL_HEADER型的結構。對PE文件,這一部分當然不是"可選的"。COFF格式允許單獨實現來定義一個超出標準IMAGE_FILE_HEADER附加信息的結構。IMAGE_OPTIONAL_HEADER裏面的域是PE的實現者感到超出IMAGE_FILE_HEADER基本信息以外非常關鍵的信息。
並非 IMAGE_OPTIONAL_HEADER 的所有域都是重要的(見圖4)。比較重要,需要知道的是ImageBase 和 SubSystem 域。你可以忽略其它域的描述。
表3 IMAGE_FILE_HEADER 的域:
WORD Magic
表現為一些類別的標誌字,通常是0X010B 。
BYTE MajorLinkerVersion
BYTE MinorLinkerVersion
生成這個文件的連接器的版本。這個數字以十進制顯示比用十六進制好。一個典型的連接器版本是2.23。
DWORD SizeOfCode
所有代碼塊的進位尺寸。通常大多數文件只有一個代碼塊,所以這個域和 .TEXT 塊匹配。
DWORD SizeOfInitializedData
已初始化的數據組成的塊的大小(不包括代碼段)。然而,和它在文件中的表現形式並不一致。
DWORD SizeOfUninitializedData
載入器虛擬內存中申請空間,但在磁盤上的文件中並不佔用空間的塊的尺寸。這些塊在程序啓動時不需要指定初值,因此術語名就是"未初始化的數據"。未初始化的數據通常在一個名叫 .bss 的塊中。
DWORD AddressOfEntryPoint
載入器開始執行這個程序的地址,即這個PE文件的入口地址。這是一個RVA,通常在 .text 塊中。
DWORD BaseOfCode
代碼塊起始地址的RVA 。在內存中,代碼塊通常在PE首部之後,數據塊之前。在微軟的連接器產生的EXE文件中,這個值通常是0x1000 。Borland 的連接器 TLINK32 也一樣,把映像第一個代碼塊的RVA和映像基址相加,填入這個域。
譯註:這個域好像一直沒有什麼用
DWORD BaseOfData
數據塊起始地址的RVA 。在內存中,數據塊經常在最後,在PE首部和代碼塊之後。
譯註:這個域好像也一直沒有什麼用
DWORD ImageBase
連接器創建一個可執行文件時,它假定這個文件被映射到內存中的一個指定的地方,這個地址就存在這個域中,假定一個載入地址可以使連接器優化以便節省空間。如果載入器真的把這個文件映射到了這個地方,在運行之前代碼不需要任何改變。在為WindowsNT 創建的可執行文件中,默認的ImageBase 是0x10000。對DLL,默認是0x40000。在Window95中,地址0x10000不能用來載入32位EXE文件,因為這個區域在一個被所有進程共享的線性地址空間中。因此,微軟把Win32可執行文件的默認基址改為0x40000,假定基址為0x10000 的老程序坐在Windows95 中需要更長的載入時間,這是因為載入器需要重定位基址。
譯註:這個域即"Prefered Load Address",如果沒有什麼意外,這就是該PE文件載入內存後的地址。
DWORD SectionAlignment
映射到內存中時,每個塊都必須保證開始於這個值的整數倍。為了分頁的目的,默認的SectionAlignment 是 0x1000。
DWORD FileAlignment
在PE文件中,組成每個塊的生鮮數據必須保證開始於這個值的整數倍。默認值是0x200 字節,也許是為了保證塊都開始於一個磁盤扇區(一個扇區通常是 512 字節)。這個域和NE文件中的段/資源對齊(segment/resource alignment)尺寸是等價的。和NE文件不同的是,PE文件通常沒有數百個的塊,所以,為了對齊而浪費的通常空間很少。
WORD MajorOperatingSystemVersion
WORD MinorOperatingSystemVersion
這個程序運行需要的操作系統的最小版本號。這個域有點含糊,因為Subsystem 域(後面將會説到)可以提供類似的功能。這個域在到目前為止的Win32中默認是1.0。
WORD MajorImageVersion
WORD MinorImageVersion
一個可由用户定義的域。這允許你有不同的EXE和DLL版本。你可以通過鏈接器的 /version 選項設置這個域的值。例如:"link /version:2.0 myobj.obj"。
WORD MajorSubsystemVersion
WORD MinorSubsystemVersion
這個程序運行需要的最小子系統版本號。這個域的一個典型值是3.10 (表示WindowsNT 3.1)。
DWORD Reserved1
通常是 0 。
DWORD SizeOfImage
載入器必須關心的這個映像所有部分的大小總和。是從映像的開始到最後一個塊結尾這段區域的大小。最後一個塊結尾按SectionAlignment進位。
譯註:這個很重要,可以大,但不可以小!
DWORD SizeOfHeaders
PE首部和塊表的大小。塊的實際數據緊跟在所有首部組件之後。
DWORD CheckSum
這個文件的CRC校驗和。在微軟可執行格式中,這個域被忽略並且置為0 。這個規則的一個例外情況是信任服務,這類EXE文件必須有一個合法的校驗和。
WORD Subsystem
可執行文件用户界面使用的子系統類型。WINNT.H 定義了下面這些值:
NATIVE 1 不需要子系統(比如設備驅動
WINDOWS_GUI 2 在Windows圖形用户界面子系統下運行
WINDOWS_CUI 3 在Windows字符子系統下運行(控制枱程序
OS2_CUI 5 在OS/2字符子系統下運行(僅對OS/2 1.x)
POSIX_CUI 7 在 Posix 字符子系統下運行
WORD DllCharacteristics
指定在何種環境下一個DLL的初始化函數(比如DllMain)將被調用的標誌變量。這個值經常被置為0 。但是操作系統在下面四種情況下仍然調用DLL的初始化函數。
下面的值定義為:
1 DLL第一次載入到進程中的地址空間中時調用
2 一個線程結束時調用
4 一個線程開始時調用
8 退出DLL時調用
DWORD SizeOfStackReserve
為初始線程保留的虛擬內存總數。然而並不是所有這些內存都被提交(見下一個域)。這個域的默認值是0x100000(1Mbytes)。如果你在CreateThread 中把堆棧尺寸指定為 0 ,結果將是用這個相同的值(0x10000)。
DWORD SizeOfStackCommit
開始提交的初始線程堆棧總數。對微軟的連接器,這個域默認是0x1000字節(一頁),TLINK32 是兩頁。
DWORD SizeOfHeapReserve
為初始進程的堆保留的虛擬內存總數。這個堆的句柄可以用GetPocessHeap 得到。並不是所有這些內存都被提交(見下一個域)。
DWORD SizeOfHeapCommit
開始為進程堆提交的內存總數。默認是一頁。

pe文件文件格式

文件的含義
PE文件的意思是Portable Executable(可移植,可執行),它是win32可執行文件的標準格式.它的一些特性繼承unix的COFF文件格式,同時保留了與舊版MS-DOS和WINDOWS的兼容.其可移植可執行意味着是跨win32平台的.
文件的層次結構
PE文件最前面緊隨DOS MZ文件頭的是一個DOS可執行文件(Stub).這使得PE文件成為一個合法的MS-DOS可執行文件.DOS MZ文件頭後面是一個32位的PE文件標誌0x50450000(IMAGE_NT_SIGNATURE),即PE00.接下來的是PE的映像文件頭,包含的信息有該程序的運行平台,有多少個節,文件鏈接的時間,文件的命名格式.後面還緊跟一個可選映像頭,包含PE文件的邏輯分佈信息,程序加載信息,開始地址,保留的堆棧數量,數據段大小等.可選頭還有一個重要的域,稱為:“數據目錄表”的數組,表的每一項都是指向某一節的指針.可選映像頭後面緊跟的是節表和節.節通過節表來實現索引.實際上,節的內容才是真正執行的數據和程序.每一個節都有相關的標誌.每一個節會被一個或多個目錄表指向,目錄表可通過可選頭的“數據目錄表”的入口找到.就像輸出函數表或基址重定位表.也存在沒有目錄表指向的節.
文件層次解釋
A.DOS STUB和DOS頭
DOS插樁程序在大多數情況下由彙編器/編譯器自動產生.通常它調用INT 21H服務9來顯示上述字符串.可以通過IMAGE_DOS_HEADER結構來識別一個合法的DOS頭.這個結構的頭兩個字節肯定是"MZ".可通過該結構的e_lfanew成員來找到PE文件的開始標誌.MS-DOS頭部佔據了PE文件的頭64個字節.在微軟的WINNT.H中可以找到其內容結構的描述.
文件頭
在DOS STUB後是PE文件頭(PE header).PE文件頭是PE相關結構IMGAE_NT_HEADERS的簡稱,即NT映像頭,存放PE整個文件信息發佈的重要字段,包含了PE裝載器用到的重要域.執行體在操作系統中執行時,PE裝載器將從DOS MZ頭中找到PE頭文件的起始偏移量e_lfanew,從而跳過DOS STUB直接定位真正的PE文件.它由3部分組成:
(1)PE文件標誌(4H字節)
PE文件標誌0x50450000即PE00,標誌着NT映像頭的開始,也是PE文件中與windows有關內容的開始.
(2)映像文件(14H字節)
是NT映像文件的主要部分,包含PE文件的基本信息
(3)可選映像頭
包含PE文件的邏輯分佈信息.
C.節表
節表其實是緊跟NT映像文件的一個結構數組.其成員數目由映像文件頭結構NumberOFSectios域的值來決定.
D.節
PE文件的真正內容劃分為塊,稱之為節.節的劃分基於各組數據的共同屬性.惟有節的屬性設置決定了節的特性和功能.典型的windows NT應用程序可以具有9個節:.texr,.bss.rdata,.data,.rsrc,edata,idata,pdata,.debug
判斷一個文件是否為PE文件
var //檢測指定文件是否有效PE文件
PEDosHead: TImageDosHeader;
PENTHead: TImageNtHeaders;
m_file: integer;
begin
Result := False;
m_file := FileOpen(filename, fmOpenRead or fmShareDenyNone); //只讀和其它任意
if m_File > 0 then
try
FileSeek(m_file, 0, soFromBeginning); //將指針挪至文件頭
FileRead(m_file, PEDosHead, SizeOf(PEDosHead)); //讀PEDosHead結構
FileSeek(m_file, PEDosHead._lfanew, soFromBeginning); //將指針挪至_lfanew
FileRead(m_file, PENTHead, SizeOf(PENTHead)); //讀PENTHead結構
finally
FileClose(m_file);
end;
if (PENTHead.Signature = IMAGE_NT_SIGNATURE) then //檢驗文件頭部第一個字的值是否等於 IMAGE_DOS_SIGNATURE
Result := True;
end;
pe文件結構圖