A captured image often has no import data: the descriptor is gone, the IAT directory is
gone, and nobody took a write watch. The table is still findable, because the rewrite left
a relation behind -- the slot holds the address the call targets -- so the call sites
become the search key.
discover_iat scans the non-executable sections for pointer-aligned entries equal to the
target of a relative call that leaves the image, then clusters the matches by proximity.
One cluster is used and its span becomes the inventory. Two are printed and refused: a
duplicated table, a copied table and a table split by a section boundary all look like
this from the outside, and picking one produces a file that loads and calls the wrong API
for one import. A single match is called weak rather than used.
Discovery runs when the described slots explained none of the calls, not only when the
inventory came back empty -- a protector that leaves a plausible-looking but useless
descriptor is the case the narrower rule missed. --iat suppresses it because that is the
reader asserting the range, and --no-discover is the control that keeps "the search
worked" from being a statement about the fixture.
walk_sites and pair_sites replace the single scan-and-pair pass, because discovery needs
the targets before any slot exists and one function could not express that order.
decide_result gains ambiguous-iat, which exits 1 like the other two verdicts that mean
this run did not finish the job.
Tests: the same image with its slots outside every directory recovers the two pairs the
explicit range produced; the control with --no-discover still reports needs-trace; a
second copy of the table in another section is refused with its own token; and a search
that matched nothing says so instead of reading as a clean image.
The reference gains the search, its two refusals, and the pair of failures that look
identical from the report -- slots holding tags have no value relation to search, and a
wrong base finds nothing either, so the base is checked first.
references/iat-virtualization-and-call-site-rewriting.md records the two edits one
protector family makes to a native image: the import slot stops holding an address and
the indirect call that read it becomes a relative call to a stub. It carries the pairing
rule -- a site's target equals a slot's value -- the three placements of the filler byte
in a six-byte rewrite and the refusal when there is none, the divergence between builds
that modify the code section and builds that only redirect the table, the small set of
imports redirected twice, the choice between repairing, extracting a payload, observing
and reporting, and the limits an automatic unpacker inherits from its own dump.
scripts/iat_callsite_restore.py implements the mechanical half offline: inventory the
slots from three sources, pair by value, decide the six-byte window per site, refuse an
ambiguous slot and a site whose sixth byte is the next instruction, rewrite, then
re-decode every written site from the file it wrote. It is report-only unless --out is
given, and it does not unpack: a virtualised entry has no site list to restore.
Registered in both symptom tables, the routing, reference and script indexes, the
capability registry as call_site_restore, and the coverage, route-inventory and evidence
tables. dynamic-debugging.md gains the instrument rule the second write-up demonstrates
(a target that runs at full speed and dies when stopped has a perturbation detector),
memory-and-dumps.md gains the call-site step of a rebuild, environment.md gains the
compatibility-shim trap, and protectors-and-packers.md gains one recognition row plus a
pointer rather than the detail, because that file makes no product-level claims.
The target-behaviour claims stay unverified: no protected sample was analysed here. What
is observed is the tool's behaviour on a generated image -- both filler positions, both
refusals, both displacement forms -- and what the source material states about itself.
FINDINGS F16 records the defect this pass produced, a slot's magnitude read as its
meaning, with the test that fails without the fix. .gitignore gains ref/, which was not
covered by the existing rule because that rule carries a leading dot.