mirror of
https://github.com/QM4RS/FridaBox.git
synced 2026-09-16 22:22:14 +02:00
257 lines
8.9 KiB
Markdown
257 lines
8.9 KiB
Markdown
# Engineering journey
|
|
|
|
This document records the engineering problems that turned FridaBox from a
|
|
virtual-app proof of concept into a repeatable instrumentation workspace. It is
|
|
not a claim that every Android application will run; it is a map of the concrete
|
|
failure modes investigated, the narrow fixes selected, and the evidence used to
|
|
accept those fixes.
|
|
|
|
## 1. Establishing a buildable baseline
|
|
|
|
### Problem
|
|
|
|
The inherited project did not build reproducibly on the available Windows host.
|
|
AGP marker resolution differed from direct module resolution, the requested NDK
|
|
revision was unavailable, and `ndk-build` could not parse an `APP_BUILD_SCRIPT`
|
|
path containing spaces.
|
|
|
|
### Resolution
|
|
|
|
- Map Android plugin IDs to the official AGP module.
|
|
- Use the installed complete NDK 29.0.14206865 revision explicitly.
|
|
- Add a Bcore-scoped `FRIDABOX_NDK_PROJECT_DIR` override so only the native
|
|
build sees a no-space junction path.
|
|
- Record the exact deviation instead of silently changing toolchains.
|
|
|
|
### Evidence
|
|
|
|
The baseline investigation and commands are preserved in
|
|
[`BASELINE.md`](BASELINE.md). Both Java/Kotlin and native ARM64 builds now run
|
|
from the workspace path.
|
|
|
|
## 2. Finding the only useful early-load point
|
|
|
|
### Problem
|
|
|
|
Loading Gadget in the host `Application` instruments the wrong process. Loading
|
|
after guest `Application.onCreate()` is too late for early hooks. Loading before
|
|
virtual identity, IO redirection, and `LoadedApk` creation leaves Frida without
|
|
the correct guest environment.
|
|
|
|
### Resolution
|
|
|
|
Insert `FridaGadgetLoader.loadIfEnabled()` inside
|
|
`BActivityThread.handleBindApplication()` after runtime/native/ClassLoader setup
|
|
and before `makeApplication()`.
|
|
|
|
### Evidence
|
|
|
|
The sample guest logs `attachBaseContext` and `onCreate`. During Computer mode,
|
|
neither appeared until the Gadget client attached. A build verification task
|
|
also rejects source ordering regressions.
|
|
|
|
## 3. Preserving the original APK
|
|
|
|
### Problem
|
|
|
|
The common Gadget workflow mutates and resigns an APK, which changes the object
|
|
under study. Importing through Android's PackageInstaller would also place the
|
|
guest in the real system package database.
|
|
|
|
### Resolution
|
|
|
|
Stream the selected base APK through SAF into private storage, hash while
|
|
copying, inspect the archive, verify the private copy again, make it read-only,
|
|
and install only through the virtual package manager.
|
|
|
|
### Evidence
|
|
|
|
The generated sample asset and imported copy were byte-identical. `adb shell pm
|
|
list packages` did not contain the sample package while FridaBox could launch it.
|
|
|
|
## 4. Selecting the guest ClassLoader
|
|
|
|
### Problem
|
|
|
|
Frida's default Java factory can observe the host loader first in a virtual
|
|
process. A valid guest class then appears missing even though the application is
|
|
running.
|
|
|
|
### Resolution
|
|
|
|
Store the actual `LoadedApk` ClassLoader in `GuestRuntimeRegistry`. Load a
|
|
bootstrap agent before the user's script and assign that loader to
|
|
`Java.classFactory.loader`, with bounded retries during transient startup.
|
|
|
|
### Evidence
|
|
|
|
The controller reported `dalvik.system.PathClassLoader` and the sample hook
|
|
resolved `com.qm4rs.fridabox.sample.Target` before `Application.onCreate()`.
|
|
|
|
## 5. Discovering the correct Gadget endpoint
|
|
|
|
### Problem
|
|
|
|
Every virtual guest process begins with the same preferred Gadget port. Multiple
|
|
processes may move to fallback ports, while another unrelated Frida endpoint may
|
|
already exist on the device. A fixed `adb forward tcp:27042 tcp:27042` cannot
|
|
identify which guest it reached.
|
|
|
|
### Resolution
|
|
|
|
Use Gadget's bounded `pick-next` range, forward each candidate, probe it with a
|
|
FridaBox registry agent, reject endpoints without the expected registry, and map
|
|
valid endpoints to package and process.
|
|
|
|
### Evidence
|
|
|
|
`tools/attach_guest.py --list` discovered the correct sample endpoint and printed
|
|
package, process, user ID, virtual process ID, source APK, and ClassLoader.
|
|
|
|
## 6. Adapting agents to Frida 17
|
|
|
|
### Problem
|
|
|
|
Frida 17 API-loaded agents no longer receive the Java bridge implicitly. Plain
|
|
source scripts could load yet fail when calling `Java.perform()`.
|
|
|
|
### Resolution
|
|
|
|
Pin `frida-java-bridge` 7.0.13 and `frida-compile` 19.0.5, import the bridge
|
|
explicitly, commit reproducible bundles under `scripts/dist`, and teach the
|
|
controller to choose a compiled counterpart automatically.
|
|
|
|
### Evidence
|
|
|
|
Clean `npm ci` agent builds succeeded and the bundled sample hook changed
|
|
`Target.add(2, 3)` to `1337`.
|
|
|
|
## 7. Running an agent without a computer
|
|
|
|
### Problem
|
|
|
|
Interactive Gadget listen mode requires a host to attach and resume execution.
|
|
It cannot deliver the requested cable-free, persistent per-app behavior.
|
|
|
|
### Resolution
|
|
|
|
Add a per-package Script interaction mode. FridaBox copies the selected agent
|
|
byte-for-byte into private storage, records its SHA-256, generates an adjacent
|
|
relative-path config, and loads a private Gadget copy on every On-device launch.
|
|
|
|
### Evidence
|
|
|
|
A 198,960-byte real-world agent was imported with an identical SHA-256, marked
|
|
0400, loaded without a controller, and reached `local_script_active` before the
|
|
Unity guest became interactive.
|
|
|
|
## 8. Preventing launch-mode leakage
|
|
|
|
### Problem
|
|
|
|
Gadget is a native library. Once loaded, switching a living process to Clean
|
|
mode cannot reliably reverse its threads, mappings, hooks, or listener.
|
|
|
|
### Resolution
|
|
|
|
Treat mode changes as process-boundary changes. Stop the virtual package before
|
|
every relaunch and make each new process read the current per-package policy.
|
|
|
|
### Evidence
|
|
|
|
The validated clean launch used a different PID, emitted an explicit disabled
|
|
state, opened no Gadget listener, and restored the sample result from `1337` to
|
|
`5`.
|
|
|
|
## 9. Making runtime state honest across processes
|
|
|
|
### Problem
|
|
|
|
The guest registry is process-local. A host-screen read of cached
|
|
SharedPreferences could display stale `Waiting for computer` state even after an
|
|
On-device agent was active in another process.
|
|
|
|
### Resolution
|
|
|
|
Keep the registry authoritative inside the guest and use a deliberately small
|
|
controller snapshot with cross-process reload semantics for presentation.
|
|
|
|
### Evidence
|
|
|
|
The persisted state was correct during the failure. Reloading eliminated the
|
|
stale host display and subsequent On-device launches reported
|
|
`local_script_active` consistently.
|
|
|
|
## 10. Android 16 executable-file constraints
|
|
|
|
### Problem
|
|
|
|
Copying a writable native executable into app-private storage triggered modern
|
|
Android warnings and created an avoidable writable/executable overlap.
|
|
|
|
### Resolution
|
|
|
|
Finish copying and configuration first, then set the private Gadget executable
|
|
to mode 0555 and the agent to 0400 before loading.
|
|
|
|
### Evidence
|
|
|
|
The final Android 16 run reported the expected permissions and no longer emitted
|
|
the writable-executable warning for the private Gadget copy.
|
|
|
|
## 11. Building a real product boundary
|
|
|
|
### Problem
|
|
|
|
The inherited launcher mixed unrelated cloning, GMS, Xposed, fake-location, and
|
|
floating-overlay features with the instrumentation research. Its namespace,
|
|
resources, dependencies, and branding obscured the actual FridaBox workflow.
|
|
|
|
### Resolution
|
|
|
|
- Replace the launcher with a FridaBox-owned workspace and runtime UI.
|
|
- Move all host source and tests to `com.qm4rs.fridabox`.
|
|
- Replace the inherited `Application` shell with a minimal FridaBox runtime
|
|
bootstrap.
|
|
- Remove 7,000+ lines of unused UI code, obsolete resources, legacy AARs, and
|
|
stale product documentation.
|
|
- Keep the mature runtime engine as an internal compatibility boundary and
|
|
preserve its required attribution.
|
|
|
|
### Evidence
|
|
|
|
Debug/release assembly, app/Bcore tests, lint/check, reinstall, launcher resume,
|
|
guest restoration, and a real Clean-mode Unity launch all passed after removal.
|
|
|
|
## 12. Release safety and reproducibility
|
|
|
|
### Problem
|
|
|
|
Research APKs often accidentally ship with a debug key or depend on locally
|
|
generated assets whose provenance cannot be reproduced.
|
|
|
|
### Resolution
|
|
|
|
- Release signing is configured only when all four explicit keystore variables
|
|
are present; partial configuration fails and no debug fallback exists.
|
|
- R8 and resource shrinking are enabled for release.
|
|
- Gadget version and SHA-256 are pinned and verified before every app build.
|
|
- The debug sample is generated and embedded byte-identically.
|
|
- Frida 17 agents are reproducible from lockfile-pinned npm dependencies.
|
|
|
|
### Evidence
|
|
|
|
The debug signature verified, the unsigned release contained no debug signature,
|
|
and all custom integrity/order/packaging tasks passed. Current artifact hashes
|
|
are recorded in [`TEST_RESULTS.md`](TEST_RESULTS.md).
|
|
|
|
## Current research boundary
|
|
|
|
FridaBox demonstrates a robust architecture for ordinary single-file ARM64 APKs,
|
|
not universal Android virtualization. Split delivery, privileged/system apps,
|
|
Play Integrity, hardware-backed attestation, isolated services, WebView renderer
|
|
processes, and adversarial commercial RASP remain distinct research problems.
|
|
The project documents these boundaries rather than hiding them; see
|
|
[`LIMITATIONS.md`](LIMITATIONS.md) and
|
|
[`DETECTION_SURFACES.md`](DETECTION_SURFACES.md).
|