sniff! Documentation
Complete reference for setting up, using, and extending sniff! — the Android HTTPS traffic interception toolkit by x-lock.
Overview
sniff! automates the full Android HTTPS interception pipeline — ADB device management, mitmproxy setup, and Frida-based SSL pinning bypass — from a single interface. Select an app, pick a bypass script, and start capturing decrypted traffic in seconds.
It provides both a terminal UI (TUI) built on BubbleTea and a modern web dashboard built on Next.js. The Go backend orchestrates all subprocesses and streams captured flows in real-time via Server-Sent Events.
Prerequisites
| Requirement | Version | Purpose |
|---|---|---|
| Go | 1.21+ | Build the backend |
| Bun | 1.0+ | Build the frontend |
| ADB | Any | Android device communication |
| Frida | 16+ | Runtime instrumentation |
| frida-server | Match Frida version | Runs on device (arm64) |
| mitmproxy | 10+ | HTTPS proxy (mitmdump) |
| Rooted Android | Android 7+ | Target device |
| mitmproxy CA | Installed as system CA | Device trusts proxy |
Device Setup
Your Android device needs to be rooted (Magisk recommended) with USB debugging enabled. The mitmproxy CA certificate must be installed as a system certificate, and frida-server must be pushed to the device.
# Push frida-server to device
adb push frida-server-16.x.x-android-arm64 /data/local/tmp/fs-helper-64
adb shell "chmod 755 /data/local/tmp/fs-helper-64"
# Install mitmproxy CA as system cert (requires root)
adb push ~/.mitmproxy/mitmproxy-ca-cert.cer /sdcard/
# Then: Settings > Security > Install from storageInstallation
Development
git clone https://github.com/x-lock/sniff.git
cd sniff
# Install frontend dependencies
cd site && bun install && cd ..
# Start both backend (:9090) and frontend (:3000)
./devThe dev script starts the Go backend on port 9090 and the Next.js dev server on port 3000, then opens the browser automatically.
Production Build
# Build frontend + Go binary
./build-sniff!
# Run in web mode
./sniff! --web
# Run in TUI mode
./sniff!
# Run with a preset target package
./sniff! com.example.appUsage
Web UI
Launch with ./sniff! --web or ./dev for development. The dashboard is organized into these pages:
| Page | Path | Description |
|---|---|---|
| Overview | /dashboard | Stats cards and quick action menu |
| Capture | /dashboard/capture | Live flow table, filters, detail panel, terminal |
| App Modes | /dashboard/modes | App-specific capture presets |
| HAR Inspector | /dashboard/har | Load, search, and analyze HAR files |
| Terminal | /dashboard/terminal | Full-page Frida output and backend logs |
| Device | /dashboard/device | ADB connection, Frida, proxy status |
| Apps | /dashboard/apps | Browse and select target app |
| Scripts | /dashboard/scripts | Manage Frida bypass scripts |
| Settings | /dashboard/settings | Configure all settings |
TUI Mode
Launch with ./sniff! for a full terminal interface.
| Key | Action |
|---|---|
| c | Start standard capture |
| m | Start MITM-only capture (no Frida) |
| n | App-specific capture modes |
| s | Settings |
| f | Frida script selection |
| d | Device info & management |
| a | Browse installed apps |
| l | Full log viewer |
| e | Export captured flows |
| q | Quit |
During capture:
| Key | Action |
|---|---|
| t / T | Cycle resource type filter |
| f | Toggle text filter |
| x | Clear flows |
| e | Export to file |
| r | Restart target activity |
| Tab | Toggle focus between flows and logs |
| Enter | View flow detail |
| Esc | Back |
Capture Workflow
When you start a standard capture, sniff! executes this sequence automatically:
On stop: proxy is cleared, Frida detaches, SELinux is restored to Enforcing.
Capture Modes
Beyond standard capture, sniff! includes specialized modes that encode app-specific bypass knowledge.
| Mode | When to Use |
|---|---|
| Standard | General apps. Full pipeline: Frida + mitmproxy + system proxy. |
| MITM Only | Apps where CA is already trusted. Skips Frida entirely. |
| Signup Handoff | Apps with external browser auth (PingOne/DaVinci). Lets signup flow run direct, enables proxy after callback. |
| LinkedIn Cronet | LinkedIn app. Spawns with Cronet patch to disable QUIC and DNS bypass. |
| DailyPay | DailyPay app. Standard bypass + APEX cert injection into isolated mount namespace. |
| Speedway | Speedway/7-Eleven. Extended ART settle time + Distil/Imperva bot protection stub. |
| Papa Johns | Papa Johns (Flutter). Uses iptables REDIRECT instead of system proxy + BoringSSL native hooks. |
Frida Scripts
Built-in Scripts
| ID | Name | Label | Target |
|---|---|---|---|
| universal | Universal SSL Unpin | BEST | TrustManager, OkHttp, Conscrypt, ProxySelector, HostnameVerifier, WebView, TrustKit, Netty |
| trustmanager | TrustManager Only | LIGHTWEIGHT | Android platform TLS only — lowest crash risk |
| okhttp | OkHttp Pinner + Proxy | OKHTTP APPS | OkHttp CertificatePinner + ProxySelector |
| proxy_only | Proxy Redirect Only | DIAGNOSTIC | Forces traffic through proxy without SSL bypass |
| webview | WebView + HttpsURL | HYBRID APPS | WebView auto-proceed + HttpsURLConnection bypass |
| react_native | React Native | RN APPS | OkHttpClientProvider, TrustKit, NetworkingModule |
| flutter | Flutter / Dart | FLUTTER | Native BoringSSL hooks in libflutter.so (needs iptables) |
Plus 7 app-specific scripts for Pilot Flying J, LinkedIn, DailyPay, Speedway, and Papa Johns.
Custom Scripts
Create custom scripts from the web UI (Scripts page → New Script) or upload .js files. Custom scripts are stored in frida_scripts/custom/ with a META header:
// META: {"label":"CUSTOM","desc":"My bypass script"}
Java.perform(function() {
var TrustManager = Java.use("javax.net.ssl.X509TrustManager");
// Your hooks here
});All scripts — built-in and custom — can be edited from the web UI. Changes are saved directly to the script file on disk.
API Reference
The Go backend serves a REST API on port 9090 (configurable). All endpoints are prefixed with /api/.
State & Events
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/state | Current capture state, mode, flow/log counts, settings |
| GET | /api/events | SSE stream — real-time flows, logs, state changes |
SSE event types:
| Event | Data | When |
|---|---|---|
| state | { capturing, captureMode, captureName } | Capture started/stopped |
| flow | Full flow object | New HTTP flow captured |
| log | { Time, Msg, Style } | Backend log entry |
| clear | {} | Flows cleared |
Capture Control
| Method | Endpoint | Body | Description |
|---|---|---|---|
| POST | /api/capture/start | { mode, package } | Start capture. Returns 409 if already capturing. |
| POST | /api/capture/stop | {} | Stop active capture |
| POST | /api/capture/clear | {} | Clear captured flows |
Available modes: standard, mitm_only, signup_handoff, linkedin_cronet, linkedin_replay, dailypay, speedway, papajohns
Flows
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/flows | All captured flows as JSON array |
| GET | /api/flows?id=N | Single flow by index |
Flow object shape:
{
"ts": 1711584000.123,
"method": "POST",
"url": "https://api.example.com/v1/auth",
"host": "api.example.com",
"path": "/v1/auth",
"status": 200,
"req_size": 256,
"resp_size": 1024,
"content_type": "application/json",
"req_headers": { "Authorization": "Bearer ..." },
"resp_headers": { "Set-Cookie": "..." },
"req_body": "{\"username\":\"...\"}",
"resp_body": "{\"token\":\"...\"}"
}Bodies are truncated to 5,000 characters.
Device
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/device | Device model, Android version, SDK, SELinux, Frida status, proxy, host IP |
| POST | /api/device/frida/start | Start frida-server on device |
| POST | /api/device/proxy/clear | Clear system proxy setting |
Scripts
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/scripts | All scripts (built-in + custom) with metadata |
| GET | /api/scripts/content?id=X | Full source code of a script |
| POST | /api/scripts/custom | Create: { name, content, label, desc } |
| PUT | /api/scripts/custom | Update: { id, name, content, label, desc } |
| DELETE | /api/scripts/custom?id=X | Delete custom script |
Settings
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/settings | All settings as key-value array |
| PUT | /api/settings | Update setting: { key, value } |
| POST | /api/export | Export flows: { format } (json or har) |
Configuration
Settings are stored in settings.json and editable via the TUI or web UI.
| Key | Default | Description |
|---|---|---|
| package | "" | Target Android package name |
| port | 8080 | mitmproxy listen port |
| attach_delay | 10 | Seconds to wait for ART JIT before Frida attach |
| ignore_hosts | PerimeterX regex | Hosts to pass through without interception |
| frida_script_id | "universal" | Active Frida script ID |
| captures_dir | ./captures | Directory for exported captures |
| frida_server | /data/local/tmp/fs-helper-64 | Path to frida-server on device |
| host_ip | Auto-detect | Host machine IP for proxy config |
| export_format | "json" | Export format: "json" or "har" |
| ui_mode | "tui" | "tui" or "web" |
| web_port | 9090 | Web UI port |
Architecture
sniff!/
├── main.go # TUI, capture sequences, ADB, Frida, mitmdump
├── web.go # HTTP API, SSE streaming, REST handlers
├── go.mod # Go dependencies (BubbleTea + stdlib)
├── frida_scripts/ # 14+ built-in .js bypass scripts
│ └── custom/ # User-created scripts
├── site/ # Next.js web dashboard
│ ├── src/app/ # App Router pages
│ ├── src/components/ # React components
│ └── src/lib/ # API client, state, filters, types
├── dev # Dev script (backend + frontend)
└── build-sniff! # Production build scriptThe Go backend manages all subprocesses (Frida, mitmdump, ADB) via os/exec. Captured flows are written to a JSONL file by a mitmdump Python addon, then tailed by the Go process at 300ms intervals and streamed to connected clients via SSE.
/tmp/sniff_flows.jsonl (one JSON per flow)The Frida injection works by hooking Java-level SSL methods (TrustManager, OkHttp CertificatePinner, etc.) or native functions (BoringSSL for Flutter) at runtime, making the app accept the mitmproxy CA certificate.
An x-lock open source project
Open Dashboard