TL;DR: Zeek and tshark turn raw PCAPs into beacon evidence in minutes
Pair Zeek’s conn.log and ssl.log with tshark display filters to surface C2 beaconing, JA3 hashes, and DNS tunnels from any capture — in under an hour, you can build a repeatable triage lab that turns network evidence into a defensible verdict.
Command-line network forensics has a well-established playbook: load the capture, eyeball the conversations, chase whatever looks weird. That workflow collapses the moment you’re handed a multi-gigabyte PCAP from an incident at 2 a.m. Modern triage demands automation—log generation, statistical analysis, fingerprint extraction—all of it repeatable and scriptable. Zeek and tshark give you exactly that: two complementary engines that turn raw packets into structured evidence without ever opening a GUI. This guide walks you through building that lab from scratch, using real malware captures you can legally download.
Lab setup: installing Zeek and tshark on Linux
Start with a hardened Linux VM or container—Ubuntu 22.04 LTS works fine. Zeek (formerly Bro, renamed in 2018 under the zeek.org project) is available via the OpenSUSE Build Service packages or source; tshark ships with Wireshark’s package family.
# tshark via distro packages
sudo apt update
sudo apt install -y tshark
# Zeek via the official OpenSUSE build service (Ubuntu 22.04)
echo 'deb http://download.opensuse.org/repositories/security:/zeek/xUbuntu_22.04/ /' | sudo tee /etc/apt/sources.list.d/zeek.list
curl -fsSL https://download.opensuse.org/repositories/security:/zeek/xUbuntu_22.04/Release.key | gpg --dearmor | sudo tee /etc/zeek-repo.key >/dev/null
sudo apt-key add /etc/zeek-repo.key
sudo apt update
sudo apt install -y zeek
# Or run Zeek from Docker if you prefer disposable labs
docker pull zeek/zeek
Verify the installs:
zeek --version # expect zeek version 6.x or newer
tshark -v # expect TShark (Wireshark) 4.x
Keep a clean directory layout so every case is reproducible:
~/triage/
├── pcaps/ # raw captures, never modified
├── logs/ # zeek output per case
├── scripts/ # your custom zeek scripts and awk tools
└── notes/ # findings, timeline, verdicts
Getting sample PCAPs: Malware-Traffic-Analysis and CTF captures
You need real, known-bad traffic to calibrate your instincts. The gold standard is Malware-Traffic-Analysis.net’s training exercises, curated by Brad Duncan. CISA and its Malware Initial Access report series also publish Indicators of Attack alongside PCAPs—see CISA’s threat advisories for current material. Good starter captures:
- Emotet / Qakbot infection exercises — demonstrate HTTP callbacks, redirect chains, and post-exploitation beacons over TLS.
- Cobalt Strike exercise captures — the canonical teaching sample for jittered C2 beaconing and certificate anomalies.
- CTF archives (DEF CON, online CTF platforms) — frequently include DNS-tunneling challenges purpose-built for this kind of analysis.
Run every sample inside an isolated VM with no host networking. Analysis is safe; exposure is not.
First pass with Zeek: generating conn.log, dns.log, and ssl.log
Zeek’s offline processing mode is the workhorse of triage. From your case directory:
cd ~/triage/logs
zeek -r ../pcaps/sample.pcap local
Within seconds you’ll have a directory of structured, tab-separated logs. The three you’ll live in:
- conn.log — one row per connection. Key fields:
uid(unique connection ID),id.orig_h(source IP),id.resp_h(destination),duration,orig_bytesandresp_bytes(volume each direction), andts(epoch timestamp). - dns.log — every DNS query and response, including query name, query type, and rcode.
- ssl.log — TLS handshake metadata: version, cipher suites, server name (SNI), certificate details, and—critically—
ja3andja3sfingerprints.
The immediate triage win: cut out the destination hosts your host talked to, sorted by bytes:
cat conn.log | zeek-cut id.orig_h id.resp_h orig_bytes resp_bytes | sort -k2 -n | tail -20
Detecting C2 beacons with Zeek’s interval analysis
How do you identify a C2 beacon in a PCAP using Zeek logs? Beacons follow a schedule—malware polls its operator on a fixed or jittered timer (commonly 30–60 seconds). That schedule leaves a fingerprint in conn.log: the same source/destination pair repeating with near-constant inter-arrival times and small, similar byte counts.
Zeek ships with script packages that automate this. The detect-traffic-cycles script (part of the core distribution under misc/) flags host pairs communicating in regular intervals:
# add to local.zeek or run standalone
@load misc/detect-traffic-cycles
zeek -r ../pcaps/sample.pcap local
For finer control, a minimal custom interval check over the logs works too:
# group conn.log by (src, dst, dst_port) and compute inter-arrival deltas
awk -F't' '$7=="tcp" {key=$3"-"$5"-"$6; if (ts[key]) {delta=$2-ts[key]; printf "%s %.2fn", key, delta} ts[key]=$2}' conn.log < /dev/null | sort | uniq -c | sort -rn | head
Inter-arrival deltas clustering tightly around one value—say 59.9, 60.1, 60.0, 59.8—is beacon behavior, full stop. Legitimate user traffic doesn’t keep a schedule.
Hunting beacons with tshark: display filters and statistical bucketing
tshark shines where you need surgical, repeatable extraction. What display filters isolate periodic TLS or HTTP callbacks?
# TLS traffic to a specific destination, plus handshake types
tshark -r ../pcaps/sample.pcap -Y 'tls.record.content_type == 23 && ip.dst == 203.0.113.50' -T fields -e frame.time_epoch -e ip.src -e ip.dst -e tcp.dstport
# Small periodic HTTP GET callbacks — a classic beacon signature
tshark -r ../pcaps/sample.pcap -Y 'http.request.method == "GET" && http.request.uri == "/"' -T fields -e frame.time_epoch -e ip.dst
Then bucket the inter-arrival times into a histogram with awk to defeat small amounts of jitter:
tshark -r ../pcaps/sample.pcap -Y 'ip.dst==203.0.113.50 && tcp.dstport==443' -T fields -e frame.time_epoch
| awk 'NR>1 {d=$1-prev; printf "%dn", int(d/5)*5} {prev=$1}' | sort -n | uniq -c
Ten callbacks landing in one five-second bucket is a beacon. This is the technique behind dedicated beacon hunters like RITA (below)—you’re just doing it by hand, and understanding it makes the automated scoring legible.
Extracting JA3 fingerprints from ssl.log and handshakes
How is a JA3 fingerprint extracted and matched against known malware? JA3 (by Salesforce engineers, open-sourced on GitHub) is an MD5 hash over the TLS Client Hello’s version, cipher suites, extensions, elliptic curves, and curve formats. Malicious frameworks—Cobalt Strike most famously—use distinctive, static TLS stacks, so their JA3 values match across campaigns.
Zeek computes this for you. Check the ja3 (client) and ja3s (server) columns in ssl.log:
cat ssl.log | zeek-cut id.orig_h id.resp_h server_name ja3 ja3s | sort -u
With tshark, parse the Client Hello directly:
tshark -r ../pcaps/sample.pcap -Y 'tls.handshake.type == 1' -T fields -e ip.src -e ip.dst -e tls.handshake.extensions_server_name
Copy the well-known JA3 hash associated with default Cobalt Strike (widely documented as 72a589da586844d7f0818ce684948eea) and cross-reference via your ThreatConnect/MISP instance or public JA3 search services. One caveat that matters: JA3 is a correlation signal, not proof. Common libraries—Go’s crypto/tls, curl, Python requests—share fingerprints across benign and malicious software. Treat matching JA3 as a lead, then confirm with the interval analysis and DNS evidence above.
Spotting DNS tunnels: high-entropy subdomains and query ratios
What patterns indicate DNS tunneling in dns.log? Tunneling tools (dnscat2, iodine, DNSExfiltrator) encode exfiltrated data into subdomain labels—frequently base32 or base64—which produces two signatures:
- Abnormally long, high-entropy query names — 40+ character labels with no dictionary structure.
- Extreme query ratios — hundreds of unique subdomains under a single authoritative domain, mostly TXT or NULL record types.
# longest queries in dns.log
cat dns.log | zeek-cut query qtype_name | awk '{print length($1), $1, $2}' | sort -rn | head -20
# tshark: count TXT queries per domain
tshark -r ../pcaps/sample.pcap -Y 'dns.qry.type == 16' -T fields -e dns.qry.name | cut -d'.' -f3- | sort | uniq -c | sort -rn
High entropy is a quick screen—shannon entropy above ~3.5 bits per character on a long label is suspicious. The OWASP foundation’s documentation on data exfiltration channels, and CISA’s advisories on DNS abuse, both emphasize unique-subdomain counts per domain as the more discriminating metric: hundreds of unique queries under one zone in minutes is tunneling until proven otherwise.
Correlating findings: building a triage workflow from PCAP to verdict
Individually, each technique produces leads. Together, they produce verdicts. Your repeatable checklist:
- Generate logs:
zeek -r case.pcap local. - Rank external destinations by byte volume and connection count from conn.log.
- Run interval analysis on the top talkers; flag host pairs with tight inter-arrival clustering.
- Pull JA3/ja3s for every flagged pair; check against known-malware fingerprint lists.
- Screen DNS: long high-entropy queries and unique-subdomain ratios per domain.
- Cross-check IPs and domains against threat intel—CISA advisories, abuse.ch feeds, your SIEM’s enrichment.
- Write the verdict: beaconing confirmed/suspected/cleared, with the timestamped evidence rows attached.
Zeek and tshark complement each other here by design: Zeek gives you broad, structured coverage of the entire capture in one pass; tshark gives you surgical, scriptable extraction when you need to interrogate one conversation or validate a hypothesis. Wireshark’s GUI remains useful for the final eyeball, but it doesn’t scale and it isn’t repeatable—your checklist is.
Extending the lab: RITA, Zeek Intel framework, and automated alerting
Once manual triage works, automate it:
- RITA (Real Intelligence Threat Analytics) — consumes Zeek logs and applies statistical beacon scoring (including variance and jitter tolerance) plus DNS tunnel detection out of the box. It’s the industrialized version of the awk histograms you just built.
- Zeek Intel framework — load indicator feeds (IPs, domains, JA3 hashes) and have Zeek annotate or raise notices when PCAP traffic matches. Point it at abuse.ch’s Feodo Tracker or CISA’s AIS feeds.
- SIEM piping — ship Zeek logs via Filebeat or Vector to Elastic/Splunk, then build saved detections on the same interval-math you validated manually.
Build the lab once. Every capture after that becomes a twenty-minute exercise instead of a night of scroll-scroll-scrolling in Wireshark—and that difference is what separates responders who keep up from responders who drown.
Frequently Asked Questions
What is a C2 beacon and why does it show up as regular intervals?
Malware polls its operator on a timer or jittered schedule—checking in every 30 or 60 seconds, for instance. That polling creates repeatable inter-arrival times between connections to the same destination, which show up plainly in conn.log timestamps as deltas clustering around a constant value.
Do I need Zeek and tshark, or is Wireshark enough?
Wireshark works for manual inspection of small captures. But Zeek automates structured log generation across the whole file, and tshark enables scripted, repeatable extraction—essential when your PCAP is gigabytes or you need to process dozens of captures per week.
Can JA3 fingerprints give false positives?
Yes—common TLS libraries share fingerprints across benign and malicious software. JA3 is a correlation signal, not proof of malware; always corroborate with behavioral evidence like beacon intervals and DNS patterns.
Where can I legally download malicious PCAPs for practice?
Malware-Traffic-Analysis.net and CTF archives provide curated captures for defensive training. Analyze them offline in an isolated lab VM with no host network access.
How does jitter affect beacon detection?
Attackers randomize intervals slightly to defeat exact-match timing. Bucketing arrival times into histograms (e.g., five-second buckets) absorbs jitter while still revealing the schedule—this is exactly how RITA’s beacon scoring works.
Related reading
- Write YARA Rules That Catch Ransomware Families: A Detection Lab with yara-python
- Agentic AI Security Cheat Sheet: Threat Models, MCP Hardening Checklist and Detection Hooks
