Skip to content

Frida Integration

Frida is a dynamic instrumentation toolkit that lets you inject JavaScript into running mobile apps — intercepting function calls, modifying return values, and observing internal behavior in real time. Ghost integrates Frida to solve one of the hardest problems in mobile testing: apps that refuse to let you see their network traffic because they use SSL certificate pinning, root/jailbreak detection, or other security measures.

When an app pins its SSL certificates, it rejects Ghost’s proxy certificate even though you’ve installed it on the device. Normal proxy setup isn’t enough. With Frida, Ghost can reach inside the running app and override the certificate validation functions, making the app accept Ghost’s proxy certificate. The same approach works for bypassing root/jailbreak detection (apps that refuse to run on test devices) and for tracing or hooking any function in the app.

Key design decision: Ghost uses Frida as a subprocess (calling frida and frida-ps commands), not via Go/Python bindings. This maintains Ghost’s pure-Go single-binary build — no CGo, no Python dependencies baked in. Python and Frida tools are installed separately.

Pinned version: 16.5.9 — tested against Ghost’s target iOS and Android versions.

What this diagram shows:

Ghost’s Frida Manager sits in the backend and orchestrates everything by launching Frida tools as subprocesses. Device enumeration uses Python’s Frida API directly (because frida-ls-devices requires a TTY and crashes as a subprocess). App listing uses frida-ps. Script injection uses the frida CLI with -n (attach to running process) or -f (spawn/relaunch the app).

All output from Frida subprocesses flows into a per-session ring buffer (1,000 lines), which is streamed to the frontend via WebSocket events in real time and also available to the AI agent as tool results.

  • Python 3 — Frida tools are Python packages
  • pip3 (preferred) or pip — for installing Frida. Ghost detects the available package manager automatically (tries pip3 first, then pip)
  • USB connectivity — for connecting to physical devices (libimobiledevice for iOS, adb for Android)
  • frida-server on the target device (see below)

Ghost can install Frida tools automatically via the Security Tools panel (with streaming progress output), or you can install manually:

Terminal window
pip3 install frida-tools==16.5.9

Proxy-aware installation: Ghost creates a combined CA bundle (system CAs + Ghost’s own CA certificate) and sets SSL_CERT_FILE, REQUESTS_CA_BUNDLE, and PIP_CERT environment variables. This means pip can download packages even when traffic passes through Ghost’s MITM proxy — Ghost’s proxy certificate is trusted for the pip connection.

Ghost’s status check runs frida --version (10-second timeout) to detect whether Frida is installed and which version.

The target device needs frida-server running — this is the component that Frida connects to for injecting scripts:

PlatformSetup
Rooted AndroidPush the frida-server binary to /data/local/tmp/, make it executable (chmod +x), and run it as root (su -c ./frida-server &). The server listens on a local port that ADB forwards.
Jailbroken iOSInstall Frida from Cydia or Sileo — it runs automatically as a daemon.
Non-jailbroken iOSEmbed frida-gadget into a re-signed IPA. The app loads the gadget on launch, which connects to Frida automatically.

Ghost lists connected devices using Python’s Frida API directly:

python3 -c "import frida,json; print(json.dumps([{'id':d.id,'name':d.name,'type':d.type} for d in frida.enumerate_devices()]))"

This returns device ID, name, and type (USB, local, or remote). The command runs with a 10-second timeout — if the device takes longer to enumerate, Ghost retries on the next poll.

Why not use frida-ls-devices? Because it uses Python’s prompt_toolkit which requires an interactive terminal (TTY) and crashes when run as a subprocess.

Lists installed and running apps on a specific device:

Terminal window
frida-ps -D {deviceId} -ai --json

The -ai flag shows all installed apps (not just running ones), and --json requests JSON output. If JSON output fails (older Frida versions), Ghost falls back to parsing the text table format. 15-second timeout.

Lists all running processes:

Terminal window
frida-ps -D {deviceId} --json

Returns process name and PID. Same JSON-with-text-fallback behavior. 15-second timeout.

TypePurposeWhat It Does
ssl_bypassBypass certificate pinningHooks the app’s SSL/TLS verification functions to accept any certificate — iOS: SecTrustEvaluateWithError, NSURLSession challenges. Android: TrustManagerImpl, OkHttp3 CertificatePinner, WebViewClient.onReceivedSslError.
root_bypassBypass root/jailbreak detectionHooks file existence checks and detection libraries — iOS: NSFileManager.fileExistsAtPath (for jailbreak paths), UIApplication.canOpenURL (for cydia://). Android: RootBeer (10+ methods), Runtime.exec su check, SafetyNet detection.
traceFunction tracingHooks specific functions to log arguments and return values as they’re called. Uses frida-trace with -i flags for each function name.
customArbitrary scriptsInjects any Frida JavaScript you write. Full access to Frida’s API.
ModeCommandWhen To Use
Attachfrida -D {deviceId} -n {appName} -l {script}Hook into an app that’s already running. Your hooks start immediately but you miss anything that happened during app launch.
Spawnfrida -D {deviceId} -f {appIdentifier} -l {script}Kill and relaunch the app with your hooks active from the very start. Necessary for bypasses that need to be in place before the app’s initialization code runs (like SSL pinning configured at startup). Note: spawn uses the app’s bundle identifier (e.g., com.example.app), not the display name.

Both modes write the script to a temporary file (ghost-frida-*.js) that’s automatically cleaned up when the session ends.

Session key format: {deviceID}:{appName}:{sessionType} — this means you can have one SSL bypass session and one trace session for the same app simultaneously, but not two SSL bypass sessions.

Starting a session:

  1. Ghost writes the script to a temp file
  2. Launches the frida process as a persistent subprocess
  3. Waits 3 seconds for initial output or early exit
  4. If the process exits within those 3 seconds, it means something went wrong (device not connected, app not found, script error) — Ghost returns the error output
  5. If the process is still running, the session is registered and a frida.session.started WebSocket event is broadcast

Stopping a session:

  1. Ghost cancels the subprocess context
  2. Waits up to 3 seconds for the process to exit gracefully
  3. If it doesn’t exit within 3 seconds, force-kills the process
  4. Cleans up the temporary script file
  5. Broadcasts frida.session.stopped WebSocket event

Automatic cleanup: A goroutine monitors each Frida process — when the process exits on its own (the target app was closed, the device was disconnected), Ghost automatically cleans up the session and broadcasts the stopped event. The frontend shows a “Hook disconnected” toast with a “Reconnect” button.

Each session has its own ring buffer that stores the last 1,000 lines of output. The buffer uses a cursor-based read mechanism — you can ask “give me all lines since cursor X” and get only new lines.

Output is streamed from the Frida subprocess via stdout and stderr (with scanner buffers of 64 KB initial, 256 KB max per line). Each line is:

  1. Written to the ring buffer
  2. Broadcast as a frida.output WebSocket event with {key, line, stream: "stdout"|"stderr"}

The frontend batches these events at 100ms intervals (to prevent DOM thrashing from high-frequency output) and caps display at 500 lines per session on the client side.

The Frida panel opens from the command bar and provides a full-featured Frida workstation.

The panel uses a 65/35 horizontal split — the script editor takes 65% of the width, and the console takes 35%.

Gate states:

  • Loading: Spinner while fetching Frida status and devices
  • Not installed: Message with a link to the Security Tools panel for one-click installation
  • Error: Error message with a retry button

The top area contains a target bar with two dropdown selectors:

  • Device selector — lists connected devices with type icons (USB=cyan, local=gray, remote=purple)
  • App selector — lists apps on the selected device with search, sorted with running apps first

Below the target bar:

  • Script name — text input for naming your script
  • Monaco editor — full code editor with syntax highlighting, word wrap, bracket colorization, JetBrains Mono 13px font, no minimap. Has both dark and light themes matching Ghost’s design system. Includes IntelliSense type definitions (~310 lines of TypeScript declarations) for Frida’s JavaScript API — Java.*, ObjC.*, Interceptor.*, Module.*, Memory.*, Process.*, NativePointer, Stalker.*, and more.

Action bar at the bottom:

  • Inject (cyan) — attach to the running app. Keyboard shortcut: Cmd+Enter (Ctrl+Enter on Windows)
  • Spawn (amber) — kill and relaunch with script
  • Snippets (dropdown) — load a built-in snippet
  • Save (Cmd+S / Ctrl+S) — save script to database

Three tabs at the bottom:

TabWhat It Shows
ConsoleLive output from Frida sessions. Lines are color-coded: [+] = green (success), [-] = red (error), [!] = amber (warning), [*] = cyan (info), [REQ] = blue (request), [RES] = purple (response). Has auto-scroll toggle, copy, clear, and session filter.
HooksList of active Frida sessions with stop and filter buttons. Shows an error retry banner when a session disconnects unexpectedly.
TrafficParses [REQ] and [RES] output lines into structured entries. Shows a bar chart of top hosts and a live feed of intercepted requests.

When expanded, shows:

  • Devices list — each device shows its type badge (USB, local, remote/socket) with platform-appropriate icons
  • Apps list — searchable, with running apps sorted first
  • Active hooks section — currently running Frida sessions with quick-stop buttons

Below the editor, a collapsible section lists all saved Frida scripts. Features:

  • Load into editor (with dirty-check confirmation dialog if you have unsaved changes)
  • Delete (with confirmation)
  • Active indicator — cyan left border on the currently loaded script

Scripts are stored in SQLite (frida_scripts table) with fields: name, description, code, category (ssl_bypass, root_bypass, trace, custom), platform (ios, android, or empty for cross-platform), and timestamps.

Pre-built Frida scripts accessible from a dropdown button. Automatically filtered to show only snippets matching the detected platform:

SnippetPlatformWhat It Hooks
SSL Pinning BypassAndroidTrustManager, OkHttp3, SSLContext, WebView (5 hooks)
SSL Pinning BypassiOSSecTrustEvaluateWithError, SecTrustEvaluate, NSURLSession, TrustKit, AFNetworking
Root Detection BypassAndroidFile.exists, RootBeer (13 methods), Runtime.exec, SystemProperties, native access()
Jailbreak Detection BypassiOSNSFileManager, canOpenURL, stat/lstat, fopen, fork(), IOSSecuritySuite
HTTP Request TracerAndroidOkHttp3 RealCall (4.x + 3.x fallback), HttpURLConnection
Network Request LoggeriOSNSURLSessionTask.resume, completion handler wrapping, delegate callbacks
Crypto Key LoggerAndroidSecretKeySpec, Cipher.doFinal with hex dump
Empty TemplateAllBasic template with Java.available + ObjC.available guards

Platform auto-detection: Ghost examines the selected device name and ID to determine the platform — devices with “iPhone”, “iPad”, or “Simulator” in the name, or Apple UDID patterns in the ID, are classified as iOS. Everything else is Android. The snippet list filters accordingly (plus always showing “all” platform snippets).

In security mode, the AI agent has six Frida tools:

ToolParametersWhat It Does
frida_check(none)Runs frida --version and enumerates devices. Returns installation status, version, and connected device list.
frida_list_appsdevice_id (required)Runs frida-ps -D {id} -a to list apps on the device. Returns raw text output. 15-second timeout.
frida_bypass_ssldevice_id, app (both required)Attaches to the app with Ghost’s bundled SSL bypass script. Creates a persistent ssl_bypass session via the Frida Manager. Source is "agent".
frida_root_bypassdevice_id, app, platform (all required)Selects the appropriate bypass script (iOS or Android) based on the platform parameter. Creates a persistent root_bypass session.
frida_tracedevice_id, app, functions[] (all required), duration_seconds (optional, default 30, max 120)Runs frida-trace -D {id} -n {app} -i {fn}... for the specified duration. Returns output truncated at 100 lines. Flag injection prevention: function names starting with - are rejected.
frida_injectdevice_id, app, script, description (all required)Writes the script to a temp file, runs frida -D {id} -n {app} -l {path} --no-pause with 60-second timeout. One-shot execution (not a persistent session — temp file deleted after).
Scan ModeWhat’s Allowed
PassiveAll Frida tools are forbidden — Frida modifies app behavior, which is inherently active.
Active Safefrida_check, frida_list_apps, and frida_trace are allowed without approval. frida_bypass_ssl, frida_root_bypass, and frida_inject require explicit approval via request_approval before execution.
Active FullAll Frida tools are allowed without approval.

All Frida agent tools validate that device_id and app parameters don’t start with - — this prevents flag injection attacks where a malicious input like --eval malicious_code could be passed to the Frida CLI command.

EventWhen It FiresPayload
frida.session.startedA new Frida session begins (attach or spawn)SessionDTO — key, device_id, app, type, label, source (“panel” or “agent”), started_at
frida.session.stoppedA session ends (manual detach or process death)SessionDTO — same fields
frida.outputA line of output from a Frida process{key, line, stream} — stream is "stdout" or "stderr"
MethodEndpointDescription
GET/api/v1/frida/statusInstallation status — {installed, version, pinned_version, package_manager, package_manager_ok}
GET/api/v1/frida/devicesList connected devices. Returns empty array (not error) if Frida isn’t installed.
GET/api/v1/frida/devices/{id}/appsList apps on device. JSON output with text fallback. 15s timeout.
GET/api/v1/frida/devices/{id}/processesList running processes on device. 15s timeout.
MethodEndpointBody LimitDescription
POST/api/v1/frida/attach512 KBAttach to running app. Body: {device_id, app, script, session_type?, label?}. Defaults type to "custom".
POST/api/v1/frida/spawn512 KBSpawn (kill + relaunch) app with script. Same body format.
POST/api/v1/frida/detach4 KBDetach from session. Body: {key}. Returns 404 if session not found.
GET/api/v1/frida/sessionsList all active sessions. Always returns array (never null).
GET/api/v1/frida/sessions/{key}/outputGet session output. Query: cursor (uint64, default 0). Returns {lines[], cursor} for polling.
DELETE/api/v1/frida/sessions/{key}Stop and clean up a session.
MethodEndpointBody LimitDescription
GET/api/v1/frida/scriptsList all saved scripts (newest first)
POST/api/v1/frida/scripts256 KBSave a new script. Body: {name, description?, code, category?, platform?}. Category defaults to "custom".
PUT/api/v1/frida/scripts/{id}256 KBUpdate a script. Partial update — only non-empty fields are overwritten.
DELETE/api/v1/frida/scripts/{id}Delete a saved script. Returns 404 if not found.

Bypass SSL pinning — The most common use case. The app pins its server’s certificate and rejects Ghost’s proxy certificate. Use the SSL Pinning Bypass snippet (or let the AI agent run frida_bypass_ssl) to override the certificate validation. Now the app trusts Ghost’s proxy and you can see all its traffic.

Test on real devices — Some apps detect rooted/jailbroken devices and refuse to run. Use the Root/Jailbreak Detection Bypass to hide these indicators. The app runs normally on your test device, and you can test with Ghost’s proxy active.

Trace API calls — Want to understand how the app communicates internally? Use frida_trace to hook specific functions (like URLSession.dataTask on iOS or OkHttp.newCall on Android) and see every call with its arguments and return value.

Extract encryption keys — If the app encrypts data before sending it (making the proxy traffic unreadable even after SSL bypass), use the Crypto Key Logger snippet to capture encryption keys and plaintext at the point of encryption.

Custom instrumentation — Write arbitrary Frida JavaScript to hook any function in the app. The Monaco editor provides IntelliSense for Frida’s entire API, and you can see output in real time in the console.