After initial compromise, malware phones home on a schedule. This post explains beaconing patterns, how to measure jitter and interval consistency, and how to find a beacon hiding in normal HTTP traffic.
What is a beacon?
Once malware establishes a foothold on a host, it needs a way to receive instructions and return results to the attacker. Rather than maintaining a persistent connection — which would be immediately obvious — it sleeps for a fixed interval, then wakes, sends a short HTTP or HTTPS request to a command-and-control (C2) server, collects any queued commands, executes them, and sleeps again.
This pattern is called beaconing. It is the communication heartbeat of nearly every modern piece of malware, from commodity RATs to nation-state implants. The beacon might fire every 60 seconds, every 5 minutes, or once a day depending on the operator's preference for speed versus stealth.
The anatomy of a beacon request
A basic beacon HTTP request has recognisable characteristics:
- Fixed destination: always the same IP or domain.
- Small, consistent payload: check-in data is small (host ID, task queue). Response is small unless a command is waiting.
- Consistent User-Agent: the implant sends the same UA string every time, often a static default from the C2 framework (Cobalt Strike's default UA was
Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)— IE 9, on a modern host). - Regular timing: the interval between requests is consistent, possibly with added jitter to avoid exact-second matching.
Jitter and why it matters for detection
Sophisticated implants add randomness (jitter) to their sleep intervals. Instead of sleeping exactly 300 seconds, they sleep between 240–360 seconds. This breaks naive detection rules that look for exact-interval repetition.
However, jitter does not break statistical analysis. If you collect 50 intervals between requests, the standard deviation and coefficient of variation (CV = σ/μ) will still be much lower than legitimate browsing traffic, which is highly irregular. Legitimate user traffic has a CV close to 1.0 or higher. Beacons typically have a CV below 0.3 even with 20% jitter applied.
# Python: compute interval statistics from timestamps
import statistics
intervals = [301, 298, 304, 299, 303, 297, 302, 300] # seconds
mean = statistics.mean(intervals)
stdev = statistics.stdev(intervals)
cv = stdev / mean
print(f'mean={mean:.1f}s stdev={stdev:.2f} CV={cv:.3f}')
# → mean=300.5s stdev=2.39 CV=0.008 ← unmistakably a beaconFinding beacons in network logs
In a pcap, the workflow is:
- Group by destination: collect all connections to each external IP. Sort by connection count descending. High-frequency connections to unfamiliar IPs are candidates.
- Extract timestamps: for each candidate, list the time of every request and compute inter-arrival intervals.
- Apply statistics: low CV, consistent payload size, unusual hours (beacons don't take lunch breaks) are high-confidence signals.
- Inspect headers: stale or inconsistent User-Agent, missing Accept-Language, Accept-Encoding in a specific order — these are signs of a programmatic HTTP client, not a browser.
Common C2 frameworks and their tells
- Cobalt Strike: default URI patterns (
/jquery-3.3.1.min.js,/pixel.gif), Malleable C2 profiles change this — but many operators use defaults. - Metasploit Meterpreter: default port 4444, binary over TCP — easy to spot if not tunnelled.
- Sliver: HTTPS with default self-signed certificates. JA3 fingerprint is distinctive.
- PoshC2: often PowerShell User-Agent strings in the request headers.
Practice it
The Phantom Heartbeat challenge in FoilLab puts you in front of a packet capture where a beacon is hiding among normal web traffic. You need to identify the host, measure the interval, decode the callback, and extract the flag.