Files
apk-reverse/docs/tool-verification/EXTENSION-absorption-kx.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

71 lines
4.8 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.
# Extension verification — absorption pass: four public write-ups (Kanxue, 2026)
Covers the absorption of four public reverse-engineering write-ups the user supplied
(2026-08/09 postings; titles, authors and every sample identity anonymized in this record —
the write-ups themselves were already anonymized by their authors). Following the
absorption-pass convention (`EXTENSION-desensitization.md`): **only phase, checklist and
command patterns cross over**, nothing target-shaped, and each absorbed claim carries its own
strength label.
**Headline: two write-ups produced a new reference (`references/native-vm-recovery.md`, every
step `inferred`), one produced four pitfalls (P43–P46) and one tool fix; one write-up's
dispatcher-margin lesson was already absorbed in the previous pass. The sources are public
postings; no sample, tool output or target fact from them entered the repository.**
---
## 1. What was read
| Source (anonymized) | Shape | Absorbed into |
|---|---|---|
| W1: full arm64 custom-VM recovery (long-form) | 0x00–0x1A method narrative | `references/native-vm-recovery.md` (existence signals, dispatcher separation, partial deflattening, relocation tables, PC proofs, descriptor/ISA cautions, clean-re-VM loop) |
| W2: VMP interpreter recovery with trace-driven IR lifting | anchor/trace/lifting narrative | `references/native-vm-recovery.md` (register anchoring + triple verification, nested segment stacks, STORE-backward slicing, byte provenance, IR shape), `vm_handler_slice.py` nested-loop diagnostic |
| W3: login-capture war story (packed + MADP/Weex + gomobile) | route-by-route复盘 | `references/pitfalls.md` P43–P46; the stack-ABI note in `references/hardware-watchpoints.md` |
| W4: manual OLLVM deflattening | predicate algebra + IDA scripting | cross-checked against existing content; its MOV/MOVK constant-propagation state mapping is referenced as the static counterpart in `native-vm-recovery.md` §11 — no new file |
## 2. The one code change, and why — observed
`vm_handler_slice.py` gained `nested_loop_segments()`: a block repeating 4+ times **inside one
segment** flags a nested dispatch loop. Reasoning absorbed from W2: handler chains repeat
across segments, never within one — a within-segment repeat is a second dispatcher running
under the first, and W2 measured 45-of-46 entries nested with 57–1997 instructions lost per
nesting under a flat accumulator. Host-tested on a synthetic nested fixture:
```
$ python vm_handler_slice.py --log nested.log --dispatcher libvm.so+0x1000
note: segments 2 contain a block repeating 4+ times inside one segment: a nested dispatch
loop ... slice per level; the segment-stack fix is documented in
references/native-vm-recovery.md
```
The same edit fixed a latent bug this pass's review found: `diagnostics()` had been measuring
`len()` of `(run, closed)` tuples (always 2) since the open-tail change, quietly disabling the
spin-loop note on the real code path. Both regressions are now pinned by tests.
## 3. The judgement calls — what did NOT cross over
- W1/W2's Ghidra/SLEIGH-specific mechanics (p-code slicing internals, seed definitions bound to
their toolchain): only the **method** crossed (store-backward, provenance-over-value); the
repository ships no Ghidra script and none is implied.
- W3's working exploit details (hook source, injection directory, SELinux labels of the
delivery mechanism): the delivery route already has its own file
(`references/lsposed-and-modules.md`, `kernel-event-injection.md`); what crossed is the
**layer-identification discipline** (each dead end names the next layer; prefer the highest
plaintext boundary; a RegisterNative inventory is one run for a full method table).
- W4's full IDA script: the technique is named (state-register MOV/MOVK constant propagation,
CMP-site collection, predecessor walk past blocks with no CMP on the state register) and
pointed at from `native-vm-recovery.md` §11; writing an IDA-independent implementation is a
future pass, not an absorbed claim.
- Every sample identity in all four sources: anonymized by the authors, kept anonymized here;
`.ref/` (the local copies) is untracked and git-ignored in effect (never added).
## 4. Strength accounting
- `native-vm-recovery.md`: every method step `inferred` (absorbed, not executed here) — stated
in the file's own header, mirrored in `evidence-summary.md` and the capability matrix.
- `vm_handler_slice.py` nested diagnostic: `observed` on the host fixture (this pass).
- Pitfalls P43–P46: absorbed as `inferred` lessons with their mechanism stated; each names the
check that would have caught it, per this repository's pitfall format.
- No claim from the write-ups was upgraded to `observed` by absorption. That is the point of
the convention: a method someone else paid for is a lead, not a measurement.