Powered by Pagefind — search only works after pnpm build
Lesson 2 of 5 · 30 min read
Beacon Hunting — Interval Analysis, Jitter, and Byte Signatures
Beacon hunting is statistical pattern recognition. The adversary’s implant fires on a timer — and timers produce patterns that human behavior does not. The hunter’s job is to separate programmatic from human in a sea of connections.
The Multi-Factor Beacon Score
No single metric reliably identifies beacons without flooding results with benign programmatic traffic (software updates, telemetry, NTP). The RITA model uses four factors:
Factor 1: Connection Rate to Destination
// Connections per hour from single host to single external destinationZeek_conn_CL| where TimeGenerated > ago(24h)| where local_orig_b == true and local_resp_b == false| summarize conn_count = count(), unique_hours = dcount(bin(TimeGenerated, 1h)) by id_orig_h_s, id_resp_h_s, id_resp_p_i| extend conn_rate_per_hour = toreal(conn_count) / toreal(unique_hours)| where conn_rate_per_hour > 20 // >20 connections/hour = programmatic
Factor 2: Interval Regularity (CV)
Computing true interval statistics requires time-series ordered data. In Sentinel/KQL:
// Simplified: compute interval from sorted timestamps per dstlet src = "10.1.2.3";let dst = "91.213.50.5";Zeek_conn_CL| where id_orig_h_s == src and id_resp_h_s == dst| order by TimeGenerated asc| extend prev_time = prev(TimeGenerated)| extend interval_sec = datetime_diff('second', TimeGenerated, prev_time)| where isnotnull(interval_sec) and interval_sec > 0| summarize mean_interval = avg(interval_sec), std_interval = stdev(interval_sec), conn_count = count()| extend cv = std_interval / mean_interval| where conn_count > 20 // enough samples
CV interpretation:
CV < 0.05: extremely regular (clock-precise)
CV 0.05–0.30: low jitter (automated with ≤10-20% jitter)
CV 0.30–0.60: moderate jitter (aggressive jitter profile, hard to detect statistically)
CV > 0.60: high variance (human-like or very high jitter)
Threshold for flagging: CV < 0.30 is the standard beacon threshold. CV < 0.60 is worth investigation when combined with other factors.
// How many internal hosts talk to this destination?Zeek_conn_CL| where TimeGenerated > ago(30d)| where local_orig_b == true and local_resp_b == false| summarize host_count = dcount(id_orig_h_s) by id_resp_h_s// Beacon candidates: host_count == 1 (only this host) or <=5
Safe Harboring — The FP Killer
Without exclusions, beacon hunts return thousands of results dominated by:
Windows Update servers (regular, byte-consistent)
Antivirus telemetry (regular heartbeats)
Domain controller traffic (NetLogon, DNS, LDAP)
Browser autoupdate checks
NTP servers
Build your safe harbor list:
let safe_harbor_ips = dynamic(["13.107.0.0/14", "52.96.0.0/14", // Microsoft "8.8.8.8", "1.1.1.1", // DNS resolvers "17.0.0.0/8", // Apple "216.58.0.0/16"]); // Google// Exclude in beacon hunt:| where not(ipv4_is_in_range(id_resp_h_s, "13.107.0.0/14"))// Or use: ASN-based exclusion for Microsoft/Akamai/Cloudflare
ASN-based exclusion is more robust than IP-based because CDN IPs change frequently:
# ASNs to always safe-harbor in beacon hunts:AS8075 Microsoft (update.microsoft.com, office365.com)AS16509 Amazon AWS (may include legitimate SaaS)AS13335 Cloudflare (may be C2 — use with caution, exclude only for >=100-host destinations)AS15169 GoogleAS20940 Akamai
The Long-Connection Alternative
When beacon interval math is unreliable (sampled data, high jitter), hunt for long connections instead. C2 sessions that maintain persistent connections appear as single conn.log entries with very long duration.
Zeek_conn_CL| where TimeGenerated > ago(7d)| where local_orig_b == true and local_resp_b == false| where duration_d > 3600 // > 1 hour| where service_s !in ("ssl", "http") or isempty(service_s) // prefer unusual services| summarize session_count = count(), total_duration = sum(duration_d), max_resp_bytes = max(resp_bytes_l) by id_orig_h_s, id_resp_h_s, id_resp_p_i| where session_count > 5 // recurring long sessions| order by total_duration desc
Long connections + non-standard service + non-CDN destination = high-priority investigation.