igf uses a 3-layer communication architecture:
UI (React) ←──Socket.IO / REST──→ Server (Hono) ←──Frida RPC──→ Agent (in-process)
- UI ↔ Server: Socket.IO for real-time RPC and events, REST for data queries and file transfers
- Server ↔ Agent: Frida's
script.exportsRPC mechanism over USB/network
Every platform agent (fruity/droid) exports four methods via rpc.exports:
The primary dispatch function. Routes calls through the module registry:
// Example: list files in root directory
script.exports.invoke("fs", "ls", ["/"])
// Example: dump keychain items (iOS)
script.exports.invoke("keychain", "list", [])Returns an array of all available namespace.method strings:
script.exports.interfaces()
// → ["checksec.all", "checksec.single", "classdump.hierarchy", "fs.ls", "fs.read", ...]Restores previously saved hook rules (pins). Called automatically on session start to re-establish hooks from a prior session:
script.exports.restore([
{ category: "objc", symbol: "-[NSURLSession dataTaskWithRequest:]", ... }
])Captures the current set of active hook rules for persistence:
const rules = script.exports.snapshot()
// → [{ category: "objc", symbol: "...", ... }, ...]The registry is defined in agent/src/common/registry.ts:
function createRegistry(route) {
function invoke(ns, fn, args) {
const iface = route[ns]
if (!iface) throw new Error(`${ns} not found`)
const method = iface[fn]
if (!method) throw new Error(`${ns}.${fn} not found`)
return method(...args)
}
function interfaces() {
// yields all "namespace.method" strings
}
return { invoke, interfaces }
}Each platform defines a router mapping namespaces to module objects:
// agent/src/fruity/router.ts
export default {
checksec, // binary security checks
classdump, // runtime class extraction
cookies, // HTTP cookie access
crypto, // crypto API interception control
fs, // filesystem operations
info, // app info (bundle, version, paths)
keychain, // keychain dumping
// ...
}Types flow end-to-end: RemoteRPC<T> converts synchronous module methods to async promises, matching the RPC boundary.
| Namespace | Purpose |
|---|---|
/devices |
Device list change notifications |
/session |
Individual instrumentation session |
| Event | Payload | Description |
|---|---|---|
ready |
(pid: number) |
Session initialized, agent loaded |
denied |
() |
Session attach denied by access rules |
log |
(level: string, text: string) |
Agent log output |
syslog |
(text: string) |
System log entry |
hook |
(msg: BaseHookMessage) |
Hook interception event |
crypto |
(msg: BaseHookMessage, data?: ArrayBuffer) |
Crypto API call captured |
nsurl |
(event: NSURLEvent) |
Network request event (iOS) |
droidHttp |
(event: HttpEvent) |
HTTP/WebSocket request event (Android) |
jni |
(event: JNIEvent) |
JNI call event (Android) |
flutter |
(event: Record<string, unknown>) |
Flutter method channel event |
xpc |
(event: XPCSocketEvent) |
XPC/NSXPC message event (iOS) |
hermes |
(event: { url, hash, size }) |
Hermes bytecode capture event |
privacy |
(msg: PrivacyMessage) |
Sensitive API access event |
memoryScan |
(event: MemoryScanEvent, data?: ArrayBuffer) |
Memory scan progress/result event |
lifecycle |
(event: string) |
App lifecycle change (active/inactive/foreground/background) |
detached |
(reason: string) |
Session disconnected |
fatal |
(detail: unknown) |
Fatal error |
change |
() |
Device list changed (on /devices namespace) |
invalid |
() |
Invalid session parameters |
| Event | Payload | Description |
|---|---|---|
rpc |
(namespace, method, args, ack) |
Invoke agent RPC method |
eval |
(source, name, ack) |
Evaluate JavaScript in agent |
clearLog |
(type: "syslog" | "agent", ack) |
Clear log file |
The /session namespace expects these query params on connect:
| Param | Values | Description |
|---|---|---|
device |
device ID | Target Frida device |
platform |
fruity | droid |
iOS or Android |
mode |
app | daemon |
Attach to app or daemon process |
bundle |
bundle ID | App identifier (for app mode) |
pid |
number | Process ID (for daemon mode) |
name |
string | Display name |
All endpoints are prefixed with /api.
| Method | Path | Description |
|---|---|---|
GET |
/version |
Get Frida and igf versions |
GET |
/d.ts/pack |
Get agent TypeScript type definitions |
GET |
/devices |
List connected devices |
GET |
/device/:device/apps |
List apps on device |
GET |
/device/:device/processes |
List processes on device |
GET |
/device/:device/icon/:bundle |
Get app icon (PNG) |
GET |
/device/:device/info |
Get device system parameters |
POST |
/device/:device/kill/:pid |
Kill a process |
PUT |
/devices/remote/:hostname |
Add remote Frida device |
DELETE |
/devices/remote/:hostname |
Remove remote Frida device |
| Method | Path | Description |
|---|---|---|
GET |
/download/:device/:pid?path=... |
Download file from device |
HEAD / GET |
/dump/:device/:pid?path=... |
Dump a decrypted app binary from device |
GET |
/resource/:device/:pid?type=...&name=... |
Download Android resource |
GET |
/apk-entry/:device/:pid?apk=...&entry=... |
Download an APK zip entry |
POST |
/upload/:device/:pid |
Upload file to device |
| Method | Path | Description |
|---|---|---|
GET |
/logs/:device/:identifier/:type |
Stream log file (syslog/agent) |
DELETE |
/logs/:device/:identifier |
Delete all logs |
GET |
/hooks/:device/:identifier |
Query hook logs (paginated) |
DELETE |
/hooks/:device/:identifier |
Clear hook logs |
GET |
/history/crypto/:device/:identifier |
Query crypto logs |
DELETE |
/history/crypto/:device/:identifier |
Clear crypto logs |
GET |
/history/jni/:device/:identifier |
Query JNI traces |
DELETE |
/history/jni/:device/:identifier |
Clear JNI traces |
GET |
/history/flutter/:device/:identifier |
Query Flutter logs |
DELETE |
/history/flutter/:device/:identifier |
Clear Flutter logs |
GET |
/history/nsurl/:device/:identifier |
Query network request logs |
DELETE |
/history/nsurl/:device/:identifier |
Clear network request logs |
GET |
/history/nsurl/:device/:identifier/har |
Export iOS network logs as HAR |
GET |
/history/nsurl/:device/:identifier/attachment/:requestId |
Download request body |
GET |
/history/http/:device/:identifier |
Query Android HTTP/WebSocket logs |
DELETE |
/history/http/:device/:identifier |
Clear Android HTTP/WebSocket logs |
GET |
/history/http/:device/:identifier/har |
Export Android HTTP logs as HAR |
GET |
/history/http/:device/:identifier/attachment/:requestId |
Download Android request/response body |
GET |
/history/xpc/:device/:identifier |
Query XPC/NSXPC logs |
DELETE |
/history/xpc/:device/:identifier |
Clear XPC/NSXPC logs |
GET |
/history/privacy/:device/:identifier |
Query privacy API access logs |
DELETE |
/history/privacy/:device/:identifier |
Clear privacy API access logs |
GET |
/hermes/:device/:identifier |
Query captured Hermes bytecode blobs |
GET |
/hermes/:device/:identifier/download/:id |
Download captured Hermes bytecode |
GET |
/hermes/:device/:identifier/analyze/:id |
Analyze Hermes bytecode metadata |
GET |
/hermes/:device/:identifier/decompile/:id |
Decompile Hermes bytecode |
GET |
/hermes/:device/:identifier/disassemble/:id |
Disassemble Hermes bytecode |
GET |
/hermes/:device/:identifier/xrefs/:id |
Query Hermes xrefs |
DELETE |
/hermes/:device/:identifier/:id |
Delete one Hermes capture |
DELETE |
/hermes/:device/:identifier |
Clear Hermes captures |
GET |
/pins/:device/:identifier |
Load pin snapshot |
DELETE |
/pins/:device/:identifier |
Clear pin snapshot |
Query parameters for history endpoints: limit, offset, since, category (hooks/privacy), method (JNI), protocol (XPC), and severity (privacy).
| Method | Path | Description |
|---|---|---|
POST |
/llm |
Send text to configured LLM, returns text response |
POST |
/llm/stream |
Send text to configured LLM, streams text response |
See LLM Setup for configuration.
-
User clicks "Add Hook" in the Hooks panel, selects
-[NSURLSession dataTaskWithRequest:] -
Frontend emits Socket.IO RPC:
socket.emit("rpc", "pins", "add", [{ category: "objc", symbol: "..." }], callback)
-
Server (
ws.ts) receives therpcevent and calls:script.exports.invoke("pins", "add", [{ category: "objc", symbol: "..." }])
-
Agent (
common/pins.ts) installs the Interceptor hook in the target process -
Agent sends hook events back via
send()when the hooked method is called -
Server receives script messages, persists to
HookStore(SQLite), and emits:socket.emit("hook", { symbol, direction, timestamp, ... })
-
Frontend receives the
hookevent, updates TanStack Query cache, and renders the log entry -
Server auto-saves the pin snapshot after changes via
script.exports.snapshot(), persisted to disk for session recovery
- Frontend calls
rpc.fs.ls("/var/mobile")via the proxy - Proxy emits
socket.emit("rpc", "fs", "ls", ["/var/mobile"], callback) - Server calls
script.exports.invoke("fs", "ls", ["/var/mobile"]) - Agent's
fsmodule calls Frida'sObjC.classes.NSFileManagerAPIs - Result propagates back through
ackcallback to the frontend promise
Types flow from agent to frontend:
- Agent modules define method signatures in TypeScript
tsgogenerates type definitions toagent/types/- Vite aliases
@agentto../agent/typesso the GUI can import them RemoteRPC<T>type helper wraps all methods as async (reflecting the RPC boundary)createProxy()returns a typed proxy matching the agent interface
This ensures that calling rpc.fs.ls("/") in the frontend is fully type-checked against the agent's actual fs.ls implementation.