Understanding PE Headers: A Complete Guide to the Windows Portable Executable Format

Introduction

The Portable Executable (PE) format is an essential structure for Windows binaries such as executables (EXE) and dynamic link libraries (DLLs). It consists of a set of PE headers, each playing a crucial role in how the file loads, executes, and interacts with the operating system. This guide explores the key PE headers, their specific purposes, internal structures, and sizes, offering a comprehensive understanding for developers, reverse engineers, and cybersecurity professionals. By mastering the PE file format, you can gain deeper insights into PE file analysis and enhance your understanding of system programming.



What is the Portable Executable (PE) Format?

The Portable Executable (PE) format is a structure used by Windows binaries to ensure efficient execution on the system. From PE headers to section headers, each component is designed for a specific purpose, whether it’s managing metadata, memory mapping, or optimizing runtime behavior. Understanding the PE format structure helps in analyzing how Windows executables and DLLs operate within the system.


PE File Structure Overview

A PE file comprises several headers and sections:

  1. DOS Header
  2. PE Signature
  3. File Header
  4. Optional Header
  5. Data Directories
  6. Section Headers
  7. Sections

Headers and Sections in Detail

1. DOS Header

  • Purpose: A legacy header from MS-DOS that contains the "MZ" magic number and a pointer to the PE header.
  • Structure Name: IMAGE_DOS_HEADER
  • Size: 64 bytes

Key Fields:

  • e_magic: Identifies the file as an executable with the "MZ" signature.
  • e_lfanew: Points to the NT headers.
typedef struct _IMAGE_DOS_HEADER {
    WORD  e_magic;      /* 00: MZ Header signature */
    WORD  e_cblp;       /* 02: Bytes on last page of file */
    WORD  e_cp;         /* 04: Pages in file */
    WORD  e_crlc;       /* 06: Relocations */
    WORD  e_cparhdr;    /* 08: Size of header in paragraphs */
    WORD  e_minalloc;   /* 0a: Minimum extra paragraphs needed */
    WORD  e_maxalloc;   /* 0c: Maximum extra paragraphs needed */
    WORD  e_ss;         /* 0e: Initial (relative) SS value */
    WORD  e_sp;         /* 10: Initial SP value */
    WORD  e_csum;       /* 12: Checksum */
    WORD  e_ip;         /* 14: Initial IP value */
    WORD  e_cs;         /* 16: Initial (relative) CS value */
    WORD  e_lfarlc;     /* 18: File address of relocation table */
    WORD  e_ovno;       /* 1a: Overlay number */
    WORD  e_res[4];     /* 1c: Reserved words */
    WORD  e_oemid;      /* 24: OEM identifier (for e_oeminfo) */
    WORD  e_oeminfo;    /* 26: OEM information; e_oemid specific */
    WORD  e_res2[10];   /* 28: Reserved words */
    DWORD e_lfanew;     /* 3c: Offset to extended header */
}

1.1 DOS Stub

  • Purpose: Displays a message such as "This program cannot be run in DOS mode" when executed in a DOS environment.
  • Size: Variable, typically ~128 bytes.

2. PE Signature

  • Purpose: Marks the file as a Portable Executable file.
  • Structure Name: Not a structure but a constant value ("PE\0\0").
  • Size: 4 bytes

Here’s your content formatted for a Blogger post. Simply copy and paste it into your blog editor:


3. NT Header (IMAGE_NT_HEADERS) 

The NT Header plays a crucial role in PE (Portable Executable) files, encapsulating the FileHeader and OptionalHeader, which provide essential details about the PE file. Like the DOS Header, the NT Header has a signature to validate it. This signature, often "PE", is represented as 0x50 and 0x45 bytes. Since the signature is a DWORD, it is represented as 0x50450000, padded with two null bytes. You can access the NT Header using the e_lfanew member in the DOS Header.


NT Header Structure: 32-bit and 64-bit Variants

32-bit Version:

typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

64-bit Version:

typedef struct _IMAGE_NT_HEADERS64 {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

The primary difference lies in the OptionalHeader structure, which varies between IMAGE_OPTIONAL_HEADER32 and IMAGE_OPTIONAL_HEADER64.


3.1. PE Signature

  • Purpose: Identifies the file as a PE file.
  • Structure Name: Not a structure; it’s a constant value ("PE\0\0").
  • Size: 4 bytes
  • Key Fields:
    • "PE\0\0"

3.2. File Header / PE Header

  • Purpose: Contains metadata about the file, such as the target machine and the number of sections.
  • Structure Name: IMAGE_FILE_HEADER
  • Size: 20 bytes
  • Key Fields:
    • Machine: Target architecture (e.g., x86, x64).
    • NumberOfSections: Number of sections in the file.
    • Characteristics: Flags describing file attributes.
typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine;
  WORD  NumberOfSections;
  DWORD TimeDateStamp;
  DWORD PointerToSymbolTable;
  DWORD NumberOfSymbols;
  WORD  SizeOfOptionalHeader;
  WORD  Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

3.3. Optional Header

  • Purpose: Provides details about the executable, such as entry points and memory layout.
  • Structure Name: IMAGE_OPTIONAL_HEADER
    • Varies for 32-bit and 64-bit versions.
  • Size:
    • 32-bit: 96 bytes
    • 64-bit: 112 bytes
  • Key Fields:
    • AddressOfEntryPoint: Entry point of the executable.
    • ImageBase: Preferred memory address for loading.
    • Subsystem: Indicates the runtime environment (e.g., GUI, console).

Optional Header Structure

64-bit Version:

typedef struct _IMAGE_OPTIONAL_HEADER64 {
  WORD  Magic; /* 0x20b */
  BYTE MajorLinkerVersion;
  BYTE MinorLinkerVersion;
  DWORD SizeOfCode;
  DWORD SizeOfInitializedData;
  DWORD SizeOfUninitializedData;
  DWORD AddressOfEntryPoint;
  DWORD BaseOfCode;
  ULONGLONG ImageBase;
  DWORD SectionAlignment;
  DWORD FileAlignment;
  WORD MajorOperatingSystemVersion;
  WORD MinorOperatingSystemVersion;
  WORD MajorImageVersion;
  WORD MinorImageVersion;
  WORD MajorSubsystemVersion;
  WORD MinorSubsystemVersion;
  DWORD Win32VersionValue;
  DWORD SizeOfImage;
  DWORD SizeOfHeaders;
  DWORD CheckSum;
  WORD Subsystem;
  WORD DllCharacteristics;
  ULONGLONG SizeOfStackReserve;
  ULONGLONG SizeOfStackCommit;
  ULONGLONG SizeOfHeapReserve;
  ULONGLONG SizeOfHeapCommit;
  DWORD LoaderFlags;
  DWORD NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
}

32-bit Version:

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;



4. Data Directories

The Data Directories are pointers in the Optional Header, linking to important tables like imports and exports.

  • Structure Name: IMAGE_DATA_DIRECTORY
  • Size: 8 bytes per entry (16 entries = 128 bytes)
  • Key Fields:
    • VirtualAddress: Address of the table.
    • Size: Size of the table.
typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress;
  DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

The Data Directory array is of size `IMAGE_NUMBEROF_DIRECTORY_ENTRIES` which is a constant value of `16`. Each element in the array represents a specific data directory which includes some data about a PE section or a Data Table (the place where specific information about the PE is saved).

A specific data directory can be accessed using its index in the array.

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

Key Data Directories

Export Directory

Contains addresses of exported functions, found mainly in DLLs (e.g., kernel32.dll exporting CreateFileA).

Import Address Table

Holds addresses of functions imported from other files (e.g., Application.exe importing CreateFileA from kernel32.dll).


5. Section Headers (Section Table)

  • Purpose: Describe individual sections like .text (code), .data (initialized data), and .rdata (read-only data).
  • Structure Name: IMAGE_SECTION_HEADER
  • Size: 40 bytes per section

Key Fields:

  • Name: Name of the section (e.g., .text, .data).
  • VirtualSize: Size of the section in memory.
  • SizeOfRawData: Size of the section on disk.
typedef struct _IMAGE_SECTION_HEADER {
  BYTE  Name[IMAGE_SIZEOF_SHORT_NAME];
  union {
    DWORD PhysicalAddress;
    DWORD VirtualSize;
  } Misc;
  DWORD VirtualAddress;
  DWORD SizeOfRawData;
  DWORD PointerToRawData;
  DWORD PointerToRelocations;
  DWORD PointerToLinenumbers;
  WORD  NumberOfRelocations;
  WORD  NumberOfLinenumbers;
  DWORD Characteristics;
}


6. Sections

Sections store the actual content of the executable (code, data, imports, exports, etc.). The specific sections and their contents include:

Section Purpose Contents Characteristics
.text Code section Executable instructions (machine code). Execute
.rdata Read-only data Import Table, Export Table, strings, constants. Read
.data Initialized data Global/static variables with initial values. Read
.bss Uninitialized data Global/static variables without initial values. Read
.idata Import-related data Import Address Table (IAT), descriptors. Read
.edata Export-related data Export Directory Table, exported symbols. Read
.reloc Relocation information Base Relocation Table. Read
.rsrc Resources Icons, dialogs, menus, etc. Read

6.1. .text (Code Section)

  • Purpose: Contains the executable instructions (machine code) of the program.
  • Structure Name: IMAGE_SECTION_HEADER
  • Contents:
    • Compiled machine instructions.
    • Entry point code for the program.
  • Characteristics:
    • IMAGE_SCN_CNT_CODE: Indicates this is a code section.
    • IMAGE_SCN_MEM_EXECUTE: The section is executable.
    • IMAGE_SCN_MEM_READ: The section is readable.
  • Key Fields in Section Header:
    • Name: Typically ".text".
    • VirtualSize: Size of the section in memory.
    • PointerToRawData: Offset of the section's raw data in the file.
    • Characteristics: Attributes for the section.

6.2. .rdata (Read-Only Data Section)

  • Purpose: Contains read-only data, such as constants, strings, and sometimes import/export-related data.
  • Structure Name: IMAGE_SECTION_HEADER
  • Contents:
    • Strings and constants.
    • Import Table (Import Address Table, Import Lookup Table).
    • Export Table (if not in .edata).
  • Characteristics:
    • IMAGE_SCN_CNT_INITIALIZED_DATA: Contains initialized data.
    • IMAGE_SCN_MEM_READ: Read-only data.
  • Key Fields in Section Header:
    • Name: Typically ".rdata".
    • VirtualSize: Size of the section in memory.
    • PointerToRawData: Offset of the section's raw data in the file.
    • Characteristics: Attributes for the section.

6.3. .data (Initialized Data Section)

  • Purpose: Stores global and static variables with initial values.
  • Structure Name: IMAGE_SECTION_HEADER
  • Contents:
    • Initialized global/static variables.
    • Writable data used by the program.
  • Characteristics:
    • IMAGE_SCN_CNT_INITIALIZED_DATA: Contains initialized data.
    • IMAGE_SCN_MEM_READ: The section is readable.
    • IMAGE_SCN_MEM_WRITE: The section is writable.
  • Key Fields in Section Header:
    • Name: Typically ".data".
    • VirtualSize: Size of the section in memory.
    • PointerToRawData: Offset of the section's raw data in the file.
    • Characteristics: Attributes for the section.

6.4. .bss (Uninitialized Data Section)

  • Purpose: Stores uninitialized global and static variables.
  • Structure Name: IMAGE_SECTION_HEADER
  • Contents:
    • Uninitialized global/static variables.
    • Memory is zero-initialized at runtime.
  • Size:
    • File size: Typically 0 (uninitialized data is not stored in the file).
    • Virtual size: Rounded to SectionAlignment.
  • Characteristics:
    • IMAGE_SCN_CNT_UNINITIALIZED_DATA: Contains uninitialized data.
    • IMAGE_SCN_MEM_READ: The section is readable.
    • IMAGE_SCN_MEM_WRITE: The section is writable.
  • Key Fields in Section Header:
    • Name: Typically ".bss".
    • VirtualSize: Size of the section in memory.
    • PointerToRawData: Usually 0 (since no raw data is stored in the file).
    • Characteristics: Attributes for the section.

6.5. .idata (Import Section)

  • Purpose: Contains information about imported functions and libraries.
  • Structure Name: IMAGE_SECTION_HEADER
  • Contents:
    • Import Address Table (IAT).
    • Import Lookup Table (ILT).
    • IMAGE_IMPORT_DESCRIPTOR structures.
  • Characteristics:
    • IMAGE_SCN_CNT_INITIALIZED_DATA: Contains initialized data.
    • IMAGE_SCN_MEM_READ: Readable.
  • Key Fields in Section Header:
    • Name: Typically ".idata".
    • VirtualSize: Size of the section in memory.
    • PointerToRawData: Offset of the section's raw data in the file.
    • Characteristics: Attributes for the section.

6.6. .edata (Export Section)

  • Purpose: Contains information about exported functions and symbols.
  • Structure Name: IMAGE_SECTION_HEADER
  • Contents:
    • IMAGE_EXPORT_DIRECTORY structure.
    • Addresses of exported symbols.
    • Symbol names and ordinals.
  • Characteristics:
    • IMAGE_SCN_CNT_INITIALIZED_DATA: Contains initialized data.
    • IMAGE_SCN_MEM_READ: Readable.
  • Key Fields in Section Header:
    • Name: Typically ".edata".
    • VirtualSize: Size of the section in memory.
    • PointerToRawData: Offset of the section's raw data in the file.
    • Characteristics: Attributes for the section.

6.7. .reloc (Relocation Section)

  • Purpose: Contains relocation information for fixing addresses when the PE file is loaded at a non-preferred base address.
  • Structure Name: IMAGE_SECTION_HEADER
  • Contents:
    • IMAGE_BASE_RELOCATION structures.
    • Relocation entries for specific addresses.
  • Characteristics:
    • IMAGE_SCN_CNT_INITIALIZED_DATA: Contains initialized data.
    • IMAGE_SCN_MEM_READ: Readable.
  • Key Fields in Section Header:
    • Name: Typically ".reloc".
    • VirtualSize: Size of the section in memory.
    • PointerToRawData: Offset of the section's raw data in the file.
    • Characteristics: Attributes for the section.

6.8. .rsrc (Resource Section)

  • Purpose: Stores application resources, such as icons, menus, dialogs, and strings.
  • Structure Name: IMAGE_SECTION_HEADER
  • Contents:
    • Resource data in a hierarchical format.
  • Characteristics:
    • IMAGE_SCN_CNT_INITIALIZED_DATA: Contains initialized data.
    • IMAGE_SCN_MEM_READ: Readable.
  • Key Fields in Section Header:
    • Name: Typically ".rsrc".
    • VirtualSize: Size of the section in memory.
    • PointerToRawData: Offset of the section's raw data in the file.
    • Characteristics: Attributes for the section.


Summary Table

Header/Section Structure Name Size (Bytes) Purpose
DOS Header `IMAGE_DOS_HEADER` 64 Legacy; points to PE header.
PE Signature Constant (`"PE\0\0"`) 4 Identifies the file as PE.
File Header `IMAGE_FILE_HEADER` 20 File metadata.
Optional Header `IMAGE_OPTIONAL_HEADER32`/
`IMAGE_OPTIONAL_HEADER64`
96 / 112 Detailed executable info.
Section Headers `IMAGE_SECTION_HEADER` 40 per section Describes file sections.
Data Directories `IMAGE_DATA_DIRECTORY` 128 Points to tables like import/export.
Import Descriptor `IMAGE_IMPORT_DESCRIPTOR` 20 per entry Details about imported DLLs/functions.
Export Directory `IMAGE_EXPORT_DIRECTORY` 40 Details about exported symbols.
Base Relocation Table `IMAGE_BASE_RELOCATION` Varies Relocation info for memory mapping.
Resource Directory `IMAGE_RESOURCE_DIRECTORY` Varies Resource data (icons, strings, etc.).
Debug Directory `IMAGE_DEBUG_DIRECTORY` 28 per entry Debugging info pointers.


Summary

The Portable Executable (PE) format is the backbone of Windows executable files, facilitating efficient interaction between applications and the Windows operating system. Understanding its components—such as PE headers, section headers, and data directories—is essential for anyone involved in PE file analysis. From the DOS Header to the Section, each section defines the structure and behavior of the file during runtime.

Whether you're a developer optimizing your code, a reverse engineer dissecting Windows binaries, or a cybersecurity professional investigating potential threats, mastering the PE format is key to navigating and manipulating Windows executables effectively. This guide provides a foundational understanding to help you advance your skills in binary analysis, PE file analysis, and system programming.


Bhanu Namikaze

Bhanu Namikaze is an Ethical Hacker, Security Analyst, Blogger, Web Developer and a Mechanical Engineer. He Enjoys writing articles, Blogging, Debugging Errors and Capture the Flags. Enjoy Learning; There is Nothing Like Absolute Defeat - Try and try until you Succeed.

No comments:

Post a Comment