mirror of
https://github.com/newliver666/apk-reverse.git
synced 2026-09-28 21:56:59 +02:00
2beade67f20ae4e76de670a2a31287ab13c076bf
10 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2beade67f2 |
Reconstruction pass: make the gates tell the truth, make the package self-contained, keep the one-hop symptom index
An external audit of this repository landed on four structural findings, and this pass is the
response to them. Everything below was re-measured on this tree before being acted on; two of the
audit's claims needed a correction of my own, and both corrections are noted in place.
**The gates were optimistic, and one of them did not read content at all.**
doctor.py the capability table was hard-coded (`repack + sign = bool(java) and python`), so a
machine with a JDK and no zipalign/apksigner was told it could re-sign; the script
inventory was a hand-written list of 28 against 51 files on disk, omitting the nine
most important scripts. It now scans the directory (`registered/checked = 52 / 52`,
unjudgeable entries counted as `unknown`, never skipped) and resolves capability as a
real requires-closure, reporting BLOCKED with the missing piece and an installable
next step. Measured: the old doctor reports `[OK] repack + sign` on a JDK-only PATH
where keytool, jarsigner, zipalign, apksigner and all eight smali jars are absent.
The report is allowed to look bad; that is the point.
check_commands.py (new)
anchor checks prove a section exists, never that a command runs. This parses each
script's argparse table with `ast` and checks every documented invocation against it.
146 commands / 41 interfaces, 0 drift. It caught `sig_probe.py --live --pkg`, which
does not exist -- the reference had been wrong since it was written.
check_routing.py (new)
the symptom index now has one home (`SKILL.md`, because symptom -> file must stay one
hop) and the inventory has another (`references/routing.md`). This gate proves the
mirror agrees, that every reference file is named, and that every script is named.
**so_constpatch.py was corrupting APKs it rebuilt.** The writer copied filename and timestamp onto a
fresh ZipInfo and recompressed the whole archive: 435-entry real package, `extra` lost on 205 entries,
`external_attr` rewritten on all 435, 132 untouched entries recompressed, and `resources.arsc` pushed
to an unaligned offset. Android's own `zipalign -c -v 4` fails the old output and passes the new one;
a 26-case fixture suite pins it, and `extractNativeLibs="false"` is now refused by default with a
pointer at `repack.py`. Fixing it exposed the same defect in `repack.py`, where `^lib/[^/]+\.so$`
cannot match `lib/arm64-v8a/*.so` -- so its alignment handling had never once fired on a real APK.
**The package was not self-contained.** 59 references pointed at the repository root, which `npx
skills add` does not install. They now converge on `references/evidence-summary.md` (capability,
conclusion, strength, evidence that ships) plus `evidence/` with the machine-readable companions: 8
references remain and each is explicitly labelled "repository root, not shipped". A drill in which
the new files were copied to an isolated directory and asked three questions answered all three.
**SKILL.md: 572 lines / 15,733 tokens -> 398 lines / ~10,900 tokens**, without losing the two things
that had to stay. The symptom index remains inline; the reference and script indexes moved to the
on-demand routing file. The freed budget went into making the rules *stronger*, not shorter: the
constraints section is now a table of thirteen non-negotiable constraints each paired with the check
that catches a violation, and the tooling obligations are instructions whose violation is a defect
(hedging language in SKILL.md: 1 line, down from a paragraph of hedged advice). Long references gained
a navigation head so a reader can decide from the top whether to load one.
**New: an executable test layer.** `tests/` (unit, CLI contract, no-device integration) plus
`pytest.ini`, `tests/run_tests.py`, a minimal CI job, and `evals/evals.json` written but never run --
stated as such in the file. 608 passed / 158 skipped / 25 xfailed. The 25 xfails are a work order, not
a pass: thirteen scripts report a missing input by exiting 1 instead of 2, seven let a traceback
escape, four treat `--help` as an error, and `preflight --json` lacks a `status` field. A regression
row for `dex_patch_bytes.py` reproduces a previous artifact byte-for-byte
(`67ae1e58e7a44777e5ac8f9f823ab21e…`) and additionally pins the four bytes that may move.
**Two real defects the new tests found in the tooling itself.** `check_repo.py` decoded subprocess
output with the locale encoding, so on a GBK console a script printing an em dash in its usage line
was reported as crashed; it now decodes UTF-8 explicitly. And a CLI contract test drove a *real
attached device* through `coldstart.py` -- logcat, screenshots, five orphan `adb logcat` processes --
which is fixed twice over: child processes run in a private temporary cwd, and the fifteen
device-bound scripts are never executed beyond `--help`/argument parsing.
**Quoting on the device side is now one implementation.** `devsh.su` escaped only double quotes, so
`;`, `$( )` and backticks in a caller-supplied value still reached the device shell while
`coldstart.Device.shell` quoted correctly. Both use `scripts/device_shell.py` now: POSIX single-quote
escaping, argv arrays by default, and identifier validation that refuses a malformed package, path or
component with exit 2 instead of a traceback. The integration test that used to pin the *defect* (it
asserted `su -c "…"` with `\"` escapes) now asserts the property, across five metacharacter payloads.
Gates: check_repo 0 problems (including the content-reading identity scan over git ls-files),
check_refs 0 dangling / 0 warning, check_routing coherent (53 symptom / 45 reference / 54 script rows),
check_budget 0 failure / 0 warning with the always-loaded body now measured in tokens rather than in
"narrative lines with the index excluded", check_commands 146 checked / 0 drift, pytest 608 passed.
Not done, and stated rather than implied: CI has never run on a runner; the evals have never been
executed; the 25 xfails are open; no device-side install/launch was performed in this pass.
|
||
|
|
6cffc3d2ac |
Absorption pass: take the mechanics from four external skill repositories, keep our discipline
Four projects were cloned for comparison (reverse-skill, awesome-game-security,
android-reverse-engineering-claude-skill, MobileRE-Skill) and read for what they do that this
repository does not. None is vendored and none becomes a runtime dependency: what crosses over is a
phase order, a checklist or a command pattern, with the source URL and the retrieval date recorded in
the file that absorbed it.
The rule applied to every candidate: does this make a judgement easier or a verification harder? The
repository is a decision-and-verification layer, and the four things kept are all of that kind.
new scripts/scan_leaks.py target identity scanned before publishing, with an explicit
do-not-anonymize list and four RESULT=/exit states
new references/precedents/ the positive half of the record: route including dead ends, a
grade per assertion, measured pits, and a write-back checklist
new scripts/svc_scan.py does a libc-level hook structurally see this call at all
new scripts/anti_detect_probe.js observer-only: name the check, and report the environment it reads
new references/desensitization-and-leak-scans.md
new references/dart-aot.md sections the 32-bit snapshot's real string-record format
check_repo.py now delegates the identity scan to scripts/scan_leaks.py and runs it over
git ls-files, so the gate reads content instead of layout for the first time. An explicit file list
cannot be narrowed by the default work-area exemptions -- that defect was caught by the gate itself.
Two claims that this repository had already written down turned out to be wrong, and both are
recorded as refuted rather than quietly rewritten:
dart-aot.md the armv7 string table was documented as [len*2 u32le][UTF-16LE]. It is not. The same
extractor keeps 4,980 chained entries on arm64 and zero on armv7, and the byte layer
shows the 32-bit record is [header u32][byte-count u32le][UTF-8 payload]: two adjacent
literals carry 0x46 (70) and 0x48 (72), exactly their byte lengths. The zero was a
format mismatch, not an empty table. The engine-version banner on the same target is
66 bytes of "0.0.1" with a blank build id, so the documented regex returns a
version-shaped lie.
leak scan the pass that added the scanner found 26 strong hits inside its own evidence file on
the day it was written -- the fixture's literal match values, quoted to prove the
rules fire. The corpus may contain values; the published surface may not.
Measured, not asserted. The check that fires was named on a public MASTG target: three arms with one
variable, the control alive at the end and the probe arm dead in about 300 ms, with strstr("frida") at
t=303 ms and no tombstone. Reproducibility is bounded honestly -- one full sequence in three arms, on
a target whose whole window is sub-second. svc_scan.py's two independent decoders agree on an
identical 214-site set, and ptrace-free dumping gained a negative boundary: a target whose dex is
deflated inside its APK has no whole-image dex in memory to export, for reasons that say nothing about
protection.
Also in this pass: the detector-locating pipeline (detection-and-anti-analysis.md Step 3) with its
branch conditions; the layered Java-to-svc descent and the dump's honesty boundary in
advanced-unpacking.md; a sourced capability-gap list in toolchain.md where every row is graded
URL-verified or measured; the RESULT=-token convention; and SKILL.md opening with an immediate-action
block, because the symptom index and the gates were sitting in the part of a long file nobody reads.
Gates: check_repo.py 0 problem, check_refs.py 0 dangling / 0 warning, check_budget.py 0 failure /
0 warning / 1 pre-existing note. The scan reports RESULT=leaks_found_strong_only, exit 0: four
endpoint/weak hits, all of them addresses from the RFC 5737 documentation range in usage examples.
|
||
|
|
fa2f62107f |
docs: stop citing workbench paths that are not shipped
Eight citations in the reference documents pointed at tools/ paths, and .gitignore excludes tools/ -- so neither an installed skill nor a clone of this repository can open them. Each is now either an inline statement of the fact or a pointer to the evidence file under docs/tool-verification/ that actually holds it. One of the eight was introduced by the benchmark pass itself (protocol-reverse.md cited a workbench script). The evidence records keep quoting tools/_work/... deliberately: there they are operation records of where a run happened. docs/tool-verification/README.md now states that distinction and the rule that follows from it -- a reference document never cites a tools/ path as its evidence, even when the work happened there. |
||
|
|
6221a072ca |
Benchmark pass: measure the documented routes, close five tool gaps, correct four claims
Public targets under the existing routes, recorded per row in tests/benchmark.md with each row's own strength label: the MASTG crackmes (equal-length patch -> repack -> re-sign with a zero-change control, and the anti-debug race with its time-to-death baseline), DexPatcher's ezAndroid sample, two real split APK sets, and a self-built extraction-skeleton set. Hardening-shape discrimination now separates Java2C from an extraction shell, a VMP and JNI sinking. New: java2c_probe.py and java2c-and-jni-sinking.md; split-apk.md with repack.py growing set inventory, unified re-signing and merge; protobuf_decode_raw.py cross-checked against the official runtime; vmp_diff_harness.py and its cost table; kernelsu_syscall_mask.py with three kernel-side templates; stalker_trace.js gains Stalker.exclude() and a zero-event warning. Corrections, each because a measurement contradicted a recorded conclusion: the trivial-body ratio is bimodal rather than a threshold and the old threshold claim was deleted; a Dex-VMP verdict was withdrawn after dexdump contradicted a hand-written opcode table; the assumption that Stalker.exclude() fixes zero-event delivery is disproved (it keeps the process alive, nothing more); and a repack.py signing-path claim expired mid-pass when a second writer added one. Emulated and kernel-side routes are labelled unverified where no toolchain or device could run them; each evidence file states what was not run. |
||
|
|
bea9955eb6 |
docs: normalize the last target identifiers left in the verification record
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. |
||
|
|
ce2e4f98e1 |
Extension pass: module route, extraction shells, emulation/RPC, DBI, kernel layer, protocols
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. |
||
|
|
5fa80a5bea |
find_refs: read .dex directly, and stop reporting unreadable input as zero refs
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. |
||
|
|
206a2f5465 |
Toolchain guidance, observation ordering, and target-neutral records
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. |
||
|
|
90f7a58615 |
Record the selective-install measurement and the external checks
The brief asked for the shared-tooling question to be settled by the install product rather than by preference. Measured with --skill apk-reverse -y --copy in an empty directory: the skill is copied whole and self-contained into each detected agent directory (six here), every file under <agent>/skills/apk-reverse/ -- SKILL.md, all 28 references, all 34 scripts including dexpatch/. Nothing outside the skill directory is installed, and there is no cross-skill sharing mechanism. So a root-level shared/ can never work: it is not part of the product. With one skill there is nothing to share, and had the skill been split the halves would have needed duplicated scripts or a path that does not survive installation. VERIFY-OUTPUT.md gains a section recording which premises were re-checked against primary sources and which did not hold: the blutter build budget and compiler floor, aotopsy as the no-toolchain route, the idalib-mcp shift away from the GUI plugin, and that "GNU readelf is on PATH" is false here (it is pyelftools' readelf.py, and MinGW objdump/nm reject aarch64 outright). Also records one intermittent check_repo.py exit 1 that printed "0 problem(s)" and did not reproduce in five later runs, with the line numbers of the encoding path that produces it. |
||
|
|
e193f425ba |
Measure the toolkit on a real Flutter target and correct what it claimed
Ran the scripts against a real Flutter AOT / multi-dex / 36-.so APK (Dart 3.6.0, com.tingfeng.tool) and recorded what each one actually did. Two of them were wrong in the direction that reads as a target finding, one Coverage claim overstated what ships, and four documentation statements were false. Fixed: dart_pprefs.py decoded only the general-purpose register file, so `ldr dN,[x27,#imm]` (0xFD400000) was never matched and every double constant read from the object pool was dropped -- 302 pool offsets and 1802 sites on this sample, one of them with 150 references that would have been reported as zero. The documented advice is to count references before editing a shared constant, so a one-sided quiet gap here produces exactly the wrong decision. Now covers both register files; a purpose-written arbiter agrees with it to 0/0 across .text, against 0/302 before. Documented but deliberately not fixed, because a fix would not have been run: elf_plt.py misses the last PLT stub slot (`off < fsz - 16`), which on libloader.so turned a real, called import (dl_iterate_phdr) into "not a resolved stub". find_refs.py advertises dex-directory input but iter_files() accepts only .smali, so it answers "no references" with exit 0 for input it never read. so_constpatch.py's rebuild drops the zip `extra` field and destroys 4 KB alignment. Each has its root cause and fix sketch recorded. Documentation corrections in dart-aot.md: blutter's asm/ does contain full instruction listings with pool annotations -- the file said to ignore it, and it is the richest artifact in the chain. The first build costs ~78 s, not "tens of minutes"; MSVC 19.34 suffices, not VS 2022. The file-PP offset mapping is not recoverable from the binary (4241 shared strings, 4237 distinct deltas), so a snapshot-decoding front end is a dependency, now stated. Documented aotopsy as the no-toolchain route, and the trap that its version label is a structural profile name (it says 3.6.2 for a 3.6.0 snapshot). Coverage now separates what was measured from what is inferred, names blutter/aotopsy as a prerequisite instead of implying the pool decodes itself, lists the scripts this pass did not run, and says plainly that the packer / VMP / integrity-check scenarios were not exercised at all. Added hand-off points for JNI, dex-observed hardening, and the existing native boundary documents, plus docs/verification-jiongnew/ -- the evidence record, including the four gate outputs verbatim. No second skill: the gap is one front-end dependency, not a subject area, and relocating Flutter docs would not have supplied it. |