← Back to all projects

LEARN WINDOWS PE DLL DEEP DIVE

Learn Windows PE Format, DLLs & Executables: From Zero to System Programmer

Goal: Deeply understand the Windows Portable Executable format, DLL mechanics, and executable internals—from parsing raw bytes to building your own loader and understanding every section, import, export, and relocation.


Why This Matters

Every .exe and .dll on Windows follows the Portable Executable (PE) format. When you double-click an application, Windows parses this format to:

  • Map code sections into memory
  • Resolve imported functions from other DLLs
  • Apply relocations if the preferred base address is taken
  • Execute initialization code (DllMain)

Understanding PE internals is essential for:

  • Debugging: Know exactly what’s happening when a DLL fails to load
  • Security: Understand malware techniques, DLL injection, IAT hooking
  • Performance: Optimize load times, understand delay loading
  • Reverse Engineering: Analyze binaries without source code
  • Systems Programming: Build tools like debuggers, profilers, packers

After completing these projects, the PE format will never be a black box again.


Core Concept Analysis

The PE File Structure

┌─────────────────────────────────────────────────────────────┐
│                     DOS Header (64 bytes)                    │
│  - Magic number "MZ" (0x5A4D)                               │
│  - e_lfanew: offset to PE signature                         │
├─────────────────────────────────────────────────────────────┤
│                     DOS Stub (variable)                      │
│  - "This program cannot be run in DOS mode"                 │
├─────────────────────────────────────────────────────────────┤
│                   PE Signature ("PE\0\0")                    │
├─────────────────────────────────────────────────────────────┤
│                  COFF File Header (20 bytes)                 │
│  - Machine type (x86, x64, ARM)                             │
│  - Number of sections                                        │
│  - Timestamp, characteristics                                │
├─────────────────────────────────────────────────────────────┤
│               Optional Header (PE32: 96 bytes)               │
│                          (PE32+: 112 bytes)                  │
│  - Entry point RVA                                          │
│  - Image base, section alignment                            │
│  - Data directories (imports, exports, relocations...)      │
├─────────────────────────────────────────────────────────────┤
│                    Section Headers                           │
│  - .text (code), .data (initialized data)                   │
│  - .rdata (read-only data), .bss (uninitialized)           │
│  - .pdata (exception info), .rsrc (resources)               │
│  - .reloc (relocations), .edata (exports)                   │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│                     Section Data                             │
│                   (actual code and data)                     │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Key Data Directories

Index Name Purpose
0 Export Table Functions this module provides
1 Import Table Functions this module needs
2 Resource Table Icons, strings, dialogs
3 Exception Table SEH/unwind info (.pdata)
5 Base Relocation Fixups for ASLR
6 Debug Debug information
12 IAT Import Address Table
13 Delay Import Delay-loaded DLLs

Address Types

  • VA (Virtual Address): Absolute address in memory
  • RVA (Relative Virtual Address): Offset from image base
  • File Offset: Offset in the file on disk
  • Conversion: VA = ImageBase + RVA

DLL Loading Process

LoadLibrary("user32.dll")
        │
        ▼
┌───────────────────┐
│ 1. Find DLL       │ ← Search order: app dir, system32, PATH...
└─────────┬─────────┘
          ▼
┌───────────────────┐
│ 2. Map to Memory  │ ← Create file mapping, map sections
└─────────┬─────────┘
          ▼
┌───────────────────┐
│ 3. Apply Relocs   │ ← If loaded at different base address
└─────────┬─────────┘
          ▼
┌───────────────────┐
│ 4. Resolve Imports│ ← Walk import table, fill IAT
└─────────┬─────────┘
          ▼
┌───────────────────┐
│ 5. Call DllMain   │ ← DLL_PROCESS_ATTACH
└─────────┬─────────┘
          ▼
     Return HMODULE

Project 1: PE Header Dumper

  • File: LEARN_WINDOWS_PE_DLL_DEEP_DIVE.md
  • Main Programming Language: C
  • Alternative Programming Languages: C++, Rust, Python (for prototyping)
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Binary Parsing / PE Format
  • Software or Tool: PE files (.exe, .dll)
  • Main Book: “Windows Internals, Part 1” by Pavel Yosifovich et al.

What you’ll build: A command-line tool that reads any PE file and dumps all headers in human-readable format—DOS header, PE signature, COFF header, Optional header, Data directories, and Section headers.

Why it teaches PE Format: This is your foundation. Before you can manipulate PE files, you must read them. Every field you parse will cement your understanding of how Windows sees executables.

Core challenges you’ll face:

  • Parsing the DOS header and finding PE signature → maps to understanding the MZ/PE structure
  • Handling PE32 vs PE32+ differences → maps to 32-bit vs 64-bit executable formats
  • Converting RVAs to file offsets → maps to section alignment and virtual mapping
  • Interpreting characteristic flags → maps to executable properties (DLL, large address aware, etc.)

Key Concepts:

  • DOS Header Structure: “Windows Internals, Part 1” Chapter 3 - Yosifovich
  • PE Optional Header: Microsoft PE Format Specification (MSDN)
  • Section Characteristics: “Practical Binary Analysis” Chapter 3 - Andriesse
  • Memory Mapping vs File Layout: “Windows Internals, Part 1” Chapter 5

Difficulty: Intermediate Time estimate: Weekend Prerequisites:

  • C programming: structs, pointers, file I/O
  • Understanding of hexadecimal and binary
  • Basic Windows API (CreateFile, ReadFile)
  • No prior project required

Real world outcome:

C:\> pedump.exe kernel32.dll

=== DOS HEADER ===
e_magic:    0x5A4D (MZ)
e_lfanew:   0x000000F0

=== PE SIGNATURE ===
Signature:  0x00004550 (PE)

=== COFF FILE HEADER ===
Machine:              0x8664 (AMD64)
NumberOfSections:     7
TimeDateStamp:        0x6579A2B1 (2023-12-13 10:42:25)
Characteristics:      0x2022 (EXECUTABLE_IMAGE | LARGE_ADDRESS_AWARE | DLL)

=== OPTIONAL HEADER (PE32+) ===
Magic:                0x20B (PE32+)
EntryPoint:           0x0007A3C0
ImageBase:            0x00007FFC00000000
SectionAlignment:     0x1000
FileAlignment:        0x200
SizeOfImage:          0x000B8000

=== DATA DIRECTORIES ===
[0]  Export:          RVA=0x00092A40  Size=0x0000D8C6
[1]  Import:          RVA=0x000A0308  Size=0x000000B4
[5]  BaseReloc:       RVA=0x000A1000  Size=0x00005A24
...

=== SECTIONS ===
Name      VirtAddr   VirtSize   RawAddr    RawSize    Characteristics
.text     0x00001000 0x00078A22 0x00000400 0x00078C00 CODE|EXECUTE|READ
.rdata    0x0007A000 0x00026662 0x00079000 0x00026800 INITIALIZED|READ
.data     0x000A1000 0x00001558 0x0009F800 0x00000600 INITIALIZED|READ|WRITE
.pdata    0x000A3000 0x00006D8C 0x0009FE00 0x00006E00 INITIALIZED|READ
.rsrc     0x000AA000 0x00000520 0x000A6C00 0x00000600 INITIALIZED|READ
.reloc    0x000AB000 0x00005A24 0x000A7200 0x00005C00 INITIALIZED|DISCARDABLE|READ

Implementation Hints:

Start by understanding the key structures from <winnt.h>:

IMAGE_DOS_HEADER (64 bytes)
├── e_magic (WORD) = 0x5A4D ("MZ")
├── ... (legacy DOS fields)
└── e_lfanew (LONG) = offset to PE signature

At offset e_lfanew:
PE_SIGNATURE (DWORD) = 0x00004550 ("PE\0\0")

IMAGE_FILE_HEADER (20 bytes)
├── Machine
├── NumberOfSections
├── TimeDateStamp
├── PointerToSymbolTable
├── NumberOfSymbols
├── SizeOfOptionalHeader
└── Characteristics

IMAGE_OPTIONAL_HEADER (PE32: 96 bytes, PE32+: 112 bytes)
├── Magic (0x10B = PE32, 0x20B = PE32+)
├── AddressOfEntryPoint
├── ImageBase
├── SectionAlignment
├── FileAlignment
├── SizeOfImage
├── NumberOfRvaAndSizes
└── DataDirectory[16]

Questions to guide your implementation:

  1. How do you determine if a file is PE32 or PE32+?
  2. Why is SizeOfOptionalHeader in the COFF header, not fixed?
  3. How would you convert an RVA to a file offset? (Hint: find which section contains it)
  4. What happens if e_lfanew points outside the file?

Approach:

  1. Open file, read first 2 bytes, verify “MZ”
  2. Seek to offset 0x3C, read e_lfanew
  3. Seek to e_lfanew, verify “PE\0\0”
  4. Read COFF header (fixed 20 bytes)
  5. Read Optional header (size from COFF header)
  6. Read Section headers (NumberOfSections × 40 bytes)

Learning milestones:

  1. You parse the DOS and PE headers → You understand the file structure
  2. You correctly handle PE32 vs PE32+ → You understand bitness differences
  3. You can convert RVA to file offset → You understand section mapping
  4. You decode all flags and characteristics → You understand PE metadata

Project 2: Import Table Analyzer

  • File: LEARN_WINDOWS_PE_DLL_DEEP_DIVE.md
  • Main Programming Language: C
  • Alternative Programming Languages: C++, Rust, Python
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: PE Format / Dynamic Linking
  • Software or Tool: PE files, Dependency Walker replacement
  • Main Book: “Windows Internals, Part 1” by Pavel Yosifovich et al.

What you’ll build: A tool that parses the Import Directory of any PE file and displays all imported DLLs and their functions—showing both import by name and import by ordinal.

Why it teaches IAT: The Import Address Table is how Windows resolves external function calls at load time. Understanding this is crucial for debugging “missing DLL” errors, understanding dynamic linking, and security research (IAT hooking).

Core challenges you’ll face:

  • Navigating the Import Directory Table → maps to understanding IMAGE_IMPORT_DESCRIPTOR
  • Following the ILT/IAT structure → maps to Import Lookup Table vs Import Address Table
  • Handling ordinal vs name imports → maps to the high bit flag mechanism
  • Dealing with bound imports → maps to performance optimization in PE

Key Concepts:

  • Import Directory Structure: Microsoft PE Format Specification - Section 6.4
  • Import Lookup Table: “Practical Binary Analysis” Chapter 4 - Andriesse
  • Ordinal Imports: “Windows Internals, Part 1” Chapter 3
  • Bound Imports: “Windows via C/C++” Chapter 20 - Richter

Difficulty: Intermediate Time estimate: Weekend Prerequisites:

  • Completed Project 1 (PE Header Dumper)
  • Understanding of RVA to file offset conversion
  • Familiarity with pointer chasing in binary data

Real world outcome:

C:\> imports.exe notepad.exe

=== IMPORT DIRECTORY ===

[1] KERNEL32.dll
    Hint  Function
    ----  --------
    0x02A1  GetModuleHandleW
    0x0412  LoadLibraryExW
    0x0267  GetProcAddress
    0x01F4  GetLastError
    0x0128  CreateFileW
    0x05C3  WriteFile
    0x00B7  CloseHandle
    ... (47 more functions)

[2] USER32.dll
    Hint  Function
    ----  --------
    0x0172  GetMessageW
    0x00EC  DispatchMessageW
    0x0291  SendMessageW
    0x0076  CreateWindowExW
    0x028E  SetWindowTextW
    Ordinal #123  (imported by ordinal)
    ... (82 more functions)

[3] GDI32.dll
    Hint  Function
    ----  --------
    0x0041  CreateFontIndirectW
    0x011E  SelectObject
    0x0047  DeleteObject
    ...

Total: 14 DLLs, 312 imported functions

Implementation Hints:

The Import Directory is found via Data Directory index 1:

OptionalHeader.DataDirectory[1] = { RVA, Size }
                                      │
                                      ▼
            ┌─────────────────────────────────────────┐
            │ IMAGE_IMPORT_DESCRIPTOR #1              │
            │   OriginalFirstThunk (RVA to ILT)       │
            │   TimeDateStamp                         │
            │   ForwarderChain                        │
            │   Name (RVA to DLL name)                │
            │   FirstThunk (RVA to IAT)               │
            ├─────────────────────────────────────────┤
            │ IMAGE_IMPORT_DESCRIPTOR #2              │
            │   ...                                   │
            ├─────────────────────────────────────────┤
            │ NULL terminator (all zeros)             │
            └─────────────────────────────────────────┘

Each Import Descriptor points to an Import Lookup Table:

ILT (array of IMAGE_THUNK_DATA)
┌─────────────────────────────────┐
│ 0x00007FFC12345678              │ ← If high bit set: ordinal
│ 0x0000000000089A40              │ ← Otherwise: RVA to hint/name
│ 0x0000000000089A5C              │
│ 0x0000000000000000              │ ← NULL terminator
└─────────────────────────────────┘

At RVA 0x89A40:
┌──────┬───────────────────┐
│ Hint │ "GetProcAddress\0"│
└──────┴───────────────────┘
  (2 bytes)   (null-terminated)

Key insight: For PE32+, check bit 63 for ordinal. For PE32, check bit 31.

Questions to guide implementation:

  1. What’s the difference between OriginalFirstThunk and FirstThunk?
  2. Why does Windows need both ILT and IAT?
  3. What happens to these tables after the PE is loaded?
  4. How do bound imports optimize load time?

Learning milestones:

  1. You enumerate all imported DLLs → You understand the import descriptor chain
  2. You list functions by name → You understand the ILT/hint-name structure
  3. You handle ordinal imports → You understand the high-bit mechanism
  4. You understand ILT vs IAT → You grasp the runtime patching concept

Project 3: Export Table Analyzer

  • File: LEARN_WINDOWS_PE_DLL_DEEP_DIVE.md
  • Main Programming Language: C
  • Alternative Programming Languages: C++, Rust, Python
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: PE Format / DLL Exports
  • Software or Tool: DLLs (kernel32.dll, user32.dll)
  • Main Book: “Windows Internals, Part 1” by Pavel Yosifovich et al.

What you’ll build: A tool that parses the Export Directory of DLLs and displays all exported functions with their ordinals, RVAs, and names—including detection of forwarded exports.

Why it teaches Export Tables: Every DLL provides functions through its export table. Understanding this structure lets you see what any DLL offers, detect forwarded functions (like kernel32!HeapAllocntdll!RtlAllocateHeap), and build your own DLLs properly.

Core challenges you’ll face:

  • Navigating the Export Directory → maps to IMAGE_EXPORT_DIRECTORY structure
  • Correlating ordinals with names → maps to AddressOfNames vs AddressOfNameOrdinals
  • Detecting forwarded exports → maps to when RVA points within export section
  • Handling exports by ordinal only → maps to anonymous exports

Key Concepts:

  • Export Directory Structure: Microsoft PE Format Specification - Section 6.3
  • Ordinal Base and Bias: “Windows Internals, Part 1” Chapter 3
  • Export Forwarding: “Windows via C/C++” Chapter 20 - Richter
  • Binary Search in Export Table: MSDN GetProcAddress documentation

Difficulty: Intermediate Time estimate: Weekend Prerequisites:

  • Completed Project 1 (PE Header Dumper)
  • Completed Project 2 (Import Table Analyzer)
  • Understanding of parallel arrays

Real world outcome:

C:\> exports.exe kernel32.dll

=== EXPORT DIRECTORY ===
Name:               KERNEL32.dll
Ordinal Base:       1
Number of Functions: 1665
Number of Names:     1665

Ordinal  RVA        Name
-------  ---------  ----
0001     0x0001A3C0  AcquireSRWLockExclusive
                     → NTDLL.RtlAcquireSRWLockExclusive (FORWARDED)
0002     0x0001A3D0  AcquireSRWLockShared
                     → NTDLL.RtlAcquireSRWLockShared (FORWARDED)
...
0156     0x00023F40  CreateFileW
0157     0x00024120  CreateFileMappingW
...
0823     0x00078A40  HeapAlloc
                     → NTDLL.RtlAllocateHeap (FORWARDED)
...
1432     0x0008C320  VirtualAlloc
1433     0x0008C4A0  VirtualAllocEx
...

Forwarded exports: 423
Direct exports:    1242
Ordinal-only:      0

Implementation Hints:

The Export Directory is at Data Directory index 0:

IMAGE_EXPORT_DIRECTORY
├── Characteristics (unused, 0)
├── TimeDateStamp
├── MajorVersion, MinorVersion
├── Name (RVA to DLL name)
├── Base (ordinal base, usually 1)
├── NumberOfFunctions (total exports)
├── NumberOfNames (named exports ≤ NumberOfFunctions)
├── AddressOfFunctions (RVA to EAT)
├── AddressOfNames (RVA to name pointer array)
└── AddressOfNameOrdinals (RVA to ordinal array)

Three parallel arrays work together:

AddressOfNames[i]        → RVA to "FunctionName"
AddressOfNameOrdinals[i] → Index into AddressOfFunctions
AddressOfFunctions[j]    → RVA to function code (or forward string)

Detecting forwarded exports: If AddressOfFunctions[j] points within the export section bounds (between DataDirectory[0].RVA and RVA+Size), it’s a forward string like "NTDLL.RtlAllocateHeap".

Questions for implementation:

  1. Why are there three separate arrays instead of one struct array?
  2. How does GetProcAddress use binary search on the name array?
  3. What’s the ordinal base and why does it matter?
  4. Can a DLL export a function without a name?

Learning milestones:

  1. You list all exports → You understand the export directory
  2. You correlate names with ordinals → You understand the parallel array design
  3. You detect forwards → You understand the forward mechanism
  4. You handle ordinal-only exports → You understand the complete export picture

Project 4: Section Mapper & Visualizer

  • File: LEARN_WINDOWS_PE_DLL_DEEP_DIVE.md
  • Main Programming Language: C
  • Alternative Programming Languages: C++, Rust, Python
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: PE Format / Memory Mapping
  • Software or Tool: PE files
  • Main Book: “Practical Binary Analysis” by Dennis Andriesse

What you’ll build: A tool that visualizes how PE sections map from file to memory, showing the transformation between file layout and virtual memory layout, with color-coded section characteristics.

Why it teaches Sections: PE sections are where code and data actually live. Understanding .text, .data, .rdata, .bss, .pdata, .rsrc, and .reloc is fundamental. This project makes the file-to-memory mapping tangible.

Core challenges you’ll face:

  • Understanding FileAlignment vs SectionAlignment → maps to disk vs memory layout
  • Calculating virtual addresses from file offsets → maps to the mapping algorithm
  • Interpreting section characteristics → maps to permissions and properties
  • Handling .bss and uninitialized data → maps to zero-fill sections

Key Concepts:

  • Section Headers: “Practical Binary Analysis” Chapter 3 - Andriesse
  • Alignment Requirements: Microsoft PE Format Specification - Section 4
  • Section Characteristics Flags: “Windows Internals, Part 1” Chapter 5
  • Virtual Memory Layout: “Windows Internals, Part 1” Chapter 5

Difficulty: Intermediate Time estimate: Weekend Prerequisites:

  • Completed Project 1 (PE Header Dumper)
  • Basic understanding of virtual memory

Real world outcome:

C:\> secmap.exe myapp.exe

=== PE SECTION MAPPING ===

Image Base:        0x00400000
Section Alignment: 0x1000 (4096)
File Alignment:    0x200 (512)

FILE LAYOUT                          MEMORY LAYOUT
────────────────                     ────────────────
0x00000000 ┌────────────┐            0x00400000 ┌────────────┐
           │ Headers    │  ────────▶            │ Headers    │
0x00000400 ├────────────┤            0x00401000 ├────────────┤
           │   .text    │  ────────▶            │   .text    │
           │ (code)     │                       │ R-X        │
0x00005200 ├────────────┤            0x00406000 ├────────────┤
           │   .rdata   │  ────────▶            │   .rdata   │
           │ (readonly) │                       │ R--        │
0x00006800 ├────────────┤            0x00408000 ├────────────┤
           │   .data    │  ────────▶            │   .data    │
           │ (globals)  │                       │ RW-        │
0x00007000 ├────────────┤            0x0040A000 ├────────────┤
           │   .pdata   │  ────────▶            │   .pdata   │
           │ (unwind)   │                       │ R--        │
0x00007600 ├────────────┤            0x0040B000 ├────────────┤
           │   .rsrc    │  ────────▶            │   .rsrc    │
           │ (resources)│                       │ R--        │
0x00007E00 ├────────────┤            0x0040C000 ├────────────┤
           │   .reloc   │  ────────▶            │   .reloc   │
           │ (relocs)   │                       │ R-- DISC   │
0x00008200 └────────────┘            0x0040D000 └────────────┘

SECTION DETAILS:
Name      VirtSize   VirtAddr   RawSize    RawAddr    Perms
.text     0x00004A22 0x00001000 0x00004C00 0x00000400 CODE|EXEC|READ
.rdata    0x00001234 0x00006000 0x00001400 0x00005200 INIT|READ
.data     0x00002000 0x00008000 0x00000800 0x00006800 INIT|READ|WRITE
.pdata    0x00000456 0x0000A000 0x00000600 0x00007000 INIT|READ
.rsrc     0x00000678 0x0000B000 0x00000800 0x00007600 INIT|READ
.reloc    0x00000320 0x0000C000 0x00000400 0x00007E00 INIT|DISC|READ

Implementation Hints:

Each section header (IMAGE_SECTION_HEADER, 40 bytes):

├── Name[8]              (8-byte name, NOT null-terminated if 8 chars)
├── VirtualSize          (size in memory)
├── VirtualAddress       (RVA of section start)
├── SizeOfRawData        (size on disk, aligned to FileAlignment)
├── PointerToRawData     (file offset)
├── PointerToRelocations (object files only)
├── PointerToLinenumbers (deprecated)
├── NumberOfRelocations
├── NumberOfLinenumbers
└── Characteristics      (flags: code, data, read, write, execute, etc.)

Key relationships:

  • VirtualAddress is aligned to SectionAlignment (typically 0x1000)
  • PointerToRawData is aligned to FileAlignment (typically 0x200)
  • SizeOfRawData can be > VirtualSize (padding) or < (uninitialized data)

Common section names and purposes:

  • .text: Executable code
  • .data: Initialized read-write data (globals)
  • .rdata: Read-only data (strings, vtables, imports)
  • .bss: Uninitialized data (zero-filled, usually merged with .data)
  • .pdata: Exception handling info (x64)
  • .xdata: Unwind data (x64)
  • .rsrc: Resources (icons, dialogs, version info)
  • .reloc: Base relocations
  • .tls: Thread-local storage

Questions for implementation:

  1. Why is VirtualSize sometimes smaller than SizeOfRawData?
  2. What does DISCARDABLE mean for .reloc?
  3. How do you know which section contains a given RVA?
  4. Why do headers get their own “section” in memory?

Learning milestones:

  1. You enumerate all sections → You understand section headers
  2. You calculate memory addresses → You understand the mapping math
  3. You decode characteristics → You understand section permissions
  4. You visualize file vs memory → You’ve internalized the PE loading model

Project 5: Relocation Parser & Simulator

  • File: LEARN_WINDOWS_PE_DLL_DEEP_DIVE.md
  • Main Programming Language: C
  • Alternative Programming Languages: C++, Rust
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: PE Format / ASLR / Memory Layout
  • Software or Tool: DLLs (they always have relocations)
  • Main Book: “Windows Internals, Part 1” by Pavel Yosifovich et al.

What you’ll build: A tool that parses the Base Relocation Table, displays all relocation entries, and simulates what would happen if the PE were loaded at a different base address.

Why it teaches Relocations: When Windows can’t load a DLL at its preferred base address (due to ASLR or conflicts), it must “fix up” all absolute addresses in the code. This project demystifies that critical process.

Core challenges you’ll face:

  • Parsing the relocation table format → maps to IMAGE_BASE_RELOCATION blocks
  • Understanding relocation types → maps to HIGHLOW, DIR64, etc.
  • Calculating fixups → maps to delta = new_base - preferred_base
  • Simulating the patching process → maps to what the loader actually does

Key Concepts:

  • Base Relocation Directory: Microsoft PE Format Specification - Section 6.6
  • ASLR and Why Relocations Matter: “Windows Internals, Part 2” Chapter 5
  • Relocation Types: “Practical Binary Analysis” Chapter 4 - Andriesse
  • Position-Independent Code: Intel/AMD64 Architecture Manuals

Difficulty: Advanced Time estimate: 1 week Prerequisites:

  • Completed Projects 1-4
  • Understanding of absolute vs relative addressing
  • Understanding of ASLR

Real world outcome:

C:\> relocs.exe mydll.dll

=== BASE RELOCATION TABLE ===

Preferred Base:  0x10000000
Relocation Size: 0x00001234

Block at RVA 0x00001000 (Size: 0x0048, Entries: 34)
  Offset  Type       Patch Location      Current Value
  ------  ----       --------------      -------------
  0x0012  HIGHLOW    0x10001012          0x10005678
  0x0056  HIGHLOW    0x10001056          0x1000ABCD
  0x0089  DIR64      0x10001089          0x0000000010008000
  ...

Block at RVA 0x00002000 (Size: 0x002C, Entries: 18)
  ...

=== SIMULATION: Rebasing to 0x20000000 ===

Delta: +0x10000000

Sample patches:
  0x10001012: 0x10005678 → 0x20005678
  0x10001056: 0x1000ABCD → 0x2000ABCD
  0x10001089: 0x10008000 → 0x20008000

Total relocations: 847 entries in 12 blocks

Implementation Hints:

The Base Relocation Table (Data Directory index 5) is a series of blocks:

IMAGE_BASE_RELOCATION
├── VirtualAddress (RVA of page, e.g., 0x1000)
├── SizeOfBlock (size including header + entries)
└── TypeOffset[] (array of 16-bit entries)
    ┌────────────────────────────────┐
    │ 4-bit type │ 12-bit offset    │
    └────────────────────────────────┘

Common relocation types:

  • 0 (ABSOLUTE): Padding, skip
  • 3 (HIGHLOW): 32-bit fixup (common in PE32)
  • 10 (DIR64): 64-bit fixup (common in PE32+)

The patching algorithm:

delta = actual_load_address - preferred_base
for each block:
    page_rva = block.VirtualAddress
    for each entry in block:
        if entry.type == HIGHLOW:
            ptr = image_base + page_rva + entry.offset
            *ptr += delta  // 32-bit addition
        if entry.type == DIR64:
            ptr = image_base + page_rva + entry.offset
            *ptr += delta  // 64-bit addition

Questions for implementation:

  1. Why are relocations grouped by page (4KB blocks)?
  2. Why do EXEs often have no relocations while DLLs always do?
  3. What happens if ASLR is enabled but the PE has no .reloc section?
  4. Why is type 0 (ABSOLUTE) used for alignment padding?

Learning milestones:

  1. You parse relocation blocks → You understand the block structure
  2. You decode type/offset pairs → You understand the encoding
  3. You simulate fixups → You can predict what the loader does
  4. You understand ASLR impact → You grasp why relocations exist

Project 6: Dependency Walker Clone

  • File: LEARN_WINDOWS_PE_DLL_DEEP_DIVE.md
  • Main Programming Language: C++
  • Alternative Programming Languages: C, Rust, Python
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: PE Format / DLL Loading / Dependency Resolution
  • Software or Tool: Dependency Walker replacement
  • Main Book: “Windows via C/C++” by Jeffrey Richter

What you’ll build: A recursive dependency analyzer that walks the import table of a PE file, finds all dependent DLLs, and builds a complete dependency tree—detecting missing DLLs, circular dependencies, and version mismatches.

Why it teaches DLL Mechanics: Real-world debugging often involves “DLL Hell”—missing dependencies, wrong versions, conflicting DLLs. This project makes you understand the entire DLL search and load process.

Core challenges you’ll face:

  • Implementing DLL search order → maps to the complex Windows DLL lookup rules
  • Recursive dependency resolution → maps to dependencies have dependencies
  • Detecting missing/circular deps → maps to error conditions in loading
  • Handling delay-loaded imports → maps to optional dependencies

Key Concepts:

  • DLL Search Order: MSDN “Dynamic-Link Library Search Order”
  • Side-by-Side Assemblies: “Windows via C/C++” Chapter 21 - Richter
  • Known DLLs Registry Key: “Windows Internals, Part 1” Chapter 3
  • Activation Context: “Windows Internals, Part 1” Chapter 3

Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites:

  • Completed Projects 1-3 (PE parsing, imports, exports)
  • Understanding of filesystem operations
  • Familiarity with Windows system directories

Real world outcome:

C:\> depends.exe myapp.exe

=== DEPENDENCY TREE ===

myapp.exe
├── KERNEL32.dll (C:\Windows\System32\kernel32.dll)
│   ├── NTDLL.dll (C:\Windows\System32\ntdll.dll)
│   └── KERNELBASE.dll (C:\Windows\System32\KernelBase.dll)
│       └── NTDLL.dll (already listed)
├── USER32.dll (C:\Windows\System32\user32.dll)
│   ├── win32u.dll (C:\Windows\System32\win32u.dll)
│   │   └── NTDLL.dll (already listed)
│   ├── GDI32.dll (C:\Windows\System32\gdi32.dll)
│   │   └── ... (3 dependencies)
│   └── KERNEL32.dll (already listed)
├── MSVCR140.dll (C:\Windows\System32\msvcp140.dll)
│   ├── KERNEL32.dll (already listed)
│   └── VCRUNTIME140.dll (C:\Windows\System32\vcruntime140.dll)
└── mylib.dll [NOT FOUND!]
    Search paths tried:
    - C:\Users\dev\myapp\mylib.dll
    - C:\Windows\System32\mylib.dll
    - C:\Windows\mylib.dll
    - (PATH directories...)

=== SUMMARY ===
Total DLLs: 23
Missing: 1 (mylib.dll)
Delay-loaded: 2 (SHELL32.dll, ADVAPI32.dll)
Circular refs: 0

Implementation Hints:

Windows DLL Search Order (Desktop Apps, SafeDllSearchMode enabled):

  1. The directory containing the application EXE
  2. The System directory (GetSystemDirectory: C:\Windows\System32)
  3. The 16-bit System directory (C:\Windows\System)
  4. The Windows directory (GetWindowsDirectory: C:\Windows)
  5. The current directory
  6. Directories in PATH environment variable

Special cases:

  • “Known DLLs” (HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs) bypass search
  • API sets (api-ms-*) are virtual and redirect to real DLLs
  • Side-by-side assemblies use activation contexts and manifests

Algorithm outline:

resolve_dependencies(pe_path, visited_set):
    if pe_path in visited_set:
        return  // Already processed (prevent cycles)
    visited_set.add(pe_path)

    pe = parse_pe(pe_path)
    for each import_dll in pe.imports:
        found_path = search_dll(import_dll, pe_path)
        if found_path:
            print_tree_node(import_dll, found_path)
            resolve_dependencies(found_path, visited_set)
        else:
            print_missing(import_dll)

Questions for implementation:

  1. How do you handle API sets like api-ms-win-core-file-l1-1-0.dll?
  2. What’s the difference between Safe and non-Safe DLL search mode?
  3. How do manifests affect DLL loading (side-by-side)?
  4. How would you detect DLL hijacking vulnerabilities?

Learning milestones:

  1. You resolve direct dependencies → You understand basic DLL search
  2. You build a recursive tree → You understand transitive dependencies
  3. You detect missing DLLs → You can debug “DLL not found” errors
  4. You handle special cases → You understand the full loading picture

Project 7: Custom DLL Loader (Manual Mapper)

  • File: LEARN_WINDOWS_PE_DLL_DEEP_DIVE.md
  • Main Programming Language: C
  • Alternative Programming Languages: C++, Rust
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 4: Expert
  • Knowledge Area: PE Loading / Memory Management / Dynamic Linking
  • Software or Tool: Implementing LoadLibrary from scratch
  • Main Book: “Windows Internals, Part 1” by Pavel Yosifovich et al.

What you’ll build: Your own LoadLibrary implementation that manually maps a DLL into memory: allocating virtual memory, copying sections, applying relocations, resolving imports, and calling DllMain.

Why it teaches DLL Mechanics: This is the ultimate test of PE understanding. You’re doing what the Windows loader does. After this, DLL loading will never be mysterious again.

Core challenges you’ll face:

  • Allocating memory with correct permissions → maps to VirtualAlloc and section characteristics
  • Mapping sections to correct addresses → maps to understanding alignment
  • Applying relocations → maps to handling non-preferred base address
  • Resolving imports recursively → maps to filling the IAT
  • Calling DllMain correctly → maps to initialization and threading rules

Key Concepts:

  • VirtualAlloc and Memory Permissions: MSDN VirtualAlloc documentation
  • DllMain Rules: “Windows via C/C++” Chapter 20 - Richter
  • TLS Callbacks: “Practical Malware Analysis” Chapter 13 - Sikorski
  • Loader Lock: “Windows Internals, Part 1” Chapter 3

Difficulty: Expert Time estimate: 2-4 weeks Prerequisites:

  • Completed Projects 1-6
  • Strong understanding of virtual memory
  • Windows API experience (VirtualAlloc, VirtualProtect)

Real world outcome: (See in original response above - the full manual loader demo)

Implementation Hints: (See detailed hints in original response above)

Learning milestones:

  1. You allocate and map sections → You understand the memory layout
  2. You apply relocations → You understand address fixups
  3. You resolve imports → You understand dynamic linking
  4. DllMain runs successfully → You’ve built a working loader

Project 8-15 Summary

Continue with Projects 8-15 as detailed in the comprehensive guide above, including:

  • Project 8: IAT Hooking Framework
  • Project 9: DLL Proxy Generator
  • Project 10: Delay Load Analyzer & Simulator
  • Project 11: DllMain Rule Enforcer
  • Project 12: ABI Compatibility Checker
  • Project 13: PE Packer / Unpacker
  • Project 14: PE Diff Tool
  • Project 15: Memory Dumper & PE Reconstructor

Project Comparison Table

Project Difficulty Time Depth Fun
1. PE Header Dumper Intermediate Weekend ⭐⭐⭐ ⭐⭐
2. Import Table Analyzer Intermediate Weekend ⭐⭐⭐⭐ ⭐⭐⭐
3. Export Table Analyzer Intermediate Weekend ⭐⭐⭐⭐ ⭐⭐⭐
4. Section Mapper Intermediate Weekend ⭐⭐⭐⭐ ⭐⭐⭐
5. Relocation Parser Advanced 1 week ⭐⭐⭐⭐⭐ ⭐⭐⭐
6. Dependency Walker Clone Advanced 1-2 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐
7. Custom DLL Loader Expert 2-4 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
8. IAT Hooking Framework Expert 2 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
9. DLL Proxy Generator Advanced 1-2 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐
10. Delay Load Analyzer Advanced 1 week ⭐⭐⭐⭐ ⭐⭐⭐
11. DllMain Rule Enforcer Advanced 2 weeks ⭐⭐⭐⭐ ⭐⭐⭐
12. ABI Compatibility Checker Advanced 2 weeks ⭐⭐⭐⭐ ⭐⭐⭐
13. PE Packer / Unpacker Expert 1 month+ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
14. PE Diff Tool Intermediate 1-2 weeks ⭐⭐⭐⭐ ⭐⭐⭐
15. Memory Dumper Expert 2-3 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

If you’re new to PE format:

Start with Projects 1-4 in order.

If you want to understand DLL mechanics deeply:

After 1-4, do Projects 5, 6, 7, 10, 11.

If you’re interested in security/reversing:

Focus on Projects 7, 8, 13, 15.

If you’re a library developer:

Prioritize Projects 9, 11, 12.


Summary

Project Main Language
1. PE Header Dumper C
2. Import Table Analyzer C
3. Export Table Analyzer C
4. Section Mapper & Visualizer C
5. Relocation Parser & Simulator C
6. Dependency Walker Clone C++
7. Custom DLL Loader (Manual Mapper) C
8. IAT Hooking Framework C
9. DLL Proxy Generator C++
10. Delay Load Analyzer & Simulator C
11. DllMain Rule Enforcer C++
12. ABI Compatibility Checker C++
13. PE Packer / Unpacker C
14. PE Diff Tool C++
15. Memory Dumper & PE Reconstructor C
Capstone: PE Development Toolkit C++