mirror of
https://github.com/QM4RS/FridaBox.git
synced 2026-09-29 20:42:28 +02:00
feat: ship commercial FridaBox workspace
This commit is contained in:
@@ -13,6 +13,7 @@ android {
|
||||
|
||||
|
||||
aidlPackagedList "android/app/IServiceConnection.aidl"
|
||||
aidlPackagedList "android/app/IBinderSession.aidl"
|
||||
aidlPackagedList "android/accounts/IAccountManagerResponse.aidl"
|
||||
buildFeatures {
|
||||
aidl true
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package android.app;
|
||||
|
||||
/** @hide */
|
||||
interface IBinderSession {
|
||||
}
|
||||
@@ -18,9 +18,10 @@
|
||||
package android.app;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.app.IBinderSession;
|
||||
|
||||
/** @hide */
|
||||
interface IServiceConnection {
|
||||
void connected(in ComponentName name, IBinder service);
|
||||
void connected(in ComponentName name, IBinder service, IBinderSession session, boolean dead);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package black.android.app;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.IBinder;
|
||||
|
||||
import top.niunaijun.blackreflection.annotation.BClassName;
|
||||
import top.niunaijun.blackreflection.annotation.BMethod;
|
||||
|
||||
@BClassName("android.app.IServiceConnection")
|
||||
public interface IServiceConnectionL {
|
||||
@BMethod
|
||||
void connected(ComponentName ComponentName0, IBinder IBinder1);
|
||||
}
|
||||
+13
-2
@@ -1,6 +1,7 @@
|
||||
package top.niunaijun.blackbox.fake.delegate;
|
||||
|
||||
import android.app.IServiceConnection;
|
||||
import android.app.IBinderSession;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
@@ -9,6 +10,7 @@ import android.os.RemoteException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import black.android.app.BRIServiceConnectionL;
|
||||
import black.android.app.BRIServiceConnectionO;
|
||||
import top.niunaijun.blackbox.utils.compat.BuildCompat;
|
||||
|
||||
@@ -48,7 +50,6 @@ public class ServiceConnectionDelegate extends IServiceConnection.Stub {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connected(ComponentName name, IBinder service) throws RemoteException {
|
||||
connected(name, service, false);
|
||||
}
|
||||
@@ -57,7 +58,17 @@ public class ServiceConnectionDelegate extends IServiceConnection.Stub {
|
||||
if (BuildCompat.isOreo()) {
|
||||
BRIServiceConnectionO.get(mConn).connected(mComponentName, service, dead);
|
||||
} else {
|
||||
mConn.connected(name, service);
|
||||
BRIServiceConnectionL.get(mConn).connected(mComponentName, service);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connected(ComponentName name, IBinder service, IBinderSession session, boolean dead)
|
||||
throws RemoteException {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 36) {
|
||||
mConn.connected(mComponentName, service, session, dead);
|
||||
} else {
|
||||
connected(name, service, dead);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package top.niunaijun.blackbox.instrumentation;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** Loads Frida Gadget at most once in the current Linux process. */
|
||||
@@ -25,8 +26,17 @@ public final class FridaGadgetLoader {
|
||||
if (!ATTEMPTED.compareAndSet(false, true)) return false;
|
||||
try {
|
||||
InstrumentationStatusStore.recordBinding();
|
||||
Log.i(TAG, "Loading Frida Gadget for " + GuestRuntimeRegistry.getGuestProcessName());
|
||||
System.loadLibrary("frida-gadget");
|
||||
String packageName = GuestRuntimeRegistry.getGuestPackageName();
|
||||
String mode = InstrumentationSettings.getModeForPackage(packageName);
|
||||
if (InstrumentationSettings.MODE_LOCAL_SCRIPT.equals(mode)) {
|
||||
String scriptPath = InstrumentationSettings.getScriptPathForPackage(packageName);
|
||||
File runtime = LocalScriptGadgetRuntime.prepare(packageName, scriptPath);
|
||||
Log.i(TAG, "Loading on-device Frida agent for " + GuestRuntimeRegistry.getGuestProcessName());
|
||||
System.load(runtime.getAbsolutePath());
|
||||
} else {
|
||||
Log.i(TAG, "Loading Frida Gadget listener for " + GuestRuntimeRegistry.getGuestProcessName());
|
||||
System.loadLibrary("frida-gadget");
|
||||
}
|
||||
loaded = true;
|
||||
InstrumentationStatusStore.recordLoaded();
|
||||
Log.i(TAG, "Frida Gadget loaded");
|
||||
|
||||
+53
-4
@@ -13,6 +13,12 @@ public final class InstrumentationSettings {
|
||||
public static final String KEY_SCAN_COUNT = "frida_port_scan_count";
|
||||
public static final String KEY_ADVANCED_LOGS = "show_advanced_logs";
|
||||
private static final String PACKAGE_PREFIX = "package_enabled_";
|
||||
private static final String PACKAGE_MODE_PREFIX = "package_mode_";
|
||||
private static final String PACKAGE_SCRIPT_PREFIX = "package_script_";
|
||||
|
||||
public static final String MODE_COMPUTER = "computer";
|
||||
public static final String MODE_LOCAL_SCRIPT = "local_script";
|
||||
public static final String MODE_CLEAN = "clean";
|
||||
|
||||
private InstrumentationSettings() {
|
||||
}
|
||||
@@ -27,13 +33,56 @@ public final class InstrumentationSettings {
|
||||
}
|
||||
|
||||
public static boolean isEnabledForPackage(String packageName) {
|
||||
SharedPreferences preferences = preferences();
|
||||
return preferences.getBoolean(KEY_ENABLED, true)
|
||||
&& preferences.getBoolean(PACKAGE_PREFIX + packageName, true);
|
||||
return preferences().getBoolean(KEY_ENABLED, true)
|
||||
&& !MODE_CLEAN.equals(getModeForPackage(packageName));
|
||||
}
|
||||
|
||||
public static void setEnabledForPackage(String packageName, boolean enabled) {
|
||||
preferences().edit().putBoolean(PACKAGE_PREFIX + packageName, enabled).commit();
|
||||
setModeForPackage(packageName, enabled ? MODE_COMPUTER : MODE_CLEAN);
|
||||
}
|
||||
|
||||
public static String getModeForPackage(String packageName) {
|
||||
SharedPreferences preferences = preferences();
|
||||
String mode = preferences.getString(PACKAGE_MODE_PREFIX + packageName, null);
|
||||
if (isValidMode(mode)) return mode;
|
||||
return preferences.getBoolean(PACKAGE_PREFIX + packageName, true)
|
||||
? MODE_COMPUTER : MODE_CLEAN;
|
||||
}
|
||||
|
||||
public static void setModeForPackage(String packageName, String mode) {
|
||||
String safeMode = isValidMode(mode) ? mode : MODE_COMPUTER;
|
||||
preferences().edit()
|
||||
.putString(PACKAGE_MODE_PREFIX + packageName, safeMode)
|
||||
.putBoolean(PACKAGE_PREFIX + packageName, !MODE_CLEAN.equals(safeMode))
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static String getScriptPathForPackage(String packageName) {
|
||||
return preferences().getString(PACKAGE_SCRIPT_PREFIX + packageName, null);
|
||||
}
|
||||
|
||||
public static void setScriptPathForPackage(String packageName, String path) {
|
||||
SharedPreferences.Editor editor = preferences().edit();
|
||||
if (path == null || path.trim().isEmpty()) {
|
||||
editor.remove(PACKAGE_SCRIPT_PREFIX + packageName);
|
||||
} else {
|
||||
editor.putString(PACKAGE_SCRIPT_PREFIX + packageName, path);
|
||||
}
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public static void clearPackage(String packageName) {
|
||||
preferences().edit()
|
||||
.remove(PACKAGE_PREFIX + packageName)
|
||||
.remove(PACKAGE_MODE_PREFIX + packageName)
|
||||
.remove(PACKAGE_SCRIPT_PREFIX + packageName)
|
||||
.commit();
|
||||
}
|
||||
|
||||
private static boolean isValidMode(String mode) {
|
||||
return MODE_COMPUTER.equals(mode)
|
||||
|| MODE_LOCAL_SCRIPT.equals(mode)
|
||||
|| MODE_CLEAN.equals(mode);
|
||||
}
|
||||
|
||||
public static int getBasePort() {
|
||||
|
||||
+24
-3
@@ -17,21 +17,42 @@ public final class InstrumentationStatusStore {
|
||||
|
||||
public static void recordBinding() {
|
||||
String packageName = GuestRuntimeRegistry.getGuestPackageName();
|
||||
String mode = InstrumentationSettings.getModeForPackage(packageName);
|
||||
String state;
|
||||
if (!GuestRuntimeRegistry.isInstrumentationEnabled()) {
|
||||
state = "disabled";
|
||||
} else if (InstrumentationSettings.MODE_LOCAL_SCRIPT.equals(mode)) {
|
||||
state = "loading_local_script";
|
||||
} else {
|
||||
state = "waiting_for_attach";
|
||||
}
|
||||
preferences().edit()
|
||||
.putString("runtime_package", packageName)
|
||||
.putString("runtime_process", GuestRuntimeRegistry.getGuestProcessName())
|
||||
.putInt("runtime_user_id", GuestRuntimeRegistry.getGuestUserId())
|
||||
.putInt("runtime_vpid", GuestRuntimeRegistry.getVirtualProcessId())
|
||||
.putString("runtime_source", GuestRuntimeRegistry.getGuestSourceDir())
|
||||
.putString("runtime_class_loader", classLoaderDescription())
|
||||
.putBoolean("runtime_enabled", GuestRuntimeRegistry.isInstrumentationEnabled())
|
||||
.putString("runtime_state", GuestRuntimeRegistry.isInstrumentationEnabled()
|
||||
? "waiting_for_attach" : "disabled")
|
||||
.putString("runtime_mode", mode)
|
||||
.putString("runtime_script", InstrumentationSettings.getScriptPathForPackage(packageName))
|
||||
.putString("runtime_state", state)
|
||||
.putString("runtime_error", null)
|
||||
.putLong("runtime_timestamp", GuestRuntimeRegistry.getInitializationTimestamp())
|
||||
.commit();
|
||||
}
|
||||
|
||||
private static String classLoaderDescription() {
|
||||
ClassLoader loader = GuestRuntimeRegistry.getGuestClassLoader();
|
||||
if (loader == null) return null;
|
||||
return loader.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(loader));
|
||||
}
|
||||
|
||||
public static void recordLoaded() {
|
||||
preferences().edit().putString("runtime_state", "loaded").putString("runtime_error", null).commit();
|
||||
String mode = preferences().getString("runtime_mode", InstrumentationSettings.MODE_COMPUTER);
|
||||
String state = InstrumentationSettings.MODE_LOCAL_SCRIPT.equals(mode)
|
||||
? "local_script_active" : "computer_attached";
|
||||
preferences().edit().putString("runtime_state", state).putString("runtime_error", null).commit();
|
||||
}
|
||||
|
||||
public static void recordError(String error) {
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package top.niunaijun.blackbox.instrumentation;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import top.niunaijun.blackbox.BlackBoxCore;
|
||||
|
||||
/** Prepares a private Gadget copy configured to autonomously load one guest agent. */
|
||||
final class LocalScriptGadgetRuntime {
|
||||
private static final String AGENT_ROOT = "fridabox-agents";
|
||||
private static final String AGENT_NAME = "agent.js";
|
||||
private static final String RUNTIME_NAME = "libfridabox-agent.so";
|
||||
private static final String CONFIG_NAME = "libfridabox-agent.config.so";
|
||||
|
||||
private LocalScriptGadgetRuntime() {
|
||||
}
|
||||
|
||||
static File prepare(String packageName, String scriptPath) throws IOException {
|
||||
Context context = BlackBoxCore.getContext();
|
||||
if (scriptPath == null || scriptPath.trim().isEmpty()) {
|
||||
throw new IOException("No on-device JavaScript agent is selected");
|
||||
}
|
||||
File root = new File(context.getFilesDir(), AGENT_ROOT).getCanonicalFile();
|
||||
File script = new File(scriptPath).getCanonicalFile();
|
||||
if (!isInside(root, script) || !AGENT_NAME.equals(script.getName()) || !script.isFile()) {
|
||||
throw new IOException("Selected JavaScript agent is outside FridaBox private storage");
|
||||
}
|
||||
if (!script.setReadable(true, true) || !script.setWritable(false, false)) {
|
||||
throw new IOException("Unable to secure the selected JavaScript agent");
|
||||
}
|
||||
|
||||
File directory = script.getParentFile();
|
||||
File source = new File(context.getApplicationInfo().nativeLibraryDir, "libfrida-gadget.so");
|
||||
if (!source.isFile()) throw new IOException("Packaged Frida Gadget is missing");
|
||||
|
||||
File runtime = new File(directory, RUNTIME_NAME);
|
||||
if (!runtime.isFile() || runtime.length() != source.length()) {
|
||||
copyAtomically(source, runtime);
|
||||
}
|
||||
if (!runtime.setReadable(true, false)
|
||||
|| !runtime.setExecutable(true, false)
|
||||
|| !runtime.setWritable(false, false)) {
|
||||
throw new IOException("Unable to secure private Frida Gadget permissions");
|
||||
}
|
||||
|
||||
File config = new File(directory, CONFIG_NAME);
|
||||
writeUtf8Atomically(config, buildConfig(packageName));
|
||||
return runtime;
|
||||
}
|
||||
|
||||
static String buildConfig(String packageName) {
|
||||
return "{\n" +
|
||||
" \"interaction\": {\n" +
|
||||
" \"type\": \"script\",\n" +
|
||||
" \"path\": \"" + AGENT_NAME + "\",\n" +
|
||||
" \"on_change\": \"reload\",\n" +
|
||||
" \"parameters\": { \"package\": \"" + json(packageName) + "\" }\n" +
|
||||
" },\n" +
|
||||
" \"runtime\": \"qjs\",\n" +
|
||||
" \"teardown\": \"minimal\"\n" +
|
||||
"}\n";
|
||||
}
|
||||
|
||||
static boolean isInside(File root, File child) {
|
||||
String rootPath = root.getAbsolutePath();
|
||||
String childPath = child.getAbsolutePath();
|
||||
return childPath.startsWith(rootPath + File.separator);
|
||||
}
|
||||
|
||||
private static String json(String value) {
|
||||
if (value == null) return "";
|
||||
return value.replace("\\", "\\\\").replace("\"", "\\\"")
|
||||
.replace("\n", "\\n").replace("\r", "\\r");
|
||||
}
|
||||
|
||||
private static void copyAtomically(File source, File destination) throws IOException {
|
||||
File temporary = new File(destination.getParentFile(), destination.getName() + ".partial");
|
||||
if (temporary.exists() && !temporary.delete()) throw new IOException("Unable to replace temporary Gadget");
|
||||
try (FileInputStream input = new FileInputStream(source);
|
||||
FileOutputStream output = new FileOutputStream(temporary)) {
|
||||
byte[] buffer = new byte[128 * 1024];
|
||||
int count;
|
||||
while ((count = input.read(buffer)) >= 0) output.write(buffer, 0, count);
|
||||
output.getFD().sync();
|
||||
}
|
||||
replace(temporary, destination);
|
||||
}
|
||||
|
||||
private static void writeUtf8Atomically(File destination, String value) throws IOException {
|
||||
byte[] expected = value.getBytes(StandardCharsets.UTF_8);
|
||||
if (destination.isFile() && destination.length() == expected.length) {
|
||||
byte[] current = new byte[expected.length];
|
||||
try (FileInputStream input = new FileInputStream(destination)) {
|
||||
if (input.read(current) == current.length && java.util.Arrays.equals(current, expected)) return;
|
||||
}
|
||||
}
|
||||
File temporary = new File(destination.getParentFile(), destination.getName() + ".partial");
|
||||
try (FileOutputStream output = new FileOutputStream(temporary)) {
|
||||
output.write(expected);
|
||||
output.getFD().sync();
|
||||
}
|
||||
replace(temporary, destination);
|
||||
}
|
||||
|
||||
private static void replace(File temporary, File destination) throws IOException {
|
||||
if (destination.exists() && !destination.delete()) throw new IOException("Unable to replace " + destination.getName());
|
||||
if (!temporary.renameTo(destination)) throw new IOException("Unable to install " + destination.getName());
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package top.niunaijun.blackbox.instrumentation;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class LocalScriptGadgetRuntimeTest {
|
||||
@Test
|
||||
public void configUsesAutonomousScriptInteraction() {
|
||||
String config = LocalScriptGadgetRuntime.buildConfig("sample.\"guest");
|
||||
assertTrue(config.contains("\"type\": \"script\""));
|
||||
assertTrue(config.contains("\"path\": \"agent.js\""));
|
||||
assertTrue(config.contains("sample.\\\"guest"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void privatePathCheckRejectsSiblingPrefix() {
|
||||
File root = new File("/data/user/0/host/files/fridabox-agents");
|
||||
assertTrue(LocalScriptGadgetRuntime.isInside(root, new File(root, "guest/agent.js")));
|
||||
assertFalse(LocalScriptGadgetRuntime.isInside(root, new File(root.getPath() + "-other/agent.js")));
|
||||
}
|
||||
}
|
||||
+25
-1
@@ -3,6 +3,16 @@ plugins {
|
||||
alias(libs.plugins.jetbrains.kotlin.android)
|
||||
}
|
||||
|
||||
def releaseStoreFile = System.getenv("FRIDABOX_RELEASE_STORE_FILE")
|
||||
def releaseStorePassword = System.getenv("FRIDABOX_RELEASE_STORE_PASSWORD")
|
||||
def releaseKeyAlias = System.getenv("FRIDABOX_RELEASE_KEY_ALIAS")
|
||||
def releaseKeyPassword = System.getenv("FRIDABOX_RELEASE_KEY_PASSWORD")
|
||||
def releaseSigningValues = [releaseStoreFile, releaseStorePassword, releaseKeyAlias, releaseKeyPassword]
|
||||
def releaseSigningConfigured = releaseSigningValues.every { it != null && !it.trim().isEmpty() }
|
||||
if (releaseSigningValues.any { it != null && !it.trim().isEmpty() } && !releaseSigningConfigured) {
|
||||
throw new GradleException("Release signing is incomplete. Set all four FRIDABOX_RELEASE_* environment variables.")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
namespace 'top.niunaijun.blackboxa'
|
||||
@@ -28,10 +38,24 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
if (releaseSigningConfigured) {
|
||||
release {
|
||||
storeFile file(releaseStoreFile)
|
||||
storePassword releaseStorePassword
|
||||
keyAlias releaseKeyAlias
|
||||
keyPassword releaseKeyPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.debug
|
||||
if (releaseSigningConfigured) {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,18 +17,24 @@
|
||||
android:allowBackup="false"
|
||||
android:extractNativeLibs="true"
|
||||
android:fullBackupContent="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:icon="@drawable/ic_fridabox_app"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:roundIcon="@drawable/ic_fridabox_app"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.BlackBox"
|
||||
android:theme="@style/Theme.FridaBox"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
tools:replace="android:allowBackup"
|
||||
tools:targetApi="n">
|
||||
<activity
|
||||
android:name=".fridabox.FridaBoxActivity"
|
||||
android:exported="false" />
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".view.fake.FollowMyLocationOverlay"
|
||||
android:exported="false" />
|
||||
@@ -36,15 +42,9 @@
|
||||
<activity android:name=".view.gms.GmsManagerActivity" />
|
||||
<activity
|
||||
android:name=".view.main.WelcomeActivity"
|
||||
android:exported="true"
|
||||
android:exported="false"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/WelcomeTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
android:theme="@style/Theme.FridaBox" />
|
||||
<activity android:name=".view.list.ListActivity" />
|
||||
<activity android:name=".view.fake.FakeManagerActivity" />
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<gradient android:angle="315" android:startColor="#1A2942" android:centerColor="#141D30" android:endColor="#111827" />
|
||||
<corners android:radius="28dp" />
|
||||
<stroke android:width="1dp" android:color="#334862" />
|
||||
</shape>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FF05070B" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFFFF" android:pathData="M3,13h4l2.4,-7.2L14,20l3,-7h4v-2h-5.3l-1.5,3.5L9.6,1L5.5,11H3z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFFFF" android:pathData="M19.4,13a7.7,7.7 0,0 0,0.1 -1,7.7 7.7,0 0,0 -0.1,-1l2.1,-1.7 -2,-3.4 -2.5,1a8,8 0,0 0,-1.7 -1L15,3h-4l-0.4,2.8a8,8 0,0 0,-1.7 1l-2.5,-1 -2,3.4L6.6,11a7.7,7.7 0,0 0,-0.1 1,7.7 7.7,0 0,0 0.1,1l-2.1,1.7 2,3.4 2.5,-1a8,8 0,0 0,1.7 1L11,21h4l0.4,-2.8a8,8 0,0 0,1.7 -1l2.5,1 2,-3.4zM13,15.5a3.5,3.5 0,1 1,0 -7,3.5 3.5,0 0,1 0,7z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFFFF" android:pathData="M3,3h8v8H3zM13,3h8v5h-8zM13,10h8v11h-8zM3,13h8v8H3z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108">
|
||||
<path android:fillColor="#080C14" android:pathData="M18,4 H90 C97.7,4 104,10.3 104,18 V90 C104,97.7 97.7,104 90,104 H18 C10.3,104 4,97.7 4,90 V18 C4,10.3 10.3,4 18,4 Z" />
|
||||
<path android:fillColor="#64E8E8" android:pathData="M43,26 L22,47 C18.1,50.9 18.1,57.1 22,61 L43,82 L50,75 L29,54 L50,33 Z" />
|
||||
<path android:fillColor="#9B87F5" android:pathData="M65,26 L86,47 C89.9,50.9 89.9,57.1 86,61 L65,82 L58,75 L79,54 L58,33 Z" />
|
||||
<path android:fillColor="#F8FAFC" android:pathData="M49,44 h10 v8 h-10z M49,57 h10 v8 h-10z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="32dp" android:height="32dp" android:viewportWidth="32" android:viewportHeight="32">
|
||||
<path android:fillColor="#64E8E8" android:pathData="M12.2,5.5 L4.2,13.5 C2.8,14.9 2.8,17.1 4.2,18.5 L12.2,26.5 L15,23.7 L7.3,16 L15,8.3 Z" />
|
||||
<path android:fillColor="#9B87F5" android:pathData="M19.8,5.5 L28,13.5 C29.4,14.9 29.4,17.1 28,18.5 L19.8,26.5 L17,23.7 L24.7,16 L17,8.3 Z" />
|
||||
<path android:fillColor="#F8FAFC" android:pathData="M14.2,12.2 h3.6 v3 h-3.6z M14.2,16.8 h3.6 v3 h-3.6z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/fridabox_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/fb_background"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/fb_background"
|
||||
android:elevation="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:background="@color/fb_background"
|
||||
android:contentInsetStart="18dp"
|
||||
android:contentInsetEnd="18dp"
|
||||
app:logo="@drawable/ic_fridabox_mark"
|
||||
app:logoDescription="@string/fb_brand"
|
||||
app:subtitle="@string/fb_brand_tagline"
|
||||
app:subtitleTextColor="@color/fb_text_secondary"
|
||||
app:title="@string/fb_brand"
|
||||
app:titleTextColor="@color/fb_text_primary" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/content_scroll"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:fillViewport="true"
|
||||
android:overScrollMode="never"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/app_bar">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="420dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/fb_content_margin"
|
||||
android:paddingTop="@dimen/fb_content_top"
|
||||
android:paddingEnd="@dimen/fb_content_margin"
|
||||
android:paddingBottom="@dimen/fb_content_bottom"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_max="@dimen/fb_max_content_width" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="3dp"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
app:indicatorColor="@color/fb_primary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/app_bar"
|
||||
app:trackColor="@color/fb_surface" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/import_fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:layout_marginBottom="18dp"
|
||||
android:letterSpacing="0"
|
||||
android:text="@string/fb_import_apk"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/fb_black"
|
||||
app:backgroundTint="@color/fb_primary"
|
||||
app:icon="@drawable/ic_fb_add"
|
||||
app:iconTint="@color/fb_black"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
android:id="@+id/bottom_navigation"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="72dp"
|
||||
android:background="@color/fb_surface"
|
||||
app:itemActiveIndicatorStyle="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
|
||||
app:itemIconTint="@color/fb_text_primary"
|
||||
app:itemTextColor="@color/fb_text_primary"
|
||||
app:labelVisibilityMode="labeled"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:menu="@menu/fridabox_navigation" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/nav_workspace" android:icon="@drawable/ic_fb_workspace" android:title="@string/fb_nav_workspace" />
|
||||
<item android:id="@+id/nav_runtime" android:icon="@drawable/ic_fb_runtime" android:title="@string/fb_nav_runtime" />
|
||||
<item android:id="@+id/nav_settings" android:icon="@drawable/ic_fb_settings" android:title="@string/fb_nav_settings" />
|
||||
</menu>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="fb_content_margin">32dp</dimen>
|
||||
<dimen name="fb_content_top">28dp</dimen>
|
||||
<dimen name="fb_content_bottom">120dp</dimen>
|
||||
<dimen name="fb_card_radius">24dp</dimen>
|
||||
<dimen name="fb_max_content_width">920dp</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="fb_background">#080C14</color>
|
||||
<color name="fb_surface">#111827</color>
|
||||
<color name="fb_surface_high">#182235</color>
|
||||
<color name="fb_surface_tint">#1D2940</color>
|
||||
<color name="fb_primary">#64E8E8</color>
|
||||
<color name="fb_primary_dark">#1AAFB5</color>
|
||||
<color name="fb_secondary">#9B87F5</color>
|
||||
<color name="fb_text_primary">#F8FAFC</color>
|
||||
<color name="fb_text_secondary">#9CAEC5</color>
|
||||
<color name="fb_outline">#2B3A52</color>
|
||||
<color name="fb_success">#42D39A</color>
|
||||
<color name="fb_warning">#F5BF5B</color>
|
||||
<color name="fb_error">#FB7185</color>
|
||||
<color name="fb_black">#05070B</color>
|
||||
<color name="fb_transparent">#00000000</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="fb_content_margin">18dp</dimen>
|
||||
<dimen name="fb_content_top">20dp</dimen>
|
||||
<dimen name="fb_content_bottom">112dp</dimen>
|
||||
<dimen name="fb_card_radius">22dp</dimen>
|
||||
<dimen name="fb_max_content_width">840dp</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="fb_brand">FridaBox</string>
|
||||
<string name="fb_brand_tagline">Mobile instrumentation workspace</string>
|
||||
<string name="fb_nav_workspace">Workspace</string>
|
||||
<string name="fb_nav_runtime">Runtime</string>
|
||||
<string name="fb_nav_settings">Settings</string>
|
||||
<string name="fb_import_apk">Import APK</string>
|
||||
<string name="fb_hero_eyebrow">PRIVATE · NON-ROOT · ARM64</string>
|
||||
<string name="fb_hero_title">Instrument apps. Keep control.</string>
|
||||
<string name="fb_hero_body">Run trusted JavaScript agents directly on the device, connect from a computer, or launch a clean guest—per app.</string>
|
||||
<string name="fb_guest_workspace">Guest workspace</string>
|
||||
<string name="fb_guest_workspace_hint">Each guest keeps its own launch mode and agent.</string>
|
||||
<string name="fb_no_guests">Your workspace is empty</string>
|
||||
<string name="fb_no_guests_body">Import one ARM64 base APK to create a private FridaBox guest.</string>
|
||||
<string name="fb_mode_local">On-device</string>
|
||||
<string name="fb_mode_computer">Computer</string>
|
||||
<string name="fb_mode_clean">Clean</string>
|
||||
<string name="fb_mode_local_title">On-device agent</string>
|
||||
<string name="fb_mode_local_body">The selected JavaScript runs automatically before app startup. No cable or controller is required.</string>
|
||||
<string name="fb_mode_computer_title">Computer attach</string>
|
||||
<string name="fb_mode_computer_body">Startup pauses at Frida Gadget until you attach from a computer.</string>
|
||||
<string name="fb_mode_clean_title">Clean launch</string>
|
||||
<string name="fb_mode_clean_body">Starts the guest without loading Frida Gadget.</string>
|
||||
<string name="fb_select_script">Select JavaScript</string>
|
||||
<string name="fb_replace_script">Replace agent</string>
|
||||
<string name="fb_launch">Launch</string>
|
||||
<string name="fb_more">Manage</string>
|
||||
<string name="fb_no_script">No JavaScript agent selected</string>
|
||||
<string name="fb_script_ready">Agent ready</string>
|
||||
<string name="fb_runtime_title">Runtime</string>
|
||||
<string name="fb_runtime_subtitle">Latest guest process and instrumentation state</string>
|
||||
<string name="fb_settings_title">Settings</string>
|
||||
<string name="fb_settings_subtitle">Connection defaults and product information</string>
|
||||
<string name="fb_save_settings">Save settings</string>
|
||||
<string name="fb_global_instrumentation">Allow instrumentation</string>
|
||||
<string name="fb_advanced_logs">Show advanced runtime details</string>
|
||||
<string name="fb_base_port">Gadget base port</string>
|
||||
<string name="fb_scan_count">Port discovery range</string>
|
||||
<string name="fb_security_title">Run only agents you trust</string>
|
||||
<string name="fb_security_body">An on-device agent executes inside the virtual guest process and has the same access as that process. FridaBox stores a private byte-for-byte copy of the selected file.</string>
|
||||
<string name="fb_about_title">Independent by design</string>
|
||||
<string name="fb_about_body">FridaBox combines a private Android virtualization layer with Frida Gadget. It does not modify imported APKs and it does not require root.</string>
|
||||
<string name="fb_copy_command">Copy command</string>
|
||||
<string name="fb_refresh">Refresh</string>
|
||||
<string name="fb_choose_trusted_title">Choose a trusted JavaScript agent?</string>
|
||||
<string name="fb_choose_trusted_body">The file will be copied into FridaBox private storage and executed automatically whenever this guest starts in On-device mode.</string>
|
||||
<string name="fb_choose">Choose file</string>
|
||||
<string name="fb_cancel">Cancel</string>
|
||||
<string name="fb_computer_launch_title">Ready to attach from a computer?</string>
|
||||
<string name="fb_computer_launch_body">The guest will pause before its Application starts. Connect with Frida to resume it.</string>
|
||||
<string name="fb_clear_data">Clear guest data</string>
|
||||
<string name="fb_runtime_details">Runtime details</string>
|
||||
<string name="fb_remove_guest">Remove guest</string>
|
||||
<string name="fb_remove_title">Remove this guest?</string>
|
||||
<string name="fb_remove_body">The virtual app, its virtual data, and its saved on-device agent will be removed from FridaBox. The Android PackageManager installation is not changed.</string>
|
||||
<string name="fb_remove">Remove</string>
|
||||
<string name="fb_details">App details</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="Theme.FridaBox" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<item name="colorPrimary">@color/fb_primary</item>
|
||||
<item name="colorPrimaryVariant">@color/fb_primary_dark</item>
|
||||
<item name="colorOnPrimary">@color/fb_black</item>
|
||||
<item name="colorSecondary">@color/fb_secondary</item>
|
||||
<item name="colorSecondaryVariant">@color/fb_secondary</item>
|
||||
<item name="colorOnSecondary">@color/fb_black</item>
|
||||
<item name="colorSurface">@color/fb_surface</item>
|
||||
<item name="colorOnSurface">@color/fb_text_primary</item>
|
||||
<item name="colorError">@color/fb_error</item>
|
||||
<item name="android:colorAccent">@color/fb_primary</item>
|
||||
<item name="android:windowBackground">@color/fb_background</item>
|
||||
<item name="android:navigationBarColor">@color/fb_surface</item>
|
||||
<item name="android:statusBarColor">@color/fb_background</item>
|
||||
<item name="android:windowLightStatusBar">false</item>
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item>
|
||||
<item name="android:fontFamily">sans</item>
|
||||
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
|
||||
<item name="materialAlertDialogTheme">@style/ThemeOverlay.FridaBox.Dialog</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.FridaBox.Dialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
|
||||
<item name="colorSurface">@color/fb_surface_high</item>
|
||||
<item name="colorOnSurface">@color/fb_text_primary</item>
|
||||
<item name="colorPrimary">@color/fb_primary</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -52,3 +52,33 @@ Build and test:
|
||||
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.
|
||||
|
||||
## Production release
|
||||
|
||||
The release variant enables R8 optimization, code shrinking, and resource
|
||||
shrinking. It never falls back to the Android debug signing key.
|
||||
|
||||
Build an unsigned release artifact for later signing:
|
||||
|
||||
```powershell
|
||||
$env:FRIDABOX_NDK_PROJECT_DIR='D:\FridaBoxBuild\Bcore'
|
||||
.\gradlew.bat :app:assembleRelease
|
||||
```
|
||||
|
||||
For a signed production build, provide all four variables before running the
|
||||
same task:
|
||||
|
||||
```powershell
|
||||
$env:FRIDABOX_RELEASE_STORE_FILE='D:\secure\fridabox-release.jks'
|
||||
$env:FRIDABOX_RELEASE_STORE_PASSWORD='<store password>'
|
||||
$env:FRIDABOX_RELEASE_KEY_ALIAS='fridabox'
|
||||
$env:FRIDABOX_RELEASE_KEY_PASSWORD='<key password>'
|
||||
.\gradlew.bat :app:assembleRelease
|
||||
```
|
||||
|
||||
If only some signing variables are present, configuration fails instead of
|
||||
producing an ambiguously signed artifact. Keep the keystore and credentials
|
||||
outside the repository and CI logs.
|
||||
|
||||
The ARM64 release output is written to
|
||||
`app/build/outputs/apk/release/FridaBox_4.0.0_arm64-v8a-release.apk`.
|
||||
|
||||
@@ -33,6 +33,18 @@ 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.
|
||||
|
||||
## On-device mode
|
||||
|
||||
Computer-side forwarding and controller tools are not used in On-device mode.
|
||||
FridaBox creates a private Gadget copy with an adjacent Script-interaction
|
||||
configuration and a relative `agent.js` path. Gadget loads that script before
|
||||
returning to guest Application creation. The selected script is stored per
|
||||
package and reused automatically until it is replaced or the guest is removed.
|
||||
|
||||
Use Computer mode when the script needs an interactive host, RPC calls, or
|
||||
observable `send()` messages. Use On-device mode for autonomous hooks and
|
||||
in-process behavior.
|
||||
|
||||
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.
|
||||
|
||||
@@ -68,3 +68,54 @@ Runtime validation passed on a Samsung SM-S928B running ARM64 Android 16/API 36:
|
||||
|
||||
The command and log transcript, including two device-discovered fixes, is in
|
||||
`docs/device-validation.log`.
|
||||
|
||||
## Commercial workspace and per-app mode validation
|
||||
|
||||
Validation date: 2026-07-20
|
||||
|
||||
The redesigned FridaBox launcher and all three per-app modes were validated on
|
||||
the same Samsung SM-S928B, ARM64 Android 16/API 36 device with the imported
|
||||
`com.paeezanstudio.pesarkhande` 3.3.7 guest.
|
||||
|
||||
- The independent FridaBox launcher, icon, dark product theme, responsive
|
||||
workspace cards, bottom navigation, import action, and selected-mode states
|
||||
rendered correctly at 1080 x 2340.
|
||||
- `pesarkhande-agent.js` (198,960 bytes, SHA-256
|
||||
`41dd04f7a6a4b8de47fcd94ee5646f43effd8f73b36eda64d46a65f4f304fa49`)
|
||||
was selected through Android's document picker and copied without modification.
|
||||
- On-device mode loaded the private Gadget and Script configuration without a
|
||||
controller, then returned to `beforeCreateApplication`; the Unity game reached
|
||||
its interactive home screen.
|
||||
- Runtime reported `local_script_active`, package
|
||||
`com.paeezanstudio.pesarkhande`, virtual user ID 0, virtual process slot 1,
|
||||
the private source APK, and `dalvik.system.PathClassLoader`.
|
||||
- Computer mode paused before `beforeCreateApplication`. Direct
|
||||
`frida -U gadget` attachment resumed the guest and enumerated 416 native
|
||||
modules; the first five were `app_process64`, `linker64`,
|
||||
`libandroid_runtime.so`, `libbinder.so`, and `libcutils.so`.
|
||||
- Clean mode recycled the main guest PID from 26464 to 27819, emitted
|
||||
`Instrumentation disabled for this guest process`, opened no Gadget listener,
|
||||
and launched the game normally.
|
||||
- A stale cross-process SharedPreferences cache initially made Runtime display
|
||||
`Waiting for computer` for a successful on-device launch. Multi-process reload
|
||||
semantics fixed the display; the persisted state was already correct.
|
||||
- The private JavaScript file is mode 0400 and the private Gadget executable is
|
||||
mode 0555 at launch. Android 16 no longer reports the writable-executable
|
||||
warning for the FridaBox Gadget copy.
|
||||
|
||||
Final automated builds and tests passed:
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat :app:assembleDebug :app:assembleRelease :Bcore:testDebugUnitTest :app:testDebugUnitTest
|
||||
```
|
||||
|
||||
Artifacts:
|
||||
|
||||
- debug: 21,049,981 bytes, SHA-256
|
||||
`80e70b33fca741e4f805aa233cdfaf5bc6fe2030e93c8fd825611eb5c407c917`;
|
||||
- release: 13,266,764 bytes, SHA-256
|
||||
`caa2218194fcbe91c10d0d29a74b7401aaed53f340b8a0d6668321ddae48ddfb`.
|
||||
|
||||
The release artifact was intentionally unsigned because no production keystore
|
||||
was supplied. `apksigner` confirmed the debug APK verifies and the release APK
|
||||
does not contain a debug signature.
|
||||
|
||||
+47
-21
@@ -1,27 +1,53 @@
|
||||
# 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.
|
||||
FridaBox keeps every imported application inside its private virtual workspace.
|
||||
Importing an APK does not install that package into Android's real PackageManager.
|
||||
|
||||
Debug builds expose **Install demo guest**. The action installs the generated
|
||||
`com.qm4rs.fridabox.sample` APK only into BlackBox. Then run:
|
||||
## Import an application
|
||||
|
||||
```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
|
||||
```
|
||||
1. Open **Workspace** and tap **Import APK**.
|
||||
2. Select one ARM64 base `.apk` through Android's document picker.
|
||||
3. FridaBox validates the APK, records its SHA-256, and creates a private guest.
|
||||
|
||||
After startup resumes, press the sample button. The visible result should be
|
||||
`1337`, demonstrating that the guest ClassLoader was selected.
|
||||
Split-only packages (`.apks`, `.xapk`, and `.apkm`) and unsupported native ABIs
|
||||
are rejected.
|
||||
|
||||
**Clear virtual app data** and **Remove from virtual space** affect only the
|
||||
BlackBox virtual environment. They do not invoke Android's real package manager.
|
||||
## Choose a launch mode
|
||||
|
||||
Every guest remembers one of three independent modes:
|
||||
|
||||
- **On-device**: select a trusted `.js` file once. FridaBox stores a byte-for-byte
|
||||
private copy, records its SHA-256, and loads it through Frida Gadget's Script
|
||||
interaction before the guest Application starts. No cable, computer, Frida CLI,
|
||||
port forwarding, or controller process is required on later launches.
|
||||
- **Computer**: starts the loopback-only Gadget listener and pauses before the
|
||||
guest Application. Attach through the normal Frida workflow, for example:
|
||||
|
||||
```text
|
||||
frida -U gadget -l path/to/agent.js
|
||||
```
|
||||
|
||||
- **Clean**: recycles the virtual process and launches without loading Frida
|
||||
Gadget. The selected on-device agent is retained for future use.
|
||||
|
||||
Switching modes always stops the previous guest process before the next launch.
|
||||
|
||||
## On-device agents
|
||||
|
||||
Select **On-device**, tap **Select JavaScript**, review the trust warning, and
|
||||
choose a `.js` file up to 16 MiB. The original file is not modified. The private
|
||||
copy and the private Gadget executable are made read-only before execution.
|
||||
|
||||
An autonomous script has no computer-side message handler. Calls such as `send()`
|
||||
may be intentionally unobserved, while hooks, replacements, Java calls, native
|
||||
interceptors, and in-app overlays continue to run in the guest process.
|
||||
|
||||
## Runtime and management
|
||||
|
||||
The **Runtime** tab reports the latest package, process, virtual user ID, virtual
|
||||
process slot, source APK, ClassLoader, selected mode, state, and latest error.
|
||||
Use **Manage** on an app card for app details, runtime details, virtual data
|
||||
clearing, or removal from the private workspace.
|
||||
|
||||
Only run JavaScript agents you trust. An on-device agent executes with the same
|
||||
access as the virtual guest process.
|
||||
|
||||
@@ -161,3 +161,107 @@ 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.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
2026-07-20 - Commercial UI and per-app execution modes
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Device reconfirmation:
|
||||
|
||||
> adb shell getprop ro.product.cpu.abi
|
||||
arm64-v8a
|
||||
|
||||
> adb shell getprop ro.build.version.release
|
||||
16
|
||||
|
||||
> adb shell getprop ro.build.version.sdk
|
||||
36
|
||||
|
||||
> adb install -r -t app\build\outputs\apk\debug\FridaBox_4.0.0_arm64-v8a-debug.apk
|
||||
Performing Streamed Install
|
||||
Success
|
||||
|
||||
The branded launcher was inspected by screenshot and UIAutomator at 1080 x
|
||||
2340. FridaBox opened directly into Workspace; the selected mode had a distinct
|
||||
state, the header was not clipped, and Import APK did not overlap guest actions.
|
||||
|
||||
On-device agent import:
|
||||
|
||||
> adb push D:\Reverse\Game-Hacking\pesarkhande\release\pesarkhande-agent.js /sdcard/Download/pesarkhande-agent.js
|
||||
1 file pushed, 198960 bytes
|
||||
|
||||
> Get-FileHash -Algorithm SHA256 D:\Reverse\Game-Hacking\pesarkhande\release\pesarkhande-agent.js
|
||||
41dd04f7a6a4b8de47fcd94ee5646f43effd8f73b36eda64d46a65f4f304fa49
|
||||
|
||||
The UI selected On-device -> Select JavaScript -> pesarkhande-agent.js and
|
||||
displayed the same SHA-256 prefix. The private configuration was:
|
||||
|
||||
{
|
||||
"interaction": {
|
||||
"type": "script",
|
||||
"path": "agent.js",
|
||||
"on_change": "reload",
|
||||
"parameters": { "package": "com.paeezanstudio.pesarkhande" }
|
||||
},
|
||||
"runtime": "qjs",
|
||||
"teardown": "minimal"
|
||||
}
|
||||
|
||||
Autonomous launch evidence (no Frida client was attached):
|
||||
|
||||
07-20 02:46:59.510 760 760 I FridaBox.Gadget: Loading on-device Frida agent for com.paeezanstudio.pesarkhande
|
||||
07-20 02:46:59.562 760 760 D nativeloader: Load /data/user/0/com.qm4rs.fridabox/files/fridabox-agents/com.paeezanstudio.pesarkhande/libfridabox-agent.so ...: ok
|
||||
07-20 02:46:59.569 760 760 I FridaBox.Gadget: Frida Gadget loaded
|
||||
07-20 02:46:59.570 760 760 D BlackBoxLoader: beforeCreateApplication: pkg com.paeezanstudio.pesarkhande, processName com.paeezanstudio.pesarkhande,userID:0
|
||||
|
||||
The top resumed activity was ProxyActivity$P0 and the Unity guest reached its
|
||||
interactive home screen. Private file permissions after launch:
|
||||
|
||||
- agent.js: `-r--------`, 198960 bytes
|
||||
- libfridabox-agent.config.so: `-rw-------`, 210 bytes
|
||||
- libfridabox-agent.so: `-r-xr-xr-x`, 25212656 bytes
|
||||
|
||||
Runtime snapshot:
|
||||
|
||||
- runtime_state: local_script_active
|
||||
- runtime_package: com.paeezanstudio.pesarkhande
|
||||
- runtime_process: com.paeezanstudio.pesarkhande:Metrica
|
||||
- runtime_user_id: 0
|
||||
- runtime_vpid: 1
|
||||
- runtime_source: /data/user/0/com.qm4rs.fridabox/blackbox/data/app/com.paeezanstudio.pesarkhande/base.apk
|
||||
- runtime_class_loader: dalvik.system.PathClassLoader@6c0723b
|
||||
- runtime_error: none
|
||||
|
||||
Computer mode regression:
|
||||
|
||||
07-20 02:30:02.989 26464 26464 I FridaBox.Gadget: Loading Frida Gadget listener for com.paeezanstudio.pesarkhande
|
||||
07-20 02:30:03.026 26464 26487 I Frida: Listening on 127.0.0.1 TCP port 27042
|
||||
|
||||
No `beforeCreateApplication` line existed before attachment.
|
||||
|
||||
> frida -U gadget -q -e "send({type:'fridabox-probe',pid:Process.id,moduleCount:Process.enumerateModules().length,firstModules:Process.enumerateModules().slice(0,5).map(function(m){return m.name;})})"
|
||||
message: {'type': 'send', 'payload': {'type': 'fridabox-probe', 'pid': 26464, 'moduleCount': 416, 'firstModules': ['app_process64', 'linker64', 'libandroid_runtime.so', 'libbinder.so', 'libcutils.so']}} data: None
|
||||
|
||||
07-20 02:31:04.593 26464 26464 I FridaBox.Gadget: Frida Gadget loaded
|
||||
07-20 02:31:04.594 26464 26464 D BlackBoxLoader: beforeCreateApplication: pkg com.paeezanstudio.pesarkhande, processName com.paeezanstudio.pesarkhande,userID:0
|
||||
|
||||
Clean mode regression after virtual-process recycling:
|
||||
|
||||
- previous main PID: 26464
|
||||
- clean main PID: 27819
|
||||
|
||||
07-20 02:33:27.289 27819 27819 I FridaBox.Gadget: Instrumentation disabled for this guest process
|
||||
07-20 02:33:27.290 27819 27819 D BlackBoxLoader: beforeCreateApplication: pkg com.paeezanstudio.pesarkhande, processName com.paeezanstudio.pesarkhande,userID:0
|
||||
|
||||
There was no Gadget listener or Gadget load in the clean process. The saved
|
||||
agent remained available, and the final device state was restored to On-device.
|
||||
|
||||
Final build:
|
||||
|
||||
> .\gradlew.bat :app:assembleDebug :app:assembleRelease :Bcore:testDebugUnitTest :app:testDebugUnitTest
|
||||
BUILD SUCCESSFUL
|
||||
|
||||
- debug APK: 21049981 bytes, SHA-256 80e70b33fca741e4f805aa233cdfaf5bc6fe2030e93c8fd825611eb5c407c917
|
||||
- release APK: 13266764 bytes, SHA-256 caa2218194fcbe91c10d0d29a74b7401aaed53f340b8a0d6668321ddae48ddfb
|
||||
- debug signature verification: passed
|
||||
- release signature verification: intentionally unsigned; no debug key fallback
|
||||
|
||||
Reference in New Issue
Block a user