Skip to content

Synthesis: PE IAT gap-padding overwrites planted code (1221/1611 instructions corrupted on cutwail) #216

Description

@r0ny123

Summary

PeSynthesizer._writeThunks pads gaps inside each DLL's IAT group by walking va from min(rva) to max(rva) in ptr_size steps and writing an ordinal-0 thunk into every slot not present in import_rvas. _writeThunkAt (PeSynthesizer.py:135-140) accepts any region that contains the address — there is no executable/data check, and the padding loop has no cap on the span it may cover.

When a report's imported_functions are not densely contiguous, this walks straight through the planted code and overwrites it. A warning is emitted, but the bytes are already gone.

Reproduction

Against the repo's own tests/cutwail_xored fixture on master (4.4.1), giving the report two IAT slots 0x1000 apart — the sparse/dumped-sample shape:

report.xmetadata["imported_functions"] = {
    base + 0x1000: ("kernel32.dll", "CreateFileA"),
    base + 0x2000: ("kernel32.dll", "ExitProcess"),
}
synth = report.synthesizeBinary(output_format="PE", with_strings=False)

Comparing each planted instruction in the synthesized image against the report's own bytes:

baseline (no imports):      1611 instructions checked,    0 corrupted
sparse IAT 0x1000/0x2000:   1611 instructions checked, 1221 corrupted

with the warning 1023 gap slot(s) in IAT group for kernel32.dll padded with ordinal-0 thunks.

So ~76% of the recovered code in the synthesized binary is replaced by 00 00 00 80 ordinal thunks.

Why it matters

The trigger is precisely the memory-dump/unpacked-sample case SMDA targets: multiple or sparse IATs, or a single mis-attributed slot far from its DLL group. Both the padding loop and the primary thunk writes look like they need a gate — the slot should have to land in a non-executable region, and the group span should be bounded.

Note the asymmetry already present in the same file: the .smdaIAT path at PeSynthesizer.py:~397 does perform an overlap check before adding its region, so the intent exists, just not on this path.

Relationship to #214

#214 tracks recording runtime-resolved/custom IATs. This is narrower and separable: it is a memory-corruption bug in the existing native-IAT padding path, reproducible with ordinary contiguous-format input that merely has a gap. #214's third bullet ("Decide whether custom IAT slots that fall inside code/data regions should be tolerated without corrupting those bytes") is the policy question; this issue is the concrete present-day corruption.


Found during a whole-repo audit of src/smda/** and re-verified against master before filing. Synthesis is documented as experimental, so filing rather than patching — happy to send a PR if you'd like a particular gating approach.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions