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

4.8 KiB
Raw Blame History

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.