Commit Graph
10 Commits
Author SHA1 Message Date
root 5d7c41bdd5 Find the import table from the calls when no directory describes it
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.
2026-10-27 16:45:46 +08:00
root 9ce5ac29d2 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.
2026-10-27 16:34:45 +08:00
root e5b45d9072 Add the import-layer repair and the Themida absorption
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.
2026-10-27 16:29:59 +08:00
root af9603c985 Add the two reading facts the first triage skipped
assembly-idioms.md records that an __int64 comes back in EDX:EAX, with a 64-bit
argument taking two stack slots, and that a direct absolute operand names a
global rather than a local.

Both were present in the tutorial corpus and left out of the previous commit.
The rest of its C-to-assembly material stays out: a reference needs a symptom that
routes to it, and not being able to read a listing is a prerequisite, not a
symptom of an artifact.
2026-10-25 12:54:01 +08:00
root 68d128fe86 Add the CRT startup and main-discovery reference
references/crt-startup-and-main.md covers what runs before main: the entry
symbols the linker places, the startup order under VS2015 and later, the VC++ 6.0
prologue, __chkesp and SEH tells in 32-bit builds, the /GS cookie construction,
and the procedure for reaching main in a stripped image.

assembly-idioms.md gains two sections: structure layout and stride (pack
arithmetic, by-value copies, hidden return pointers, two-dimensional strides)
and dispatch shapes (comparison chain, address table, offset table, binary
tree), with the case set recovered from the bounds check rather than from
quoted thresholds.

Register the file in both symptom tables, the routing index, the coverage and
route-inventory tables and the evidence note. The startup shapes come from a
public tutorial corpus and are graded unverified; only the call into main,
observed in a run, would promote them.
2026-10-25 12:48:04 +08:00
root c6ef1860cd Add the CPU-vendor divergence reference
references/cpu-vendor-divergence.md records where the two x86-64 implementations
diverge: undefined results, vendor-string dispatch, feature-gated decoding,
system-instruction fault timing, and model-specific cost, each stated with what
it changes for reading a binary or writing a specification.

Register it in the routing index, both symptom tables and the route table; add
the coverage row (inferred, no second-vendor host), the route-inventory row
(argued) and the evidence note that every measurement here came from one
implementation.

Append P38 for an undefined result recorded as a defined one, and point
assembly-idioms and hypervisor-observation at the new file.
2026-10-24 18:49:46 +08:00
root 9fd8422d8f Fill the stub-authoring, import-mechanism and unwind gaps
injection-and-hooking.md gains where a stub can live, the jump encodings and the
64-bit distance problem, what the stub must preserve, the order the writes land in,
and how the bytes are produced and checked before use.

recon.md gains an import-cluster table: which API sets name which mechanism, and
which reference continues each one.

static-analysis.md reads the x64 exception directory as a function table, with the
three validity checks, the unwind record's contents, and the three limits. compiler-
fingerprints.md points at it.

dotnet-decompilation.md loses a product-name-with-version literal that the leak scan
reports as a strong finding once the file is tracked.
2026-10-24 15:35:36 +08:00
root 8e50676eba Extend the reference library with assembly-level and runtime reading
Add references/assembly-idioms.md: the x86/x64 conventions, frame and epilogue
fingerprints, string-instruction and x87 idioms, virtual dispatch, and WOW64 path
redirection, each stated with what it changes about reading or patching a routine.

Add the managed-decompilation, Unity IL2CPP and packaged-Python references, with
the capability registry, host-probe and plan entries they depend on.

Register all four in the routing index, symptom table, route inventory, coverage and
evidence records, and add P37 for a 32-bit target's registry and file views.
2026-10-24 15:30:09 +08:00
root 63b2aca8d8 Fix the budget gate and add CI, lint and coverage guards
- check_budget.py escalated a hard index-row violation to 'warn' on both
  branches, so INDEX_ROW_HARD could never fail anything. It fails now, and
  tests/unit/test_budget_measurement.py pins both thresholds.
- The corpus note counted scripts/__pycache__/*.pyc, so the same commit
  reported 27 scripts after the suite had run and 16 on a clean checkout.
- TOKENS_PER_CHAR is re-derived from a measurement (26,795 chars -> 6,065
  tokens = 4.418); the comment's old figure and its arithmetic were both
  wrong for this file.
- check_repo.py's yaml sentinel carries the type ignore, so mypy is clean
  with PyYAML installed as well as without it.
- The CPUID test now skips with the probe's own note on a host without
  CPUID instead of failing, and pytest.ini points at tests/README.md, which
  exists, instead of an EXTENSION file that never did.

Tooling: .flake8 (a measured 120-column limit), .gitattributes, .gitignore
and .github/workflows/ci.yml (the five gates, the suite on 3.11-3.14 and a
lint job), which is what CONTRIBUTING.md already claimed CI runs. The
pull-request template it refers to now exists.

Content: pitfalls P26-P34 -- the analysis-level failures the catalogue was
missing (sampling read as observation, a self-built parser trusted, a
verification that could not have failed, a shell-mangled command line) --
registered in the symptom index and its routing mirror. FINDINGS F11-F15
record the defects above.

Tests: the coverage guard derives its subject from scripts/ rather than a
hand-written list, pe_triage.py and pe_diff.py gain their first tests, and
plan.py / preflight.py are declared uncovered with reasons.
2026-10-24 14:55:02 +08:00
inliver233 863fe6b1bf Init 2026-10-24 01:50:24 +08:00