Skip to content

RUNBOOK // Active C2 Beacon Hunting & Egress Severing

SEVERITY 1 // CRITICAL NETWORK & C2 PERSISTENCE Target Time to Sever: < 20 minutes

Adversary Command and Control (C2) agents (such as Cobalt Strike, Sliver, Havoc, or Mythic) maintain communication with an external listener via periodic polling requests (“beacons”).

To evade static time-threshold alerts, modern C2 agents apply Jitter (e.g., sleep 60s $\pm$ 20%). This runbook provides the mathematical and operational procedures to detect beaconing, trace the calling process, extract C2 configuration, and sever the communication channel.


📡 Step 1: Identifying Beaconing Host via KQL (Defender XDR)

Section titled “📡 Step 1: Identifying Beaconing Host via KQL (Defender XDR)”

Detects regular, repeated outbound network connections to external IP addresses over HTTP/HTTPS, calculating time delta regularity even in the presence of jitter:

let Lookback = 24h;
let MinConnections = 30; // Minimum 30 connections in window
DeviceNetworkEvents
| where Timestamp > ago(Lookback)
| where ActionType == "ConnectionSuccess"
| where not(ipv4_is_private(RemoteIP)) // Exclude internal traffic
| where RemotePort in (80, 443, 8080, 8443, 4444)
| project Timestamp, DeviceName, DeviceId, InitiatingProcessFileName, InitiatingProcessId, RemoteIP, RemoteUrl, RemotePort
| sort by DeviceName, RemoteIP, Timestamp asc
| serialize
| extend PrevTime = prev(Timestamp), PrevIP = prev(RemoteIP), PrevDevice = prev(DeviceName)
| where DeviceName == PrevDevice and RemoteIP == PrevIP
| extend TimeDeltaSec = datetime_diff('second', Timestamp, PrevTime)
| summarize
TotalBeacons = count(),
AvgIntervalSec = avg(TimeDeltaSec),
StdDevIntervalSec = stdev(TimeDeltaSec), // Low standard deviation indicates regular beaconing
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
CallingProcesses = make_set(InitiatingProcessFileName)
by DeviceName, RemoteIP, RemoteUrl, RemotePort
| where TotalBeacons >= MinConnections
| extend JitterRatio = round(StdDevIntervalSec / AvgIntervalSec, 2)
| where JitterRatio < 0.40 // Jitter ratio under 40% indicates programmatic beaconing
| sort by TotalBeacons desc

🔬 Step 2: Host Process Attribution (Finding the Injected Process)

Section titled “🔬 Step 2: Host Process Attribution (Finding the Injected Process)”

Adversaries rarely beacon from an obvious malware.exe. They inject shellcode into legitimate system binaries (svchost.exe, explorer.exe, notepad.exe):

1. Identify Calling Process on Endpoint via PowerShell

Section titled “1. Identify Calling Process on Endpoint via PowerShell”
Terminal window
# Match remote C2 IP directly to local process ID and path
$c2IP = "185.220.101.5"
Get-NetTCPConnection | Where-Object { $_.RemoteAddress -eq $c2IP } | ForEach-Object {
$proc = Get-Process -Id $_.OwningProcess
[PSCustomObject]@{
LocalPort = $_.LocalPort
RemoteIP = $_.RemoteAddress
RemotePort = $_.RemotePort
PID = $_.OwningProcess
ProcessName = $proc.Name
Path = $proc.Path
StartTime = $proc.StartTime
}
} | Format-Table -AutoSize

2. Verify Process Injection / Hollow (Check for Unbacked RWX Memory)

Section titled “2. Verify Process Injection / Hollow (Check for Unbacked RWX Memory)”

If the calling process is a legitimate binary (e.g. svchost.exe), check if its threads are executing from memory unbacked by disk files:

Terminal window
# Quick scan for suspect threads in the process
Get-Process -Id <PID> | Select-Object -ExpandProperty Threads |
Select-Object Id, StartAddress, ThreadState |
Format-Table -AutoSize

Immediately sever the C2 channel without rebooting the host (preserving volatile memory):

  1. Firewall / Proxy Perimeter Block:
    • Add the C2 Remote IP and Domain to firewall egress drop rules.
    • Add domain to DNS sinkhole (0.0.0.0).
  2. Endpoint Live Response Isolation:
    • Execute MDE Isolation to keep telemetry alive while dropping socket connections.
  3. Terminate Calling Thread/Process:
    Terminal window
    Stop-Process -Id <PID> -Force

💾 Step 4: Extracting C2 Configuration from Memory (Cobalt Strike / Sliver)

Section titled “💾 Step 4: Extracting C2 Configuration from Memory (Cobalt Strike / Sliver)”

C2 payloads in memory contain rich configuration metadata (Watermark, Public Key, Sleep Time, Jitter, User-Agent, URI Paths, Failover IPs):

Terminal window
# Create mini-dump of the beaconing process
procdump.exe -ma <PID> C:\Forensics\C2_ProcessDump.dmp

2. Parse C2 Config with 1768.py / CobaltStrikeConfig

Section titled “2. Parse C2 Config with 1768.py / CobaltStrikeConfig”

Run Didier Stevens’ 1768.py parser or Volatility 3 against the memory dump:

Terminal window
# Extract Cobalt Strike beacon configuration block
python 1768.py C:\Forensics\C2_ProcessDump.dmp
BeaconType: HTTPS
Port: 443
SleepTime: 60000 (60s)
MaxGetSize: 1048576
Jitter: 20%
C2Server: 185.220.101.5,/api/v1/update
UserAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)...
Watermark: 426194201 # Useful for tracking adversary campaign infrastructure!

🛡️ Step 5: Post-Severing Containment Checklist

Section titled “🛡️ Step 5: Post-Severing Containment Checklist”
  • Check if the same C2 IP was queried by any other host in the enterprise proxy logs.
  • Submit the extracted C2 Watermark / User-Agent to Threat Intel (VirusTotal, AlienVault OTX) to discover related staging servers.
  • Check the endpoint for scheduled tasks or run keys configured to spawn a backup beacon.
  • Verify if privileged credentials were used on the endpoint while the C2 was active.