4 Commits
Author SHA1 Message Date
dependabot[bot]andGitHub 330c634715 build(deps): bump org.tukaani:xz to 1.12 (#16)
Apply the bug-fix update after a successful Android CI run.
2026-07-28 19:30:44 +03:30
dependabot[bot]andGitHub 4f4e04aafa build(deps): bump ConstraintLayout to 2.2.1 (#13)
Apply the patch update after a successful Android CI run.
2026-07-28 19:30:35 +03:30
dependabot[bot]andGitHub 16f51cf1d5 build(deps): bump androidx.appcompat:appcompat to 1.7.1 (#15)
Update AppCompat dependencies after a successful Android CI run.
2026-07-28 19:21:03 +03:30
Mahdi Karzari c89c6c4f81 Release FridaBox 4.2.0 with runtime bridges 2026-07-28 18:35:19 +03:30
42 changed files with 6958 additions and 208 deletions
+1 -1
View File
@@ -106,7 +106,7 @@ dependencies {
implementation 'com.google.android.material:material:1.3.0'
implementation(project(":black-reflection"))
annotationProcessor(project(":compiler"))
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'com.moandjiezana.toml:toml4j:0.7.2'
@@ -17,6 +17,10 @@ public final class InstrumentationSettings {
public static final String KEY_GADGET_VERSION = "selected_gadget_version";
public static final String KEY_GADGET_ABI = "selected_gadget_abi";
public static final String KEY_GADGET_SHA256 = "selected_gadget_sha256";
public static final String KEY_JAVA_BRIDGE_ENABLED = "runtime_bridge_java_enabled";
public static final String KEY_JAVA_BRIDGE_VERSION = "runtime_bridge_java_version";
public static final String KEY_IL2CPP_BRIDGE_ENABLED = "runtime_bridge_il2cpp_enabled";
public static final String KEY_IL2CPP_BRIDGE_VERSION = "runtime_bridge_il2cpp_version";
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_";
@@ -150,6 +154,41 @@ public final class InstrumentationSettings {
.commit();
}
public static boolean isRuntimeBridgeEnabled(String bridgeId) {
String key = runtimeBridgeEnabledKey(bridgeId);
return key != null && preferences().getBoolean(key, false);
}
public static String getRuntimeBridgeVersion(String bridgeId, String fallback) {
String key = runtimeBridgeVersionKey(bridgeId);
return key == null ? fallback : preferences().getString(key, fallback);
}
public static boolean setRuntimeBridge(String bridgeId, boolean enabled, String version) {
RuntimeBridgeCatalog.BridgeSpec spec = RuntimeBridgeCatalog.find(bridgeId);
String enabledKey = runtimeBridgeEnabledKey(bridgeId);
String versionKey = runtimeBridgeVersionKey(bridgeId);
if (spec == null || !spec.supportsVersion(version) || enabledKey == null || versionKey == null) {
return false;
}
return preferences().edit()
.putBoolean(enabledKey, enabled)
.putString(versionKey, version)
.commit();
}
private static String runtimeBridgeEnabledKey(String bridgeId) {
if (RuntimeBridgeCatalog.JAVA_ID.equals(bridgeId)) return KEY_JAVA_BRIDGE_ENABLED;
if (RuntimeBridgeCatalog.IL2CPP_ID.equals(bridgeId)) return KEY_IL2CPP_BRIDGE_ENABLED;
return null;
}
private static String runtimeBridgeVersionKey(String bridgeId) {
if (RuntimeBridgeCatalog.JAVA_ID.equals(bridgeId)) return KEY_JAVA_BRIDGE_VERSION;
if (RuntimeBridgeCatalog.IL2CPP_ID.equals(bridgeId)) return KEY_IL2CPP_BRIDGE_VERSION;
return null;
}
static int clamp(int value, int minimum, int maximum, int fallback) {
return value < minimum || value > maximum ? fallback : value;
}
@@ -8,6 +8,8 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import top.niunaijun.blackbox.BlackBoxCore;
@@ -41,7 +43,10 @@ final class LocalScriptGadgetRuntime {
File log = new File(directory, LOG_NAME);
writeUtf8Atomically(log, "");
File instrumentedAgent = new File(directory, INSTRUMENTED_AGENT_NAME);
writeInstrumentedAgentAtomically(instrumentedAgent, script, buildLogBridge(log));
List<byte[]> runtimeBridges = RuntimeBridgeCatalog.loadEnabledBundles(
context, InstrumentationSettings.getSelectedGadgetVersion());
writeInstrumentedAgentAtomically(
instrumentedAgent, script, buildLogBridge(log), runtimeBridges);
if (!instrumentedAgent.setReadable(true, true) || !instrumentedAgent.setWritable(false, false)) {
throw new IOException("Unable to secure the instrumented JavaScript agent");
}
@@ -154,7 +159,7 @@ final class LocalScriptGadgetRuntime {
}
private static void writeInstrumentedAgentAtomically(
File destination, File source, String bridge) throws IOException {
File destination, File source, String bridge, List<byte[]> runtimeBridges) throws IOException {
File temporary = new File(destination.getParentFile(), destination.getName() + ".partial");
if (temporary.exists() && !temporary.delete()) {
throw new IOException("Unable to replace temporary JavaScript agent");
@@ -164,7 +169,7 @@ final class LocalScriptGadgetRuntime {
byte[] buffer = new byte[64 * 1024];
int count;
while ((count = input.read(buffer)) >= 0) sourceBytes.write(buffer, 0, count);
byte[] instrumented = instrumentAgent(sourceBytes.toByteArray(), bridge);
byte[] instrumented = instrumentAgent(sourceBytes.toByteArray(), bridge, runtimeBridges);
try (FileOutputStream output = new FileOutputStream(temporary)) {
output.write(instrumented);
output.getFD().sync();
@@ -174,9 +179,22 @@ final class LocalScriptGadgetRuntime {
}
static byte[] instrumentAgent(byte[] source, String bridge) throws IOException {
return instrumentAgent(source, bridge, Collections.<byte[]>emptyList());
}
static byte[] instrumentAgent(
byte[] source, String bridge, List<byte[]> runtimeBridgeBundles) throws IOException {
int offset = injectionOffset(source);
if (offset < 0) throw new IOException("Invalid Frida bundle header");
byte[] prefix = (bridge + "(function (send) {\n").getBytes(StandardCharsets.UTF_8);
ByteArrayOutputStream prefixOutput = new ByteArrayOutputStream();
prefixOutput.write(bridge.getBytes(StandardCharsets.UTF_8));
for (byte[] bundle : runtimeBridgeBundles) {
byte[] body = extractEntryModule(bundle);
prefixOutput.write(body, 0, body.length);
prefixOutput.write('\n');
}
prefixOutput.write("(function (send) {\n".getBytes(StandardCharsets.UTF_8));
byte[] prefix = prefixOutput.toByteArray();
byte[] suffix = "\n})(globalThis.__fridaboxSend);\n".getBytes(StandardCharsets.UTF_8);
if (offset > 0) return instrumentBundle(source, prefix, suffix, offset);
ByteArrayOutputStream output = new ByteArrayOutputStream(source.length + prefix.length + suffix.length);
@@ -186,11 +204,28 @@ final class LocalScriptGadgetRuntime {
return output.toByteArray();
}
static byte[] extractEntryModule(byte[] bundle) throws IOException {
int bodyOffset = injectionOffset(bundle);
if (bodyOffset <= 0) throw new IOException("Runtime bridge is not a Frida bundle");
int lengthStart = moduleLengthStart(bundle, bodyOffset);
int lengthEnd = lengthStart;
long declaredLength = 0;
while (lengthEnd < bodyOffset && bundle[lengthEnd] >= '0' && bundle[lengthEnd] <= '9') {
declaredLength = declaredLength * 10 + (bundle[lengthEnd] - '0');
if (declaredLength > Integer.MAX_VALUE) throw new IOException("Invalid Frida bundle module length");
lengthEnd++;
}
if (lengthEnd == lengthStart || lengthEnd >= bodyOffset || bundle[lengthEnd] != ' ') {
throw new IOException("Invalid Frida bundle module length");
}
long bodyEndLong = bodyOffset + declaredLength;
if (bodyEndLong > bundle.length) throw new IOException("Invalid Frida bundle module length");
return java.util.Arrays.copyOfRange(bundle, bodyOffset, (int) bodyEndLong);
}
private static byte[] instrumentBundle(
byte[] source, byte[] prefix, byte[] suffix, int bodyOffset) throws IOException {
int lengthStart = 0;
while (lengthStart < bodyOffset && source[lengthStart] != '\n') lengthStart++;
lengthStart++;
int lengthStart = moduleLengthStart(source, bodyOffset);
int lengthEnd = lengthStart;
long declaredLength = 0;
while (lengthEnd < bodyOffset && source[lengthEnd] >= '0' && source[lengthEnd] <= '9') {
@@ -220,6 +255,13 @@ final class LocalScriptGadgetRuntime {
return output.toByteArray();
}
private static int moduleLengthStart(byte[] source, int bodyOffset) throws IOException {
int lengthStart = 0;
while (lengthStart < bodyOffset && source[lengthStart] != '\n') lengthStart++;
if (lengthStart >= bodyOffset) throw new IOException("Invalid Frida bundle header");
return lengthStart + 1;
}
static int injectionOffset(byte[] source) {
byte[] magic = "📦".getBytes(StandardCharsets.UTF_8);
if (!startsWith(source, magic)) return 0;
@@ -0,0 +1,131 @@
package top.niunaijun.blackbox.instrumentation;
import android.content.Context;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** Offline, build-pinned runtime bridges available to autonomous Frida agents. */
public final class RuntimeBridgeCatalog {
public static final String JAVA_ID = "java";
public static final String IL2CPP_ID = "il2cpp";
public static final String MINIMUM_GADGET_VERSION = "17.0.0";
private static final int MAX_BUNDLE_BYTES = 2 * 1024 * 1024;
private static final List<BridgeSpec> SPECS = Collections.unmodifiableList(Arrays.asList(
new BridgeSpec(
JAVA_ID,
"Java",
"Official Frida runtime bridge",
"7.0.13",
new String[]{"7.0.13", "7.0.12", "7.0.11"}),
new BridgeSpec(
IL2CPP_ID,
"IL2CPP",
"Community bridge for Unity IL2CPP",
"0.13.1",
new String[]{"0.13.1", "0.13.0", "0.12.2"})
));
private RuntimeBridgeCatalog() {
}
public static List<BridgeSpec> specs() {
return SPECS;
}
public static BridgeSpec find(String id) {
for (BridgeSpec spec : SPECS) {
if (spec.id.equals(id)) return spec;
}
return null;
}
public static boolean supportsRuntimeBridges(String gadgetVersion) {
int[] parsed = parseVersion(gadgetVersion);
return parsed != null && parsed[0] >= 17;
}
public static String selectedVersion(BridgeSpec spec) {
String selected = InstrumentationSettings.getRuntimeBridgeVersion(spec.id, spec.defaultVersion);
return spec.supportsVersion(selected) ? selected : spec.defaultVersion;
}
static List<byte[]> loadEnabledBundles(Context context, String gadgetVersion) throws IOException {
if (!supportsRuntimeBridges(gadgetVersion)) return Collections.emptyList();
List<byte[]> bundles = new ArrayList<>();
for (BridgeSpec spec : SPECS) {
if (!InstrumentationSettings.isRuntimeBridgeEnabled(spec.id)) continue;
String version = selectedVersion(spec);
String asset = "fridabox/bridges/" + spec.id + "-" + version + ".js";
try (InputStream input = context.getAssets().open(asset)) {
bundles.add(readLimited(input));
} catch (IOException error) {
throw new IOException("Runtime bridge asset is unavailable: " + spec.id + " " + version, error);
}
}
return bundles;
}
private static byte[] readLimited(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[32 * 1024];
int total = 0;
int count;
while ((count = input.read(buffer)) >= 0) {
total += count;
if (total > MAX_BUNDLE_BYTES) throw new IOException("Runtime bridge bundle is unexpectedly large");
output.write(buffer, 0, count);
}
if (total == 0) throw new IOException("Runtime bridge bundle is empty");
return output.toByteArray();
}
static int[] parseVersion(String value) {
if (value == null) return null;
String normalized = value.trim();
if (normalized.startsWith("v") || normalized.startsWith("V")) normalized = normalized.substring(1);
String[] parts = normalized.split("[.-]", 4);
if (parts.length < 1 || parts.length > 4) return null;
int[] result = new int[]{0, 0, 0};
try {
for (int index = 0; index < Math.min(3, parts.length); index++) {
if (parts[index].isEmpty()) return null;
result[index] = Integer.parseInt(parts[index]);
if (result[index] < 0) return null;
}
return result;
} catch (NumberFormatException ignored) {
return null;
}
}
public static final class BridgeSpec {
public final String id;
public final String title;
public final String description;
public final String defaultVersion;
private final List<String> versions;
BridgeSpec(String id, String title, String description, String defaultVersion, String[] versions) {
this.id = id;
this.title = title;
this.description = description;
this.defaultVersion = defaultVersion;
this.versions = Collections.unmodifiableList(Arrays.asList(versions.clone()));
}
public List<String> versions() {
return versions;
}
public boolean supportsVersion(String version) {
return version != null && versions.contains(version);
}
}
}
@@ -7,6 +7,7 @@ import org.junit.Test;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
public class LocalScriptGadgetRuntimeTest {
@Test
@@ -54,10 +55,45 @@ public class LocalScriptGadgetRuntimeTest {
assertTrue(result.endsWith("})(globalThis.__fridaboxSend);\n"));
}
@Test
public void selectedRuntimeBridgeRunsBeforeTheUserAgent() throws Exception {
String bridgeBody = "globalThis.Java = { available: true };";
byte[] bridgeBundle = bundle("/bridges/java.js", bridgeBody);
String source = "console.log(Java.available);";
String result = new String(LocalScriptGadgetRuntime.instrumentAgent(
source.getBytes(StandardCharsets.UTF_8),
"console.log('log bridge');\n",
Collections.singletonList(bridgeBundle)), StandardCharsets.UTF_8);
assertTrue(result.indexOf(bridgeBody) < result.indexOf(source));
assertTrue(result.contains("(function (send) {\n" + source));
}
@Test
public void entryModuleExtractionRejectsPlainJavaScript() {
boolean rejected = false;
try {
LocalScriptGadgetRuntime.extractEntryModule("plain".getBytes(StandardCharsets.UTF_8));
} catch (Exception expected) {
rejected = true;
}
assertTrue(rejected);
}
@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")));
}
private static byte[] bundle(String name, String body) {
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
String header = "\uD83D\uDCE6\n" + bodyBytes.length + " " + name + "\n\u2704\n";
byte[] headerBytes = header.getBytes(StandardCharsets.UTF_8);
byte[] result = new byte[headerBytes.length + bodyBytes.length];
System.arraycopy(headerBytes, 0, result, 0, headerBytes.length);
System.arraycopy(bodyBytes, 0, result, headerBytes.length, bodyBytes.length);
return result;
}
}
@@ -0,0 +1,27 @@
package top.niunaijun.blackbox.instrumentation;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class RuntimeBridgeCatalogTest {
@Test
public void bridgeControlsStartAtFridaSeventeen() {
assertFalse(RuntimeBridgeCatalog.supportsRuntimeBridges("16.7.19"));
assertTrue(RuntimeBridgeCatalog.supportsRuntimeBridges("17.0.0"));
assertTrue(RuntimeBridgeCatalog.supportsRuntimeBridges("17.16.0"));
assertTrue(RuntimeBridgeCatalog.supportsRuntimeBridges("v18.1.2"));
assertFalse(RuntimeBridgeCatalog.supportsRuntimeBridges("not-a-version"));
}
@Test
public void everyBridgeHasAValidDefaultAndPinnedAlternatives() {
for (RuntimeBridgeCatalog.BridgeSpec spec : RuntimeBridgeCatalog.specs()) {
assertNotNull(RuntimeBridgeCatalog.find(spec.id));
assertTrue(spec.supportsVersion(spec.defaultVersion));
assertTrue(spec.versions().size() >= 2);
}
}
}
+28
View File
@@ -5,6 +5,34 @@ versioning for its public releases.
## [Unreleased]
## [4.2.0] - 2026-07-28
### Added
- Optional, version-selectable Java and Unity IL2CPP runtime bridges for Frida
Gadget 17 and newer, embedded privately into the on-device agent at launch.
- Reproducible bridge build inputs and packaged Java 7.0.11-7.0.13 and IL2CPP
0.12.2, 0.13.0, and 0.13.1 bridge versions.
### Changed
- Simplified the application navigation to Workspace, Gadgets, and Settings by
removing the redundant Runtime destination.
- Refined the liquid-glass bottom navigation with icon-only active indicators,
heavier active icons, tactile button motion, and animated screen transitions.
- Centered the Gadget version browser and moved in-app notifications above the
bottom navigation.
- Simplified Settings by removing obsolete global instrumentation and advanced
runtime-detail controls.
### Fixed
- Preserve the Gadget screen scroll position when selecting Gadget or bridge
versions instead of rebuilding at the top of the page.
- Keep bridge enablement and version choices device-local and apply them to the
next private on-device agent without requiring imports in user scripts.
- Restore clear spacing between the Settings controls and security warning.
## [4.1.1] - 2026-07-28
### Fixed
+8 -3
View File
@@ -69,15 +69,20 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## Frida Java bridge and agent compiler
## Frida Java and IL2CPP runtime bridges and agent compiler
The compiled JavaScript agents under `scripts/dist/` include
`frida-java-bridge` 7.0.13. They are built with `frida-compile` 19.0.5.
FridaBox also packages offline runtime-bridge bundles for Java 7.0.11 through
7.0.13 and `frida-il2cpp-bridge` 0.12.2 through 0.13.1. These optional bundles
are only composed into a private on-device agent when the user enables them.
- Java bridge origin: `https://github.com/frida/frida-java-bridge`
- IL2CPP bridge origin: `https://github.com/vfsfitvnm/frida-il2cpp-bridge`
- Compiler origin: `https://github.com/frida/frida-compile`
- License: LGPL-2.0 with the wxWindows Library Licence 3.1 exception, as
declared by the respective official packages.
- Java bridge and compiler license: LGPL-2.0 with the wxWindows Library Licence
3.1 exception, as declared by the respective official packages.
- IL2CPP bridge license: MIT, as declared by its package and repository.
The authoritative licence text and exception are the same wxWindows Library
Licence described above. Exact package versions and dependency integrity hashes
+22 -2
View File
@@ -132,6 +132,26 @@ tasks.register("verifyInstrumentationOrdering") {
}
}
tasks.register("verifyRuntimeBridgeAssets") {
doLast {
def bridgeAssets = [
"java-7.0.11.js", "java-7.0.12.js", "java-7.0.13.js",
"il2cpp-0.12.2.js", "il2cpp-0.13.0.js", "il2cpp-0.13.1.js"
]
def bridgeRoot = file("src/main/assets/fridabox/bridges")
bridgeAssets.each { name ->
def asset = new File(bridgeRoot, name)
if (!asset.isFile() || asset.length() == 0 || asset.length() > 2 * 1024 * 1024) {
throw new GradleException("Missing or invalid runtime bridge asset: ${name}")
}
def source = new String(asset.bytes, java.nio.charset.StandardCharsets.UTF_8)
if (!source.startsWith("📦\n") || !source.contains("\n✄\n")) {
throw new GradleException("Runtime bridge is not a valid frida-compile bundle: ${name}")
}
}
}
}
tasks.register("verifyTablerAndroidIcons", Exec) {
workingDir(rootProject.projectDir)
def nodeExecutable = System.getProperty("os.name").toLowerCase().contains("windows") ? "node.exe" : "node"
@@ -157,7 +177,7 @@ tasks.register("verifyDemoGuestUnmodified") {
tasks.named("check").configure {
dependsOn("verifyDebugApkHasNoGadget", "verifyInstrumentationOrdering",
"verifyDemoGuestUnmodified", "verifyTablerAndroidIcons")
"verifyDemoGuestUnmodified", "verifyTablerAndroidIcons", "verifyRuntimeBridgeAssets")
}
dependencies {
@@ -167,7 +187,7 @@ dependencies {
implementation libs.material
implementation libs.constraintlayout
implementation libs.core.ktx
implementation "org.tukaani:xz:1.10"
implementation "org.tukaani:xz:1.12"
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6,8 +6,6 @@ import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.PropertyValuesHolder
import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageInfo
@@ -23,11 +21,15 @@ import android.os.Bundle
import android.provider.OpenableColumns
import android.text.InputType
import android.view.Gravity
import android.view.HapticFeedbackConstants
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.WindowManager
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import android.view.animation.OvershootInterpolator
import android.widget.FrameLayout
import android.widget.GridLayout
@@ -36,7 +38,6 @@ import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.Space
import android.widget.TextView
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
@@ -54,6 +55,7 @@ import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackbox.instrumentation.InstrumentationSettings
import top.niunaijun.blackbox.instrumentation.RuntimeBridgeCatalog
import top.niunaijun.blackbox.utils.ProcessAbi
import com.qm4rs.fridabox.databinding.ActivityFridaboxBinding
import java.io.File
@@ -64,7 +66,7 @@ import java.util.concurrent.Executors
/** Product workspace for importing, configuring, and launching FridaBox guests. */
class FridaBoxActivity : AppCompatActivity() {
private enum class Screen { WORKSPACE, GADGETS, RUNTIME, SETTINGS }
private enum class Screen { WORKSPACE, GADGETS, SETTINGS }
private data class ImportBubbleViews(
val scrim: View,
@@ -107,6 +109,10 @@ class FridaBoxActivity : AppCompatActivity() {
binding = ActivityFridaboxBinding.inflate(layoutInflater)
setContentView(binding.root)
pendingScriptPackage = savedInstanceState?.getString("pending_script_package")
settings.edit()
.remove(InstrumentationSettings.KEY_ENABLED)
.remove(InstrumentationSettings.KEY_ADVANCED_LOGS)
.apply()
binding.importFabIcon.setOnClickListener { showImportActions() }
binding.bottomNavigation.setOnItemSelectedListener { item ->
@@ -114,12 +120,16 @@ class FridaBoxActivity : AppCompatActivity() {
when (item.itemId) {
R.id.nav_workspace -> showWorkspace()
R.id.nav_gadgets -> showGadgets()
R.id.nav_runtime -> showRuntime()
R.id.nav_settings -> showSettings()
else -> return@setOnItemSelectedListener false
}
animateNavigationSelection(item.itemId)
animateContentIn()
true
}
binding.bottomNavigation.setOnItemReselectedListener { item ->
animateNavigationSelection(item.itemId)
}
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (importMenuOverlay != null) {
@@ -704,7 +714,6 @@ class FridaBoxActivity : AppCompatActivity() {
setLoading(false)
result.onSuccess { launched ->
if (!launched) toast("This guest has no launchable activity")
else if (mode == InstrumentationSettings.MODE_COMPUTER) showRuntime(packageName)
}.onFailure { toast("Launch failed: ${it.message}") }
}
}
@@ -803,10 +812,6 @@ class FridaBoxActivity : AppCompatActivity() {
popup.dismiss()
showAppDetails(info, appLabel)
})
panel.addView(menuActionButton(getString(R.string.fb_runtime_details), R.drawable.ic_fb_runtime) {
popup.dismiss()
showRuntime(info.packageName)
})
panel.addView(menuActionButton(getString(R.string.fb_clear_data), R.drawable.ic_fb_clear) {
popup.dismiss()
clearApp(info.packageName)
@@ -1451,9 +1456,10 @@ class FridaBoxActivity : AppCompatActivity() {
}
}
private fun showGadgets() {
private fun showGadgets(preserveScroll: Boolean = false) {
val previousScrollY = binding.contentScroll.scrollY
screen = Screen.GADGETS
resetScreen(R.id.nav_gadgets, showImport = false)
resetScreen(R.id.nav_gadgets, showImport = false, resetScroll = !preserveScroll)
binding.toolbar.title = getString(R.string.fb_gadgets_title)
val abi = gadgetManager.detectedAbi()
@@ -1497,6 +1503,10 @@ class FridaBoxActivity : AppCompatActivity() {
}
})
if (selected != null && RuntimeBridgeCatalog.supportsRuntimeBridges(selected.version)) {
binding.content.addView(runtimeBridgeControls(selected.version))
}
val installed = gadgetManager.installed()
if (installed.isNotEmpty()) {
binding.content.addView(labelText(
@@ -1506,6 +1516,106 @@ class FridaBoxActivity : AppCompatActivity() {
binding.content.addView(installedGadgetRow(gadget, selected?.identity == gadget.identity))
}
}
if (preserveScroll) restoreScrollBeforeNextDraw(previousScrollY)
}
private fun runtimeBridgeControls(gadgetVersion: String): View {
return LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
addView(labelText(
getString(R.string.fb_runtime_bridges), R.color.fb_text_secondary, 12f, false
).apply { setPadding(0, 0, 0, dp(6)) })
addView(labelText(
getString(R.string.fb_runtime_bridges_body, gadgetVersion),
R.color.fb_text_secondary,
12.5f,
false
).apply { setPadding(0, 0, 0, dp(10)) })
RuntimeBridgeCatalog.specs().forEach { spec ->
addView(runtimeBridgeRow(spec))
}
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = dp(20) }
}
}
private fun runtimeBridgeRow(spec: RuntimeBridgeCatalog.BridgeSpec): View {
val version = RuntimeBridgeCatalog.selectedVersion(spec)
val enabled = InstrumentationSettings.isRuntimeBridgeEnabled(spec.id)
return surfaceCard().apply {
addView(LinearLayout(this@FridaBoxActivity).apply {
orientation = LinearLayout.VERTICAL
setPadding(dp(16), dp(14), dp(16), dp(14))
addView(LinearLayout(this@FridaBoxActivity).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
addView(verticalText(spec.title, spec.description), LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f
))
addView(SwitchMaterial(this@FridaBoxActivity).apply {
isChecked = enabled
contentDescription = getString(R.string.fb_runtime_bridge_toggle, spec.title)
var rollingBack = false
setOnCheckedChangeListener { button, checked ->
if (rollingBack) return@setOnCheckedChangeListener
if (!InstrumentationSettings.setRuntimeBridge(spec.id, checked, version)) {
rollingBack = true
button.isChecked = !checked
rollingBack = false
notify(getString(R.string.fb_runtime_bridge_save_failed))
} else {
notify(getString(
if (checked) R.string.fb_runtime_bridge_enabled else R.string.fb_runtime_bridge_disabled,
spec.title
))
}
}
}, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply { marginStart = dp(8) })
})
addView(outlineButton(getString(R.string.fb_runtime_bridge_version, version)) { button ->
showRuntimeBridgeVersions(
spec,
RuntimeBridgeCatalog.selectedVersion(spec),
button as MaterialButton
)
}.apply {
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
dp(42)
).apply { topMargin = dp(10) }
})
})
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = dp(10) }
}
}
private fun showRuntimeBridgeVersions(
spec: RuntimeBridgeCatalog.BridgeSpec,
current: String,
versionButton: MaterialButton
) {
val versions = spec.versions().toTypedArray()
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.fb_runtime_bridge_choose_version, spec.title))
.setSingleChoiceItems(versions, versions.indexOf(current)) { dialog, which ->
val enabled = InstrumentationSettings.isRuntimeBridgeEnabled(spec.id)
if (InstrumentationSettings.setRuntimeBridge(spec.id, enabled, versions[which])) {
dialog.dismiss()
versionButton.text = getString(R.string.fb_runtime_bridge_version, versions[which])
} else {
notify(getString(R.string.fb_runtime_bridge_save_failed))
}
}
.setNegativeButton(getString(R.string.fb_cancel), null)
.show()
}
private fun installedGadgetRow(gadget: InstalledGadget, active: Boolean): View {
@@ -1518,7 +1628,7 @@ class FridaBoxActivity : AppCompatActivity() {
runCatching { gadgetManager.select(gadget) }
.onSuccess {
notify(getString(R.string.fb_gadget_selected, gadget.version))
showGadgets()
showGadgets(preserveScroll = true)
}
.onFailure { notify(it.message ?: getString(R.string.fb_gadget_select_failed)) }
}
@@ -1548,7 +1658,7 @@ class FridaBoxActivity : AppCompatActivity() {
installed != null -> runCatching { gadgetManager.select(installed) }
.onSuccess {
dialog.dismiss()
showGadgets()
showGadgets(preserveScroll = true)
}
.onFailure { notify(it.message ?: getString(R.string.fb_gadget_select_failed)) }
else -> {
@@ -1586,7 +1696,7 @@ class FridaBoxActivity : AppCompatActivity() {
private fun showGadgetBrowser(abi: GadgetAbi) {
val dialog = Dialog(this)
val root = FrameLayout(this).apply {
setPadding(dp(12), dp(32), dp(12), dp(12))
setPadding(dp(12), dp(20), dp(12), dp(20))
setOnClickListener { dialog.dismiss() }
}
val panel = surfaceCard().apply {
@@ -1637,8 +1747,8 @@ class FridaBoxActivity : AppCompatActivity() {
root.addView(panel, FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
(resources.displayMetrics.heightPixels * 0.82f).toInt(),
Gravity.BOTTOM
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.CENTER
))
dialog.setContentView(root)
dialog.setCanceledOnTouchOutside(true)
@@ -1653,6 +1763,20 @@ class FridaBoxActivity : AppCompatActivity() {
}
dialog.show()
dialog.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
panel.apply {
alpha = 0f
scaleX = 0.96f
scaleY = 0.96f
translationY = dp(10).toFloat()
animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.translationY(0f)
.setDuration(280L)
.setInterpolator(DecelerateInterpolator(1.6f))
.start()
}
val request = ++catalogRequest
val identities = HashSet<String>()
@@ -1722,88 +1846,12 @@ class FridaBoxActivity : AppCompatActivity() {
if (hadSelection) R.string.fb_gadget_downloaded else R.string.fb_gadget_downloaded_selected,
release.version
))
showGadgets()
showGadgets(preserveScroll = true)
}.onFailure { notify(getString(R.string.fb_gadget_download_failed, it.message ?: "Unknown error")) }
}
}
}
private fun showRuntime(packageHint: String? = null) {
screen = Screen.RUNTIME
resetScreen(R.id.nav_runtime, showImport = false)
binding.toolbar.title = getString(R.string.fb_runtime_title)
binding.toolbar.subtitle = getString(R.string.fb_runtime_subtitle)
val packageName = packageHint ?: settings.getString("runtime_package", null)
val state = settings.getString("runtime_state", "idle") ?: "idle"
val mode = settings.getString("runtime_mode", packageName?.let {
InstrumentationSettings.getModeForPackage(it)
} ?: InstrumentationSettings.MODE_CLEAN) ?: InstrumentationSettings.MODE_CLEAN
val stateColor = when (state) {
"local_script_active", "computer_attached" -> R.color.fb_success
"failed" -> R.color.fb_error
"waiting_for_attach", "loading_local_script" -> R.color.fb_warning
else -> R.color.fb_text_secondary
}
binding.content.addView(pageHeading(
getString(R.string.fb_runtime_title),
getString(R.string.fb_runtime_subtitle)
))
binding.content.addView(messageCard(
runtimeStateLabel(state),
packageName ?: "No guest process has reported runtime state yet.",
stateColor
))
if (mode == InstrumentationSettings.MODE_COMPUTER) {
val command = "frida -U gadget -l path/to/agent.js"
binding.content.addView(infoCard("Computer attach", buildString {
append("The guest waits at Gadget until a controller attaches.\n\n")
append(command)
}, command))
} else if (mode == InstrumentationSettings.MODE_LOCAL_SCRIPT) {
val scriptName = packageName?.let { metadata.getString("$it.scriptName", null) }
val scriptHash = packageName?.let { metadata.getString("$it.scriptSha", null) }
binding.content.addView(infoCard(
"On-device agent",
buildString {
append(scriptName ?: "No agent selected")
if (scriptHash != null) append("\nSHA-256 ").append(scriptHash)
append("\n\nThe private agent is loaded autonomously by Frida Gadget.")
}
))
if (packageName != null) {
binding.content.addView(outlineButton("Open agent logs") {
if (!FridaBoxGuestLogOverlay.show(this, packageName)) {
toast("No agent log is available for $packageName")
}
}.apply {
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(50)).apply {
topMargin = dp(14)
}
})
}
} else {
binding.content.addView(infoCard("Clean launch", getString(R.string.fb_mode_clean_body)))
}
val details = listOf(
"Guest package" to (packageName ?: "Not reported"),
"Guest process" to (settings.getString("runtime_process", "—") ?: "—"),
"Virtual process slot" to settings.getInt("runtime_vpid", -1).toString(),
"Guest source" to (settings.getString("runtime_source", "—") ?: "—"),
"Virtual user ID" to settings.getInt("runtime_user_id", -1).toString(),
"ClassLoader" to (settings.getString("runtime_class_loader", "Not reported") ?: "Not reported"),
"Mode" to modeLongLabel(mode),
"Latest error" to (settings.getString("runtime_error", "None") ?: "None")
)
binding.content.addView(detailsCard("Process registry", details))
binding.content.addView(outlineButton(getString(R.string.fb_refresh)) { showRuntime(packageHint) }.apply {
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(50)).apply { topMargin = dp(14) }
})
}
private fun showSettings() {
screen = Screen.SETTINGS
resetScreen(R.id.nav_settings, showImport = false)
@@ -1820,35 +1868,25 @@ class FridaBoxActivity : AppCompatActivity() {
setPadding(dp(18), dp(18), dp(18), dp(18))
}
controls.addView(body)
val enabled = SwitchMaterial(this).apply {
text = getString(R.string.fb_global_instrumentation)
setTextColor(color(R.color.fb_text_primary))
isChecked = settings.getBoolean(InstrumentationSettings.KEY_ENABLED, true)
}
val logs = SwitchMaterial(this).apply {
text = getString(R.string.fb_advanced_logs)
setTextColor(color(R.color.fb_text_primary))
isChecked = settings.getBoolean(InstrumentationSettings.KEY_ADVANCED_LOGS, false)
}
val port = numberInput(getString(R.string.fb_base_port), settings.getInt(InstrumentationSettings.KEY_BASE_PORT, 27042))
val count = numberInput(getString(R.string.fb_scan_count), settings.getInt(InstrumentationSettings.KEY_SCAN_COUNT, 32))
body.addView(enabled)
body.addView(logs)
body.addView(port.first)
body.addView(count.first)
body.addView(primaryButton(getString(R.string.fb_save_settings)) {
settings.edit()
.putBoolean(InstrumentationSettings.KEY_ENABLED, enabled.isChecked)
.putInt(InstrumentationSettings.KEY_BASE_PORT,
InstrumentationPreferenceParser.parsePort(port.second.text?.toString().orEmpty(), 27042))
.putInt(InstrumentationSettings.KEY_SCAN_COUNT,
InstrumentationPreferenceParser.parseScanCount(count.second.text?.toString().orEmpty(), 32))
.putBoolean(InstrumentationSettings.KEY_ADVANCED_LOGS, logs.isChecked)
.apply()
notify("Settings saved")
}.apply {
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(50)).apply { topMargin = dp(12) }
})
controls.layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = dp(14) }
binding.content.addView(controls)
binding.content.addView(messageCard(
getString(R.string.fb_security_title),
@@ -1902,50 +1940,6 @@ class FridaBoxActivity : AppCompatActivity() {
}
}
private fun infoCard(title: String, bodyText: String, copyValue: String? = null): View {
val card = surfaceCard()
val body = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(dp(18), dp(18), dp(18), dp(18))
addView(labelText(title, R.color.fb_text_primary, 16f, true))
addView(labelText(bodyText, R.color.fb_text_secondary, 13f, false).apply {
setPadding(0, dp(8), 0, 0)
setTextIsSelectable(true)
})
if (copyValue != null) addView(outlineButton(getString(R.string.fb_copy_command)) {
(getSystemService(CLIPBOARD_SERVICE) as ClipboardManager)
.setPrimaryClip(ClipData.newPlainText("FridaBox command", copyValue))
notify("Command copied")
}.apply {
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(46)).apply { topMargin = dp(12) }
})
}
card.addView(body)
card.layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = dp(14) }
return card
}
private fun detailsCard(title: String, rows: List<Pair<String, String>>): View {
val card = surfaceCard()
val body = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(dp(18), dp(18), dp(18), dp(18))
addView(labelText(title, R.color.fb_text_primary, 16f, true))
}
rows.forEach { (label, value) ->
body.addView(labelText(label.uppercase(Locale.ROOT), R.color.fb_text_secondary, 10.5f, true).apply {
setPadding(0, dp(13), 0, 0)
})
body.addView(labelText(value, R.color.fb_text_primary, 13f, false).apply { setTextIsSelectable(true) })
}
card.addView(body)
card.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
return card
}
private fun verticalText(title: String, subtitle: String): LinearLayout {
return LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
@@ -1997,7 +1991,7 @@ class FridaBoxActivity : AppCompatActivity() {
strokeColor = checkedColors(color(R.color.fb_primary), color(R.color.fb_glass_outline))
backgroundTintList = checkedColors(color(R.color.fb_primary), color(R.color.fb_glass_surface_soft))
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
}
}.also(::installPressMotion)
}
private fun primaryButton(label: String, action: (View) -> Unit): MaterialButton {
@@ -2014,7 +2008,7 @@ class FridaBoxActivity : AppCompatActivity() {
strokeWidth = dp(1)
elevation = dp(3).toFloat()
setOnClickListener(action)
}
}.also(::installPressMotion)
}
private fun outlineButton(label: String, action: (View) -> Unit): MaterialButton {
@@ -2029,7 +2023,7 @@ class FridaBoxActivity : AppCompatActivity() {
strokeColor = ColorStateList.valueOf(color(R.color.fb_glass_outline))
backgroundTintList = ColorStateList.valueOf(color(R.color.fb_glass_surface_soft))
setOnClickListener(action)
}
}.also(::installPressMotion)
}
private fun iconButton(iconResource: Int, description: String, action: () -> Unit): MaterialButton {
@@ -2044,15 +2038,120 @@ class FridaBoxActivity : AppCompatActivity() {
}
}
private fun resetScreen(navId: Int, showImport: Boolean): Int {
private fun installPressMotion(view: View) {
view.isHapticFeedbackEnabled = true
view.setOnTouchListener { target, event ->
if (!target.isEnabled) return@setOnTouchListener false
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
target.animate().cancel()
target.performHapticFeedback(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
HapticFeedbackConstants.CONTEXT_CLICK
} else {
HapticFeedbackConstants.VIRTUAL_KEY
}
)
target.animate()
.scaleX(0.965f)
.scaleY(0.965f)
.alpha(0.9f)
.translationY(dp(1).toFloat())
.setDuration(85L)
.setInterpolator(AccelerateInterpolator(1.4f))
.start()
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
target.animate().cancel()
target.animate()
.scaleX(1f)
.scaleY(1f)
.alpha(1f)
.translationY(0f)
.setDuration(240L)
.setInterpolator(OvershootInterpolator(1.45f))
.start()
}
}
false
}
}
private fun animateNavigationSelection(itemId: Int) {
updateNavigationIconEmphasis(itemId)
val item = binding.bottomNavigation.findViewById<View>(itemId) ?: return
val target = firstImageDescendant(item) ?: item
target.animate().cancel()
target.scaleX = 0.9f
target.scaleY = 0.9f
target.translationY = dp(2).toFloat()
target.animate()
.scaleX(1.18f)
.scaleY(1.18f)
.translationY(0f)
.setDuration(360L)
.setInterpolator(OvershootInterpolator(1.8f))
.start()
}
private fun updateNavigationIconEmphasis(selectedItemId: Int) {
intArrayOf(R.id.nav_workspace, R.id.nav_gadgets, R.id.nav_settings).forEach { itemId ->
val item = binding.bottomNavigation.findViewById<View>(itemId) ?: return@forEach
val icon = firstImageDescendant(item) ?: return@forEach
icon.animate().cancel()
val scale = if (itemId == selectedItemId) 1.18f else 1f
icon.scaleX = scale
icon.scaleY = scale
icon.translationY = 0f
}
}
private fun firstImageDescendant(view: View): ImageView? {
if (view is ImageView) return view
if (view !is ViewGroup) return null
for (index in 0 until view.childCount) {
firstImageDescendant(view.getChildAt(index))?.let { return it }
}
return null
}
private fun animateContentIn() {
binding.content.animate().cancel()
binding.content.alpha = 0.62f
binding.content.translationY = dp(8).toFloat()
binding.content.animate()
.alpha(1f)
.translationY(0f)
.setDuration(260L)
.setInterpolator(DecelerateInterpolator(1.5f))
.start()
}
private fun restoreScrollBeforeNextDraw(scrollY: Int) {
if (scrollY <= 0) return
val scroll = binding.contentScroll
scroll.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
if (scroll.viewTreeObserver.isAlive) {
scroll.viewTreeObserver.removeOnPreDrawListener(this)
}
val maximum = (scroll.getChildAt(0)?.height ?: 0) - scroll.height
scroll.scrollTo(0, scrollY.coerceIn(0, maximum.coerceAtLeast(0)))
return true
}
})
}
private fun resetScreen(navId: Int, showImport: Boolean, resetScroll: Boolean = true): Int {
dismissImportActions(immediate = true)
screenGeneration += 1
binding.content.removeAllViews()
binding.contentScroll.scrollTo(0, 0)
if (resetScroll) binding.contentScroll.scrollTo(0, 0)
binding.importFabIcon.isVisible = showImport
changingNavigation = true
binding.bottomNavigation.selectedItemId = navId
changingNavigation = false
updateNavigationIconEmphasis(navId)
setLoading(false)
return screenGeneration
}
@@ -2068,28 +2167,12 @@ class FridaBoxActivity : AppCompatActivity() {
)
}
private fun runtimeStateLabel(state: String): String = when (state) {
"local_script_active" -> "On-device agent active"
"computer_attached" -> "Computer attached"
"waiting_for_attach" -> "Waiting for computer"
"loading_local_script" -> "Loading on-device agent"
"failed" -> "Instrumentation failed"
"disabled" -> "Clean runtime"
else -> "Runtime idle"
}
private fun modeShortLabel(mode: String): String = when (mode) {
InstrumentationSettings.MODE_LOCAL_SCRIPT -> getString(R.string.fb_mode_local)
InstrumentationSettings.MODE_CLEAN -> getString(R.string.fb_mode_clean)
else -> getString(R.string.fb_mode_computer)
}
private fun modeLongLabel(mode: String): String = when (mode) {
InstrumentationSettings.MODE_LOCAL_SCRIPT -> getString(R.string.fb_mode_local_title)
InstrumentationSettings.MODE_CLEAN -> getString(R.string.fb_mode_clean_title)
else -> getString(R.string.fb_mode_computer_title)
}
private fun modeColor(mode: String, background: Boolean): Int {
val resource = when (mode) {
InstrumentationSettings.MODE_LOCAL_SCRIPT -> if (background) R.color.fb_glass_surface_soft else R.color.fb_success
@@ -2132,13 +2215,14 @@ class FridaBoxActivity : AppCompatActivity() {
}
private fun notify(message: String) {
Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).apply {
setAnchorView(binding.bottomNavigation)
setTextColor(color(R.color.fb_text_primary))
view.background = ContextCompat.getDrawable(this@FridaBoxActivity, R.drawable.bg_glass_popup)
view.elevation = dp(12).toFloat()
show()
}
}
private fun toast(message: String) = Toast.makeText(this, message, Toast.LENGTH_LONG).show()
private fun toast(message: String) = notify(message)
private fun dp(value: Int) = (value * resources.displayMetrics.density).toInt()
private fun humanSize(bytes: Long): String {
@@ -0,0 +1,42 @@
<?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="@android:color/transparent"
android:strokeColor="@android:color/white"
android:strokeWidth="2.7"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5" />
<path
android:fillColor="@android:color/transparent"
android:strokeColor="@android:color/white"
android:strokeWidth="2.7"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M12 12l8 -4.5" />
<path
android:fillColor="@android:color/transparent"
android:strokeColor="@android:color/white"
android:strokeWidth="2.7"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M12 12l0 9" />
<path
android:fillColor="@android:color/transparent"
android:strokeColor="@android:color/white"
android:strokeWidth="2.7"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M12 12l-8 -4.5" />
<path
android:fillColor="@android:color/transparent"
android:strokeColor="@android:color/white"
android:strokeWidth="2.7"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M16 5.25l-8 4.5" />
</vector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/ic_fb_gadget_active" />
<item android:drawable="@drawable/ic_fb_gadget" />
</selector>
@@ -0,0 +1,21 @@
<?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="@android:color/transparent"
android:strokeColor="@android:color/white"
android:strokeWidth="2.7"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065" />
<path
android:fillColor="@android:color/transparent"
android:strokeColor="@android:color/white"
android:strokeWidth="2.7"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</vector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/ic_fb_settings_active" />
<item android:drawable="@drawable/ic_fb_settings" />
</selector>
@@ -0,0 +1,35 @@
<?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="@android:color/transparent"
android:strokeColor="@android:color/white"
android:strokeWidth="2.7"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M4 5a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4" />
<path
android:fillColor="@android:color/transparent"
android:strokeColor="@android:color/white"
android:strokeWidth="2.7"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M14 5a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4" />
<path
android:fillColor="@android:color/transparent"
android:strokeColor="@android:color/white"
android:strokeWidth="2.7"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M4 15a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4" />
<path
android:fillColor="@android:color/transparent"
android:strokeColor="@android:color/white"
android:strokeWidth="2.7"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M14 15a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4" />
</vector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/ic_fb_workspace_active" />
<item android:drawable="@drawable/ic_fb_workspace" />
</selector>
@@ -145,9 +145,11 @@
android:layout_marginBottom="8dp"
android:background="@drawable/bg_glass_navigation"
android:elevation="14dp"
app:itemActiveIndicatorStyle="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
app:itemActiveIndicatorStyle="@style/Widget.FridaBox.Navigation.ActiveIndicator"
app:itemIconTint="@color/fb_text_primary"
app:itemRippleColor="@color/fb_glass_highlight"
app:itemTextAppearanceActive="@style/TextAppearance.FridaBox.Navigation.Label"
app:itemTextAppearanceInactive="@style/TextAppearance.FridaBox.Navigation.Label"
app:itemTextColor="@color/fb_text_primary"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
@@ -1,7 +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_gadgets" android:icon="@drawable/ic_fb_gadget" android:title="@string/fb_nav_gadgets" />
<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" />
<item android:id="@+id/nav_workspace" android:icon="@drawable/ic_fb_workspace_nav" android:title="@string/fb_nav_workspace" />
<item android:id="@+id/nav_gadgets" android:icon="@drawable/ic_fb_gadget_nav" android:title="@string/fb_nav_gadgets" />
<item android:id="@+id/nav_settings" android:icon="@drawable/ic_fb_settings_nav" android:title="@string/fb_nav_settings" />
</menu>
@@ -10,6 +10,7 @@
<color name="fb_glass_outline">#66FFFFFF</color>
<color name="fb_glass_outline_soft">#2EFFFFFF</color>
<color name="fb_glass_highlight">#38FFFFFF</color>
<color name="fb_nav_indicator">#3864E8E8</color>
<color name="fb_primary">#64E8E8</color>
<color name="fb_primary_dark">#1AAFB5</color>
<color name="fb_secondary">#9B87F5</color>
+8 -8
View File
@@ -4,7 +4,6 @@
<string name="fb_brand_tagline">Mobile instrumentation workspace</string>
<string name="fb_nav_workspace">Workspace</string>
<string name="fb_nav_gadgets">Gadgets</string>
<string name="fb_nav_runtime">Runtime</string>
<string name="fb_nav_settings">Settings</string>
<string name="fb_import">Import</string>
<string name="fb_import_from_apps">Import from apps</string>
@@ -29,21 +28,15 @@
<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>
@@ -51,7 +44,6 @@
<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>
@@ -78,4 +70,12 @@
<string name="fb_gadget_download_failed">Gadget download failed: %1$s</string>
<string name="fb_catalog_failed_body">Unable to load compatible Gadget releases from GitHub.</string>
<string name="fb_gadget_unsupported_title">Unsupported device ABI</string>
<string name="fb_runtime_bridges">Runtime bridges</string>
<string name="fb_runtime_bridges_body">Available for Gadget %1$s. Enabled bridges are embedded into the private on-device agent at the next launch.</string>
<string name="fb_runtime_bridge_toggle">Enable %1$s bridge</string>
<string name="fb_runtime_bridge_version">Version %1$s</string>
<string name="fb_runtime_bridge_choose_version">Choose %1$s version</string>
<string name="fb_runtime_bridge_enabled">%1$s bridge enabled for the next on-device launch</string>
<string name="fb_runtime_bridge_disabled">%1$s bridge disabled</string>
<string name="fb_runtime_bridge_save_failed">Unable to save runtime bridge settings</string>
</resources>
@@ -26,4 +26,23 @@
<item name="colorOnSurface">@color/fb_text_primary</item>
<item name="colorPrimary">@color/fb_primary</item>
</style>
<style name="Widget.FridaBox.Navigation.ActiveIndicator" parent="">
<item name="android:width">44dp</item>
<item name="android:height">32dp</item>
<item name="marginHorizontal">0dp</item>
<item name="shapeAppearance">@style/ShapeAppearance.FridaBox.NavigationPill</item>
<item name="android:color">@color/fb_nav_indicator</item>
</style>
<style name="ShapeAppearance.FridaBox.NavigationPill" parent="ShapeAppearance.MaterialComponents.LargeComponent">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">50%</item>
</style>
<style name="TextAppearance.FridaBox.Navigation.Label" parent="TextAppearance.MaterialComponents.Caption">
<item name="android:fontFamily">sans</item>
<item name="android:textSize">12sp</item>
<item name="android:textStyle">normal</item>
</style>
</resources>
+2 -2
View File
@@ -10,8 +10,8 @@ ext {
targetSdkVersion = 28
minSdk = 21
versionCode = 402
versionName = "4.1.1"
versionCode = 403
versionName = "4.2.0"
xVersion = '1.1.0'
hiddenApiBypass = '4.3'
}
+4
View File
@@ -27,6 +27,10 @@ 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`.
The same build command generates the optional Java and IL2CPP bridge assets
used by On-device mode. Their version catalog and composition model are
documented in [RUNTIME_BRIDGES.md](RUNTIME_BRIDGES.md).
UI control icons are generated from pinned `@tabler/icons` sources with
`npm run generate:android-icons`. The app `check` task runs the matching
verification script and fails if a committed Android vector is out of sync.
+70
View File
@@ -0,0 +1,70 @@
# Runtime bridges
## Why this exists
Frida 17.0.0 removed the Java, Objective-C, and Swift runtime bridges from the
GumJS runtime. An autonomous Gadget Script interaction therefore no longer gets
the `Java` global implicitly. Frida's REPL and `frida-trace` remain compatible
because `frida-tools` injects those bridges itself.
Primary references:
- `https://frida.re/news/2025/05/17/frida-17-0-0-released/`
- `https://frida.re/docs/bridges/`
- `https://frida.re/docs/gadget/#using-the-language-bridges`
`frida-il2cpp-bridge` is different: it is a community module and was never a
built-in GumJS bridge. Its current compatibility statement covers Unity 5.3.0
through 6000.3.x, with Android and Linux as the tested platforms:
- `https://github.com/vfsfitvnm/frida-il2cpp-bridge`
## FridaBox architecture
Frida scripts have isolated globals. Loading a bridge in a second Script does
not make `Java` or `Il2Cpp` visible to the user's Script. FridaBox instead builds
each supported package version with `frida-compile`, stores the resulting bundle
as an APK asset, and extracts its entry module while preparing the private
on-device agent. The final execution order is:
1. FridaBox log capture;
2. enabled runtime-bridge entry modules;
3. the user's unchanged imported agent copy.
Only the generated runtime copy is composed. The originally imported agent and
its recorded digest are not modified.
Bridge settings appear only when the active Gadget is 17.0.0 or newer. They
apply to On-device mode on the next process launch. Computer mode uses Gadget's
Listen interaction; bridge availability there belongs to the attaching client
(`frida`, `frida-trace`, or the API-created agent), because remotely created
Scripts have separate isolates.
## Pinned versions
The APK contains these reproducible, offline choices:
| Bridge | Versions | Default | Source |
| --- | --- | --- | --- |
| Java | 7.0.11, 7.0.12, 7.0.13 | 7.0.13 | official `frida-java-bridge` npm package |
| IL2CPP | 0.12.2, 0.13.0, 0.13.1 | 0.13.1 | community `frida-il2cpp-bridge` npm package |
The npm registry contained 92 Java bridge versions from 3.1.1 through 7.0.13
and 77 published IL2CPP versions through 0.13.1 when this catalog was researched
on 2026-07-28. FridaBox deliberately ships a recent curated set instead of every
historical package to keep the APK small and the test surface bounded.
## Rebuilding
Run:
```powershell
npm ci
python tools/build_frida_agents.py
```
The package aliases in `package.json` allow multiple versions to coexist. The
generated bridge bundles are written to
`app/src/main/assets/fridabox/bridges/`. Gradle's
`verifyRuntimeBridgeAssets` task validates that every catalog entry exists and
has a Frida bundle header.
+25 -1
View File
@@ -1,6 +1,30 @@
# Test results
Validation date: 2026-07-27
Validation date: 2026-07-28
## FridaBox 4.2.0 release validation
The signed 4.2.0 release was built after the complete app check and app/Bcore
unit-test suites passed. Each split contains exactly one native ABI, reports
`versionCode 403` and `versionName 4.2.0`, is 16 KiB ZIP-aligned, and verifies
with APK Signature Schemes v1 and v2. All four APKs use the same production
certificate as 4.1.0 and 4.1.1 (SHA-256
`f8db4dcf0f9272bf75ac170e382ee31ca17fea2606a18aaed0cc4040423cb444`).
| ABI | Size | SHA-256 |
| --- | ---: | --- |
| `arm64-v8a` | 3,263,922 bytes | `e37af8801a3bb12c04db2140c09dfb843f8e105c04aa0c0f88f5731fa3e0cabb` |
| `armeabi-v7a` | 3,246,369 bytes | `427b2a4328624cda5881267c4bde00088c2d7df2296f49c5d66cbf4c929b64fc` |
| `x86` | 3,288,071 bytes | `8883788d694d42339332f495cbdb8b043198c52648ac168557f0d66dca0beab8` |
| `x86_64` | 3,293,258 bytes | `17695849ea9ed7884d2e1a777e9cfbf47529d84ade7a8c6e9746269e9d96b249` |
The updated ARM64 debug build was also installed on the Samsung SM-S928B
running Android 16/API 36. Runtime-bridge controls, version selection without a
scroll jump, the centered Gadget browser, Settings cleanup, raised Snackbar,
and icon-only animated bottom-navigation selection were exercised without a
fatal exception. The other three ABI variants were build-, package-, ELF-,
signature-, alignment-, and regression-test-validated but were not exercised
on physical hardware in this release cycle.
## Host validation
+12
View File
@@ -30,6 +30,18 @@ so their active base and split APKs remain available to the virtual runtime.
Instrumented launches require an active Gadget. Clean mode remains available
without one. Gadget binaries are never bundled in the FridaBox APK.
## Select runtime bridges
When the active Gadget is 17.0.0 or newer, the **Gadgets** screen shows Java and
IL2CPP runtime-bridge controls. Enable either bridge and select one of its
offline pinned versions. The choice is composed into the generated private
agent on the next **On-device** launch, so a plain script may use the `Java` or
`Il2Cpp` global without importing that package itself.
The switch does not mutate the imported script and cannot change a process that
is already running. It does not apply to **Computer** mode, where each script is
created by the attaching Frida client in its own JavaScript isolate.
## Choose a launch mode
Every guest remembers one of three independent modes:
+2 -2
View File
@@ -3,9 +3,9 @@ agp = "8.13.2"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
appcompat = "1.7.0"
appcompat = "1.7.1"
material = "1.12.0"
constraintlayout = "2.2.0"
constraintlayout = "2.2.1"
kotlin = "2.4.10"
coreKtx = "1.15.0"
+58 -1
View File
@@ -6,7 +6,13 @@
"": {
"name": "fridabox-agents",
"dependencies": {
"frida-java-bridge": "7.0.13"
"frida-il2cpp-bridge-0-12-2": "npm:frida-il2cpp-bridge@0.12.2",
"frida-il2cpp-bridge-0-13-0": "npm:frida-il2cpp-bridge@0.13.0",
"frida-il2cpp-bridge-0-13-1": "npm:frida-il2cpp-bridge@0.13.1",
"frida-java-bridge": "7.0.13",
"frida-java-bridge-7-0-11": "npm:frida-java-bridge@7.0.11",
"frida-java-bridge-7-0-12": "npm:frida-java-bridge@7.0.12",
"frida-java-bridge-7-0-13": "npm:frida-java-bridge@7.0.13"
},
"devDependencies": {
"@tabler/icons": "3.45.0",
@@ -238,12 +244,63 @@
"frida-compile": "dist/cli.js"
}
},
"node_modules/frida-il2cpp-bridge-0-12-2": {
"name": "frida-il2cpp-bridge",
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/frida-il2cpp-bridge/-/frida-il2cpp-bridge-0.12.2.tgz",
"integrity": "sha512-j9W44it4aw/6f7LBkT2bNSsH1E9OMHdWEPqWbc2zKzQEhzSfA6Ge8b9pwiBvTdmu50+09L9hzgmGN6ZpQaGJkA==",
"license": "MIT",
"bin": {
"frida-il2cpp-bridge": "cli/main.py"
}
},
"node_modules/frida-il2cpp-bridge-0-13-0": {
"name": "frida-il2cpp-bridge",
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/frida-il2cpp-bridge/-/frida-il2cpp-bridge-0.13.0.tgz",
"integrity": "sha512-y337LVmjQPFER2vRYX2klTNkWCauuj2G1WVct4QESb7ME4HC9nSVtOEfYCf77mE3zt3y5YQ0zjXR6xpwudC92A==",
"license": "MIT",
"bin": {
"frida-il2cpp-bridge": "cli/main.py"
}
},
"node_modules/frida-il2cpp-bridge-0-13-1": {
"name": "frida-il2cpp-bridge",
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/frida-il2cpp-bridge/-/frida-il2cpp-bridge-0.13.1.tgz",
"integrity": "sha512-qXjdcdI+KM5wnjCwqZTe/Tas/2pClXpDhcVWjiaQFxzSun+1vaoHEWlnMe4F09Vdz/vGkkjFOPKfvDem2A8UOQ==",
"license": "MIT",
"bin": {
"frida-il2cpp-bridge": "cli/main.py"
}
},
"node_modules/frida-java-bridge": {
"version": "7.0.13",
"resolved": "https://registry.npmjs.org/frida-java-bridge/-/frida-java-bridge-7.0.13.tgz",
"integrity": "sha512-YSyKjxbxKnSi3KSUy9vciOvTOuq0RRh9dkxzkQVEdfIIZlw20zE8D3Cq9eL2FDqUVj4YKas6Wf09kCjL5zbffg==",
"license": "LGPL-2.0 WITH WxWindows-exception-3.1"
},
"node_modules/frida-java-bridge-7-0-11": {
"name": "frida-java-bridge",
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/frida-java-bridge/-/frida-java-bridge-7.0.11.tgz",
"integrity": "sha512-px3YKGVcyBx3r2ksqnguEb1p6xlm8jqp7LX3ybaaET4fvS/8e6Ojzs7RKjaiglW/CishI4waZIuGKoSEqbrdcA==",
"license": "LGPL-2.0 WITH WxWindows-exception-3.1"
},
"node_modules/frida-java-bridge-7-0-12": {
"name": "frida-java-bridge",
"version": "7.0.12",
"resolved": "https://registry.npmjs.org/frida-java-bridge/-/frida-java-bridge-7.0.12.tgz",
"integrity": "sha512-xpoTFPQkUjNMeRnyZ6rUj/APlYGWpKEv5iekOk4lQeDcv9fv8J/s3ETjTRdSfyZ8/aPv9r8+oBmsxN2gCHRTqg==",
"license": "LGPL-2.0 WITH WxWindows-exception-3.1"
},
"node_modules/frida-java-bridge-7-0-13": {
"name": "frida-java-bridge",
"version": "7.0.13",
"resolved": "https://registry.npmjs.org/frida-java-bridge/-/frida-java-bridge-7.0.13.tgz",
"integrity": "sha512-YSyKjxbxKnSi3KSUy9vciOvTOuq0RRh9dkxzkQVEdfIIZlw20zE8D3Cq9eL2FDqUVj4YKas6Wf09kCjL5zbffg==",
"license": "LGPL-2.0 WITH WxWindows-exception-3.1"
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+8 -1
View File
@@ -2,11 +2,18 @@
"name": "fridabox-agents",
"private": true,
"scripts": {
"build:frida-agents": "python tools/build_frida_agents.py",
"generate:android-icons": "node tools/generate_tabler_android_icons.mjs",
"verify:android-icons": "node tools/generate_tabler_android_icons.mjs --check"
},
"dependencies": {
"frida-java-bridge": "7.0.13"
"frida-java-bridge": "7.0.13",
"frida-java-bridge-7-0-11": "npm:frida-java-bridge@7.0.11",
"frida-java-bridge-7-0-12": "npm:frida-java-bridge@7.0.12",
"frida-java-bridge-7-0-13": "npm:frida-java-bridge@7.0.13",
"frida-il2cpp-bridge-0-12-2": "npm:frida-il2cpp-bridge@0.12.2",
"frida-il2cpp-bridge-0-13-0": "npm:frida-il2cpp-bridge@0.13.0",
"frida-il2cpp-bridge-0-13-1": "npm:frida-il2cpp-bridge@0.13.1"
},
"devDependencies": {
"@tabler/icons": "3.45.0",
+3
View File
@@ -0,0 +1,3 @@
'use strict';
import 'frida-il2cpp-bridge-0-12-2';
+3
View File
@@ -0,0 +1,3 @@
'use strict';
import 'frida-il2cpp-bridge-0-13-0';
+3
View File
@@ -0,0 +1,3 @@
'use strict';
import 'frida-il2cpp-bridge-0-13-1';
+5
View File
@@ -0,0 +1,5 @@
'use strict';
import JavaBridge from 'frida-java-bridge-7-0-11';
if (typeof globalThis.Java === 'undefined') globalThis.Java = JavaBridge;
+5
View File
@@ -0,0 +1,5 @@
'use strict';
import JavaBridge from 'frida-java-bridge-7-0-12';
if (typeof globalThis.Java === 'undefined') globalThis.Java = JavaBridge;
+5
View File
@@ -0,0 +1,5 @@
'use strict';
import JavaBridge from 'frida-java-bridge-7-0-13';
if (typeof globalThis.Java === 'undefined') globalThis.Java = JavaBridge;
+22 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Build Frida 17 Java agents with the explicitly pinned Java bridge."""
"""Build Frida agents and the version-pinned offline runtime bridges."""
from __future__ import annotations
@@ -12,6 +12,16 @@ ROOT = pathlib.Path(__file__).resolve().parents[1]
SCRIPTS = ROOT / "scripts"
OUTPUT = SCRIPTS / "dist"
ENTRIES = ("registry-probe.js", "guest-bootstrap.js", "sample-hook.js")
BRIDGE_SCRIPTS = SCRIPTS / "bridges"
BRIDGE_OUTPUT = ROOT / "app" / "src" / "main" / "assets" / "fridabox" / "bridges"
BRIDGES = (
("java", "7.0.11"),
("java", "7.0.12"),
("java", "7.0.13"),
("il2cpp", "0.12.2"),
("il2cpp", "0.13.0"),
("il2cpp", "0.13.1"),
)
def compiler_path() -> pathlib.Path:
@@ -31,7 +41,18 @@ def main() -> int:
cwd=ROOT,
check=True,
)
BRIDGE_OUTPUT.mkdir(parents=True, exist_ok=True)
for bridge_id, version in BRIDGES:
name = f"{bridge_id}-{version}.js"
subprocess.run(
[str(compiler), str(BRIDGE_SCRIPTS / name), "-o", str(BRIDGE_OUTPUT / name), "-S", "-c"],
cwd=ROOT,
check=True,
)
print("Built Frida agents: " + ", ".join(str(OUTPUT / name) for name in ENTRIES))
print("Built runtime bridges: " + ", ".join(
str(BRIDGE_OUTPUT / f"{bridge_id}-{version}.js") for bridge_id, version in BRIDGES
))
return 0