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:
inliver233
2026-09-19 22:08:01 +08:00
parent 55432780fc
commit 0ea42265b7
12 changed files with 1537 additions and 34 deletions
+76 -6
View File
@@ -101,12 +101,82 @@ Stripped and obfuscated libraries still expose structure:
## Cross-architecture notes
- **arm64** is the common case on modern devices; prefer it and make sure the host library you pick
actually exists for that ABI.
- **x86_64** appears on emulators. Shells and payloads are ABI-specific: a file built for one ABI will
not be loaded on the other, and the failure mode looks like a missing library rather than a mismatch.
- Keep your build artifacts per-ABI and label them. Mixing them is a common source of "it crashed for no
reason" when the same APK behaves differently on an emulator versus a device.
Architecture is not background information here — it decides **which artifact you must edit** and
**whether your hook can observe anything at all**. Get it from the runtime, not from the manifest.
### What the device claims vs what is executing
Three different answers, and they disagree more often than people expect:
| Source | Answers | Command |
|---|---|---|
| The device | which ABIs the system supports | `getprop ro.product.cpu.abi` and `ro.product.cpu.abilist` |
| The package manager | **which ABI it chose for this app on this device** | `dumpsys package <pkg> \| grep primaryCpuAbi` |
| The live process | **what is actually mapped right now** | `scripts/lib_map.py --pkg <pkg>` |
Only the third is ground truth. The first two are predictions, and they are wrong exactly when it
matters: an emulator whose primary ABI is `x86_64` can be running an `arm64-v8a`-only app through a
translator, and a fat APK can have the package manager pick an ABI you did not assume.
**Consequence: if the library you patched does not appear in the live mapping, your change cannot
matter.** That is a plan problem, and no amount of re-patching will fix it.
### Translation layers change what you are observing
When an ARM-only app runs on an x86 host, a translator (Intel Houdini, `libndk_translation`, a
`native_bridge` in general) is executing the guest code. Detect it from the maps — look for
translator marker libraries and for app libraries whose architecture differs from the host's
primary ABI (`lib_map.py` reports both).
What it changes:
- **Timing.** Translation is slower and less predictable. Anything you measured about latency is
about the translator, not the code.
- **Native integrity and anti-tamper checks can behave differently** under translation, in either
direction — a check that fails on hardware may pass here, and vice versa. A pass under translation
is not evidence of a pass on hardware.
- **Hook behaviour.** Intercepting translated code is not the same as intercepting native code:
address spaces, calling conventions, and what the host sees at a syscall boundary all differ. If a
native hook reports nothing while the feature plainly runs, suspect translation before suspecting
your script.
- **Syscall-level observation shows the host's view.** A file or network call made from the guest
may look different on the host side than it would natively.
**Rule: verify the artifact on the ABI the user will actually run.** An emulator-only result is a
mid-task checkpoint, never the final claim (`verification.md`).
### Fat APKs: patching the ABI that never loads
An APK can ship `lib/arm64-v8a/`, `lib/armeabi-v7a/`, `lib/x86_64/`… The package manager extracts
**one** of them into the install-time native library directory, and only that one is loaded.
- Check `primaryCpuAbi` and the live maps before editing a `.so`. Editing `arm64-v8a` while the
device loads `armeabi-v7a` produces a build that is byte-different and behaviorally identical.
- If the deliverable must work for an unknown user, remember their device may select a different
ABI than yours. Either patch every ABI present, or state which ABI your artifact targets.
- If the app's own libraries are written at *runtime* rather than extracted from the APK, the live
paths will not be inside the APK at all (`lib_map.py` marks these `materialized`). Those belong to
whatever produced them, and a repacked APK will not carry them in that location.
### Payloads and tooling are per-ABI too
- A native payload, an injected library, or a shellcode blob built for one ABI **will not load** on
another. The failure usually surfaces as a *missing library* or a generic linker error rather than
a clean "wrong architecture" message — do not read it as "my payload is broken".
- **Pointer size differs** between 32- and 64-bit targets. A script or struct layout that assumes
8-byte words misbehaves silently on a 32-bit target.
- Keep build artifacts per-ABI and label them with the ABI in the filename. Mixing them up is a
reliable source of "it crashed for no reason" when the same APK behaves differently on an emulator
and on a device.
### Quick decision list
1. `scripts/lib_map.py --pkg <pkg> --app-only` → which app libraries are loaded, from where, and at
what architecture.
2. If a translator is present and the task needs reliable native behaviour, move to a matching
device.
3. If the library you meant to patch is absent, stop — pick the right library first.
4. Only then start editing.
## Verification