The previous pass renamed the record directory but left two concrete identifiers in
the transcripts: a package name plus device serial in TOOL-VERDICTS.md, and a
logcat-truncated tag in FINDINGS.md. Both are now <PKG> / <DEVICE>, matching the
convention the extension-pass records already use. No claim, command or conclusion
changes -- only the identifiers.
Closes the six coverage gaps named in an external review of this skill, and moves the
deliverable-form decision out of the single assumption it rested on.
New references (7):
- lsposed-and-modules.md deliver a hook module when a repack is refused
- advanced-unpacking.md extraction shells, FART-style recovery, the root-side dump
- emulation-and-rpc.md call the routine instead of reading it
- native-dbi-and-deobfuscation.md OLLVM, Stalker traces, two measured boundaries
- protocol-reverse.md protobuf/gRPC/QUIC and native-side pinning
- kernel-and-environment-hardening.md when userspace hooking cannot reach the check
- on-device-tooling.md MT Manager, its APK MCP, working from the phone
New scripts (8): dex_dump_validate.py (+ --trim for page-aligned captures),
dex_mem_scan.py, lsposed_scaffold.py, frida_rpc_serve.py + rpc_template.js,
stalker_trace.js + stalker_report.py, mt_mcp_probe.py
SKILL.md:
- G1 carries a four-form deliverable table (rebuilt APK / module / RPC service / report)
with the evidence that says when to leave the repack column, plus a matching hand-off
section on how "verified" moves with the form
- Coverage gains a documented-routes tier carrying its own strength note; Not-covered
loses the now-inaccurate "general unpacker" line and gains the limits that do apply
- 13 new symptom rows, 7 reference rows, 8 script rows, a fifth tooling obligation
- the pid-drift symptom is stated with its competing causes, not a single attribution
toolchain.md gains measured ASC/ddc cards; README gains the structure, capability and
scope entries for everything above; .gitignore stops tracking the local workspace.
Evidence: docs/tool-verification/EXTENSION-*.md (7 files), same three labels as the rest
of the record. Measured: the gradle-free module build chain (5.27 s end to end), a
successful injection confirmed through the LSPosed file channel (9 rounds) after three
logcat queries came back empty on a ROM with a broken logd, 17 dex images recovered by
root-side /proc/<pid>/mem dumping, the trivial-body metric separating a shell skeleton
from real bodies, ASC/ddc on a hardened APK, and Stalker's zero-event and crash
boundaries. The pid-drift attribution was corrected twice against better measurements.
The docstring promised "a smali tree or a directory of dex files" and delivered neither: iter_files() yielded only .smali, a direct .dex was read as UTF-8 text and regex-matched against smali syntax, and a dex-only directory yielded nothing at all. A real dex, a dex directory and a typo path all printed [total refs] 0 and a note blaming the needle, so the one tool the skill recommends for judging blast radius before patching returned a confident false zero.
A .dex is now decoded through dexutil (no baksmali needed), a directory is walked for .smali and .dex alike, an archive is opened for its classes*.dex, input that cannot be read is refused with exit 2 and a reason, and every run prints [scanned] N file(s) so the two cases can never print identically again. Also corrects the recorded exit code for this defect: it was 1, not 0, which moves it from "no signal at all" to "the signal was there and the message contradicted it".
Verified against a fixture whose call graph is known by construction (docs/tool-verification/fixtures/make_fixture_dex.py, committed as the evidence): the .dex and the equivalent smali tree both return 3/2/2, matching what was written. And against the real target: Ljava/lang/String;->length gives 356 refs in 2.3 s where it previously gave 0; a five-dex directory gives 9,472; a typo path exits 2.
Docs updated with it: F9 records the opcode-table finding the fix exposed, F2/F3 and TOOL-VERDICTS.md are corrected, and dex-patching.md now tells the reader to look at the [scanned] line before the count.
Capability-first tooling guidance, plus removal of target identity from the repo.
Toolchain (references/toolchain.md):
- New ASC (droidasc) section + README Requirements row. An agent should reach for an
indexer before any full decompile. Covers install, query subcommands, the division
of labour with ddc (ASC locates, ddc reads), and its measured limits -- including the
unverified-negative trap when a non-ASCII needle goes through a shell, and the
mangled-class case where ASC is the only route.
- ddc: two measured limits -- a renamed class is unreachable via -c, and a decompiled
body can be semantically wrong rather than merely ugly.
- New "Closing a capability gap": installing the missing tool IS the task, with measured
install costs set against the cost of the workaround.
- New "Using the kit's scripts instead of writing your own", with two-tools-disagreeing
as the bug detector it is.
- Ghidra section for the aarch64 case: headless invocation, the flattening caveat, and
the "a decompiler cannot go where the data is not" boundary.
- HaE placed in Tier 3 as traffic triage, naming the three situations where it is the
wrong next step.
Observation ordering:
- SKILL.md: new Tooling section with four obligations; G2 now requires a written
capability inventory; symptom index gains four rows (silent feature loss with no
request at all, telling your patch apart from the target's own behaviour, spawn keeps
the Activity stack down, the device server being killed by the ROM).
- dynamic-frida.md: the patch-then-detach ordering, and device-server survival notes.
- New scripts/spawn_patch_detach.py and scripts/hook_patch_only.js.
Neutrality:
- docs/verification-jiongnew/ -> docs/tool-verification/. Target-specific transcripts
dropped; all target identity removed. The record keeps the measurements and loses
the specimen.
- No target identity remains anywhere in the tree.
check_repo.py: 0 problems. check_refs.py: 182 reachable, 0 dangling, 0 warnings.