November 17, 2015
This page attempts to summarize what is required to create a working 64-bit PE file.
The Windows executable format has two variants: PE32 is the format for 32-bit programs, and PE32+ is the format for 64-bit programs. The formats are similar, but not identical. This page describes only the 64-bit variant, PE32+.
Many of these fields can be set to other values, but reasonable defaults are provided. For example, the file alignment can be set to any power of two between 512 and 64K, but there is little reason to use a larger alignment. Fields omitted from this description must be zero.
In nearly all PE executables, there is a DOS stub program after the DOS header and before the PE header. The table below shows where the DOS stub program goes, but the data for a DOS stub program is not listed. (A PE file will still run if the DOS stub is all zero.)
Fixed-layout headers (DOS, PE, Data Directories) |
Section headers (up to 96) |
.text section data |
.rdata section data |
.data section data |
Position | Value | Description |
---|---|---|
DOS header | ||
0000 | 4D 5A | "MZ", the DOS executable magic number |
003C | 000000C8 | File position of PE header |
0040 | ... | The DOS stub program goes here (not required for 64-bit execution) |
PE header | ||
00C8 | 50 45 00 00 | "PE\0\0", the PE magic number |
00CC | 8664 | Architecture (AMD64) |
00CE | ???? | Number of sections (between 1 and 96) |
00DC | 00F0 | Size of the "optional" header |
00DE | 0022 | Characteristics flags: executable, large address aware |
"Optional" header | (Not optional in executable PE files) | |
00E0 | 0B 02 | Optional header magic number |
00E2 | ?? | Linker major version (typically 0E or thereabouts) |
00E3 | ?? | Linker minor version (typically 00) |
00F0 | ???????? | RVA of code entry point (should point within .text section) |
00F8 | 0000000140000000 | Image base address |
0100 | 00001000 | Section alignment in memory (must be 4 kB) |
0104 | 00000200 | File alignment of sections (must be 512 B) |
0108 | 0005 | OS major version (typically 6) |
010A | 0000 | OS minor version (typically 0) |
0110 | 0005 | Subsystem major version (same as OS version) |
0112 | 0000 | Subsystem minor version (same as OS version) |
0118 | ???????? | Size of image in memory, including headers, rounded up to Section Alignment |
011C | ???????? | Size of all headers, rounded up to file-alignment (typically 0200) |
0124 | ???? | Subsystem (2 = Windows GUI, 3 = Windows console) |
0126 | 8160 | DLL characteristics flags: high entropy ASLR, relocatable, NX compatible, Terminal Server Aware |
0128 | 0000000000100000 | Stack memory reserved (recommend 1 MB) |
0130 | 0000000000001000 | Stack memory committed (recommend 4 kB) |
0138 | 0000000000100000 | Heap memory reserved (recommend 1 MB) |
0140 | 0000000000001000 | Heap memory committed (recommend 4 kB) |
014C | 00000010 | Number of Data Directory entries |
Data Directory Entries | This array describes the location and size of 16 Data Directories. The data pointed to by these Directories is typically part of a read-only section, later in the file. | |
0150 | DataDirectoryEntry[0] | |
0158 | DataDirectoryEntry[1] | Import Directory: This data describes the symbols that this executable needs to import from DLLs. (Corresponds to file position 0700.) |
... | ||
01B0 | DataDirectoryEntry[12] | Import Address Table Directory: This is an array of pointers. When the executable is loaded, the addresses of imported functions will be placed in this array. The actual program in this executable then calls imported functions by looking up their address in this array. (Corresponds to file position 0600.) |
... | ||
01C8 | DataDirectoryEntry[15] | The final Data Directory entry |
The rest of the file does not have a fixed layout. There are up to 96 section headers immediately following the Data Directory entries. After that comes the data for each of those sections, with each section's data aligned to the File Alignment.
The following data is an example of a small but valid three-section PE file.
Position | Value | Description |
---|---|---|
SectionHeader[0] | .text section header | |
01D0 | ".text\0\0\0" | Section name (8 bytes, zero-padded) |
01D8 | 00000034 | Size in memory |
01DC | 00001000 | RVA (a multiple of the section alignment) |
01E0 | 00000200 | Size in file (a multiple of the file alignment) |
01E4 | 00000400 | File position (a multiple of the file alignment) |
01F4 | 60000020 | Flags: executable, readable, code |
SectionHeader[1] | .rdata section header | |
01F8 | ".rdata\0\0" | Section name |
0200 | 00000154 | Size in memory |
0204 | 00002000 | RVA |
0208 | 00000200 | Size in file |
020C | 00000600 | File position |
021C | 40000040 | Flags: readable, initialized data |
SectionHeader[2] | .data section header | |
0220 | ".data\0\0\0" | Section name |
0228 | 00002400 | Size in memory |
022C | 00003000 | RVA |
0230 | 00000200 | Size in file |
0234 | 00000800 | File position |
0244 | C0000040 | Flags: readable, writable, initialized data |
.text section data | ||
0400 | ... | x64 machine code (finally!) |
.rdata section data | ||
0600 | Import information. (details below) | |
String literals and other readonly data. | ||
.data section data | ||
0800 | ... | Space for initialized and zero-initialized (.bss) variables. |
The import information in the .rdata section is complex, so it is described separately here:
Position | Value | Description |
---|---|---|
Import Address Table for kernel32.dll | These entries are 8 bytes, exactly the size of a 64-bit pointer. Each entry will be replaced with the address of a symbol that was imported from a DLL. Program instructions will reference these entries in order to find and call imported functions. (This array must be identical to the Import Lookup Table, shown below.) | |
0600 | (u64)2138 | RVA of the Hint/Name pair for this symbol. (Corresponds to file position 0738.) |
0608 | (u64)zero | Array terminator. |
Import Directory | Import info for kernel32.dll | |
0700 | 00002128 | RVA of Import Lookup Table for this DLL. (Corresponds to file position 0728.) |
070C | &"kernel32.dll\0" | RVA of the name of the DLL. |
0710 | 00002000 | RVA of the Import Address Table. (Corresponds to file position 0600.) |
0714 | (ImportDirectoryEntry)zero | Array terminator. |
Import Lookup Table for kernel32.dll | There is one Import Lookup Table for each DLL referenced in the Import Directory. | |
0728 | (u64)2138 | RVA of the Hint/Name pair for this symbol. (Corresponds to file position 0738.) |
0730 | (u64)zero | Array terminator. |
Hint/Name Pairs | Pointed to by Import Lookup Table entries. | |
0738 | "\0\0ExitProcess\0" | A 16-bit value followed by a null-terminated string. |
Null-terminated strings | DLL file names pointed to by Import Directory entries. | |
0746 | "kernel32.dll\0" |
struct DataDirectoryEntry { u32 DirectoryRVA; u32 DirectorySize; }
// Repeated according to number-of-sections specified in the header. struct SectionHeader { u8[8] Name; // Null-padded string. u32 VirtualSize; u32 VirtualAddress; u32 SizeInFile; u32 AddressInFile; u8[12] Unused; // Must be zero. u32 Characteristics; }
// Import Directory // One entry per DLL imported from. (e.g. kernel32.dll) // Array is terminated by an all-zero entry. struct ImportDirectoryEntry { u32 ImportLookupTable; // RVA of an array in .rdata. u32 Timestamp; // Can be zero. u32 ForwarderChain; // Should be zero. u32 Name; // RVA of name of DLL, a null-terminated string in .rdata. u32 ImportAddressTable; // RVA of an array in .rdata. }
// Import Lookup Table: Array of these entries. // Array is terminated by an all-zero entry. // One entry per function imported from a DLL. // One array for each entry in the Import Address Table Directory. // The Import Address Table is a copy of this array, stored elsewhere in .rdata section. struct ImportLookupTableEntry { u64 Entry; // The low 31 bits are the RVA of a Hint/Name table entry, in .rdata. // Bits 63 to 32 are zero. // (This describes only the name-based lookup format. Ordinal-based lookup is also possible.) }
// The Import Lookup Table points at these. // These structs are typically allocated in .rdata. // Must begin on an even address. struct HintNamePair { u16 OrdinalHint; // can be zero) u8[*] SymbolName; // null-terminated string }
The in-file and in-memory sizes of sections must be multiples of their respective
"section alignment" values in the header. The in-memory size will usually be larger;
whenever the section is larger in memory than on disk, the extra bytes will be zero.
This is a convenient way to define the .bss
section!