RUNBOOK // Active C2 Beacon Hunting & Egress Severing
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 windowDeviceNetworkEvents| 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”# 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 -AutoSize2. 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:
# Quick scan for suspect threads in the processGet-Process -Id <PID> | Select-Object -ExpandProperty Threads | Select-Object Id, StartAddress, ThreadState | Format-Table -AutoSize🛑 Step 3: Emergency Egress Severing
Section titled “🛑 Step 3: Emergency Egress Severing”Immediately sever the C2 channel without rebooting the host (preserving volatile memory):
- 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).
- Endpoint Live Response Isolation:
- Execute MDE Isolation to keep telemetry alive while dropping socket connections.
- 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):
1. Dump Process Memory via Live Response
Section titled “1. Dump Process Memory via Live Response”# Create mini-dump of the beaconing processprocdump.exe -ma <PID> C:\Forensics\C2_ProcessDump.dmp2. 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:
# Extract Cobalt Strike beacon configuration blockpython 1768.py C:\Forensics\C2_ProcessDump.dmpExample Extracted Config Output:
Section titled “Example Extracted Config Output:”BeaconType: HTTPSPort: 443SleepTime: 60000 (60s)MaxGetSize: 1048576Jitter: 20%C2Server: 185.220.101.5,/api/v1/updateUserAgent: 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.