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.
Architecture
Section titled “Architecture”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.
Requirements
Section titled “Requirements”- Python 3 — Frida tools are Python packages
- pip3 (preferred) or pip — for installing Frida. Ghost detects the available package manager automatically (tries
pip3first, thenpip) - USB connectivity — for connecting to physical devices (libimobiledevice for iOS, adb for Android)
- frida-server on the target device (see below)
Installation
Section titled “Installation”Ghost can install Frida tools automatically via the Security Tools panel (with streaming progress output), or you can install manually:
pip3 install frida-tools==16.5.9Proxy-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.
Device-Side Setup
Section titled “Device-Side Setup”The target device needs frida-server running — this is the component that Frida connects to for injecting scripts:
| Platform | Setup |
|---|---|
| Rooted Android | Push 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 iOS | Install Frida from Cydia or Sileo — it runs automatically as a daemon. |
| Non-jailbroken iOS | Embed frida-gadget into a re-signed IPA. The app loads the gadget on launch, which connects to Frida automatically. |
Capabilities
Section titled “Capabilities”Device Enumeration
Section titled “Device Enumeration”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.
App Enumeration
Section titled “App Enumeration”Lists installed and running apps on a specific device:
frida-ps -D {deviceId} -ai --jsonThe -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.
Process Listing
Section titled “Process Listing”Lists all running processes:
frida-ps -D {deviceId} --jsonReturns process name and PID. Same JSON-with-text-fallback behavior. 15-second timeout.
Session Types
Section titled “Session Types”| Type | Purpose | What It Does |
|---|---|---|
| ssl_bypass | Bypass certificate pinning | Hooks the app’s SSL/TLS verification functions to accept any certificate — iOS: SecTrustEvaluateWithError, NSURLSession challenges. Android: TrustManagerImpl, OkHttp3 CertificatePinner, WebViewClient.onReceivedSslError. |
| root_bypass | Bypass root/jailbreak detection | Hooks 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. |
| trace | Function tracing | Hooks specific functions to log arguments and return values as they’re called. Uses frida-trace with -i flags for each function name. |
| custom | Arbitrary scripts | Injects any Frida JavaScript you write. Full access to Frida’s API. |
Attach vs. Spawn
Section titled “Attach vs. Spawn”| Mode | Command | When To Use |
|---|---|---|
| Attach | frida -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. |
| Spawn | frida -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 Lifecycle
Section titled “Session Lifecycle”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:
- Ghost writes the script to a temp file
- Launches the
fridaprocess as a persistent subprocess - Waits 3 seconds for initial output or early exit
- 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
- If the process is still running, the session is registered and a
frida.session.startedWebSocket event is broadcast
Stopping a session:
- Ghost cancels the subprocess context
- Waits up to 3 seconds for the process to exit gracefully
- If it doesn’t exit within 3 seconds, force-kills the process
- Cleans up the temporary script file
- Broadcasts
frida.session.stoppedWebSocket 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.
Output Streaming
Section titled “Output Streaming”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:
- Written to the ring buffer
- Broadcast as a
frida.outputWebSocket 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.
Frida Panel
Section titled “Frida Panel”The Frida panel opens from the command bar and provides a full-featured Frida workstation.
Layout
Section titled “Layout”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
Script Editor
Section titled “Script Editor”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
Console
Section titled “Console”Three tabs at the bottom:
| Tab | What It Shows |
|---|---|
| Console | Live 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. |
| Hooks | List of active Frida sessions with stop and filter buttons. Shows an error retry banner when a session disconnects unexpectedly. |
| Traffic | Parses [REQ] and [RES] output lines into structured entries. Shows a bar chart of top hosts and a live feed of intercepted requests. |
Device Sidebar
Section titled “Device Sidebar”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
Saved Scripts
Section titled “Saved Scripts”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.
Built-in Snippets
Section titled “Built-in Snippets”Pre-built Frida scripts accessible from a dropdown button. Automatically filtered to show only snippets matching the detected platform:
| Snippet | Platform | What It Hooks |
|---|---|---|
| SSL Pinning Bypass | Android | TrustManager, OkHttp3, SSLContext, WebView (5 hooks) |
| SSL Pinning Bypass | iOS | SecTrustEvaluateWithError, SecTrustEvaluate, NSURLSession, TrustKit, AFNetworking |
| Root Detection Bypass | Android | File.exists, RootBeer (13 methods), Runtime.exec, SystemProperties, native access() |
| Jailbreak Detection Bypass | iOS | NSFileManager, canOpenURL, stat/lstat, fopen, fork(), IOSSecuritySuite |
| HTTP Request Tracer | Android | OkHttp3 RealCall (4.x + 3.x fallback), HttpURLConnection |
| Network Request Logger | iOS | NSURLSessionTask.resume, completion handler wrapping, delegate callbacks |
| Crypto Key Logger | Android | SecretKeySpec, Cipher.doFinal with hex dump |
| Empty Template | All | Basic 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).
AI Agent Tools
Section titled “AI Agent Tools”In security mode, the AI agent has six Frida tools:
| Tool | Parameters | What It Does |
|---|---|---|
| frida_check | (none) | Runs frida --version and enumerates devices. Returns installation status, version, and connected device list. |
| frida_list_apps | device_id (required) | Runs frida-ps -D {id} -a to list apps on the device. Returns raw text output. 15-second timeout. |
| frida_bypass_ssl | device_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_bypass | device_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_trace | device_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_inject | device_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 Mode Requirements
Section titled “Scan Mode Requirements”| Scan Mode | What’s Allowed |
|---|---|
| Passive | All Frida tools are forbidden — Frida modifies app behavior, which is inherently active. |
| Active Safe | frida_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 Full | All Frida tools are allowed without approval. |
Input Validation
Section titled “Input Validation”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.
WebSocket Events
Section titled “WebSocket Events”| Event | When It Fires | Payload |
|---|---|---|
frida.session.started | A new Frida session begins (attach or spawn) | SessionDTO — key, device_id, app, type, label, source (“panel” or “agent”), started_at |
frida.session.stopped | A session ends (manual detach or process death) | SessionDTO — same fields |
frida.output | A line of output from a Frida process | {key, line, stream} — stream is "stdout" or "stderr" |
API Reference
Section titled “API Reference”Frida Management
Section titled “Frida Management”| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/frida/status | Installation status — {installed, version, pinned_version, package_manager, package_manager_ok} |
GET | /api/v1/frida/devices | List connected devices. Returns empty array (not error) if Frida isn’t installed. |
GET | /api/v1/frida/devices/{id}/apps | List apps on device. JSON output with text fallback. 15s timeout. |
GET | /api/v1/frida/devices/{id}/processes | List running processes on device. 15s timeout. |
Session Control
Section titled “Session Control”| Method | Endpoint | Body Limit | Description |
|---|---|---|---|
POST | /api/v1/frida/attach | 512 KB | Attach to running app. Body: {device_id, app, script, session_type?, label?}. Defaults type to "custom". |
POST | /api/v1/frida/spawn | 512 KB | Spawn (kill + relaunch) app with script. Same body format. |
POST | /api/v1/frida/detach | 4 KB | Detach from session. Body: {key}. Returns 404 if session not found. |
GET | /api/v1/frida/sessions | — | List all active sessions. Always returns array (never null). |
GET | /api/v1/frida/sessions/{key}/output | — | Get session output. Query: cursor (uint64, default 0). Returns {lines[], cursor} for polling. |
DELETE | /api/v1/frida/sessions/{key} | — | Stop and clean up a session. |
Saved Scripts
Section titled “Saved Scripts”| Method | Endpoint | Body Limit | Description |
|---|---|---|---|
GET | /api/v1/frida/scripts | — | List all saved scripts (newest first) |
POST | /api/v1/frida/scripts | 256 KB | Save a new script. Body: {name, description?, code, category?, platform?}. Category defaults to "custom". |
PUT | /api/v1/frida/scripts/{id} | 256 KB | Update 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. |
Use Cases
Section titled “Use Cases”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.