WINDOWS · LESSON 01

PE Format Deep Dive

25 min

Learning Objectives

  • Map every PE header structure and its purpose
  • Read the Data Directory array
  • Understand section permissions and their security impact
  • Detect anomalies (RWX sections, malformed headers)
  • Manually parse PE structures in a hex editor
  • Identify packer/modification artifacts

The PE Is the Loader’s Contract

Windows reads the PE structure to map the file into memory, resolve imports, apply relocations, and begin execution. Reading it fluently is the foundation of Windows RE.

The Layout, End to End

DOS Header        "MZ" + e_lfanew → pointer to NT headers
NT Headers        "PE\0\0" + COFF + Optional Header
Optional Header   entry point, image base, alignments, Data Directories
Section Table     name, size, RVA, file offset, permissions
Sections          .text, .rdata, .data, .reloc, ...

DOS Header (64 bytes)

OffsetFieldSizeDescription
0x00e_magic2“MZ” (0x5A4D)
0x3Ce_lfanew4Offset to NT Headers (critical!)

The stub at 0x40 typically prints “This program cannot be run in DOS mode”. Space between 0x40 and e_lfanew = classic hiding spot.


NT Headers

Signature (4 bytes): PE\0\0 (0x00004550)

File Header (COFF) (20 bytes):

OffsetFieldSizeValues
0x00Machine20x8664 (x64), 0x014c (x86), 0xAA64 (ARM64)
0x02NumberOfSections2Count of section headers
0x04TimeDateStamp4Unix timestamp (linker) — often forged
0x08PointerToSymbolTable4COFF debug (usually 0)
0x0CNumberOfSymbols4COFF debug (usually 0)
0x10SizeOfOptionalHeader20xE0 (PE32), 0xF0 (PE32+)
0x12Characteristics2Flags: 0x0002=EXE, 0x2000=DLL, 0x0020=LargeAddrAware

Optional Header (PE32+ = 0xF0 bytes)

OffsetFieldSizeDescription
0x00Magic20x10B (PE32), 0x20B (PE32+), 0x107 (ROM)
0x02Linker Version2Major/Minor
0x04SizeOfCode4Sum of executable sections
0x08SizeOfInitializedData4Sum of initialized data
0x0CSizeOfUninitializedData4Sum of .bss
0x10AddressOfEntryPoint4RVA of entry point
0x14BaseOfCode4RVA of .text
0x18ImageBase8Preferred load address (e.g., 0x140000000)
0x20SectionAlignment4Memory alignment (0x1000)
0x24FileAlignment4Disk alignment (0x200)
0x28OS Version4Major/Minor
0x2CImage Version4Major/Minor
0x30Subsystem Version4Major/Minor
0x34Win32VersionValue4Reserved (0)
0x38SizeOfImage4Total memory size (SectionAlignment aligned)
0x3CSizeOfHeaders4Headers + section table (FileAlignment aligned)
0x40CheckSum4PE checksum (rarely verified)
0x44Subsystem21=Native, 2=GUI, 3=CUI, 9=WinCE
0x46DllCharacteristics2Mitigation flags
0x48SizeOfStackReserve8Stack reserve
0x50SizeOfStackCommit8Stack commit
0x58SizeOfHeapReserve8Heap reserve
0x60SizeOfHeapCommit8Heap commit
0x68LoaderFlags4Reserved (0)
0x6CNumberOfRvaAndSizes4Always 16
0x70DataDirectory[16]128Critical directories

DllCharacteristics (Offset 0x46) — Mitigation Flags

FlagValueMeaning
DYNAMIC_BASE0x0040ASLR enabled
NX_COMPAT0x0100DEP enabled (NX)
NO_SEH0x0400No structured exception handlers
NO_BIND0x0800Do not bind imports
APPCONTAINER0x1000AppContainer (UWP)
WDM_DRIVER0x2000WDM driver
GUARD_CF0x4000Control Flow Guard (CFG)
TERMINAL_SERVER_AWARE0x8000Terminal Server aware

CET/Guard flags (Load Config Directory, DataDirectory[10]):

  • CET: not a GuardFlags bit at all — signaled by the separate DllCharacteristicsEx field, IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT (0x1) — Shadow stacks, IBT
  • XFG: IMAGE_GUARD_XFG_ENABLED (0x00800000) — Type-based CFI
  • RFG: IMAGE_GUARD_RF_INSTRUMENTED (0x00020000) / IMAGE_GUARD_RF_ENABLE (0x00040000) / IMAGE_GUARD_RF_STRICT (0x00080000) — Return Flow Guard

The Data Directory Array (16 Entries)

Each entry: { RVA (4 bytes), Size (4 bytes) }. Zero = absent.

IndexNamePurposeSection
0ExportFunctions provided.edata
1ImportFunctions needed (IAT).idata
2ResourceIcons, manifests, version.rsrc
3Exception.pdata unwind info.pdata
4SecurityAuthenticode signature(not in sections)
5BaseRelocASLR fixups.reloc
6DebugPDB path, timestamp.rdata
7ArchitectureReserved—
8GlobalPtrRISC—
9TLSThread Local Storage callbacks.tls
10LoadConfigCFG, CET, SafeSEH, cookies.rdata
11BoundImportPre-bound IAT timestamps.rdata
12IATRuntime-resolved import addresses.idata
13DelayImportLoad on first call.didat
14COMDescriptor.NET metadata.cormeta
15Reserved——

Section Table (IMAGE_SECTION_HEADER) — 40 bytes each

OffsetFieldSizeDescription
0x00Name8Null-padded (“.text\0\0\0”)
0x08VirtualSize4Size in memory
0x0CVirtualAddress4RVA in memory
0x10SizeOfRawData4Size on disk (FileAlignment)
0x14PointerToRawData4File offset (FileAlignment)
0x18PointerToRelocations4COFF (obj files)
0x1CPointerToLinenumbers4COFF
0x20NumberOfRelocations2COFF
0x22NumberOfLinenumbers2COFF
0x24Characteristics4Permissions + flags

Section Characteristics (Flags)

FlagValueMeaning
CNT_CODE0x00000020Contains code
CNT_INITIALIZED_DATA0x00000040Contains initialized data
CNT_UNINITIALIZED_DATA0x00000080Contains uninitialized data (.bss)
MEM_EXECUTE0x20000000Executable (X)
MEM_READ0x40000000Readable (R)
MEM_WRITE0x80000000Writable (W)
ALIGN_1BYTES0x001000001-byte align
ALIGN_64BYTES0x0050000064-byte align
LNK_NRELOC_OVFL0x01000000Relocations overflow
MEM_DISCARDABLE0x02000000Discardable (e.g., .reloc)
MEM_NOT_CACHED0x04000000Not cached
MEM_NOT_PAGED0x08000000Not paged
MEM_SHARED0x10000000Shared

Common Combinations:

  • .text (code): 0x60000020 = R+X+CODE
  • .rdata (read-only data): 0x40000040 = R+INIT_DATA
  • .data (read-write data): 0xC0000040 = R+W+INIT_DATA
  • .bss (uninitialized): 0xC0000080 = R+W+UNINIT_DATA
  • .reloc: 0x42000040 = R+DISCARDABLE+INIT_DATA
  • .tls: 0xC0000040 = R+W+INIT_DATA

Section Permissions & Security

PermissionValueNormal SectionsSuspicious If
R-X0x60000000.text—
R—0x40000000.rdata, .pdata, .rsrc, .reloc—
RW-0xC0000000.data, .bss, .tls—
RWX0xE0000000NONEShellcode, JIT, unpacking, DEP bypass
R-X + W (self-modifying)——JIT, unpacking stubs

Red Flag: Any section with MEM_WRITE | MEM_EXECUTE (0xA0000000). Defeats DEP/NX.


Anomalies Worth Noticing

  1. Entry point in weird section — EntryPoint RVA inside .data/.rdata = packed/injected
  2. Huge SizeOfImage vs tiny sections — space for runtime decryption/unpacking
  3. Missing relocations with ASLR expected — DllCharacteristics & 0x40 but no .reloc
  4. TLS callbacks present (DataDirectory[9] ≠ 0) — code runs before entry point
  5. Section count mismatch — NumberOfSections ≠ actual sections
  6. Raw size > Virtual size — usually impossible, indicates corruption/packer
  7. Overlapping sections — RVAs overlap = packer trick
  8. Entry point in last section — common for packers (stub at end)

Interactive: PE Structure

Use the highlighted hex view to walk from MZ → e_lfanew → PE → Optional Header → Data Directories → Section Table.


Takeaway

The PE header is a map of everything the loader will do. Read it before anything else.

Lab 11: PE Header Forensics → PE Header Forensics


Practice Exercises

  1. Header Walk: In the hex viewer, manually parse: MZ → e_lfanew → PE signature → File Header → Optional Header → DataDirectory[1] → Import Descriptors.
  2. Mitigation Audit: Check DllCharacteristics and Load Config. Which of ASLR, DEP, CFG, CET, SafeSEH, XFG, RFG are enabled?
  3. RWX Hunt: Scan /binaries/ for sections with MEM_WRITE | MEM_EXECUTE. What do they contain?
  4. Packer Detection: A binary has 3 sections: .text (entropy 7.9), .rdata (entropy 7.8), .data (entropy 7.9). Entry point in .text at offset 0x2000. Imports: only LoadLibraryA, GetProcAddress. What packer?
  5. TLS Callback: DataDirectory[9] RVA=0x4000, Size=0x20. At 0x4000: array of RVAs [0x1000, 0x1050, 0]. What does this mean? Set BP on 0x1000.
  6. Relocation Math: ImageBase=0x140000000, ActualBase=0x7FF712340000. DIR64 fixup at PageRVA=0x1000, Offset=0x50. Original value at VA=0x140001050 is 0x140005678. What’s the patched value?

Interactive

LAB

lab-11

Apply what you learned in a hands-on challenge.

Open Lab