mirror of
https://codeberg.org/reverse-skill-lab/apk-reverse
synced 2026-11-15 20:54:31 +08:00
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.
71 lines
4.8 KiB
Markdown
71 lines
4.8 KiB
Markdown
# 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.
|