Add deliverable-form discipline, execution identity, and four reusable scripts
Generalized from a long hardened-target engagement. Additions target the failure
modes that cost the most time and are not yet covered; nothing here is sample-specific.
New discipline
- SKILL.md: a third override rule -- deliver in the environment the request requires.
A root-only or live-instrumentation result can look like success while failing the
actual requirement (unrooted, installable, self-contained). Also adds two classify
questions: what form the deliverable must take, and which architecture/library is
actually executing.
- long-task-discipline.md: "the most expensive drift" -- a constraint table (privilege,
modification form, ABI, network, persistence, distribution), the checkpoint question,
and how to report a privileged workaround as a fallback rather than a deliverable.
Plus "bound every wait": timeouts on every command, budgets calibrated from
measurement so slow and hung are distinguishable, and sampling an observable you can
inspect instead of sleeping blind.
- environment.md: look at the screen instead of driving blind; which of the two screen
evidence kinds is trustworthy (an empty accessibility tree is common for
runtime-rendered UI, where the image is primary rather than a fallback); emulator
console control and adb recovery; preflight before every experiment block.
- native-and-so.md: what the device claims vs what the package manager chose vs what is
actually mapped; translation layers and their consequences for hooks and native
checks; fat-APK ABI selection.
- runtime-data.md: when the app rewrites your edit (server refresh vs local
regeneration), why chmod/chown cannot work because the app deletes and recreates the
file, and the immutable-attribute fix; decoding a value that only looks encrypted.
- framework-runtimes.md: string encoding traps (a UTF-8 scan of a UTF-16 snapshot
returns nothing and reads as "the strings were stripped"), and non-ASCII obfuscated
identifiers -- resolve by shape, not by name.
- pitfalls.md: P23 the fix lives in a file the app rewrites; P24 the artifact changed
but the wrong one is executing; P25 "search found nothing" treated as "not there".
New scripts (parameterized, path-agnostic, all with explicit timeouts)
- preflight.py environment check: device, root, ABI/translation, clock skew, leftover
proxy/forwards, dead device server, chosen ABI, free space
- lib_map.py what is actually mapped into a live process: path, base, architecture
from the ELF header, and whether it came from the APK or was
materialized at runtime
- blob_decode.py search (don't guess) the framing of a stored value, then re-encode the
edited payload with the same parameters
- snap.py bounded burst screenshots + control-tree capture, with a stall detector
and an explicit verdict on whether the tree is usable
All four were exercised against a live device while writing: preflight correctly
flags multi-device ambiguity and detected the ARM translation layer; lib_map read real
ELF headers and identified the translation shim; blob_decode round-tripped a
rotation+deflate+base64 blob at realistic size in ~0.2s; snap detected a stalled screen
across three hashes.
This commit is contained in:
@@ -7,10 +7,11 @@ description: Reverse engineer, debloat, de-ad, patch, or re-sign Android APKs, a
|
||||
|
||||
Goal of this skill: get to a **verified, installable, still-working artifact** fast, and avoid the whole class of mistakes that silently destroy an APK.
|
||||
|
||||
Two rules override everything else in this skill:
|
||||
Three rules override everything else in this skill:
|
||||
|
||||
1. **Never ship an unverified APK.** "It assembles" is not "it works". Install it, launch it, exercise the feature you changed, on a real device or a close emulator.
|
||||
2. **Know which layer owns the behavior before you patch.** Ads, paywalls, and feature gates live in different places. Patching the wrong layer either does nothing or breaks the app. Classify first (see step 2 below), patch second.
|
||||
1. **Deliver it in the environment the request actually requires.** "It works" is not the goal; "it works under the stated constraint" is. A result obtained with root, or with live instrumentation, or with a host proxy, frequently does **not** satisfy a request for an installable artifact that works on a normal device — and it is easy to present that result as a finished one. Write the constraint down as a testable sentence in the first minutes and re-read it at every checkpoint. If you cannot meet it, say so and label any privileged workaround as a fallback, never as the deliverable. Full treatment: `references/long-task-discipline.md` §the most expensive drift.
|
||||
2. **Never ship an unverified APK.** "It assembles" is not "it works". Install it, launch it, exercise the feature you changed, on a real device or a close emulator.
|
||||
3. **Know which layer owns the behavior before you patch.** Ads, paywalls, and feature gates live in different places. Patching the wrong layer either does nothing or breaks the app. Classify first (see step 2 below), patch second.
|
||||
|
||||
## Start here: classify the target in seven questions
|
||||
|
||||
@@ -27,13 +28,23 @@ Answer these before touching a tool. Every one of them changes the whole plan.
|
||||
3. **Is the app's own code in plain dex, or moved to native/Flutter/Unity?**
|
||||
Plain dex → you can patch. Flutter (`libflutter.so` + `libapp.so`) / Unity (`libil2cpp.so`) / pure native → different toolchain entirely. See `references/recon.md` §code-location and `references/framework-runtimes.md`.
|
||||
**Runtime check (cheap -- do it before committing to a layer):** hook the obvious Java classes for the UI you care about, then reproduce that UI. If those hooks fire, the behavior is Java-owned. If they fire **zero times** while the UI is plainly on screen, the behavior is drawn by the runtime or by native code, and a dex-only plan will stall. Do not keep hunting in dex after a zero-hit probe -- that is the most expensive wrong turn in this skill's history.
|
||||
4. **Does the app verify its own signature, or does the server?**
|
||||
4. **What must the deliverable be able to do?** Write the answer as a testable sentence before
|
||||
planning anything, then re-read it at every checkpoint. This is the drift guard, and the drift it
|
||||
guards against is the most expensive one in this skill: a runtime-only result (a data edit, a live
|
||||
hook, a blocked hostname, a host proxy) can look like success while failing the actual requirement.
|
||||
The axes that decide it: **privilege** (unrooted?), **modification form** (a rebuilt, installable
|
||||
artifact, or is live instrumentation acceptable?), **ABI/device class**, **network** (must it work
|
||||
online?), **persistence** (survives restart / upgrade / fresh install?), **distribution** (must the
|
||||
shipped file be self-contained?). → `references/long-task-discipline.md` §the most expensive drift.
|
||||
5. **Does the app verify its own signature, or does the server?**
|
||||
App-side → you must bypass it. Server-side → re-signing silently breaks the app later. See `references/repack-and-sign.md` and `references/server-api.md`.
|
||||
5. **What is your device situation?** → `references/environment.md`
|
||||
Rooted real device (best), emulator with root, or no device (static only). Also: this determines whether Frida is usable.
|
||||
6. **Is one *specific feature* failing at runtime — login, registration, payment, an API-backed screen — while the rest of the app works?**
|
||||
6. **What is your device situation?** → `references/environment.md`
|
||||
Rooted real device (best), emulator with root, or no device (static only). Also: this determines whether Frida is usable. **Run `scripts/preflight.py` before your first experiment**, and again whenever a failure surprises you — device state, a dead device server, a leftover proxy, and clock drift all masquerade as a broken patch (`pitfalls.md` P9).
|
||||
7. **Which architecture is actually executing?** → `references/native-and-so.md` §Cross-architecture
|
||||
`getprop` reports what the device claims and `primaryCpuAbi` reports what the package manager chose — neither is what is running. Only the live mapping is ground truth (`scripts/lib_map.py`). If the library you meant to patch is not mapped, a translator is in play, or the ABI differs from your assumption, that changes the plan more than any patch will.
|
||||
8. **Is one *specific feature* failing at runtime — login, registration, payment, an API-backed screen — while the rest of the app works?**
|
||||
→ `references/tls-and-cert.md`. A feature-scoped network failure is very often a **TLS/certificate problem on one code path**, not a consequence of your patch. The app can even carry two independent trust chains, so "other requests work" proves nothing. Rule this out in minutes before hunting for a signature check.
|
||||
7. **Was the input a build you did not produce** (a "cracked"/"modded" APK circulating online)?
|
||||
9. **Was the input a build you did not produce** (a "cracked"/"modded" APK circulating online)?
|
||||
→ `references/third-party-builds.md`. Audit it before adopting it: such builds are frequently re-protected (sometimes with *more* layers than the original) and may carry injected components or endpoints. Never use one as a patching workbench.
|
||||
|
||||
**Long-task rule:** if this is likely to run long, open `references/long-task-discipline.md` now
|
||||
@@ -43,13 +54,13 @@ Answer these before touching a tool. Every one of them changes the whole plan.
|
||||
|
||||
## The workflow, end to end
|
||||
|
||||
1. **Recon** — `references/recon.md`. Manifest, package name, version, ABI, dex count, packer, embedded SDKs, where the app's own code lives. Ten minutes here saves hours. **If it is packed, unpack before anything else** (`references/recon.md` §unpacking): you cannot patch code you cannot read, the encrypted payload lengths tell you which dumped dex is the original, and a memory dump must be de-duplicated by hash and structurally validated before any of it is trusted.
|
||||
1. **Preflight, then Recon** — `scripts/preflight.py` before anything else if a device is involved (it takes seconds and prevents a whole class of false conclusions), then `references/recon.md`. Manifest, package name, version, ABI, dex count, packer, embedded SDKs, where the app's own code lives. Ten minutes here saves hours. **If it is packed, unpack before anything else** (`references/recon.md` §unpacking): you cannot patch code you cannot read, the encrypted payload lengths tell you which dumped dex is the original, and a memory dump must be de-duplicated by hash and structurally validated before any of it is trusted.
|
||||
2. **Extract strings and endpoints** — build a picture of the app's API surface and SDK inventory from the dex string tables. No decompiler needed for this, and it is fast. Scripts: `scripts/dex_strings.py`.
|
||||
3. **Trace to the owning class** — find the class that wraps the behavior (the app almost always wraps third-party SDKs in one helper). Reverse-lookup instructions: `references/dex-patching.md` §finding-the-call-site.
|
||||
4. **Decide the patch layer** — client SDK call / client rendering / client data consumption / server contract. See the table in `references/ad-removal.md`.
|
||||
5. **Patch surgically** — `references/dex-patching.md`. Prefer **dexlib2 method-level rewriting** over whole-tree smali round-trip. Whole-tree round-trip damages R8-optimized dex in ways that only show up at runtime.
|
||||
6. **Repack and sign** — `references/repack-and-sign.md`. **Do not strip the whole `META-INF/`.** This single mistake destroys otherwise-correct builds.
|
||||
7. **Verify on device** — `references/environment.md` + `references/verification.md`. Check: launches, the changed behavior actually changed, nothing unrelated broke, and **the app reaches its normal UI with no blocking dialog**. First prove the artifact actually changed on the device -- a package manager reporting success does not prove an interposed confirmation was accepted (P18). Capture continuously for the first ~20 seconds after launch; sampling gaps are how a blocking modal goes unseen (P20).
|
||||
7. **Verify on device** — `references/environment.md` + `references/verification.md`. Check: launches, the changed behavior actually changed, nothing unrelated broke, and **the app reaches its normal UI with no blocking dialog**. First prove the artifact actually changed on the device -- a package manager reporting success does not prove an interposed confirmation was accepted (P18). Capture continuously for the first ~20 seconds after launch, **and look at the captures** — sampling gaps are how a blocking modal goes unseen (P20), and a burst of images that were never inspected is not evidence. If the accessibility tree is empty, the image is the primary evidence rather than a fallback.
|
||||
8. **Log what you learned** — if a failure cost you more than thirty minutes, add it to `references/pitfalls.md`. That file is the most valuable artifact in this skill.
|
||||
|
||||
## Non-negotiable constraints
|
||||
@@ -59,6 +70,8 @@ Answer these before touching a tool. Every one of them changes the whole plan.
|
||||
- **Verify structure after every dex edit.** `scripts/dex_classdiff.py` must report zero differences in class set and access flags for classes you did not intend to change.
|
||||
- **Do not patch a method that is widely shared.** Before patching any helper, count its callers (`scripts/find_refs.py`). A `Long.valueOf` wrapper with 30 callers is not an ad-specific hook.
|
||||
- **Do not make an API fail to suppress a UI element.** A 404/400 on an endpoint that other features depend on takes the whole screen down with it. Suppress at the data-consumption or render layer instead.
|
||||
- **Look before you conclude — and look while you wait.** Execute, capture, and **inspect**; do not drive and sleep blind. A screen that is actually looked at answers in one step what coordinate-guessing cannot answer in five: the layout moved, a different dialog is up, a countdown is frozen, the text on screen says exactly why. Where the thing you are waiting on is visible, a sample you can inspect beats a duration you hoped was right, and byte-identical samples mean nothing is going to change. `scripts/snap.py`; `references/environment.md` §look at the screen.
|
||||
- **Put a timeout on every command, and calibrate it from measurement.** An unbounded call turns a stall into "the task stopped making progress", which is indistinguishable from slow work and costs hours silently. Time the operation once, record it, then derive the bound from it — that is what makes slow and hung distinguishable. A deadline that passes is a measurement, not a verdict. `references/long-task-discipline.md` §bound every wait.
|
||||
- **Every claim needs evidence.** "Probably", "should be", "in theory" are not findings. Either you observed it, or you label it unverified.
|
||||
- **"Done" means the user-visible outcome**, not an internal signal. A blocking dialog still on screen means the task is not done, however many errors disappeared from the log. Absence of a log line is absence of evidence, never evidence of success.
|
||||
- **Do not discard a route on compound evidence.** If a failure followed two simultaneous changes, the attribution is a hypothesis, not a finding. Re-run it single-variable before writing the route off -- mis-attributed failures have removed viable approaches for a long time.
|
||||
@@ -74,19 +87,19 @@ Load only what the current step needs.
|
||||
| `references/recon.md` | Starting any new sample; identifying packer, SDKs, code location, ABI |
|
||||
| `references/packers.md` | The app is packed/hardened, or an edit makes it die before your code runs. Also load before discarding any route as "blocked by the shell" |
|
||||
| `references/framework-runtimes.md` | The UI is not native (Flutter / React Native / Unity / Cordova), or Java-layer hooks fire zero times while the UI clearly works |
|
||||
| `references/native-and-so.md` | Patching in a `.so`, needing code to run before the app's own code, or hand-built native payloads that crash inside the linker |
|
||||
| `references/native-and-so.md` | Patching in a `.so`, needing code to run before the app's own code, hand-built native payloads that crash inside the linker, or **deciding which library/ABI is actually loaded and executing** |
|
||||
| `references/ad-removal.md` | Task involves ads, trackers, sponsored cards, splash/interstitial/reward |
|
||||
| `references/membership-and-limits.md` | Task involves VIP, subscription, paid content, unlock, "fully cracked" |
|
||||
| `references/server-api.md` | The behavior is decided by a response, or you need to know if a patch can even matter |
|
||||
| `references/dex-patching.md` | Any actual editing of dex/smali, choosing a patch layer, choosing a tool |
|
||||
| `references/repack-and-sign.md` | Rebuilding, signing, installing, or a repacked app misbehaves |
|
||||
| `references/runtime-data.md` | Local state matters: DataStore, SharedPreferences, SQLite, protobuf caches, tokens |
|
||||
| `references/runtime-data.md` | Local state matters: DataStore, SharedPreferences, SQLite, protobuf caches, tokens — **or your data edit keeps being reverted, or a stored value looks encrypted** |
|
||||
| `references/dynamic-frida.md` | Frida setup, hooking strategy, tracing caller chains, finding the real call site |
|
||||
| `references/environment.md` | Device/emulator setup, root, ADB, networking, offline devices |
|
||||
| `references/environment.md` | Device/emulator setup, root, ADB, networking, offline devices, emulator console control and recovery, **the preflight check to run before every experiment block** |
|
||||
| `references/verification.md` | Defining what "done" means; building the evidence chain |
|
||||
| `references/tls-and-cert.md` | One feature fails at runtime (login, registration, payment, an API-backed screen) while the rest of the app works |
|
||||
| `references/third-party-builds.md` | The input is a "cracked"/"modded" build you did not produce — audit it before trusting it |
|
||||
| `references/long-task-discipline.md` | The task will run long, or you are resuming one. Live record, conclusion grading, drift checkpoints, handover |
|
||||
| `references/long-task-discipline.md` | The task will run long, or you are resuming one. Live record, conclusion grading, drift checkpoints, **deliverable-form drift (rooted-only vs shippable)**, bound-your-waits, handover |
|
||||
| `references/pitfalls.md` | Always worth a skim before building. This is the failure catalogue. |
|
||||
|
||||
## Script index
|
||||
@@ -111,3 +124,7 @@ All scripts are parameterized and path-agnostic; pass paths explicitly. Run `--h
|
||||
| `scripts/frida_probe.js` | Four-layer runtime probe: app network layer + OkHttp + java.net + swallowed exception messages |
|
||||
| `scripts/run_probe.py` | Inject a probe, stream it to a timestamped log file, stay resident while you operate the app |
|
||||
| `scripts/tls_check.py` | Strict certificate check for one or more hosts (expired / wrong host / untrusted CA) |
|
||||
| `scripts/preflight.py` | Read-only environment check before every experiment block: device, root, ABI/translation, clock skew, leftover proxy/forwards, dead device server. Run this before blaming a patch. |
|
||||
| `scripts/lib_map.py` | What is **actually mapped** into a live process: per-library path, base, architecture (`ELF e_machine`), and classification (system / from-APK / runtime-materialized). Answers "which library and which ABI is really executing". |
|
||||
| `scripts/blob_decode.py` | Decode an opaque stored value by searching the parameter space (base64/base64url/hex × rotation × deflate/zlib/gzip) instead of guessing, then re-encode an edited payload with the same parameters. |
|
||||
| `scripts/snap.py` | Bounded burst screenshot + control-tree capture, with a stall detector and an explicit verdict on whether the accessibility tree is usable at all. Use it so you *look* at the screen instead of driving blind. |
|
||||
|
||||
Reference in New Issue
Block a user