The optional header is a fixed-size, frozen structure. It cannot grow every time the compiler invents a new hardening feature, yet the loader needs addresses that only exist at link time: where the /GS stack-overflow cookie lives, where the list of legitimate SEH handlers is, where Control Flow Guard's valid-target table sits, whether code integrity metadata is present. The Load Configuration is the overflow valve — one variable-length structure in .rdata, pointed to by data directory index 10, that has been extended field by field for two decades.
Because it describes the mechanisms behind the flags from the previous lesson, it is also the structure most worth getting right when you are deciding whether to trust a binary. The load-bearing fields are few — a size, a cookie pointer, an optional handler table, a set of Guard pointers — but reading them at the wrong offsets yields confident nonsense, and the structure's growth is exactly why the first field exists.
Version it by length, not by a version number:
Two consumers with different appetites read it: the loader (cookie, SEH validation, CFG targets, affinity/heap flags) and tooling that inspects hardening state. The model's missing piece: a data directory gives you an address and a size for the whole table — trust neither blindly; the struct's own Size is what says which fields are real.
The spec's "Load Configuration Layout" table, PE32+ offsets, restricted to the fields that carry the weight:
| Offset | Size | Field | What it says |
|---|---|---|---|
| 0 | 4 | Size | Total size of the structure — the version gate (see the warning below) |
| 4 | 4 | TimeDateStamp | Seconds since 1970-01-01; 0 or 0xFFFFFFFF means not meaningful |
| 8 / 10 | 2 / 2 | MajorVersion / MinorVersion | User-settable version numbers |
| 12 / 16 | 4 | GlobalFlagsClear / GlobalFlagsSet | Loader global flags to clear or set when the process starts |
| 20 | 4 | CriticalSectionDefaultTimeout | Default timeout for abandoned critical sections |
| 80 | 8 | EditList | Reserved for the system |
| 88 | 8 | SecurityCookie | VA of the /GS security cookie used by the Visual C++ GS implementation |
| 96 / 104 | 8 / 8 | SEHandlerTable / SEHandlerCount | x86 only: sorted table of RVAs of valid unique SE handlers, and its count |
| 112 | 8 | GuardCFCheckFunctionPointer | VA where the CFG check-function pointer is stored |
| 120 | 8 | GuardCFDispatchFunctionPointer | VA of the CFG dispatch pointer |
| 128 | 8 | GuardCFFunctionTable | VA of the sorted table of RVAs of every CFG-instrumented function |
| 136 | 8 | GuardCFFunctionCount | How many entries that table has |
| 144 | 4 | GuardFlags | Control-flow integrity flag bits (decoded below) |
| 148 | 12 | CodeIntegrity | Code integrity information sub-structure |
| 160 / 168 | 8 / 8 | GuardAddressTakenIatEntryTable / Count | VA and count of the address-taken IAT entry table |
| 176 / 184 | 8 / 8 | GuardLongJumpTargetTable / Count | VA and count of the longjmp target table |
GuardFlags bits, from the spec's list: 0x00000100 CF_INSTRUMENTED (control-flow checks using system support), 0x00000200 CFW_INSTRUMENTED (control-flow and write integrity), 0x00000400 CF_FUNCTION_TABLE_PRESENT (valid control-flow target metadata present), 0x00000800 SECURITY_COOKIE_UNUSED, 0x00001000 PROTECT_DELAYLOAD_IAT (read-only delay-load IAT), 0x00002000 DELAYLOAD_IAT_IN_ITS_OWN_SECTION, 0x00004000 CF_EXPORT_SUPPRESSION_INFO_PRESENT, 0x00008000 CF_ENABLE_EXPORT_SUPPRESSION, 0x00010000 CF_LONGJUMP_TABLE_PRESENT, and the top nibble 0xF0000000 is a subfield holding the CFG function-table stride (right-justified with a shift of 28).
Who reads which part: the loader needs SecurityCookie, the SEH table (on x86 — the spec marks SEHandlerTable/Count "x86 only", where exception dispatch consults it), and the CFG pointers; debuggers and security tools read GuardFlags/GuardCFFunctionCount to see what protection is really configured. The spec is blunt about the SEH path's failure mode: if a handler address falls inside the image and NO_SEH is clear, the handler must appear in the known-safe list, "otherwise, the operating system terminates the application" — the defense against x86 exception-handler hijacking.
0x140, which is exactly the structure's size (and exactly the Size value in their data directory entries), and LLVM's coff_load_configuration64 declares the first field Size. Read the first dword as Size, and only then as a version gate — the spec itself says "the size is really only a version check". Never assume a fixed total length either: the structure grew from 64 bytes (the x86 SEH era) to hundreds, so a parser that seeks to a hard-coded offset on a foreign file will confidently report another field's bytes as GuardFlags.Data directory index 10 of conpty.dll: RVA 0x13E40, size 0x140. .rdata has RVA 0x11000 at file 0xFC00, so the structure sits at file 0x12A40:
00012a40: 4001 0000 0000 0000 0000 0000 0000 0000 @............... 00012a50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ ... 00012a90: 0000 0000 0000 0000 4080 0180 0100 0000 ........@....... 00012aa0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00012ab0: f013 0180 0100 0000 0014 0180 0100 0000 ................ 00012ac0: a014 0180 0100 0000 4200 0000 0000 0000 ........B....... 00012ad0: 0075 0110 0000 0000 0000 0000 0000 0000 .u..............
4001 0000 = 0x140 (320 bytes) — Size, matching the directory's size field exactly.4080 0180 0100 0000 = 0x180018040 — SecurityCookie, a VA in .data (ImageBase 0x180000000 + 0x18040). That is the /GS cookie the compiler will XOR stack frames against.0x1800113F0 and 0x180011400 — GuardCFCheckFunctionPointer and GuardCFDispatchFunctionPointer, both in .rdata.0x1800114A0, count 4200 0000 0000 0000 = 66 CFG-instrumented functions.0075 0110 = GuardFlags 0x10017500 = CF_INSTRUMENTED (0x100) + CF_FUNCTION_TABLE_PRESENT (0x400) + PROTECT_DELAYLOAD_IAT (0x1000) + DELAYLOAD_IAT_IN_ITS_OWN_SECTION (0x2000) + CF_EXPORT_SUPPRESSION_INFO_PRESENT (0x4000) + CF_LONGJUMP_TABLE_PRESENT (0x10000), plus stride subfield 0x10000000 = 1 after the shift of 28. The CodeIntegrity sub-structure at 148 is all zero — no embedded catalog metadata.Sample A's cli-64.exe carries the same Size, 0x140, at its load config RVA 0x33D0 — modern linkers emit the full current struct whether or not every field is used.
objdump prints the directory entry but not the struct — read it with xxd once you have the file offset:
$ objdump -p conpty.dll | grep "Load Configuration"
Entry a 0000000000013e40 00000140 Load Configuration Directory
$ xxd -s 0x12a40 -l 16 conpty.dll
00012a40: 4001 0000 0000 0000 0000 0000 0000 0000 @...............Pull the three fields that matter in one line (offsets 0, 136, 144):
$ python3 -c "
import struct
d=open('conpty.dll','rb').read()
lc=0x12a40
print(hex(struct.unpack_from('<I',d,lc)[0]),
hex(struct.unpack_from('<Q',d,lc+136)[0]),
hex(struct.unpack_from('<I',d,lc+144)[0]))"
0x140 0x42 0x10017500What to look for: does the struct's Size match the directory size? Is SecurityCookie inside a writable section? Does GuardCFFunctionCount agree with the stride encoded in GuardFlags? In this file all three checks pass.
Without looking back: what does the first dword of the load configuration tell a reader, and why is it first?
You want to read conpty.dll's GuardCFFunctionCount yourself. The directory says the structure is at RVA 0x13E40; the section table says .rdata has VirtualAddress 0x11000 at PointerToRawData 0xFC00. Which file offset do you read?
The pattern is the same one from the RVA-conversion lesson: find the section that contains the RVA, then add the file pointer delta. The load configuration changes nothing about that rule — it only gives you a reason to care about getting it right.
Module 6 is done: the image can move, its hardening bits are declared, and its cookie and CFG tables are located. Module 7 opens the runtime tables with the structure that has nothing to do with loading and everything to do with what the user sees — icons, version strings, and dialogs, arranged in a three-level tree inside .rsrc.
Next: The Resource Directory — Type, Name, Language, and how Windows finds your icon.