Expand into packer/native/framework/long-task coverage; harden verification discipline
New references (loaded on demand, one topic each): - packers.md: reading the shell's rejection signal, distinguishing self-inflicted failures from genuine refusals, mapping the validation boundary via single-variable tests, component-type rules (providers vs activities), picking a native host - framework-runtimes.md: identifying Flutter/RN/Unity/Cordova, the wrong-layer trap, how to tell which layer draws a given UI before committing to a patch direction - native-and-so.md: host-library selection criteria, DT_NEEDED does-not-call-JNI_OnLoad, relocation vs page-permission constraints and relocation-free bootstrapping, replacing Java methods from native code with trampolines and fail-safe guards - long-task-discipline.md: live record structure, conclusion grading, drift checkpoints, handover contents, and the opposite failure of over-documenting Pitfalls: +P18 install reported success but the build was never installed, +P19 internal signal substituted for the user-visible outcome, +P20 not-captured treated as not-present, +P21 two changes then attributing the result, +P22 waiting on something that needs a human to advance. SKILL.md: index the new references; add a runtime check for layer ownership to Q3; add long-task rule; strengthen the verify step; add three hard constraints (user-visible definition of done, no route discarded on compound evidence, prove the device changed before measuring). No app names, package names, or sample-specific identifiers anywhere.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# Cross-Platform Runtimes
|
||||
|
||||
Load this when the app's UI is not native, or when Java-layer hooks produce no hits at all.
|
||||
|
||||
Many apps ship a native shell plus a cross-platform runtime. Their business logic, their UI, and their
|
||||
feature gates live **inside the runtime**, not in the dex. Patching the dex then does nothing — the Java
|
||||
code you can see is mostly glue. Determine the runtime before choosing where to patch.
|
||||
|
||||
## Identify the runtime early
|
||||
|
||||
Signals in the APK:
|
||||
|
||||
| Runtime | Giveaways |
|
||||
|---|---|
|
||||
| Flutter | `libflutter.so`, `libapp.so`, `assets/flutter_assets/`, Dart snapshot |
|
||||
| React Native | `libhermes.so` or `libjsc.so`, `index.android.bundle`, `assets/index.android.bundle` |
|
||||
| Unity | `libunity.so`, `libil2cpp.so`, `assets/bin/Data/`, `globalgamemanagers` |
|
||||
| Cordova / Ionic | `assets/www/`, `cordova.js` |
|
||||
| Xamarin / .NET | `libmonodroid.so`, `assemblies/` in the APK |
|
||||
|
||||
A `classes.dex` that is small relative to the total payload is another strong hint that the real code
|
||||
lives elsewhere.
|
||||
|
||||
## The layer trap
|
||||
|
||||
The expensive mistake: seeing a UI element (dialog, paywall, gate) and assuming it is a native control,
|
||||
then spending a long time looking for it in the dex.
|
||||
|
||||
If it is drawn by the runtime, the dex contains only the bridge. Symptoms of being on the wrong layer:
|
||||
|
||||
- hooking the obvious Java dialog/Activity classes produces **zero** hits while the UI clearly appears;
|
||||
- the classes that do appear in stacks are the runtime's own, with obfuscated names;
|
||||
- hooking a Java method changes nothing about the visible behaviour.
|
||||
|
||||
Establish layer ownership **before** investing in a patch direction.
|
||||
|
||||
### How to tell which layer drew a given UI
|
||||
|
||||
1. Enumerate the Java dialog and presentation classes and hook their show/creation paths.
|
||||
2. Reproduce the UI. If any hook fires, the caller stack names the detection and you are on the Java
|
||||
layer.
|
||||
3. If nothing fires while the UI is on screen, the UI is runtime-drawn or native-drawn. Switch axis.
|
||||
4. Expect runtime-owned windows to appear as ordinary system `Dialog`/`Presentation` objects used as
|
||||
**containers** for the runtime's surface. A hit on such a container is not evidence that the visible
|
||||
element is a native dialog — read the class name carefully before drawing conclusions.
|
||||
|
||||
## Flutter specifics
|
||||
|
||||
- Business logic is compiled ahead-of-time into **`libapp.so`**; UI is rendered by `libflutter.so` with
|
||||
no per-widget Java objects.
|
||||
- **Widgets, dialogs, and paywalls are not Java views.** They will never appear in Java stack traces,
|
||||
and no Java hook can intercept them.
|
||||
- The Java side is thin: one Activity, plus plugin classes. Calls cross the boundary through the
|
||||
platform-channel mechanism, whose class names are frequently obfuscated.
|
||||
- Because channel classes may be renamed, locate them **by method signature and call shape**, not by
|
||||
class name.
|
||||
|
||||
### Where to intervene in a Flutter app
|
||||
|
||||
| Goal | Layer | Notes |
|
||||
|---|---|---|
|
||||
| Stop the app being killed / refused at startup | host native library | earliest point, see `native-and-so.md` |
|
||||
| Observe what the runtime is told | platform channel | shows the messages that drive the UI |
|
||||
| Change a decision permanently | `libapp.so` | most direct, hardest to locate |
|
||||
| Change startup behaviour only | host native library constructor | survives runtime restarts |
|
||||
|
||||
Editing `libapp.so` is the most durable but locating Dart AOT code is genuinely hard: strings are the
|
||||
most reliable entry, because Dart string objects and the code referencing them can be found by scanning
|
||||
the snapshot rather than by pattern-matching machine code.
|
||||
|
||||
## Locating logic without symbols
|
||||
|
||||
- **Strings first.** User-visible text (dialog bodies, feature labels) is the cheapest anchor in any
|
||||
runtime. Find it, then find what references it.
|
||||
- **Compare two builds.** The same feature in a slightly different version often reveals the code path.
|
||||
- **Watch the boundary, not the interior.** For cross-platform apps it is usually far cheaper to observe
|
||||
what crosses between layers than to reverse the interior of the runtime.
|
||||
- **Do not assume the obfuscated names are stable or meaningful.** They are not, and treating them as
|
||||
identities leads to conclusions that break on the next build.
|
||||
|
||||
## What this changes about your plan
|
||||
|
||||
- Budget for the runtime layer **up front**. If the app is Flutter/RN/Unity, the dex is not the main
|
||||
battlefield and a dex-only plan will stall.
|
||||
- Any behavioural claim must be validated **through the runtime's own UI**, because that is what the
|
||||
user sees. A patch that changes internal state without changing the UI is not a fix.
|
||||
- Keep the layer you are working in explicit in your notes. "Patched the paywall" is meaningless without
|
||||
saying which layer owned it.
|
||||
Reference in New Issue
Block a user