8.9 KiB
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_DIRoverride 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. 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
Applicationshell 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.
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 and
DETECTION_SURFACES.md.