Modes
Overview
Frida instruments programs via Gum (C core) + GumJS (JavaScript runtime wrapping Gum). Instrumentation scripts run inside the target process with full access to Gum APIs: function hooking, library/export enumeration, memory read/write, memory pattern scanning.
Three modes address different deployment constraints:
| Mode | Use Case | Key Component |
|---|---|---|
| Injected | Spawn/attach to running processes; most common | frida-core + frida-server |
| Embedded | Jailed iOS/Android; restricted environments | frida-gadget (shared library) |
| Preloaded | Autonomous script execution; no external tooling | frida-gadget (autonomous config) |
Mode 1: Injected
When to use: Default mode. Target process is accessible (not jailed). You want to spawn, attach, or hijack a process at runtime.
How it works:
frida-corepackages GumJS into a shared library and injects it into the target- Provides a two-way communication channel to scripts
- Scripts can be loaded, communicated with, and unloaded dynamically
Additional capabilities via frida-core:
- Enumerate installed apps
- Enumerate running processes
- Enumerate connected devices
Remote device support (iOS / Android):
frida-serverruns as a daemon on the device- Exposes
frida-coreover TCP - Default listen address:
localhost:27042
Tooling: All standard Frida CLI tools (frida, frida-trace, frida-ps, etc.) use this mode by default.
Mode 2: Embedded (Gadget)
When to use: Injected mode is unavailable — e.g., jailed iOS, non-debuggable Android, or environments without a running frida-server.
How it works:
- Embed
frida-gadget(a shared library) directly into the target application - Gadget initializes via its constructor function at dynamic-linker load time
- After loading, interact with it using existing Frida tools (e.g.,
frida-trace)
Ways to embed Gadget:
- Modify the program’s source code
- Patch the binary or one of its libraries (e.g., using
insert_dylib) - Use dynamic linker preloading:
LD_PRELOAD(Linux) orDYLD_INSERT_LIBRARIES(macOS/iOS)
Naming:
- The Gadget binary can be named anything (useful to bypass anti-Frida detection on name)
- Config file must share the same base name as the binary, with
.configextension- Example:
FridaGadget.dylib→FridaGadget.config
- Example:
Platform-specific notes:
| Platform | Notes |
|---|---|
| iOS (Xcode) | Xcode may place .dylib in Frameworks/ subdirectory; Gadget also searches the parent directory for .config in this case |
| Android (non-debuggable) | Package manager only copies files from /lib if name starts with lib, ends with .so, or is gdbserver. Config file must follow the same pattern (e.g., libgadget.config.so) |
Config file format: UTF-8 JSON with root object. Top-level keys:
| Key | Type | Default | Description |
|---|---|---|---|
interaction | object | listen | Which interaction type to use |
teardown | string | "minimal" | "minimal" or "full" — cleanup on unload. Use "full" if Gadget is unloaded mid-process. |
runtime | string | "default" | Override JS runtime: "default", "qjs", or "v8" |
code_signing | string | "optional" | "optional" or "required". Set "required" for jailed iOS without debugger. Note: "required" disables the Interceptor API unless a debugger was attached before Gadget loaded. |
Gadget Interaction Types
Listen (default)
Gadget exposes a frida-server-compatible TCP interface. Process list contains only the single Gadget process (re.frida.Gadget / name Gadget).
Constructor blocks until attach() or resume() is called (enabling early instrumentation). Override with "on_load": "resume" to skip blocking.
Default config:
1{
2 "interaction": {
3 "type": "listen",
4 "address": "127.0.0.1",
5 "port": 27042,
6 "on_port_conflict": "fail",
7 "on_load": "wait"
8 }
9}
Config keys:
| Key | Type | Default | Description |
|---|---|---|---|
address | string | "127.0.0.1" | Interface to listen on. "0.0.0.0" = all IPv4; "::" = all IPv6 |
port | number | 27042 | TCP port |
certificate | string | — | PEM public+private key (inline or path) to enable TLS |
token | string | — | Secret token for authentication |
on_port_conflict | string | "fail" | "fail" or "pick-next" (try consecutive ports) |
on_load | string | "wait" | "wait" (block until connected) or "resume" (start immediately) |
origin | string | — | Required “Origin” header value for cross-origin protection |
asset_root | string | — | Directory to serve static files over HTTP/HTTPS |
Connect
Gadget connects outward to a running frida-portal and joins its cluster. Useful for managed/fleet scenarios. Constructor blocks only if spawn-gating is enabled (Device.enable_spawn_gating()).
Default config:
1{
2 "interaction": {
3 "type": "connect",
4 "address": "127.0.0.1",
5 "port": 27052
6 }
7}
Config keys:
| Key | Type | Default | Description |
|---|---|---|---|
address | string | "127.0.0.1" | Portal’s cluster interface host |
port | number | 27052 | Portal’s cluster interface TCP port |
certificate | string | — | PEM CA public key for TLS verification (required if Portal has TLS) |
token | string | — | Auth token for Portal’s cluster interface |
acl | array of strings | — | Access control list: which controller tags can interact with this process |
Note: For custom authentication, per-node ACLs, or application-specific protocol messages, instantiate PortalService programmatically instead of using the frida-portal CLI.
Script
Fully autonomous mode. Loads and executes a single JavaScript file from the filesystem before the program’s entrypoint runs. No external tooling required.
Minimal config:
1{
2 "interaction": {
3 "type": "script",
4 "path": "/home/user/explore.js"
5 }
6}
Script skeleton with optional lifecycle hooks:
1rpc.exports = {
2 init(stage, parameters) {
3 // stage: "early" (first load) or "late" (reload)
4 // parameters: object from config, or {}
5 console.log('[init]', stage, JSON.stringify(parameters));
6
7 Interceptor.attach(Module.getGlobalExportByName('open'), {
8 onEnter(args) {
9 const path = args[0].readUtf8String();
10 console.log('open("' + path + '")');
11 }
12 });
13 },
14 dispose() {
15 // Called on script unload (process exit, Gadget unload, or reload)
16 console.log('[dispose]');
17 }
18};
Lifecycle details:
init()is called before the program’s entrypoint; Gadget waits for it to return (supports returning aPromise)dispose()is optional cleanup hookconsole.log/warn/errorwrite tostdout/stderr
path resolution rules:
- Absolute paths are used directly
- Relative paths are resolved relative to the Gadget binary location
- On iOS, relative paths first check the app’s
Documentsdirectory (supports iTunes file sharing / AFC updates for debuggable apps)
Config keys:
| Key | Type | Default | Description |
|---|---|---|---|
path | string | required | Filesystem path to the script |
parameters | object | {} | Arbitrary data passed to init() as second argument |
on_change | string | "ignore" | "ignore" or "reload" — watch file and reload on change (recommended during development) |
ScriptDirectory
Loads multiple scripts from a directory. Useful for system-wide instrumentation or plugin-style tweaks. Each script can optionally have a .config sidecar for filtering and parameters.
Minimal config:
1{
2 "interaction": {
3 "type": "script-directory",
4 "path": "/usr/local/frida/scripts"
5 }
6}
Config keys:
| Key | Type | Default | Description |
|---|---|---|---|
path | string | required | Directory containing .js scripts |
on_change | string | "ignore" | "ignore" or "rescan" — rescan directory on change (recommended during development) |
Per-script sidecar config (<scriptname>.config):
| Key | Type | Description |
|---|---|---|
filter | object | Load criteria (any one match triggers load). Keys: executables (array), bundles (array), objc_classes (array) |
parameters | object | Passed to init() as second argument |
on_change | string | "ignore" or "reload" |
Example: twitter.js + twitter.config for macOS Twitter app:
1{
2 "filter": {
3 "executables": ["Twitter"],
4 "bundles": ["com.twitter.twitter-mac"],
5 "objc_classes": ["Twitter"]
6 }
7}
Filter is OR-logic: script loads if any criterion matches.
Mode 3: Preloaded
Concept: Analogous to LD_PRELOAD (Linux) / DYLD_INSERT_LIBRARIES (macOS) — but for JavaScript. Uses frida-gadget in autonomous (Script or ScriptDirectory) mode loaded via the dynamic linker.
Implementation: Same as Embedded mode, but specifically configured to use the script or script-directory interaction with no external controller. Gadget is injected via LD_PRELOAD/DYLD_INSERT_LIBRARIES and runs scripts from the filesystem automatically.
Key difference from Embedded Listen/Connect: No frida-server or frida-portal is needed at runtime.
Decision Guide
Is the target process accessible (not jailed, can attach/spawn)?
├── Yes → Use Injected mode (frida-server + standard tools)
└── No → Need to embed frida-gadget
├── Want remote interactive control?
│ ├── Direct connection → Listen interaction (Gadget as server)
│ └── Fleet/portal scenario → Connect interaction (Gadget connects to portal)
└── Want fully autonomous (no external tooling)?
├── Single script → Script interaction
└── Multiple scripts / plugin system → ScriptDirectory interaction
Related References
- Gadget full docs:
/docs/gadget/ - JavaScript API (
rpc.exports,Interceptor,Module,Socket):/docs/javascript-api/ frida-traceCLI:/docs/frida-trace/