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
- A symptom index. Each row is a failure that has already been paid for. **A matching row is a
- Four gates (G1–G4), each an action with a pass criterion. "I understand the idea" does not clear
- A two-strike rule and stop conditions. Two failures of the same shape mean the model is wrong,
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
- Deciding what form the deliverable must take before any work starts — an
- Choosing the safest patch layer for a given change, and avoiding the layers that
- Catching the repack failure that looks like success: an app that installs, launches and
- Separating your own mistakes from the app's or the server's problems — a
- Establishing which architecture and which library are actually executing, rather
- Working through packed/hardened targets: identifying the packer, unpacking, and turning a
- Handling a hardened library that terminates the process on purpose — including the
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
- Making a patched build stay patched: neutralising version checks, forced-upgrade dialogs and
- Separating a client-side sign-in gate (patchable) from an account-scoped resource (empty
- Keeping a long task honest: a live record, graded conclusions, calibrated timeouts, and
- Avoiding the specific mistakes that produce an APK that builds perfectly and dies at
- Deciding what the deliverable should be when an APK is not an option — a repack refused by
- Telling a real memory dump from an extraction-shell skeleton, by measuring the trivial-body
frida itself is refused by the target.
- Calling a routine instead of reversing it when reversing costs more than invoking: emulated
- Reading instruction-level execution evidence when a native function has been flattened into a
- Recognising when userspace hooking cannot reach the check at all (raw
svcsyscalls,
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
- Working from the phone itself: MT Manager's edit/repack/sign flow and its APK MCP surface,
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
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
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
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/<pid>/mem captures)
dex_mem_scan.py search memory captures for embedded dex images and extract each at the
size its own header declares -- for a decrypted dex sitting in an
anonymous mapping no maps entry names
lsposed_scaffold.py generate a minimal LSPosed/Xposed module project (manifest with the
xposed meta-data, assets/xposed_init, hook class, gradle-free build notes)
frida_rpc_serve.py bridge a Frida script's rpc.exports to a local caller with reconnect
handling, so a live native function can be called rather than reversed
rpc_template.js the editable companion to frida_rpc_serve.py
stalker_trace.js instruction-level tracing with Frida Stalker: configurable targets,
trigger selection, the event stream, and output-size rules
stalker_report.py reduce a stalker_trace.js log to block histograms and call sequences,
with an explicit diagnostic for the measured zero-event case
mt_mcp_probe.py probe MT Manager's on-device APK MCP (Streamable HTTP, port 8787):
JSON-RPC handshake plus the grouped tool inventory
Install
This repository is a skills repository: the skill lives at skills/apk-reverse/, which is the
layout the skills CLI resolves, and it is installed by name rather than by copying a directory:
npx skills add newliver666/apk-reverse # install every skill in the repo
npx skills add newliver666/apk-reverse --list # list what is here, install nothing
npx skills add newliver666/apk-reverse --skill apk-reverse -y
npx skills use newliver666/apk-reverse@apk-reverse # use it once, without installing
The CLI symlinks the skill into your agent's skills directory by default (--copy makes independent
copies instead), and -g installs for every project rather than the current one. With one skill in
the repository, --skill apk-reverse is redundant today; it is written out here because it is what
selects a single skill once a second one exists.
Once installed, the agent loads SKILL.md when a task matches its description, and pulls in
references/* only as needed. No global state, no machine-specific paths, and no build step.
Requirements
Nothing is mandatory; each script checks what it needs. skills/apk-reverse/scripts/doctor.py reports
which of these are present here, which scripts can therefore run, and — usefully — which tools exist
somewhere other than PATH.
If your toolchain lives outside PATH (a project-local tools/ directory, a versioned SDK folder, a
runnable .jar instead of a command), set APKREV_TOOLS to one or more directories and doctor.py
will find them:
set APKREV_TOOLS=<dir>;<dir> # Windows, e.g. an SDK or project-local tools dir
export APKREV_TOOLS=<dir>:<dir> # POSIX
The scripts themselves are plain python3 and are intended to work identically on Windows, macOS and
Linux; where a snippet is POSIX-only it is labelled. Nothing here assumes a Unix shell.
| Tool | Used for |
|---|---|
| Python 3.9+ | all scripts |
| droidasc (ASC) (optional but strongly recommended — install this first) | whole-APK cross-reference index: findrefs / listclass / getclass / getmanifest. One pip install droidasc, no JVM, no SDK, no index build. Turns "which of N thousand classes mentions this string" into a sub-second query, and it is the only route to a class whose name R8 mangled. This is the tool an agent should reach for before any full decompile — see skills/apk-reverse/references/toolchain.md §droidasc (ASC) — ask an APK "who references this?", in one query |
| ddc (optional but strongly recommended) | single-binary dex→Java decompiler with query subcommands (info, findrefs, strings --with-locations, per-class decompile). No JVM. Reads what ASC locates; also reports package identity reliably — see skills/apk-reverse/references/toolchain.md §ddc — dex-to-Java with query subcommands (worth adopting) |
| baksmali / smali + dexlib2 jars | disassembly, assembly, surgical patching |
| JDK (javac, java) | building/running the dexlib2 patcher; also provides keytool/jarsigner |
| Android SDK build-tools (aapt, zipalign, apksigner) | manifest info, alignment, signing. apksigner is the signer to use — jarsigner rewrites the archive and breaks the alignment Android R+ requires |
| uber-apk-signer (optional) | one-step align + sign |
| ADB | device work |
| Frida (host package + matching on-device server) | dynamic analysis |
| a rooted device or emulator | anything beyond static analysis |
None of these need to be on PATH: every script accepts an explicit path for the
tools it shells out to, and skills/apk-reverse/references/toolchain.md covers finding
an install that PATH does not know about (the common case for apksigner and
keytool).
Read this first
skills/apk-reverse/references/pitfalls.md. It is the most valuable file here — every entry is a
failure that produced a broken artifact while looking completely healthy.
The four that hurt most:
- Stripping the whole
META-INF/during a repack deletes ServiceLoader registrations
- Patching a byte-level string without preserving
string_idsordering gets the whole
- Rebuilding a dex with a whole-tree smali round-trip damages R8 output invisibly —
- Neutralising a native terminate path by making it not return. A spinning stub does not
Scope
Built for working on your own applications, on samples you are authorized to analyze, and in CTF/competition sandboxes. It contains no vendored third-party binaries and no target-specific data.
What it covers, and what it deliberately does not, is stated at the top of SKILL.md
under Coverage. The short version: Android only (no iOS), and deep on the layers
that have been worked through for real — dex patching, repacking, packers and custom
loaders, native tamper response, and Flutter/Dart AOT. An extension pass added a second
tier of documented routes: module-side delivery when a repack is blocked,
extraction-shell recovery and its VMP boundary, emulation and live RPC for
calling rather than reading, instruction-level tracing against OLLVM, **protocol
reversing beyond REST, the kernel-side route** map for when userspace hooking is
provably out of reach, and on-device tooling. Unity/IL2CPP logic recovery,
React Native/Hermes bytecode internals, and defeating a server-side authority are not
covered, and the skill is written to say so and stop rather than apply the nearest
documented procedure to a target it was not written for.
Three qualifications that the Coverage section states in full and that belong here too:
- Flutter/Dart AOT analysis has a dependency. The workflow begins at a pool listing
pp.txt-class output). Producing that needs a snapshot-decoding decompiler — aotopsy (a static
binary, no toolchain) or blutter (built from source, ~80 s) — and this repository does not contain
one. It is named as a prerequisite rather than left implicit.
- Not every claim in this repository has a run behind it.
docs/tool-verification/records
- The extension pass is recorded separately and is mostly inferred. Its evidence lives in
docs/tool-verification/EXTENSION-*.md, one file per topic, with its own strength note. The
common shape there is the tool was measured, the route was not — so read those files before
treating any of the newer documents as a verified path.
Repository maintenance
Three tools live at the root and are not part of the installed skill:
check_repo.py every skill discovered, frontmatter valid, scripts runnable,
documented paths resolve, README paths explicit and existing
check_refs.py every cross-reference that names a section of another
document reaches a real heading in that document
build_scripts.py audit for machine-specific leftovers (absolute paths, credentials)
docs/tool-verification/ is not part of the installed skill either. It is the evidence record
for one measurement pass against a real target: what each script actually did, which independent
method confirmed it, which defects were found, and which scenarios the target could not exercise.
It exists so the Coverage claims in SKILL.md can be checked against runs instead of trusted,
and so the gaps are written down where the next person will find them.