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.
Summary
PeSynthesizer._writeThunkspads gaps inside each DLL's IAT group by walkingvafrommin(rva)tomax(rva)inptr_sizesteps and writing an ordinal-0 thunk into every slot not present inimport_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_functionsare 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_xoredfixture onmaster(4.4.1), giving the report two IAT slots 0x1000 apart — the sparse/dumped-sample shape:Comparing each planted instruction in the synthesized image against the report's own bytes:
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 80ordinal 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
.smdaIATpath atPeSynthesizer.py:~397does 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 againstmasterbefore filing. Synthesis is documented as experimental, so filing rather than patching — happy to send a PR if you'd like a particular gating approach.