Skip to content

Feature/v0.7.6.1 into main - #49

Merged
malx-labs merged 29 commits into
mainfrom
feature/v0.7.6.1
Aug 27, 2026
Merged

malx-labs merged 29 commits into
mainfrom
feature/v0.7.6.1

Conversation

@malx-labs

Copy link
Copy Markdown
Collaborator

No description provided.

- TLS (2 new v0.7.6 rows): ,  — appended to the existing TLS table.
- Certificate Table (new v0.7.6 subsection under Signature): ,  — with the file-offset-not-RVA note.
- Relocations (§2.13, v0.7.6): all 4 codes.
- Debug Directory (§2.14, v0.7.6): all 4 codes.
- Exception (.pdata) (new): 3 tables (directory / entry / unwind) covering all 14 codes, plus a full sub-reasons taxonomy - , the  truncation tags, the  and  lists, the chain sub-reasons, and the  list for out-of-bounds.
…and before §3, written in the same four-part style as §2.11–2.14: the validator performs list, the divergence-surface explanation (architecture-specific layout + the loader's binary-search sortedness invariant), the pure struct-level parser implementation, and the reason-code contract mapping. It leads with the blue-team angle we care about — the sortedness check as a real evasion vector where a binary loses a function's unwind data by name-order alone.
…Any details key named overwrites the parent reason code. Codes using a differently-named key (e.g. -> ) are unaffected — confirmed by fixture 156 emitting and together.

This commit: rename the colliding details key  ->  in . Four sites. No logic changes; the priority-resolution helpers (, ) and their return values are untouched, so determinism is preserved.
correctly ('EXPORT_TABLE_TRUNCATED', 'EXPORT_NAME_POINTER_TABLE_UNSORTED');
7 are clobbered and 'EXPORT_DIRECTORY_OUT_OF_BOUNDS' is dead.
Of 8 delay-load codes, 3 are correct, 4 clobbered, 1 dead.

'EXPORT_DIRECTORY_INVALID_HEADER' is the worst case: all five of its
emission sites carry a 'reason' key, so the parent code has never once
appeared in output — it surfaces as five unrelated-looking strings instead.

Several clobbered values are also context-free single words — 'missing',
'format', 'unreadable', 'out_of_range', 'exceeds_image' — which as top-level
reason codes lose all indication of which directory produced them, and risk
colliding with other validators' vocabularies.
…odes

The heuristics emission layer built detection metadata as
'{'reason': reason, **details}', so any validator payload carrying a
top-level 'reason' key silently overwrote the parent reason code.
'_analyse_structural' forwards each validator's 'details' verbatim, so
55 emission sites across 12 validators were affected.

The defect was invisible in output: the clobbered result still looked
structured (a plausible string in a 'reason' field), and
'analysis['structural']' is never copied into the result, so no
unclobbered view existed to compare against. Eleven documented reason
codes had never once appeared in output, including both v0.7.6 TLS codes
(TLS_DIRECTORY_TRUNCATED, TLS_CALLBACK_RVA_INVALID) and
EXPORT_DIRECTORY_INVALID_HEADER. Three pairs of distinct parents
collapsed to identical strings ('unmapped', 'out_of_range',
'not_power_of_two'), making them indistinguishable to consumers.

Fixed at three layers:

  * _analyse_structural now re-keys a colliding 'reason' detail to
    'sub_reason' before handing off to _det. This immunises all current
    and future validators without requiring validator edits, and
    preserves both the parent code and the sub-reason. Simply reordering
    the merge was rejected: it keeps the parent by destroying the
    sub-reason.
  * _det writes 'reason' last so a caller payload cannot overwrite it.
    Verified a no-op for all 8 behavioural callers (none pass a 'reason'
    key).
  * Migrated exception_table, exports, delay_imports, debug and
    relocations to emit 'sub_reason' in details.

Also fixes a separate layer-sourcing defect found while investigating:
exports, delay_imports, exception_table and _directory_invariants read
SizeOfImage from 'analysis', where it does not exist (it lives in
'metadata['optional_header']'). The value was always None, silently
disabling every guarded bounds check. Those validators now take the
metadata layer and source it correctly, restoring
EXPORT_DIRECTORY_OUT_OF_BOUNDS, EXPORT_FUNCTION_RVA_INVALID,
DELAY_IMPORT_DIRECTORY_OUT_OF_BOUNDS,
EXCEPTION_FUNCTION_RVA_OUT_OF_BOUNDS and
EXCEPTION_DIRECTORY_OUT_OF_BOUNDS. _directory_invariants helpers now
take size_of_image explicitly rather than reading it from analysis,
with a default that preserves existing call sites.

Adds tests/unit/analysis/test_reason_codes.py: eight regression tests
covering both the output contract (every pe_structure_anomaly reason
must be a declared ReasonCode; parent and sub-reason both survive; a
legacy 'reason' payload cannot clobber) and the source contract (no
validator may use a top-level 'reason' detail key; _det's merge order
is pinned). Verified to fail against the pre-fix code. The
ReasonCodes-membership assertion is scoped to pe_structure_anomaly:
behavioural heuristics use a separate vocabulary, pinned by allowlist.

Updates 21 contract test snapshots. These changes are expected: the
snapshots recorded the clobbered output, so parent codes now appear
where sub-reason strings previously did, with the sub-reason preserved
alongside. No detection was added or removed; only the reason field
and payload shape changed.

BREAKING CHANGE: consumers keying on bare sub-reason strings (e.g.
'missing_table_rva', 'non_writable_section', 'below_image_base') must
switch to the parent reason code plus the new 'sub_reason' field. Those
strings were never the documented contract, so this is a correction
rather than a redefinition, but it is observable in output.
… test_crypto_entropy_payload has mixed quote styles
@malx-labs
malx-labs merged commit d4d3e05 into main Aug 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant