diff --git a/skills/apk-reverse/references/detection-and-anti-analysis.md b/skills/apk-reverse/references/detection-and-anti-analysis.md index c657f0a..5874cea 100644 --- a/skills/apk-reverse/references/detection-and-anti-analysis.md +++ b/skills/apk-reverse/references/detection-and-anti-analysis.md @@ -66,6 +66,25 @@ detector you were not asked about. change the user asked for. That is the drift this skill treats as most expensive, in its runtime form (`long-task-discipline.md` §the most expensive drift). +### The named Java-level root checks, and their one-line neutralizations + +Step 2A's "simple, localised check" has a known population. Once Stage 2 has *named* the detector, +these are the standard neutralizations — each is one gate, in your environment, reversible. +*(Unverified: absorbed from milksu.org notes, retrieved 2026-10-19; not one of them was run by +this repository. They are starting points to verify against your named detector, and they only +apply after the detector is named — never as a blind stack.)* + +| Named detector | Neutralization | +|---|---| +| a root-check library class (`RootBeer`-style, `isRooted`/`isRooted`-shaped method) | replace the method to return the negative constant | +| `Runtime.exec("su")` / `exec("which su")` probing for binaries | throw `IOException("Command not found")` from the hook instead of running it | +| file-existence checks on `/system/xbin/su`, `/system/bin/su`, Magisk paths | hook the file API to report absent for those paths only | +| package checks for `com.topjohnwu.magisk`, Xposed manager packages | hook `getPackageInfo` to throw `NameNotFoundException` for those names only | + +Scope the neutralization to the *named* check's class and method. A blanket hook on `Runtime.exec` +or `File.exists` fires on legitimate app behaviour too, and an observer that also intercepts is no +longer an observer (Step 2B's rule — run the watch and the block as separate loads). + ## Step 3: locating the check — the order of search Step 2 decides *whether* to fight. This step is the part the two steps around it do not provide: diff --git a/skills/apk-reverse/references/dex-patching.md b/skills/apk-reverse/references/dex-patching.md index 155edb1..bff7900 100644 --- a/skills/apk-reverse/references/dex-patching.md +++ b/skills/apk-reverse/references/dex-patching.md @@ -202,3 +202,40 @@ Search strategies that work: 5. `dex_classdiff` every patched dex against the original → expect zero structural drift. 6. Repack, sign, install, launch, exercise, read logcat. 7. If anything is off, revert that dex to the original and re-verify the control build. + +## Reading smali — the cheat sheet + +The techniques above assume you can already read what you are editing. For orientation in a large +smali tree — before any patch exists — these are the shapes that matter. *(Unverified: absorbed +from milksu.org notes, retrieved 2026-10-19; the syntax facts are `inferred` from the dalvik +bytecode spec, the reading shortcuts are someone else's workflow, not measured here.)* + +**Type descriptors:** `V` void, `Z` boolean, `B` byte, `C` char, `S` short, `I` int, `J` long, +`F` float, `D` double; objects `Ljava/lang/String;`; arrays `[I`, `[[Ljava/lang/Object;`. Method +descriptors read `(...)R` with no separator between parameters — `add(II)I` is `(II)I`. + +**Registers:** `.registers N` is the total; `p0` is `this`, `p1..` are parameters, `v0..` are +locals. Parameter registers sit at the **top** of the range, so adding locals means renumbering +nothing but growing `N` — while adding code that uses a register beyond `N` fails verification. +Any edit that introduces a new local must update `.registers` in the same edit; this is the first +thing to check when a hand-edited method fails to verify with no obvious cause. + +**The five instruction families that carry most control flow:** + +| Family | Shapes | What you read off them | +|---|---|---| +| invoke | `invoke-virtual/-static/-direct/-interface {regs}, Lcls;->m(Args)R` | every non-local call, with exact target | +| result | `move-result` / `move-result-object` immediately after an `invoke` | the value a call returned (adjacency is verifier-enforced) | +| fields | `iget/iput` instance, `sget/sput` static | field reads/writes with owner | +| branches | `if-eqz/if-nez v0, :label`, `if-eq/ne/lt/ge`, `goto` | every decision point; a patched condition lives here | +| constants | `const/4`, `const/16`, `const-string`, `const-wide` | the values compared against | + +A method's logic is usually recoverable by reading only these — ignore the register shuffling +between them on a first pass. + +**Obfuscation signatures:** ProGuard renames to `La/b/c;->a(...)` — navigate by string constants +and call targets instead of names. A wrapper like `decrypt("base64...")` feeding every string +constant is string encryption — the decrypted values are recoverable at runtime by hooking the +wrapper (`references/dynamic-frida.md` §Hook strategy) rather than by reversing it. An `if-nez` +guarding a `goto` into dead code that never executes is an opaque predicate — identify the live +branch and read only that. diff --git a/skills/apk-reverse/references/native-and-so.md b/skills/apk-reverse/references/native-and-so.md index c93e7ca..2c8c75c 100644 --- a/skills/apk-reverse/references/native-and-so.md +++ b/skills/apk-reverse/references/native-and-so.md @@ -100,6 +100,14 @@ Stripped and obfuscated libraries still expose structure: will ignore a rewrite of its own entry point, because callers never read that field — another reason the entry-point rewrite must be validated per call site. - Dump the executable range and disassemble around candidate offsets instead of trusting symbol names. +- The JNI calls themselves have a fixed reading shape — in a stripped library this is often the + fastest way to find where Java objects are touched. The first argument of an exported or + registered JNI function is the `JNIEnv*`; every JNI call through it compiles to + `ldr x8, [x0]` (load the vtable) → `ldr x8, [x8, #imm]` (slot) → `blr x8` (call), with `x0` + holding the env and later slots taking the jobject/jstring. Offsets differ per JNI version and + per call, so read the offset from a *known* JNI call in an unstripped library of the same + target ABI before trusting a number. *(The shape is `inferred` from the JNIEnv table layout; + absorbed from milksu.org notes, retrieved 2026-10-19.)* - When a payload was produced by a just-in-time compiler on the target, the displacement between its pages is often zero, which is exactly why relocation-free bootstrapping is required (above). diff --git a/skills/apk-reverse/references/recon.md b/skills/apk-reverse/references/recon.md index 90c9a85..45e732f 100644 --- a/skills/apk-reverse/references/recon.md +++ b/skills/apk-reverse/references/recon.md @@ -172,6 +172,19 @@ Search the app's **own** packages only (ignore third-party SDKs) for: Confirm by checking whether **app code** references it. If the only callers are library-internal, it is not your problem. +The same grep-the-app's-own-packages pass answers behaviour questions beyond tamper checks. +*(Unverified: absorbed from milksu.org notes, retrieved 2026-10-19 — the patterns are standard +API names, `inferred`; their hit-rate as triage shortcuts is not measured here.)* + +| Grep a jadx export for | Points at | +|---|---| +| `Runtime.exec` / `ProcessBuilder` | shell-out behaviour, often config flips or su probes | +| `addJavascriptInterface` | a JS bridge — bridge method inventory next (`references/system-ipc-audit.md`) | +| `PendingIntent.get` without `FLAG_IMMUTABLE` nearby | a mutable capability token (Android 12+ forces a flag) | +| `loadUrl` / `setAllowFileAccess` / `setAllowUniversalAccess` | WebView width — how far web content can reach (`references/system-ipc-audit.md`) | +| `DexClassLoader` / `InMemoryDexClassLoader` / `PathClassLoader` on a download path | dynamic dex loading — plugin/hot-update surface (`references/packers.md`, `references/updates-and-forced-upgrade.md`) | +| `getParcelableExtra` on an exported component, result passed to `startActivity`/`sendBroadcast` | intent redirection (`references/system-ipc-audit.md`) | + ## 6. API surface ```bash diff --git a/skills/apk-reverse/references/tls-and-cert.md b/skills/apk-reverse/references/tls-and-cert.md index 55cb697..ca34113 100644 --- a/skills/apk-reverse/references/tls-and-cert.md +++ b/skills/apk-reverse/references/tls-and-cert.md @@ -158,6 +158,18 @@ Do not: change the device clock, install your own CA into the device trust store Then the failure is about the app's own client configuration, not the platform defaults. Typical causes: a pin that no longer matches, a hostname verifier rejecting a changed host, or a custom `SSLSocketFactory` built from a bundled keystore. Locate it the same way (Step 4, item 2) and patch at the client construction site instead of applying the template above. `dynamic-frida.md` covers finding the real construction site; `dex-patching.md` covers the patch layer. +The two client-side sites, for when the attribution is done. *(Unverified: absorbed from milksu.org +notes, retrieved 2026-10-19; standard shapes, not run here — and note what each one honestly buys:* + +- **`okhttp3.CertificatePinner.check`** — replace the method body to return immediately. This + neutralizes OkHttp's own pin table only; it does not touch a verifier or factory the app + installed separately, which is why the Step 4 attribution runs first. +- **`javax.net.ssl.SSLContext.init`** — hook it and swap `args[1]` for a trust-all + `TrustManager[]`. This covers an app-built factory on that path, and is strictly an + analysis-environment move: it weakens a control the app deliberately added, so it belongs in + your instrumentation, never in a shipped patch (the same line the system-path template draws). + + ## Method notes - **Order of investigation is the lesson.** Every piece of evidence above comes from the server, the network, or the platform before anything is attributed to your patch: full exception chain, real host, out-of-band validation, second-host comparison, control build. Only when all of them point at a client trust decision is a patch justified. `pitfalls.md` P16 is this mistake written up as a failure.