English · 简体中文
Capabilities · Structure · Install · Requirements · Failure catalogue · Scope · Maintenance · Disclaimer
# apk-reverse An Agent Skill for Android APK reverse engineering, debloating, ad removal, surgical dex patching, repacking, and runtime/server analysis. It is a **skill**, not a tutorial: it is written to be loaded by an agent (Claude Code, Codex, or any harness that supports the Agent Skills format) while it works, so it is organized for progressive disclosure — a short decision-oriented `SKILL.md`, detailed references loaded only when a step needs them, and parameterized scripts you can run directly. ## How an agent is expected to consume this `SKILL.md` is deliberately written as a **procedure with gates** rather than as advice, because the observed failure mode is not ignorance — it is a model reading the whole thing, agreeing with it, and then reasoning from first principles anyway. So there are four things in the body that are meant to be *acted on*, not read: - **Four override rules (R1–R4).** Where they conflict with the current plan, they win until evidence overrides them. - **A symptom index.** Each row is a failure that has already been paid for. **A matching row is a stop signal**: load that file before running another command, rather than after a few more attempts. Reasoning past a known symptom is how the same hours get spent twice. - **Four gates (G1–G4),** each an action with a pass criterion. "I understand the idea" does not clear a gate. They exist so that classification, environment truth and a control build happen *before* the first patch, not after the third failure. - **A two-strike rule and stop conditions.** Two failures of the same shape mean the model is wrong, not the parameters. The third variant of a hypothesis that already failed twice is where rounds go to die. And one thing at the end that is meant to be *withheld*: **"done"** has a definition (six items). A clean log is not one of them. Anything short of all six is a checkpoint, and should be reported as a checkpoint with what remains. If you are an agent reading this: the cheapest possible first command is `python skills/apk-reverse/scripts/doctor.py`. It tells you which of these tools exist here, which scripts can actually run, and whether something in the environment is already poisoning your measurements. ## What it is good at - Deciding **fast** whether a request is even achievable client-side, instead of burning hours on a paywall that is enforced by a server. - Deciding **what form the deliverable must take** before any work starts — an unrooted, self-contained artifact is a different problem from "make it work on this machine", and confusing the two is the most expensive drift in this domain. - Choosing the **safest patch layer** for a given change, and avoiding the layers that break the app. - Catching the repack failure that looks like success: an app that installs, launches and renders perfectly while **every signed request is rejected**, because the client derives its request-signing key from its own signing certificate. - Separating **your own mistakes from the app's or the server's problems** — a feature-scoped failure (login, registration, payment) is often a TLS/certificate issue on one code path, not a consequence of the patch you just built. Device state, a dead device server and clock drift masquerade the same way. - Establishing **which architecture and which library are actually executing**, rather than trusting what the manifest ships or what the device claims. - Working through **packed/hardened targets**: identifying the packer, unpacking, and turning a memory dump back into a patched, installable APK. - Handling a **hardened library that terminates the process on purpose** — including the deliberate-crash shape (`fault addr 0x4`) that looks exactly like an ordinary null-dereference bug, and the "neutralise it, but never by making it *not return*" rule that decides whether the fix works or freezes the whole app in a way that looks nothing like the cause. - Knowing **which tools to reach for and where each one lies** — including the ones that only exist as a GUI, so you ask for a human instead of silently substituting a weaker method. - Making a patched build **stay** patched: neutralising version checks, forced-upgrade dialogs and self-update installers so the work cannot be switched off remotely — and recognising the hot-update/remote-config channel that can quietly undo it without any version change. - Separating a **client-side sign-in gate** (patchable) from an **account-scoped resource** (empty because the server has nothing to answer with), and knowing that forging a session produces a state worse than being signed out. - Keeping a **long task honest**: a live record, graded conclusions, calibrated timeouts, and bounded waits, so progress is not lost and the same mistake is not made twice. - Avoiding the specific mistakes that produce an APK that builds perfectly and dies at runtime. - Deciding **what the deliverable should be when an APK is not an option** — a repack refused by several independent checks is *blocked*, not expensive, and the fallback ladder is a system-level module, a local RPC service, or an honest report with a stated boundary. - Telling a **real memory dump from an extraction-shell skeleton**, and knowing which recovery route applies — including the root-side dump for when `frida` itself is refused. What the measurement can and cannot see is in `skills/apk-reverse/references/advanced-unpacking.md`. - **Calling a routine instead of reversing it** when reversing costs more than invoking: emulated execution on the host, or a live function service-ified over Frida RPC. - Reading **instruction-level execution evidence** when a native function has been flattened into a state machine by OLLVM — including the two ways Stalker was measured to bite back on a real device. - Recognising when **userspace hooking cannot reach the check at all** (raw `svc` syscalls, `init_array`-early detection), what the next layer up and down can actually do, and when escalating is the wrong answer. - Working **protocols that are not REST** — protobuf without a schema, gRPC, QUIC/HTTP3 — and native-side certificate pinning that ignores the system trust store. - Working **from the phone itself**: MT Manager's edit/repack/sign flow and its APK MCP surface, LSPosed Manager, and on-device data inspection, alongside the PC toolchain rather than instead of it. - Telling **Java2C apart from an extraction shell** before spending hours hunting a decrypted DEX that does not exist at any point in the process lifetime — the code was compiled into a `.so`. - Handling a build that arrives as a **split APK / App Bundle set**: reading the set off a device, signing every member with one keystore for `pm install-multiple`, or merging code/native members into a standalone APK when that is legal. - Working a **real Dex VMP** with the known-plaintext differential — which links can be automated and which cannot, what a compiled fixture can and cannot reach, and how to *prove* a derived private-opcode table instead of asserting one. - **Publishing what it learns without publishing the target** — a scanner that reports identity shapes with their context, an explicit list of what must *not* be redacted (tools, libraries, protocol fields, CVEs, hardening products, public crackmes) because redacting those destroys the reusable part, and exit codes that gate a commit. - Reading a **precedent** before repeating work this repository already converged on: the positive half of the record, with the route including its dead ends, a grade on every assertion, and the files the case says to write back to. ## Structure `SKILL.md`, `references/` and `scripts/` are all inside the skill directory, `skills/apk-reverse/`. Everything at the repository root is maintenance tooling shared across skills, not part of an installed skill. ``` SKILL.md a procedure with gates, not background reading: how-to-use -> four override rules (R1-R4) symptom index (a matching row is a stop signal) four gates (G1-G4, actions with pass criteria) thirteen classification questions the workflow, with a per-step skip condition and a two-strike rule what "done" means -> stop conditions -> constraints -> indexes references/ loaded on demand, one topic each recon.md identify packer, SDKs, code location, tamper checks; unpacking server-config-and-updates.md the most common shape of "ad" and the one usually mis-diagnosed: the server supplies UI the client renders (launch screen, popup, announcement, tab set). The two-layer fetch that proves it, how to find the config DTOs by the field names data classes keep, why you patch the decision and not the data, deciding the scope of "remove", and remote re-enable / cached config durability byte-level-patching.md equal-length byte edits: why they beat method rebuilding (measured), locating an instruction's exact offset without scraping listings, the instruction width traps that desynchronise a decode, neutralise a branch vs redirect it, dex header integrity field order, and the verifier's move-result rule packers.md hardened targets: rejection signals, measuring the validation boundary with single-variable tests, choosing a native host code-virtualization-and-custom-linkers.md the layer between "packed" and "clean": whole classes turned into `native` declarations, a private loader whose SONAME does not match its filename, an embedded self-decrypting payload, a Java-layer "signature killer" that logs success while a native check kills you. The keep-it/drop-it deadlock, how to separate the *checker* from the *implementation*, and the string-redirect technique that ends it without neutralizing anything framework-runtimes.md Flutter / React Native / Unity: which layer owns the UI, and how to find logic when there are no symbols (string encoding traps) dart-aot.md Dart AOT in depth: version pinning and building a matching decompiler, the object pool and reference indexes, register/boolean conventions, the three signatures that identify business logic, locating, patching. Begins with the snapshot-decoding front end it depends on (aotopsy or blutter) because the pool listing is an input, not something this skill produces itself native-and-so.md .so hosts, DT_NEEDED vs JNI_OnLoad, relocation limits, relocation-free bootstrapping, replacing Java methods natively, and which ABI/library is *actually loaded and executing* native-tamper-and-suicide.md how a hardened library kills its own process: the visible mechanisms, how to tell which one actually fires, how to find the site, forged section headers, function boundaries from PT_GNU_EH_FRAME, scanner traps, and neutralising safely detection-and-anti-analysis.md when the app fights back or the tool cannot run here: telling detection apart from a broken environment, deciding by cost instead of escalating, recognising an environment where dynamic analysis simply does not work, and keeping the "blocks my analysis" question separate from "blocks the deliverable" toolchain.md what to install, how to invoke it non-interactively, which tools are GUI-only, version-alignment traps, working offline, **"not on PATH" is not "not installed"**, and which signer to use long-task-discipline.md live record, conclusion grading, drift control, timeout and wait calibration, deliverable-form drift, captures-you-never-looked-at, long-context decay, handover ad-removal.md ad taxonomy, wrapper mapping, callback trap, global gates, verification updates-and-forced-upgrade.md keeping a patched build alive: locating the version check, the two-layer patch (no-op the routine, neutralise the comparison), what not to touch (manifest version, installer permission, host blocking), self-update and hot-update/remote-config channels, verifying that no version request is issued at all account-gates.md sign-in walls, forced phone binding, guest mode: telling a client-side gate (patchable) apart from an account-scoped resource (not), why fabricating a session is worse than staying signed out, and the unavoidable session loss after a reinstall signature-derived-keys.md when the app's own signing certificate is used as key material: detection greps, why offline extraction is unreliable, the hardcode-then-verify procedure membership-and-limits.md server vs client authority; what is and is not patchable server-api.md probe an app's API; prove who owns the gate tls-and-cert.md feature-scoped network failures: expired certs, dual trust chains third-party-builds.md auditing a "cracked"/"modded" APK before trusting it dex-patching.md patch-layer table + dexlib2 technique in depth patch-audit.md proving a patch *landed* and is *legal*: length-vs-bytes comparison, the equal-length-replacement blind spot, verifier-level legality (move-result adjacency) checked statically, text-matching patch traps, and reporting a missing patch repack-and-sign.md repack rules, unpack-and-repack, signing, post-install hazards runtime-data.md DataStore / SharedPreferences / SQLite / protobuf; when the app rewrites your edit, and decoding a value that looks encrypted dynamic-frida.md Frida setup, version pinning, the four-layer probe, hook strategy environment.md device/emulator setup, root, ADB, offline devices, log signals, emulator console control and recovery, preflight, look-at-the-screen verification.md the claim ladder; what "done" means desensitization-and-leak-scans.md publishing discipline: what must be desensitized and what must stay, the do-not-anonymize list, the leak scanner and its exit states, and the entry-point file as a prompt surface precedents/ the positive case library: route including dead ends, a grade per assertion, measured pit-falls, and the write-back checklist routing.md the on-demand inventory: every reference with when to load it, every script with what it does, and a mirror of the symptom index rasc-and-droidsaw.md the Rust re-implementation of the ASC indexer: measured speedup and identical class sets, the enum shape where it silently drops bodies, and how to build and verify it evidence-summary.md the condensation that ships with the skill: capability, one-line conclusion, strength, and the evidence you can actually open in an installed copy ../evals/ NOT a spec directory either, but the location the Agent Skills guidance recommends: `evals.json` holds the with-skill / without-skill cases this skill has **not** run, with the method for running them written into the file ../evidence/ NOT a spec directory: the machine-readable companions to the evidence summary reference above -- `capability-matrix.json` (the same rows with more fields), `tested-tool-versions.json` (versions and the probe behind each), `known-limitations.md` (the installer-facing limit list). Shipped inside the skill so an installed copy can answer "was this verified, and how strongly" without the repository pitfalls.md the failure catalogue -- read before building advanced-unpacking.md the dump landed but the bodies are empty: extraction-shell diagnosis by trivial-body ratio, FART-style active invocation and why its classic hooks died on Android 12-16, code_item splicing, the root-side dump for when frida itself is refused, and the honest VMP boundary lsposed-and-modules.md the repack is refused, so deliver a system-level hook module instead: module anatomy, a gradle-free build chain, scope configuration and how to verify injection, and the layer a Java module cannot reach emulation-and-rpc.md call the routine instead of reading it: Unidbg/Unicorn emulation and its environment-filling cost, versus service-ifying a live function over Frida RPC native-dbi-and-deobfuscation.md OLLVM shapes, Frida-Stalker traces, the trace-to-CFG route, the Stalker/QBDI/emulation decision, and two measured boundaries (a follow that delivers no events, and a crash from following a hot libc export) protocol-reverse.md protobuf without a schema, schema recovery from decompiled code, gRPC frame capture, the QUIC/HTTP3 limit, and native-side certificate pinning kernel-and-environment-hardening.md userspace hooking provably cannot reach the check: raw `svc`, init_array-early detection, what each root scheme hides, the kernel-route map with its version gate, and when to stop escalating on-device-tooling.md working from the phone itself: MT Manager edit/repack/sign and its APK MCP, LSPosed Manager, Termux+frida, on-device data inspection java2c-and-jni-sinking.md Java2C and JNI sinking, the two hardening shapes most easily confused with an extraction shell: the table that separates landing shell / extraction shell / VMP / Java2C / JNI sinking, why the code is in the `.so` and *never* in a dumped dex, and why a `Java_*` symbol search comes back empty (dynamic registration, `-fvisibility=hidden`) split-apk.md App Bundle / split APK sets: what the set is, pulling it off a device, merging into one APK vs signing the set as a unit, the install refusals and what each means, and making an installable fixture from a pulled set vmp-differential-analysis.md the known-plaintext differential for a real Dex VMP: which links can be automated and which cannot (the upload is the bottleneck), the coverage a compiled fixture can reach, how to *prove* a derived private-opcode table, smali generation, and when the route is closed coverage-and-limits.md the claim ladder applied to the skill itself: the evidence behind each covered item, the dependencies this skill does not ship, and what was never exercised handoff-boundaries.md where this skill ends and another discipline begins: the JNI form table, the packer-versus-loader split, and what "verified" means for each of the four deliverable forms scripts/ parameterized, path-agnostic doctor.py run this first: capability report + per-script runnability, finds tools installed off-PATH or as runnable jars, and surfaces the environment facts that poison experiments (clock skew, leftover adb forward / proxy, a device-side frida process already running) dexutil.py dependency-free dex reader: structural walk + exact instruction decode, dex header recompute/verify (correct checksum/signature order), branch-target and operand helpers. Library shared by the dex scripts, also runs standalone to dump one method with offsets dex_find_insn.py locate an instruction by decoded semantics and print its exact byte offset with context and both sides of any branch -- how you find a patch site instead of guessing offsets dex_patch_bytes.py equal-length byte patches from a JSON spec: semantic match, polarity pin via expect_next, equal-length enforcement, verifier check, dex header recompute, re-decode to prove it landed (--dry-run first) dex_check_verifier.py tier-3 check: does any conditional branch target a move-result (bypassing its producer)? Compares two builds and separates pre-existing findings from regressions your patch introduced coldstart.py cold-launch capture: timed screenshot burst + logcat signals + installed-build facts + launch timing, and warns when the foreground activity is not your app so_constpatch.py same-length in-place rewrite of an isolated string constant, for redirecting a library load instead of defeating a check smtool.py baksmali/smali wrapper with a configurable classpath dexpatch/ dexlib2 method-level rewriter (for changes that need new instructions) patch_smali.py method-body replacement in a smali tree dex_strpatch.py byte-level string patch with a string_ids ordering guard dex_classdiff.py prove a dex edit was surgical dex_strings.py strings/URLs/SDK markers without a decompiler dart_pool_strings.py recover literals from a Dart AOT snapshot (framed entries, the one-byte vs UTF-16 split, file offsets, run-length noise filter) dart_pprefs.py build/query the object-pool -> code-site index for a Dart snapshot dart_disasm.py annotated windowed disassembly of Dart AOT code + B/BL caller index find_refs.py count callers of a method before patching it repack.py rebuild APK, strip only signatures, keep META-INF/services/, write a 4-byte-aligned archive (resources.arsc STORED+aligned), sign, verify; also split APK / App Bundle sets: inventory, sign every member with one keystore, or merge code/native members into a standalone APK devsh.py quoting-safe ADB shell helper usb_net_proxy.py give an offline device network over USB datastore_inject.py encode/inject AndroidX DataStore preferences safely probe_api.py probe an HTTP API with the right headers grab_crash.py recover stacks hidden by a crash-reporter SDK install_test.py install + launch health check with logcat signal scan frida_probe.js four-layer runtime probe (app net layer + OkHttp + java.net + exceptions) run_probe.py inject the probe, stream it to a log file, stay resident tls_check.py strict certificate check for one or more hosts preflight.py environment check before every experiment block (device, root, ABI/translation, clock skew, leftover proxy/forwards, dead server) lib_map.py what is *actually mapped* into a live process: per-library path, base, architecture, and whether it came from the APK or was materialized at runtime elf_plt.py resolve a PLT stub to its imported symbol (x86_64 + aarch64) from the relocation table; list a symbol's callers; byte-diff two libraries and name the symbol each changed stub belongs to apk_diff.py entry-level diff of two builds: changed / added / removed, by content hash so same-size replacements are caught native_crash.py locate a native death from a log or tombstone: signal, fault address, registers, frames split app vs system, the faulting instruction, and a flag when the fault looks *arranged* blob_decode.py search, don't guess, the framing of a stored value (base64/hex x rotation x deflate); re-encode the edited payload snap.py bounded burst screenshots + control-tree capture with a stall detector, and a verdict on whether the tree is usable at all sig_probe.py find the exact signatures[0].toCharsString() value — offline candidates from an APK, or the authoritative read from a device spawn_patch_detach.py spawn under a Frida probe, detach, then launch and capture: under spawn mode the Activity stack often never comes up, and memory writes survive detach while hooks do not hook_patch_only.js the minimal probe for spawn_patch_detach.py — neutralise one native death site by offset and report PATCHED dex_dump_validate.py dedupe, validate and rank a directory of dumped dex images: sha256 grouping, header integrity, the trivial-body ratio that separates a real dump from an extraction-shell skeleton, and a most-likely-original ranking (--trim for page-aligned /proc/