Files
apk-reverse/docs/tool-verification/README.md
T
dabao1955 cd1df50a2a Complete the xvmp IR virtualizer end to end and measure the de-virtualization chain on a real fixture
The public xvmp tree ships a compiler half that was never compiled (no TU
includes the virtualizer headers; on dev trees they do not build), a weak
empty vm_exec stub, silent skips for unsupported IR, and a companion article
describing an earlier prototype that is not in the tree. This change completes
and measures the chain:

- Completed toolchain, merged into all four omp branches of that tree: gated
  new-PM pass wiring (-mllvm -irobf-irvm, section .irvm), FORMAT v3 bytecode
  (8-byte aligned slots, explicit load/store widths, stable CC_ cast kinds,
  per-index GEP byte scales, branch condition = operand 0), a strong-symbol
  vm_exec interpreter with a 23-check unit suite, and a VMP.h user header.
  Virtualized vs native output identical at -O0/-O1/-O2 on all four
  branches. One branch's ungated IRVM call in Obfuscation::runOnModule
  (rewrote .irvm functions in plain native builds) removed; IRVM is
  strictly flag-gated.

- Fixture measured end to end (fco+bcfobf+subobf+IRVM build): native-identical
  output; 17,961-step guest trace via VMP_TRACE; 24.6M-instruction host trace
  via a ptrace single-stepper; vm_handler_slice yields 95,184 segments / 18
  unique signatures; the fla outer state machine separates as a shuttle class,
  round-boundary segments split the trajectory, positional alignment names
  every handler segment (59/59 in a round; counts match the guest histograms
  exactly), zero unmapped with --opcode-map.

- Tooling hardening measured on the real fixture: opcode maps key by
  signature (first-block keys collide under a shared dispatch successor);
  apply_map keeps first-block keys as fallback, regression tests added.

- Environment boundary recorded: frida stalker does not work in these
  containers (measured past the syscall layer: primitives pass, agent loads,
  zero denied syscalls; the failure is inside the injected agent's hook
  machinery), an outer ptrace tracer conflicts with frida spawn, and the
  ptrace stepper is the working host capture path. Design lesson: a pure
  virtualized call can still be LICM-hoisted by the compiling optimizer.

Records: EXTENSION-sampling-crash.md SS5-SS6 (final-state rewrite),
known-limitations.md, ete-diff-tracing.md SS5, vm_handler_slice.py header,
capability matrix.
2026-10-16 14:45:03 +08:00

111 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tool verification record
This directory is the **evidence record for the `apk-reverse` skill measured against real
targets**, kept separate from the skill itself so the Coverage statement in `SKILL.md` can be
checked against the runs behind it, and so a later reader can tell which claims were *observed*,
which were *inferred*, and which are still *unverified*.
It is **not** a set of target-specific notes kept for reuse, and it deliberately carries **no target
identity**. The measurements are about the **tools and the skill**; they stay valid whichever APK
produced them. Where a concrete figure was needed to make a point (a class count, a file size, a
build time) the figure is kept and the target is named generically.
Targets measured: Flutter AOT applications, multi-dex, mid-size native library sets, no packer.
The APKs themselves are **not** in this repository (`.gitignore` excludes `*.apk`, and `tools/`).
**Workbench paths are an operation record, not a pointer.** A record may say a run happened *at*
`tools/_work/...` so the command can be judged; `.gitignore` excludes `tools/`, so that path is not
something a reader can open. Anything a reader is *meant* to follow is named under
`docs/tool-verification/` or inside the skill — that rule is why a reference document never cites a
`tools/` path as its evidence, even when the work happened there.
## Strength labels
Every claim carries one of three labels, used the same way `references/verification.md` uses them:
- **observed** — reproduced here, with the exact command and its output.
- **inferred** — follows from observed facts, but the step itself was not executed.
- **unverified** — assumed or reported by a tool and not independently confirmed.
## Index
| File | Covers |
|---|---|
| `TOOL-VERDICTS.md` | One verdict per tested script and external toolchain, with the independent cross-check behind it |
| `FINDINGS.md` | Findings about the skill itself: defects, contradictions, boundary evidence |
| `REPO-DECISIONS.md` | What changed in the repo as a result, and what deliberately did not |
## Extension-pass record
The extension pass (the six coverage gaps named in the external review: module ecosystem, extraction
shells and VMP, emulation/RPC, native DBI, kernel-level environment, protocol layers) keeps its
evidence here, one file per topic, in the same three labels:
| File | Covers |
|---|---|
| `EXTENSION-device-run.md` | The on-device run against a hardened Flutter sample: the two control runs, the frida refusals, the **twice-corrected** pid-drift attribution, the failed-dump artefacts checked with `dex_dump_validate.py`, and a shared-device hazard measured with Stalker |
| `EXTENSION-unpacking.md` | Extraction-shell diagnosis by trivial-body ratio, `dex_dump_validate.py` against a fixture derived from the sample's own shell dex, and the tool's own defect found during the run |
| `EXTENSION-rootdump.md` | The root-side `/proc/<pid>/maps` + `mem` route that does not use frida: 17 real dex images recovered, the shell skeleton they are not, four device-shell traps, and the negative result on anonymous/ART-heap scanning |
| `EXTENSION-lsposed.md` | Module route: the gradle-free build chain with timings, three build traps, LSPosed config-DB anatomy, the module that could never have worked, and the root-hiding configuration the device actually has |
| `EXTENSION-emulation-rpc.md` | ASC/`ddc` measured on a hardened APK, Frida-RPC exercised end to end on a live device, and the unidbg build repair |
| `EXTENSION-native-dbi.md` | Stalker: the zero-event boundary, the crash from following a hot libc export, and an offline protobuf round-trip that caught a defect in its own decoder |
| `EXTENSION-kernel-ondevice.md` | The MT Manager APK MCP probe, the module-environment facts it could verify, and the kernel routes that this device's 4.14 kernel puts out of reach |
## Benchmark-pass record
The benchmark pass put **public targets** under the documented routes — `tests/benchmark.md` is the
matrix, and these files are the evidence behind its rows. Same three labels. Several of these rows
came back negative, and four of them overturned a conclusion this repository had already recorded;
those are called out in the matrix rather than buried here.
| File | Covers |
|---|---|
| `EXTENSION-benchmark-l1-l3.md` | The first two matrix rows: the L1 equal-length patch -> repack -> re-sign chain with its on-screen behaviour change and its zero-change control, and the L3 time-to-death baseline, the two spawn/patch/detach orderings that cannot work, and the terminate path that patching the obvious death site does not stop |
| `EXTENSION-extraction-shell-bench.md` | `dex_dump_validate.py` against an 11-variant skeleton set: the trivial-body ratio as a bimodal result rather than a threshold, the three script defects the variants exposed, and the ranking that pointed at a modified image until independent review caught it |
| `EXTENSION-stalker-exclude.md` | Stalker exclusion measured across three arms: the no-follow baseline, the follow that kills the process, and the follow with 20 modules excluded that survives but still delivers zero events |
| `EXTENSION-java2c.md` | Java2C against JNI sinking: native-declaration density roughly 2000x apart, why a `Java_*` symbol search fails silently, and the Dex-to-C build chain no toolchain on this host could complete |
| `EXTENSION-vmp-diff.md` | The known-plaintext differential: a 218-of-224-opcode coverage fixture, a closed-loop check that recovered 218/218 mappings with zero fabrications, and the one link that cannot be automated |
| `EXTENSION-kernel-weapons.md` | The generated KernelSU/APatch scaffold and its three kernel-side templates, all shipped unbuilt, plus the structural correction that a userspace module cannot change a syscall return value |
| `EXTENSION-split-apk.md` | Two real split sets through both branches: unified re-signing, the merge that is refused by design, the merge that succeeds, and the signer chain this host actually needs |
| `EXTENSION-reconstruction.md` | The reconstruction pass: the doctor capability closure (a JDK-only PATH used to report "can re-sign", now `BLOCKED` with a next action), the zip rebuild that stops dropping per-entry metadata (`zipalign -c -v 4` fails the old output and passes the new), and `check_commands.py` catching a documented flag that does not exist |
| `EXTENSION-test-harness.md` | The executable half of the evidence: what `tests/` asserts and what it measured, the `dex_patch_bytes` regression reproduced byte-for-byte, the 153 skips and 26 xfails that form the exit-code work order, the device-bound exclusion forced by an incident where the suite drove a real phone, and CI written but never run |
| `EXTENSION-rasc.md` | The Rust ASC measured against the Python one: identical class-definition sets on two archives, the speedup per scenario, the build (no prebuilt artifact exists, and a 32-bit MinGW cannot link it), and **the enum shape whose method bodies it drops with no warning** |
| `EXTENSION-protobuf-raw.md` | A schema-free decoder cross-checked against the official runtime and a real DataStore container, with the packed-boundary and proto3-zero ambiguities reproduced on real bytes |
## Absorption-pass record
A later pass absorbed mechanics from external skill repositories (`reverse-skill`,
`awesome-game-security`, `android-reverse-engineering-claude-skill`, `MobileRE-Skill`), keeping each
project's own discipline: only the phase, the checklist and the command pattern cross over, with the
source URL and the access date recorded, and nothing vendored as a runtime dependency. These files
live here rather than in the skill because two of them corrected a claim the repository had already
written down.
| File | Covers |
|---|---|
| `EXTENSION-desensitization.md` | The leak scanner's rules and exemptions, its four `RESULT=` states and exit codes, the escaping false positives, and **the pass that found its own evidence file leaking 26 strong hits** — the fixture's literal match values, quoted to prove the rules fire |
| `EXTENSION-dart-aot-formats.md` | Two string-table claims tested against a real dual-ABI Flutter app: the arm64 packed scheme confirmed against literal tag bytes, and the assumed armv7 `len*2`/UTF-16 form **refuted** — the 32-bit record is `[header u32][byte-count u32le][UTF-8]`, and the extractor's zero is a format mismatch rather than an empty table |
| `EXTENSION-detection-pipeline.md` | Naming the check that fires: three arms on a public MASTG target (control alive, probe arm dead in ≈300 ms via `strstr("frida")`, with the `TracerPid=0` versus 4-frida-mappings asymmetry), `svc_scan.py`'s two decoders agreeing on an identical 214-site set, the layered Java-to-svc descent, and the ptrace-free dump boundary on targets whose dex is deflated inside the APK |
| `EXTENSION-dexsql.md` | The SQL query layer adopted as a **third indexer** beside rasc/droidasc/ddc: identical class-definition sets on four archives (zero differences either way), the string xref agreement and the `=` versus `LIKE` match-semantics trap, `decode` against `ddc disasm` line for line, and the persistent cache measured as **not** a speed switch |
## Hardware-layer pass record
The hardware pass (hardware watchpoints, ETE differential tracing, kernel-side event injection)
follows the same shape: what runs on a host is measured, and everything that needs a device or a
kernel toolchain is an explicitly-labelled template whose gate says where it closes.
| File | Covers |
|---|---|
| `EXTENSION-hw-watchpoints.md` | The ptrace hardware-watchpoint route: encoders accepted and refused exactly as the alignment rules demand, and the emitted on-device helper **compiled clean as a static aarch64 ELF** — while no watchpoint was armed on any device, and the ledger that says the ptrace route leaves `TracerPid` visible |
| `EXTENSION-ete-diff.md` | The differential half measured on synthetic flattened-dispatcher fixtures (first divergence, unique-per-side sets), the gate/capture plumbing measured to the no-device boundary, and what was deliberately not done: no perf-with-CORESIGHT build, no cs_etm stream, no overhead claim |
| `EXTENSION-kernel-instrument.md` | The kernel-side scaffold's generate/verify round trip, the spec-refusal rules shared with `hw_watch.py`, the capability map whose `no` cells do the gatekeeping, and the three-time-fenced experimental register-rewrite action |
| `EXTENSION-sampling-crash.md` | The second hardware pass: `trace_addr2sym.py` verified against a real cross-compiled `.so` (readelf-cross-checked), the interpreter-trace slicer measured on the calibrated xvmp shape (including the 4x-margin bug it found in itself), and the flattening-recipe corrections read from the two public obfuscator implementations the user supplied |
| `EXTENSION-absorption-kx.md` | The absorption pass over four public write-ups: the native-VM-recovery procedure (every step `inferred`), pitfalls P43–P46, the slicer's nested-loop diagnostic (with the latent `len(tuple)` bug this pass's review caught), and what deliberately did not cross over |
## Adding a run
Record the tools, versions and exact commands, and label each claim. Keep target-specific facts out:
what belongs here is what a future reader can apply to a *different* APK — a defect, a measured
cost, a method that did or did not work, and the cross-check that settled it.