mirror of
https://codeberg.org/reverse-skill-lab/exe-reverse
synced 2026-11-15 20:04:20 +08:00
Separate an empty slot inventory from a table that holds handles
An empty pair list had two causes and one verdict. When the run never found the import table at all, the report said needs-trace, which sends the reader off to write a trace of the protector's resolver -- the right move for a table full of handles and the wrong move for a table this run simply never located. The misdiagnosis is the expensive kind: it costs a debugger session and it looks like progress. decide_result now returns no-slots when the inventory is empty, and the printed hint says which of the two it is (supply --iat, or reconstruct the table). no-slots exits 1 with needs-trace, because the run did not do the job it was asked to do; the docstring's exit code table says so. The reference gains the matching sentence in the inventory step: an inventory that came back empty is a finding about the run, not a table that holds no addresses. Test: test_no_slot_values_is_its_own_verdict pins both sides -- no slots plus unpaired calls is no-slots, some slots plus unpaired calls is still needs-trace -- and the existing token test now carries a slot count so it cannot pass for the wrong reason.
This commit is contained in:
@@ -79,7 +79,9 @@ were counted over.
|
||||
— and record which one you used.
|
||||
2. **Inventory the slots.** Three sources, in descending order of trust: the import descriptors if
|
||||
they survived, the import address table directory's range if it did, and the addresses that a
|
||||
write watch reported. Say which source the inventory came from; they do not always agree.
|
||||
write watch reported. Say which source the inventory came from; they do not always agree. An
|
||||
inventory that came back empty is a finding about this run, not a table that holds no addresses:
|
||||
it means the range has to be supplied or rebuilt before any pairing is possible.
|
||||
3. **Pair a site to a slot by value.** The site's target address equals the slot's value. This is
|
||||
the whole pairing rule, and it is also the mechanism an open-source unpacker's fixup routine
|
||||
implements (`observed` in that source: it reads the target from the relative call, looks it up in
|
||||
|
||||
@@ -44,9 +44,9 @@ Usage:
|
||||
python iat_callsite_restore.py dumped.exe --base 0x2B0000 --iat 0x48D000,0x1000
|
||||
python iat_callsite_restore.py dumped.exe --json --full-out report.json
|
||||
|
||||
Exit codes: 0 = the analysis completed and no site was refused (result clean, needs-trace,
|
||||
report-only or repaired), 1 = at least one site was refused or a rewrite failed verification,
|
||||
2 = usage error, 3 = the input is not a parseable PE.
|
||||
Exit codes: 0 = the analysis completed and no site was refused (result no-pairs, needs-trace,
|
||||
report-only or repaired), 1 = at least one site was refused, a rewrite failed verification, or the
|
||||
run had no slot values to pair against, 2 = usage error, 3 = the input is not a parseable PE.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
@@ -367,6 +367,11 @@ def decide_result(report, wrote):
|
||||
return 'incomplete'
|
||||
if report['pairs']:
|
||||
return 'repaired' if wrote else 'report-only'
|
||||
if not report['iat']['slots']:
|
||||
# No slot values at all is not evidence that the table is untouched: it is evidence that
|
||||
# this run has nothing to pair against. Reporting that as needs-trace sends the reader to
|
||||
# write a trace they do not need, which is the same misdiagnosis in a different direction.
|
||||
return 'no-slots'
|
||||
if report['unpaired']:
|
||||
return 'needs-trace'
|
||||
return 'no-pairs'
|
||||
@@ -420,6 +425,10 @@ def print_report(report, max_sites):
|
||||
out.write('next each refusal is a decision for a person: a site with no filler byte is a '
|
||||
'structural edit, and an ambiguous slot needs the trace that says which slot the '
|
||||
'call actually used\n')
|
||||
elif report['result'] == 'no-slots':
|
||||
out.write('next this run has no slot values to pair against: pass --iat RVA,SIZE with '
|
||||
'the table range, or reconstruct the table first. Without it the sites below are '
|
||||
'calls whose target nothing in this image holds\n')
|
||||
elif report['result'] == 'needs-trace':
|
||||
out.write('next either the table does not hold these targets, or the base is not the '
|
||||
'one the image ran at -- check --base first, because a wrong base produces the '
|
||||
@@ -518,7 +527,7 @@ def main():
|
||||
if args.full_out:
|
||||
sys.stdout.write('full record %s\n' % args.full_out)
|
||||
|
||||
return 1 if report['result'] == 'incomplete' else 0
|
||||
return 1 if report['result'] in ('incomplete', 'no-slots') else 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -155,9 +155,24 @@ def test_a_clean_image_reports_no_pairs(load_script, fixtures):
|
||||
assert mod.decide_result(report, wrote=False) == "no-pairs"
|
||||
|
||||
|
||||
def test_no_slot_values_is_its_own_verdict(load_script):
|
||||
"""A run with nothing to pair against must not send the reader to write a trace.
|
||||
|
||||
Both causes -- a table that holds handles, and a table this run never found -- produce the same
|
||||
empty pair list. The first needs a trace; the second needs --iat. Collapsing them into one token
|
||||
was the first version of this rule.
|
||||
"""
|
||||
mod = load_script("iat_callsite_restore.py")
|
||||
with_no_slots = {"pairs": [], "refused": [], "unpaired": [1], "iat": {"slots": 0, "classes": {}}}
|
||||
assert mod.decide_result(with_no_slots, wrote=False) == "no-slots"
|
||||
with_slots = {"pairs": [], "refused": [], "unpaired": [1],
|
||||
"iat": {"slots": 4, "classes": {}}}
|
||||
assert mod.decide_result(with_slots, wrote=False) == "needs-trace"
|
||||
|
||||
|
||||
def test_result_tokens_follow_the_evidence(load_script):
|
||||
mod = load_script("iat_callsite_restore.py")
|
||||
base = {"pairs": [], "refused": [], "unpaired": [], "iat": {"classes": {}}}
|
||||
base = {"pairs": [], "refused": [], "unpaired": [], "iat": {"slots": 4, "classes": {}}}
|
||||
assert mod.decide_result(dict(base), wrote=False) == "no-pairs"
|
||||
assert mod.decide_result(dict(base, pairs=[1]), wrote=False) == "report-only"
|
||||
assert mod.decide_result(dict(base, pairs=[1]), wrote=True) == "repaired"
|
||||
|
||||
Reference in New Issue
Block a user