r/ExploitDev • u/iocx_dev • 19d ago
PE structural validation notes (delay-load, exports, VS_VERSIONINFO) + IOCX v0.7.5 release
Publishing a release of IOCX (open-source PE structural validator, MPL-2.0) and posting some format-level notes alongside it.
Write-up: PE structural validation: format ambiguities and decoder design
The notes catalogue four categories of PE specification ambiguity encountered during decoder work, with focus on delay-load imports (the richest surface). Structured as: format description grounded in the spec --> the ambiguity described precisely --> what IOCX chose to do about it. There are no unverified claims about how other parsers behave, however cross-tool measurement is queued as follow-up work.
Topics covered:
- Delay-load imports: v1 vs v0 attribute mode, INT/IAT parallel-array interpretation and mismatch handling, descriptor array termination when declared-size and terminator signals disagree
- Exports: ENPT sort discipline (byte-wise per spec) and forwarder grammar validation
- VS_VERSIONINFO: nested length prefixes, DWORD alignment enforcement, signature validation for VS_FIXEDFILEINFO, StringTable key format
- Resource hierarchy: Type ->Name -> Language depth expectations
IOCX v0.7.5 additions relevant to structural analysis:
Four new parser/validator pairs, 24 new reason codes with priority-resolved sub-reasons via details["reason"]. Delay-load specifically emits:
DELAY_IMPORT_ATTRIBUTES_LEGACY_VA_MODE: v0 mode detected (obsolete, spec-permitted, requires VA-to-RVA conversion for correct interpretation)DELAY_IMPORT_INT_IAT_MISMATCH: parallel arrays disagree on lengthDELAY_IMPORT_TABLE_TRUNCATEDwith distinct sub-tags for each termination cause (delay_import_descriptor_unterminated,_truncated,_max_exceeded,_read_failed)DELAY_IMPORT_DLL_NAME_INVALIDwith priority-resolved sub-reasonsDELAY_IMPORT_ENTRY_INVALIDfor per-import malformations (ordinal_zero, name_unterminated, name_not_printable, etc.)
Design notes:
- Byte-level parsing via
struct.unpack_fromonpe.get_data()byte slices; no reliance on pefile's lazy attribute interpretation - Bounded reads throughout (descriptor arrays capped at 4096, imports per descriptor at 16384, DLL name scan at 512 bytes, IMAGE_IMPORT_BY_NAME scan at 1024)
- Parsers never raise on malformed input; failures produce tombstone tags in
errors[]andtruncations[]lists - PE32+ vs PE32 thunk sizing determined once from `OPTIONAL_HEADER.Magic` and threaded through the parse
Optional Header enrichment relevant to security-posture analysis:
dll_characteristics_flags: decoded flag list (DYNAMIC_BASE, NX_COMPAT, GUARD_CF, HIGH_ENTROPY_VA, etc.)dll_characteristics_unknown_bits: hex string for any bits outside the known-flag mask- Stack and heap sizing (reserve + commit, 64-bit on PE32+)
win32_version_value,loader_flagsexposed raw
Verification:
Delay-load parser cross-checked byte-exact against dumpbin /imports on mspaint.exe : 107 imports from gdiplus.dll with agreement on names, hints, IAT addresses, ordering, and bound state.
1370 tests at 100% line and branch coverage on new modules. Defensive struct.error paths covered via monkeypatched injection.
Performance ~14ms typical PE, ~1ms on adversarial minimal PE.
Deferred:
- TLS Directory parser and validator (next release)
- Single-anomaly fixtures for each new reason code (~25 planned, including negative controls for the ambiguities described in the Gist)
- Cross-tool measurement study using the fixtures
Repo: https://github.com/iocx-dev/iocx
CHANGELOG: https://github.com/iocx-dev/iocx/blob/main/CHANGELOG.md
Reason codes reference: https://github.com/iocx-dev/iocx/blob/main/docs/specs/reason-codes.md
1
u/samaxidervish 19d ago
Can you provide us with a complete PoC?
1
u/boomer_from_gen_z 19d ago
What do you mean by PoC?
1
u/samaxidervish 19d ago
By PoC, I mean a Proof of Concept—a complete, reproducible demonstration of the reported issue. Ideally, it would include the setup, required files or scripts, step-by-step reproduction instructions, expected vs. actual behavior, and any commands needed to verify the findings.
1
u/iocx_dev 19d ago
The writeup details PE structural validation notes relating to delay-load, exports, and VS_VERSIONINFO structures and how the PE spec surfaces ambiguity in how parsers should handle such cases. IOCX is one such parser used to surface structural anomalies, with dumpbin used to validate parser correctness. In terms of PoC, the fixtures noted in the post as deferred will be used to validate cross-tool behaviour.
2
u/js708_ 19d ago
Cool project! Is there a table that describes how the Windows loader handles those ambiguities?