From d1349b3cfabdb9794f9740f5c66bc6f56024a01e Mon Sep 17 00:00:00 2001 From: inliver233 Date: Sun, 20 Sep 2026 01:00:11 +0800 Subject: [PATCH] Restructure SKILL.md as a gated procedure, not advice Why The reference material was good and mostly went unread in practice. The failure mode was not ignorance - it was reading the file, agreeing with it, and then reasoning from first principles anyway, because everything in it was phrased as guidance. Guidance can be acknowledged and set aside; a gate cannot be cleared without doing the thing. SKILL.md is now a procedure New top matter, in the order a reader meets it: - "How to use this file" states plainly that this is a procedure with gates, that this file wins over intuition until evidence overrides it, and why (every rule is residue of a failure that cost hours). - Four override rules R1-R4, each with a one-line claim, the concrete trap, and a pointer. R1 deliverable-as-a-testable-sentence, R2 one variable plus a control build, R3 never ship or claim an unverified artifact, R4 identify the owning layer and re-classify when reality disagrees. - The symptom index is retitled "a matching row is a stop signal" and now carries an explicit instruction: load the file before your next command, not after your next three attempts. - Four gates G1-G4, each an action with a pass criterion and a fail description: deliverable form, environment truth (doctor.py + preflight), code location (including the virtualization check even when recon says "no packer"), and baseline/control with recorded time-to-death. - The workflow gains a skip-condition line per step ("it seems unnecessary" is not a condition) and a two-strike rule: two failures of the same shape mean the model is wrong, not the parameters, so re-classify instead of running a third variant. - New "What done means": six items, all required, with an explicit rule that a clean log is not evidence and that anything short of six is a checkpoint to be labelled as one. - New "Stop conditions": the six moments where pushing forward is the wrong move - repeated same-shape failure, a no-effect patch about to be retried, an unexplained failure, an unproven "blocked" verdict, a success claimed from absence of errors, and a measurement that disagrees with a settled conclusion. README Documents the gated design and why it exists, and tells an agent reading it what to act on rather than read: the four rules, the symptom index as a stop signal, the gates, the two-strike rule, and the definition of done. Points at scripts/doctor.py as the cheapest first command. Not changed Three section references (`ad-removal.md` server-driven, `recon.md` code-location, `dex-patching.md` finding-the-call-site) do not match a `##` heading textually. Each target does exist - two are bolded inline subheadings and one is "## Finding the call site" - so they resolve for a reader. Left alone deliberately: rewriting them would add churn without changing behaviour. --- README.md | 36 ++++++++++++++- SKILL.md | 135 +++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 158 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 83b726a..0a54a78 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,34 @@ organized for progressive disclosure — a short decision-oriented `SKILL.md`, d 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 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 @@ -49,7 +77,13 @@ directly. ## Structure ``` -SKILL.md decision tree, workflow, hard constraints, indexes +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 packers.md hardened targets: rejection signals, measuring the validation diff --git a/SKILL.md b/SKILL.md index f41a06a..869f8da 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,30 +1,68 @@ --- name: apk-reverse -description: Reverse engineer, debloat, de-ad, patch, or re-sign Android APKs, and analyze their runtime and server-side behavior. Use when a task involves an .apk/.aab/.dex/.so sample, smali or dex patching, Frida/objection runtime hooking, repacking and re-signing, removing ads or SDK trackers, probing a mobile app's HTTP API, or deciding whether a client-side patch is even capable of achieving the goal. Covers recon, anti-tamper, ad removal, membership/paywall limits, dex-level surgical patching, repack pitfalls, device and emulator setup, and a hard-won failure catalogue. +description: Reverse engineer, debloat, de-ad, patch, or re-sign Android APKs, and analyze their runtime and server-side behavior. Use when a task involves an .apk/.aab/.dex/.so sample, smali or dex patching, Frida/objection runtime hooking, repacking and re-signing, removing ads or SDK trackers, probing a mobile app's HTTP API, or deciding whether a client-side patch is even capable of achieving the goal. Covers recon, anti-tamper, ad removal, membership/paywall limits, dex-level surgical patching, repack pitfalls, device and emulator setup, and a hard-won failure catalogue. Load the body before planning any patch work: it opens with a symptom index and four gates that must be cleared first. --- # APK Reverse Engineering & Patching -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. +Goal: reach a **verified, installable, still-working artifact** fast — and avoid the whole class of +mistakes that destroy an APK while looking completely healthy. -Three rules override everything else in this skill: +## How to use this file -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. +This file is a **procedure with gates**, not background reading. Three things are mandatory: -## Symptom index — look here before you debug from first principles +- Before you patch anything, clear the **four gates** in §Gates. They are actions with pass criteria, + not attitudes. +- When anything fails in a way your current plan does not explain, **stop and check the symptom + index**. If a row matches, load that file before running another command. +- When this file and your own reasoning disagree, **this file wins** until you have evidence that + overrides it. Every rule here is the residue of a failure that cost hours; your current intuition is + the intuition of someone who has not hit it yet. -You arrived at a symptom, not at a file name. This table maps what you are *seeing* to the file that -has already paid for that lesson. Skim it whenever something fails in a way the current plan does not -explain; the cost of one lookup is minutes, and every entry below cost someone hours. +## Four rules that override everything else + +**R1 — Write the deliverable as a testable sentence before you touch the target.** +"It works" is not the goal; "it works under the stated constraint" is. Root-assisted, live- +instrumentation, host-proxy and patched-device results frequently do **not** satisfy a request for an +installable artifact that works on a normal phone — and it is easy to present such a result as +finished. Write the sentence, re-read it at every checkpoint, and if you cannot meet it, say so +plainly and label the privileged workaround a **fallback**, never the deliverable. +→ `references/long-task-discipline.md` §the most expensive drift + +**R2 — Change one variable at a time, and keep a control build.** +An experiment that flips two things teaches nothing when it fails, and a failure you cannot attribute +will be attributed to the wrong cause. Every "the app rejects X" claim needs its own run, and every +patch needs a same-pipeline control that still fails the old way. +→ `references/long-task-discipline.md` §single-variable discipline + +**R3 — Never ship or claim an unverified artifact.** +"It assembles" is not "it works"; "the process started" is not "the feature works"; "no error in the +log" is not "the check is gone". Install it, launch it, exercise the exact feature you changed, and +look at the screen. Prove the device is running the build you made — hash it, do not trust the +filename. +→ `references/verification.md` + +**R4 — Identify the owning layer before patching, and re-classify when reality disagrees.** +Ads, paywalls, feature gates, integrity checks and update gates live in different layers (Java, dex, +native, Dart/Unity, server). Patching the wrong layer either does nothing or breaks the app. If a +patch "had no effect", the diagnosis was wrong — go back to classification instead of patching harder. +→ `references/recon.md`, then the layer-specific file the symptom index points at + +## Symptom index — a matching row is a stop signal + +You arrive at a symptom, not at a file name. Each row below is a failure that has already been paid +for. **If any row matches what you are observing, load the file before your next command** — not after +your next three attempts. Reasoning from first principles at this point is how the same hours get +spent twice; more than one entry here is a lesson that was re-derived by hand while the answer sat +unread in this repository. | What you observe | Load first | |---|---| | A repackaged/re-signed build **dies before your code runs**; `SIGSEGV`, all registers zero, `pc=0`, `fault addr` near `0x0` | `native-tamper-and-suicide.md` (deliberate crash), then `code-virtualization-and-custom-linkers.md` | | **No packer** (Application is the app's own, dex readable) **and it still dies** | `code-virtualization-and-custom-linkers.md` | -| Log says a **Java-layer** signature/integrity check **passed**, yet the process dies | `code-virtualization-and-custom-linkers.md` §a Java-layer signature killer is a decoy | -| Deleting a library fixes validation but yields `UnsatisfiedLinkError: dlopen failed: library "X" not found` | `code-virtualization-and-custom-linkers.md` §the deadlock | +| Log says a **Java-layer** signature/integrity check **passed**, yet the process dies | `code-virtualization-and-custom-linkers.md` §a Java-layer "signature killer" is a decoy | +| Deleting a library fixes validation but yields `UnsatisfiedLinkError: dlopen failed: library "X" not found` | `code-virtualization-and-custom-linkers.md` §the deadlock that eats hours | | Whole classes appear as bare `native` declarations with no body | `code-virtualization-and-custom-linkers.md` | | A library's **SONAME does not match its filename** | `code-virtualization-and-custom-linkers.md`, `native-and-so.md` | | Your edit had **no effect at all**, with no error | `packers.md` §map the validation boundary | @@ -42,6 +80,34 @@ explain; the cost of one lookup is minutes, and every entry below cost someone h | You are about to discard a route as "blocked" | `packers.md` — re-read it before writing any route off; mis-attributed failures have removed viable routes for hours | | The task has run long and you are unsure what is already proven | `long-task-discipline.md` §keep a live record | +## Gates — clear these before you patch, in order + +Each gate is an **action with a pass criterion**. Do not proceed past a gate you have not cleared, and +do not treat "I understand the idea" as clearing it. Skipping a gate is not a shortcut; it is how the +work gets redone. + +**G1 · Deliverable form.** State, in one sentence you could hand to someone else, what artifact must +exist at the end and under what constraints (rooted or not, installable on a stock device or not, must +survive updates or not, online or offline). *Pass:* the sentence names a testable constraint, not an +activity. *Fail:* you are solving a problem in an environment the deliverable will never see. + +**G2 · Environment truth.** Run `scripts/doctor.py` (and `scripts/preflight.py` if a device is in +play). *Pass:* you know which toolchains and scripts can actually run here, and you have seen the +environment warnings — clock skew, leftover `adb forward`/proxy, a device-side frida process already +running, a tool installed off-PATH. *Fail:* you are about to attribute to the target a failure caused +by your own setup. + +**G3 · Code location.** From the manifest and dex, answer: is there a packer, where does the app's own +code live (dex / native / Dart / Unity / server), and is any of it virtualized to native. *Pass:* you +can name the class that owns the behaviour you intend to change, or you have an explicit plan to find +it. *Fail:* you are about to patch a layer you have not located. If recon says "no packer", still +check the virtualization shape — see the index rows above. + +**G4 · Baseline and control.** *Pass:* you have a control run — the unmodified original, or a +zero-change repack through the same pipeline — and you have recorded the observed failure (including +**time-to-death**, if it dies). *Fail:* when the patched build misbehaves you will have nothing to +compare against, and every later measurement is unfalsifiable. + ## Start here: classify the target in thirteen questions @@ -110,9 +176,19 @@ Answer these before touching a tool. Every one of them changes the whole plan. ## The workflow, end to end +Steps are ordered. **Skip a step only when its stated skip condition is met** — "it seems +unnecessary" is not a condition, and it is the reason most of the failures in `pitfalls.md` happened. + +**Two-strike rule.** If the *same kind* of attempt fails twice, stop and go back to classification. +Do not run a third variation of a hypothesis that has already failed twice. Two failures of one shape +means the model is wrong, not that the parameters need tuning — and the third attempt is where an +entire round gets spent confirming what the first two already said. Re-read the symptom index at that +point; it exists for exactly this moment. + 1. **Preflight, then Recon** — `scripts/doctor.py` is the cheapest possible first command: it reports which toolchains and scripts can actually run here, and surfaces the environment facts that poison experiments (clock skew, leftover `adb forward`/proxy, a device-side frida process already running, a tool installed off-PATH). Then `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. **If recon says there is no packer but a re-signed build still dies**, you are in the layer `references/code-virtualization-and-custom-linkers.md` covers — do not proceed on the assumption that "no packer" means "editable". **If the app already dies on its own** — especially at a roughly constant time after launch, or with a native crash — locate the mechanism *before* planning any patch (`references/native-tamper-and-suicide.md`, `scripts/native_crash.py`). Record the observed time-to-death: it is the baseline every later attempt is measured against, and without it a surviving run cannot be told from a changed schedule. + *Skip condition:* never skipped. G2/G3 in §Gates are cleared here or not at all. 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`. @@ -123,6 +199,41 @@ Answer these before touching a tool. Every one of them changes the whole plan. 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. +## What "done" means — do not claim it earlier + +Every item below must be true before you report completion. Anything less is a **checkpoint** and must +be labelled as one, out loud, with what remains. Premature "done" is the most damaging thing you can +report, because it ends the investigation while the user believes the problem is solved. + +1. **The artifact exists and its identity is recorded** — path plus hash, not a filename. +2. **It was installed and launched on the environment the deliverable sentence names** (G1/R1). If + that environment was not available to you, say so and label the result accordingly. +3. **The behaviour you changed is verified changed** — by direct observation of the feature, not by + the absence of an error message. "The log is clean" is not evidence; "the screen shows X" is. +4. **The features it touches still work.** You exercised them. A build that starts but whose affected + feature is dead is not a result. +5. **The original limitation is stated if any survives** — with the coupling that causes it, so the + next person can decide whether to accept it. +6. **Nothing you did leaves the target or the device in a broken state** unless that was the goal, and + any privileged workaround is labelled a fallback rather than the deliverable. + +If items 1–4 hold but the environment was wrong, you have a **prototype**, not a deliverable. Say +"prototype" and name the gap. + +## Stop conditions — halt and re-classify, do not retry + +These are moments where continuing to push forward is the wrong move. Each has cost hours somewhere. + +- **The same shape of attempt failed twice.** See the two-strike rule above. +- **A patch had no effect and you were about to try a third variant of it.** No effect means the + diagnosis was wrong, not that the patch was unlucky. Re-classify the layer. +- **A new failure has no place in your current model.** That is the symptom index's trigger condition. +- **You are about to write off a route as "blocked"** without a control build proving the block is + the app's doing rather than your pipeline's. Mis-attributed blocks have removed viable routes. +- **You are about to claim success on absence of errors.** See §What "done" means. +- **A measurement disagrees with a conclusion you already recorded as settled.** Re-open the + conclusion; do not explain the measurement away. + ## Non-negotiable constraints - **Read-only inputs.** Keep the original APK/dex untouched; work on copies. Always keep a known-good baseline to diff against.