可移植可執行

可移植性可執行文件（英語：Portable Executable，縮寫為PE）是一種用於可執行文件、目標文件和動態鏈接庫的文件格式，主要使用在32位和64位的Windows操作系統上。“可移植的”是指該文件格式的通用性，可用於許多種不同的操作系統和體系結構中。PE文件格式封裝了Windows操作系統加載可執行程序代碼時所必需的一些信息。這些信息包括動態鏈接庫、API導入和導出表、資源管理數據和線程局部存儲數據。在Windows NT操作系統中，PE文件格式主要用於EXE文件、DLL文件、.sys（驅動程序）和其他文件類型。可擴展固件接口（EFI）技術規範書中說明PE格式是EFI環境中的標準可執行文件格式。開頭為DOS頭部。
PE格式是由Unix中的COFF格式修改而來的。在Windows開發環境中，PE格式也稱為PE/COFF格式。
在Windows NT操作系統中，PE格式目前支持IA-32、IA-64和x86-64（AMD64/Intel64）的指令系統。在Windows 2000之前，Windows NT還支持MIPS、Alpha和PowerPC的指令系統。由於Windows CE也在使用PE文件格式，因此PE仍然支持幾種不同型號的MIPS、ARM（包括Thumb）和SuperH指令系統。
PE文件格式的主要競爭對手是可執行與可鏈接格式（ELF）（使用於Linux和大多數Unix版本中）和Mach-O（使用於Mac OS X中）。

**佈局結構**

**文件頭部**

**MS-DOS頭與MS-DOS stub**
MS-DOS頭和MS-DOS Stub只存在於映像文件中。在MS-DOS下運行該應用程序，默認的Stub會打印出消息"This program cannot be run in DOS mode"。 MS-DOS頭的偏移位置0x3C處包括指向PE簽名的文件指針，用於定位PE的開始位置。

**COFF file header**
PE簽名是一個4字節的項：字符P和E，隨後是2個空字節。
在Winnt.h中定義的標準COFF頭的結構_IMAGE_FILE_HEADER

**optional header**
Object文件不含這部分，所以稱為“可選頭”。
在Winnt.h中定義的optional header的結構_IMAGE_OPTIONAL_HEADER

**=Standard COFF Fields =**

**=Windows Specific Fields =**

**=Data Directories=**
Export Table: .edata Section
*Import Table: .idata Section.
*Resource Table: .rsrc Section.
*Exception Table: .pdata Section.
*Certificate Table: 指向Attribute Certificate Table(由用於文件驗證的屬性證書組成的表). 屬性證書不會作為映像文件的一部分加載到內存。同樣，這個地址項的第一個字段是文件指針而不是RVA。屬性證書表的每個項都包括了一個4字節的文件指針，指向各自的屬性證書，並具有4字節的大小。
*Base Relocation Table: .reloc Section
*Debug: .debug Section. 調試數據輸出到PDB文件中，因此這個Data directory要麼全都是0，或者只指向一個類型為2（IMAGE_DEBUG_TYPE_CODEVIEW）、30個字節的調試目錄項，而這個項又指向一個包括PDB文件路徑在內的、CodeReview風格的頭。 
*Architecture: 保留,必須為0
*Global Ptr: 在全局指針寄存器中存儲的RVA值。該結構的大小必須設置為0。如果目標架構沒有使用全局指針的概念，這個數據目錄就全都設置為0（例如I386或AMD64）。
*TLS Table: .tls Section.
*Load Config Table: Load Configuration Structure. 特定於Window NT家族操作系統的數據（例如，GlobalFlag值）。 
*Bound Import: 由綁定導入描述符組成的數組，其中的每個描述符都描述了一個DLL。在創建映像的時候，該映像與DLL綁定在一起。描述符中還攜帶這些綁定的時間戳，如果這些綁定是最新的，那麼操作系統加載程序就會使用這些綁定以作為API導入的"快捷方式"。否則，加載程序就會忽視這些綁定並通過導入表解析這些導入API。 
*IAT: Import Address Table. 這個表（IAT）會在導出目錄表（第1個數據目錄）中被引用。
*Delay Import Descriptor: Delay-Load Import Tables. 包括一個由32位ImgDelayDescr結構體組成的數組，每個結構體都描述了延遲加載的導入。延遲加載（delay load）的導入是這樣一些DLL，它們被描述為隱式的導入，而作為顯式的導入進行加載（通過對LoadLibrary這個API的調用）。動態庫的延遲加載是根據需要--在第一次調用這樣一個DLL的時候執行的。這與隱式的導入不同，後者在導入的可執行體初始化的時候就立即被加載。 
*CLR Runtime Header: .cormeta Section (Object Only).
*保留: 必須全為0 

**節**
節（section）是PE文件中存儲數據的劃分。通常，加載到內存後，同一節的數據具有相同的內存訪問屬性（可讀/可寫/可執行等）。
節表緊隨文件頭部。由於沒有文件頭具有直接指向節表的指針，所以節表的位置被計算為文件頭的大小再加上1。
COFF頭的NumberOfSections字段，定義了節表中節的數量。在節頭表中，節的索引是基於1，並且節的順序是由鏈接器確定。節按照節表中定義的順序連續存放，起始RVA根據PE頭的SectionAlignment字段值進行對齊。
節頭是一個定義在Winnt.h中的40字節的結構IMAGE_SECTION_HEADER:
*Name: 8字節的ASCII字符串。表示節的名稱。節名稱開始於一個點號（例如，.reloc）。如果節名稱正好包含8個字符，就會省略null終結符。如果節名稱小於8個字符，就會使用null字符來填充數組Name。映像文件不能使用超過8個字符的節名稱。然而，在對象文件中，節名稱可以更長一些, 在這種情況下，名稱被放置在字符串表中，並且字段的第一個字節中包括了字符"/"，隨後是一個ASCII字符串，包含有字符串表中相應偏移量的十進制表示。
*VirtualSize: 4字節無符號整型的Union。在映像文件中，這個字段保存了節中的代碼或數據裝入內存的實際（未對齊的）字節大小。如果改制大於本節的SizeOfRawData, 本節由0填充. 對於object文件該節為0. 
*VirtualAddress: 4字節無符號整型。在映像文件中，為本節裝入內存後相對於image base的偏移.
*SizeOfRawData: 4字節無符號整型。在映像文件中，這個字段保存了磁盤上需要初始化的數據的字節大小，向上舍入為FileAlignment的倍數。如果SizeOfRawData小於VirtualSize，那麼裝入內存時使用0來填充節的剩餘部分。對於不需要初始化的數據,這個字段的值為0.
*PointerToRawData: 4字節無符號整型。保存了指向節的第一頁的文件指針。在映像文件中，向上舍入為FileAlignment的倍數。 對於不需要初始化的數據,這個字段的值為0.
*PointerToRelocations: 4字節無符號整型。這是一個文件指針，指向了節的重定位項的起始位置。在映像文件中，不使用這個字段，應該設置為0。 
*PointerToLinenumbers: 4字節無符號整型。這個字段保存了一個文件指針，指向節的行號項的起始位置。在映像文件中, 該字段已經過時了,應設置為0
*NumberOfRelocations: 2字節無符號整型。節的重定位項的數量. 在映像文件中，設置為0。 
*NumberOfLinenumbers: 2字節無符號整型. 節的行號條目的數量. 在映像文件中, 該字段已經過時了,應設置為0
*Characteristics: 4字節無符號整型。這個字段指定了映像文件的特徵，並保存了這些二進制標誌的位或運算值。
常見的節： date=20190904053023 
*.text: 只讀的節，包括了CLR頭、元數據、IL代碼、託管異常處理信息以及資源。
*.sdata: 可讀寫的節，與GP相關的已初始化數據
*.reloc: 只讀的節，基址重定位表包含了鏡像中所有需要重定位的內容。NT頭中的數據目錄中的Base Relocation Table（基址重定位表）域給出了基址重定位表所佔的字節數。基址重定位表被劃分成許多塊，每一塊表示一個4K頁面範圍內的基址重定位信息，它必須從32位邊界開始。
*.rsrc: 只讀的節，包括了非託管的資源目錄。
*.tls: 可讀寫的節，包括了TLS數據。- .bss(Block Start with Symbol)：未初始化全局變量- .textbss：未初始化的可執行代碼節。也即這個節具有可執行屬性，在PE文件中未實際佔用硬盤文件空間，在加載到內存時未填充數據。這用於支持Visual Studio在Debug模式下動態編譯代碼功能，也即“Edit and Continue”功能。例如，一個函數在Visual Studio中設斷點或單步調試，這時該函數在.text節中；修改源代碼後繼續執行這個函數，Visual Studio會重新編譯這個函數並把它加載到.textbss節中的未利用地址空間（原為padding的部分），並修改對這個函數調用的跳轉（jmp）表條目以及當前EIP寄存器值。- .data 代碼節- .edata 導出表- .idata 導入表- .idlsym 包含已註冊的SEH，它們用以支持IDL屬性- .pdata 64位程序的異常處理器的地址表  NT頭中的Exception Table（異常表）域指向它。- .rdata 只讀的已初始化數據（用於常量）- .sbss 與GP相關的未初始化數據 - .srdata 與GP相關的只讀數據- .text 默認代碼節

**符號表**
COFF File Header中的字段PointerToSymbolTable給出了符號表地址，字段NumberOfSymbols給出了符號表條目數量。對於映象文件，COFF調試信息是過時的，因此該字段為空。 
typedef struct {
union {
char e_nameE_SYMNMLEN;
struct {
unsigned long e_zeroes;
unsigned long e_offset;
} e;
} e;
unsigned long e_value;
short e_scnum;
unsigned short e_type;
unsigned char e_sclass;
unsigned char e_numaux;
} SYMENT;
符號表包含了所有符號與元符號的條目。
*e.e_name - 內聯的符號名字（小於等於8字節）。
*e.e.e_zeroes - flag，用於判斷是內聯符號名字還是在字符串表中的符號名字
*e.e.e_offset - 字符串表中的符號名字的偏移值
*e_value - 符號的值。如表示函數的符號，值為函數的地址。還有相對於%ebp的變量地址、寄存器變量的寄存器號、結構成員相對偏移值、枚舉成員值、struct/union/enum的尺寸
*e_scnum - 符號所屬的節的編號。節表中的節從1開始編號。節號0表示未定義（外部）符號；-1表示絕對符號（e_value是個常量而非地址）；-2表示調試符號。
*e_type - 符號類型。由基類型與派生類型組成，如“指針到整型”。   
*e_sclass - 存儲類。C_FCN，值101，".bf"或".ef" - 函數的開始/結束。C_FILE，值103，表示函數名。
*e_numaux - 輔助條目（18個字節長）的數量（通常為0或1）。符號表中的符號允許緊隨其後有額外的輔助條目。

**字符串表**
字符串表保存長度大於8的符號。字符串表緊隨符號表。首先讀出4個字節，為符號表的字節長度。隨後的4個字節總為0。到符號表的引用地址總是從這4個0字節開始。示例代碼如下：
int i;
char *s;
read(fd, &i, 4);
s = (char *)malloc(i);
memset(s, 0, 4);
read(fd, s+4, i-4);

**行號**
typedef struct {
union {
unsigned long l_symndx;  /* function name symbol index */
unsigned long l_paddr;   /* address of line number     */
} l_addr;
unsigned short l_lnno;     /* line number                */
} LINENO;
每個可執行的節有自己的行號表. 節中的每個函數獨立編號,函數的首行(有左花括號的行)編號為1. 每個函數在行號表中有一個條目, 其l_lnno為0, l_symndx為符號表中該函數. 其後是該函數每一行的條目, l_lnno甚至為該函數內的行號(1..N), l_paddr設置為該行的第一條彙編代碼的地址. 
為得到絕對行號, 需要在符號表中找到該函數的"beginning of function" symbol (類型C_FCN)為該函數的絕對行號,然後加上函數內的相對行號.

**參見**
 PE infection- EXE格式- 可執行與可鏈接格式- Mach-O- A.out- 可執行文件格式比較- 可執行文件壓縮- ar (Unix)- 應用程序虛擬化

**外部鏈接**
 Microsoft Portable Executable and Common Object File Format Specificationdate=20081208121446  (latest edition, OOXML format)- Microsoft Portable Executable and Common Object File Format Specification (1999 edition, .doc format)- The original Portable Executable article date=20080424054819  by Matt Pietrek (MSDN Magazine, March 1994)- Part I. An In-Depth Look into the Win32 Portable Executable File Formatdate=20120420121832  by Matt Pietrek (MSDN Magazine, February 2002)- Part II. An In-Depth Look into the Win32 Portable Executable File Format by Matt Pietrek (MSDN Magazine, March 2002)- The .NET File Format by Daniel Pistellidate=2013-01-30 - Ero Carrera's blog describing the PE header and how to walk throughdate=20131005003303 - PE Internals provides an easy way to learn the Portable Executable File Format date=20150318200217