feat: deliver first successful FridaBox MVP

Integrate Frida Gadget into BlackBox guest startup, add the host workflow and controller tooling, and validate the complete sample hook flow on ARM64 Android 16.
This commit is contained in:
Mahdi Karzari
2026-07-20 04:25:08 +03:30
parent 89b59836c6
commit 5140bfc6a7
56 changed files with 8573 additions and 104 deletions
+40
View File
@@ -0,0 +1,40 @@
# NewBlackbox baseline
- Foundation: `ALEX5402/NewBlackbox`, branch `main`
- Required commit: `89b59836c66f173756a4ae258cf379a957649820`
- Working branch: `feature/fridabox-mvp`
- Baseline date: 2026-07-19
- Host OS: Windows 11 amd64
- Available JVM used: Oracle JDK 24.0.1 (JDK 21 was not installed on the build host)
## Exact pinned-source result
`gradlew clean --stacktrace --console=plain` failed during configuration because
`com.android.application:com.android.application.gradle.plugin:8.13.2` could not
be resolved. A direct request to the official Google Maven artifact URL returned
HTTP 404. No source code had been changed when this result was recorded.
## Narrow baseline compatibility adjustments
The official Google Maven endpoint returned HTTP 404 for AGP and AndroidX
artifacts in this build environment. The Google repository mirror at
`https://maven.aliyun.com/repository/google` is configured ahead of `google()`;
the requested AGP 8.13.2 is retained. Plugin IDs are mapped directly to the
official `com.android.tools.build:gradle` module to avoid marker resolution.
The requested NDK
29.0.13846066 was not installed; the nearest complete installed NDK,
29.0.14206865, is used. Bcore's `ndkVersion` was also moved from the
`externalNativeBuild` block to the Android block where AGP recognizes it.
On Windows, upstream `ndk-build` cannot parse an `APP_BUILD_SCRIPT` path that
contains spaces. Bcore accepts `FRIDABOX_NDK_PROJECT_DIR` as an optional alias
for the Bcore module directory; this build used `D:\FridaBoxBuild\Bcore`, a
directory junction to the real workspace.
## Post-adjustment result
With the repository mirror, installed NDK 29 revision, and scoped Windows
space-path workaround, `:app:assembleDebug` completed successfully. The baseline
build compiled Java/Kotlin resources and both native ABIs present in the original
foundation before FridaBox changed the final target to ARM64-only.
+54
View File
@@ -0,0 +1,54 @@
# Building
## Prerequisites
- JDK 21 (the requested/recommended toolchain).
- Android SDK 35 and build tools.
- Android NDK 29.0.13846066 when available. This build host only had the closely
related complete NDK 29.0.14206865, which is recorded in `docs/BASELINE.md`.
- Python 3.10 or newer.
- Node.js 20 or newer and npm for reproducibly building Frida 17 Java agents.
Create `local.properties` with the SDK path, then fetch the pinned official
Gadget:
```powershell
python tools\fetch_frida_gadget.py
```
The script uses the GitHub Releases API and Python's standard `lzma` module. The
build fails with a direct instruction if the binary is absent. No XZ archive is
kept.
Install the pinned Java bridge/compiler and build the controller agents:
```powershell
npm ci
python tools\build_frida_agents.py
```
Frida 17 no longer bundles runtime bridges into API-loaded GumJS agents. The
compiled files under `scripts/dist/` are reproducible from the source scripts,
`package.json`, and `package-lock.json`.
On Windows, if the repository path contains spaces, create a no-space junction
and set the Bcore-only native path override:
```powershell
New-Item -ItemType Junction -Path D:\FridaBoxBuild -Target 'D:\path with spaces\FridaBox'
$env:FRIDABOX_NDK_PROJECT_DIR='D:\FridaBoxBuild\Bcore'
```
Build and test:
```powershell
.\gradlew.bat clean
.\gradlew.bat :sample-guest:assembleDebug
.\gradlew.bat :app:assembleDebug
.\gradlew.bat test
.\gradlew.bat :app:check
```
The debug host build depends on the sample build and copies its byte-identical
APK into generated debug assets. Generated sample APKs are not source-controlled.
The final host output is under `app/build/outputs/apk/debug/` and is ARM64-only.
+20
View File
@@ -0,0 +1,20 @@
# Detection surfaces
FridaBox is not undetectable. Virtualization reduces accidental identity and API
leakage but cannot reproduce a normal kernel/system installation.
- The host Linux UID remains the real UID at kernel level.
- The host SELinux domain remains visible.
- BlackBox virtual stub process names may be observed.
- Host and BlackBox classes coexist with guest classes in the ART process.
- Frida threads, mappings, sockets, and modules are observable.
- `/proc/self/maps` can reveal Frida Gadget.
- The default Frida protocol endpoint can be probed even though it is loopback-only.
- ClassLoader topology differs from a normally installed application.
- Some PackageManager, ActivityManager, and other Binder responses are synthesized.
- The system-side PackageManager does not know the guest package.
- The guest shares the host UID and process sandbox rather than receiving a
system-assigned package UID.
- Play Integrity and hardware-backed attestation cannot be faithfully virtualized.
No app-specific anti-Frida or anti-virtualization bypasses are included.
+38
View File
@@ -0,0 +1,38 @@
# Frida connection
Install the pinned controller binding:
```text
python -m pip install -r tools/requirements.txt
npm ci
python tools/build_frida_agents.py
```
The Gadget listens only on device loopback. The controller verifies ADB, selects
an authorized device, forwards TCP 27042–27073, probes each endpoint, rejects
non-Gadget/non-FridaBox endpoints, and maps endpoints through
`GuestRuntimeRegistry`.
Frida 17 Java agents explicitly import `frida-java-bridge`. The controller
loads the compiled probe/bootstrap from `scripts/dist/`; when a source path such
as `scripts/sample-hook.js` has a compiled counterpart, that bundle is selected
automatically.
```text
python tools/attach_guest.py --list
python tools/attach_guest.py --package PACKAGE
python tools/attach_guest.py --package PACKAGE --process PROCESS
python tools/attach_guest.py --package PACKAGE --script scripts/example.js
python tools/attach_guest.py --package PACKAGE --script scripts/example.js --keep-alive
```
`guest-bootstrap.js` loads before the user script and assigns the registry's
guest ClassLoader to `Java.classFactory.loader`. Its RPC exports are `info`,
`useclass`, `enumerateloadedclasses`, and `enumeratemodules`. It retries for a
bounded ten seconds when the ClassLoader is temporarily unavailable.
The controller prints the registry JSON, selected ClassLoader, and native-module
enumeration before loading the user script.
Use `tools/forward_frida_ports.py` when only port forwarding is needed. A client
major-version mismatch prints the exact `pip install frida==17.16.0` repair
command.
+25
View File
@@ -0,0 +1,25 @@
# Limitations
The MVP targets ordinary single-file APKs on ARM64 Android 12 through Android 16.
Compatibility varies because Android hidden APIs and vendor framework behavior
change, and virtualization is observable.
Explicitly unsupported or excluded:
- split APK sets and App Bundles (`.apks`, `.xapk`, `.apkm`);
- 32-bit-only native APKs and x86 guests;
- system apps, privileged permissions, or Play Store inside the container;
- full Google Play Services compatibility and Play Integrity;
- hardware-backed keystore identity or attestation emulation;
- reliable `isolatedProcess=true`, WebView renderer sandbox, app zygote, or
external-service instrumentation outside the host UID;
- reliable operation for all banking/RASP applications;
- perfect anti-virtualization or anti-instrumentation resistance.
Normal guest `android:process=":remote"` components routed through BlackBox's
`BActivityThread` receive a best-effort independent Gadget load. Each process
starts at 27042 and relies on `pick-next` for conflicts.
The static config always uses `on_load=wait`; changing the displayed base port
does not rewrite the packaged Gadget configuration in this MVP. The controller's
base/count options must match the port range used for discovery.
+70
View File
@@ -0,0 +1,70 @@
# Test results
Validation date: 2026-07-19
## Host validation
The following commands completed successfully on the build host:
```powershell
.\gradlew.bat clean
.\gradlew.bat :sample-guest:assembleDebug
.\gradlew.bat :app:assembleDebug
.\gradlew.bat test
.\gradlew.bat :app:check
.\gradlew.bat :app:verifyDebugApkFridaPackaging :app:verifyInstrumentationOrdering :app:verifyDemoGuestUnmodified
npm ci
python tools\build_frida_agents.py
python -m py_compile tools\attach_guest.py tools\forward_frida_ports.py tools\fetch_frida_gadget.py tools\build_frida_agents.py
```
For this workspace path, native builds used:
```powershell
$env:FRIDABOX_NDK_PROJECT_DIR='D:\FridaBoxBuild\Bcore'
```
Unit-test results: 16 executions passed, with zero failures, errors, or skips.
This is eight test methods run for both debug and release variants:
- `ApkInspectorTest`: 3 per variant;
- `ApkIntegrityTest`: 1 per variant;
- `InstrumentationPreferenceParserTest`: 2 per variant;
- `GuestRuntimeRegistryTest`: 2 per variant.
The final APK and custom verification tasks passed:
- output: `app/build/outputs/apk/debug/FridaBox_4.0.0_arm64-v8a-debug.apk`;
- size: 19,977,904 bytes;
- SHA-256: `a87af38ff7f4a54d2cdc0207ac68319a1a05ab8067b7acdeb057c6c967ed2573`;
- packaged ABIs: ARM64 only;
- packaged native files: `libblackbox.so`, `libfrida-gadget.so`, and
`libfrida-gadget.config.so` under `lib/arm64-v8a/`;
- Frida Gadget 17.16.0 SHA-256:
`6bf149e5d1c5ec701e7b822cab57bb243f1c2a03318fa974fe373ee711a9ed9e`;
- early-load ordering and byte-identical demo-asset checks passed.
- pinned Frida 17 registry/bootstrap/sample agents rebuilt successfully, and
controller help plus empty-range discovery completed successfully.
The build host provided JDK 24.0.1 and complete NDK 29.0.14206865 rather than
the requested JDK 21 and NDK 29.0.13846066. These substitutions and the
baseline build investigation are recorded in `docs/BASELINE.md`.
## Device validation
Runtime validation passed on a Samsung SM-S928B running ARM64 Android 16/API 36:
- latest host APK installed successfully;
- the sample installed only in BlackBox and was absent from Android user 0's
real PackageManager;
- Gadget paused startup before `SampleApplication.attachBaseContext` and
`onCreate`;
- the controller mapped port 27042 to the sample package/process and reported
user ID 0, virtual process ID 0, source APK, and guest `PathClassLoader`;
- native enumeration returned 419 modules;
- `sample-hook.js` changed `Target.add(2, 3)` from 5 to 1337;
- launching without instrumentation recycled the process, opened no Gadget
listener, and restored the visible result to 5.
The command and log transcript, including two device-discovered fixes, is in
`docs/device-validation.log`.
+27
View File
@@ -0,0 +1,27 @@
# Usage
1. Install and open the FridaBox host on an ARM64 Android 12–16 research device.
2. Tap **Import APK** and select one base `.apk` through the system document
picker. `.apks`, `.xapk`, `.apkm`, split-only packages, and 32-bit-only native
APKs are rejected.
3. Review package, version, SHA-256, private source path, and ABI status.
4. Tap **Launch instrumented**. Startup intentionally pauses before the guest
`Application` is created.
5. Run the attach command shown on **Runtime status**. Attach and load hooks.
6. Use **Launch without instrumentation** for a clean virtual process where the
Gadget is not loaded. FridaBox stops the package before switching modes.
Debug builds expose **Install demo guest**. The action installs the generated
`com.qm4rs.fridabox.sample` APK only into BlackBox. Then run:
```text
npm ci
python tools/build_frida_agents.py
python tools/attach_guest.py --package com.qm4rs.fridabox.sample --script scripts/sample-hook.js --keep-alive
```
After startup resumes, press the sample button. The visible result should be
`1337`, demonstrating that the guest ClassLoader was selected.
**Clear virtual app data** and **Remove from virtual space** affect only the
BlackBox virtual environment. They do not invoke Android's real package manager.
+163
View File
@@ -0,0 +1,163 @@
FridaBox device validation transcript
Date: 2026-07-19 (Asia/Tehran)
Device: Samsung SM-S928B, serial R5CY149SZEX
1. Device and host installation
> adb devices -l
R5CY149SZEX device product:e3qxxx model:SM_S928B device:e3q transport_id:3
> adb shell getprop ro.product.cpu.abilist
arm64-v8a
> adb shell getprop ro.build.version.release
16
> adb shell getprop ro.build.version.sdk
36
> $env:FRIDABOX_NDK_PROJECT_DIR='D:\FridaBoxBuild\Bcore'; .\gradlew.bat :app:assembleDebug --console=plain
BUILD SUCCESSFUL
> adb install -r app\build\outputs\apk\debug\FridaBox_4.0.0_arm64-v8a-debug.apk
Performing Streamed Install
Success
> adb shell am start --user 0 -n com.qm4rs.fridabox/top.niunaijun.blackboxa.view.main.WelcomeActivity
Starting: Intent { cmp=com.qm4rs.fridabox/top.niunaijun.blackboxa.view.main.WelcomeActivity }
The debug UI action "Install demo guest" installed the generated asset into
BlackBox. The UI reported:
package: com.qm4rs.fridabox.sample
version: 1.0
SHA-256: 35fa3a6d679ae0b0a18635756e1d1a23b5340d12e480eb8600768ab60af0e95e
source: /data/user/0/com.qm4rs.fridabox/files/imported-apks/sample-guest.apk
ABI: Pure Java/Kotlin (accepted)
target SDK: 28
> adb shell pm list packages --user 0 com.qm4rs.fridabox
package:com.qm4rs.fridabox
> adb shell pm list packages --user 0 com.qm4rs.fridabox.sample
<no output>
Result: the sample guest is absent from Android user 0's real PackageManager.
Samsung has an inaccessible user 150, so all PackageManager proof commands
explicitly use --user 0.
2. Pre-attach pause
The UI selected "Launch instrumented" and confirmed the early-instrumentation
warning. Before running the controller:
> adb shell ps -A | Select-String fridabox.sample
u0_a524 31797 1675 ... S com.qm4rs.fridabox.sample
> adb shell ss -ltn | Select-String 27042
LISTEN 0 0 127.0.0.1:27042 0.0.0.0:*
> adb logcat -d -v threadtime -s FridaBox.Gadget:I FridaBox.Sample:I *:S
07-19 23:11:00.878 31797 31797 I FridaBox.Gadget: Loading Frida Gadget for com.qm4rs.fridabox.sample
There was no FridaBox.Sample attachBaseContext or onCreate line. The guest main
thread was paused inside System.loadLibrary before makeApplication.
3. Controller discovery, registry, ClassLoader and native modules
> python -m pip install -r tools\requirements.txt
Successfully installed frida-17.16.0
> npm ci
Pinned packages installed from package-lock.json.
> python tools\build_frida_agents.py
Built Frida agents: scripts/dist/registry-probe.js,
scripts/dist/guest-bootstrap.js, scripts/dist/sample-hook.js
> python -u tools\attach_guest.py --package com.qm4rs.fridabox.sample --script scripts\sample-hook.js --keep-alive
PORT GUEST PACKAGE GUEST PROCESS VPID SOURCE APK
27042 com.qm4rs.fridabox.sample com.qm4rs.fridabox.sample 0 /data/user/0/com.qm4rs.fridabox/blackbox/data/app/com.qm4rs.fridabox.sample/base.apk
[FridaBox] package=com.qm4rs.fridabox.sample
[FridaBox] process=com.qm4rs.fridabox.sample
[FridaBox] userId=0
[FridaBox] virtualProcessId=0
[FridaBox] source=/data/user/0/com.qm4rs.fridabox/blackbox/data/app/com.qm4rs.fridabox.sample/base.apk
[FridaBox] ClassLoader=dalvik.system.PathClassLoader[DexPathList[[zip file "/data/user/0/com.qm4rs.fridabox/blackbox/data/app/com.qm4rs.fridabox.sample/base.apk"],nativeLibraryDirectories=[/data/user/0/com.qm4rs.fridabox/blackbox/data/app/com.qm4rs.fridabox.sample/lib, /data/user/0/com.qm4rs.fridabox/blackbox/data/app/com.qm4rs.fridabox.sample/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64]]]
GuestRuntimeRegistry: {"initializedAt": 1784490060872, "instrumentationEnabled": true, "lastError": null, "package": "com.qm4rs.fridabox.sample", "process": "com.qm4rs.fridabox.sample", "sourceDir": "/data/user/0/com.qm4rs.fridabox/blackbox/data/app/com.qm4rs.fridabox.sample/base.apk", "userId": 0, "virtualProcessId": 0}
Native modules: 419
app_process64 @ 0x5e4d8a8000 /system/bin/app_process64
linker64 @ 0x7d37dc1000 /apex/com.android.runtime/bin/linker64
libandroid_runtime.so @ 0x7cf688b000 /system/lib64/libandroid_runtime.so
libbinder.so @ 0x7d0ae9a000 /system/lib64/libbinder.so
libnativeloader.so @ 0x7d2fbc1000 /apex/com.android.art/lib64/libnativeloader.so
[sample-hook] installed for com.qm4rs.fridabox.sample
Attached on port 27042 to com.qm4rs.fridabox.sample / com.qm4rs.fridabox.sample
Post-attach lifecycle evidence:
07-19 23:11:44.774 31797 31797 I FridaBox.Gadget: Frida Gadget loaded
07-19 23:11:44.782 31797 31797 I FridaBox.Sample: Application.attachBaseContext package=com.qm4rs.fridabox.sample
07-19 23:11:44.787 31797 31797 I FridaBox.Sample: Application.onCreate package=com.qm4rs.fridabox.sample
This ordering proves the Application lifecycle resumed only after controller
attachment.
4. Java hook proof
The validation tapped the visible "CALL TARGET.ADD(2, 3)" button while the
controller session remained attached.
Controller output:
[sample-hook] Target.add(2, 3) => 1337
UI hierarchy output:
<node text="Result: 1337" class="android.widget.TextView" ... />
5. Recycled launch without instrumentation
The previous instrumented process was PID 31797. The controller detached, the
UI returned Home, and "Launch without instrumentation" stopped/recycled the
virtual process before launch.
> adb shell ps -A | Select-String fridabox.sample
u0_a524 32298 1675 ... S com.qm4rs.fridabox.sample
> adb shell ss -ltn | Select-String 27042
<no listener>
07-19 23:12:47.821 32298 32298 I FridaBox.Gadget: Instrumentation disabled for this guest process
07-19 23:12:47.826 32298 32298 I FridaBox.Sample: Application.attachBaseContext package=com.qm4rs.fridabox.sample
07-19 23:12:47.828 32298 32298 I FridaBox.Sample: Application.onCreate package=com.qm4rs.fridabox.sample
The same button then produced:
<node text="Result: 5" class="android.widget.TextView" ... />
6. Device-specific failures found and fixed
Samsung Android 16 initially froze the BlackBox :black system-service process:
libbinder.IPCThreadState: Transaction failed because process frozen.
Binder transaction failure: BR_FROZEN_REPLY
android.os.DeadObjectException at
IBPackageManagerService$Stub$Proxy.installPackageAsUser(...)
Fix: enable the existing internal DaemonService for FridaBox. Verification:
DaemonService: Foreground service started successfully
DaemonService: DaemonService started successfully
No subsequent BR_FROZEN_REPLY occurred and the virtual package remained
available across host/activity restarts.
The first Frida 17 API-loaded probe failed with:
Port 27042 rejected: ReferenceError: 'Java' is not defined
Fix: explicitly import frida-java-bridge 7.0.13 and reproducibly compile all
Java agents with frida-compile 19.0.5. The early registry probe also uses
Java.performNow(), avoiding a deadlock with Gadget's on_load=wait main thread.
Final result: all required sample runtime checks passed on ARM64 Android 16.