Network Forensics: Reading Packets to Find Attackers
Endpoint detection has matured. EDR products instrument process trees, memory regions, and file system events in ways that make traditional malware trivially detectable. Skilled attackers adapted: they live off the land, use signed binaries, and avoid writing to disk entirely. But no matter how clean their tradecraft is on the host, they still need to communicate — with their command-and-control infrastructure, with the data they are exfiltrating, with the next hop during lateral movement. The wire does not lie. Network forensics is how you catch attackers that endpoint tools miss.
Why Network Forensics
Disk artifacts can be deleted, overwritten, or never created. Registry keys can be cleaned up in the same script that deploys a payload. But network traffic happened. A C2 beacon that fired every four minutes for six weeks left six weeks of DNS queries, TCP connections, and TLS handshakes. Lateral movement that used WMI or PsExec left SMB session records and authentication events in flow logs. Data exfiltration that staged output in memory and pushed it over HTTPS still generated anomalous outbound volume at an unusual hour to an infrastructure provider the organization has never touched.
Network forensics does not replace endpoint analysis. It completes it. Flow logs provide coverage on segments where EDR is not deployed — network devices, IoT, OT, legacy systems. PCAP catches behaviors that host telemetry never sees. DNS logs expose C2 infrastructure before a single byte of payload is exchanged. The combination of host and network evidence produces timelines that neither source can build alone.
Traffic Capture: Picking Your Tools
Wireshark is the standard for interactive PCAP analysis. For capture on a remote host or during an active incident:
# Capture all traffic on eth0, write to a rolling 100 MB file set
tcpdump -i eth0 -w /tmp/capture-%Y%m%d-%H%M%S.pcap -G 60 -C 100
# Capture only traffic to/from a suspect IP, full packet content
tcpdump -i eth0 host 203.0.113.45 -w /tmp/suspect.pcap
# Capture DNS and HTTP without packet body (metadata only, lower noise)
tcpdump -i eth0 'port 53 or port 80 or port 443' -s 96 -w /tmp/dns_http.pcap
# Read a PCAP and filter in Wireshark syntax using tshark
tshark -r /tmp/capture.pcap -Y "dns.flags.response == 1 and dns.resp.len > 512"
Zeek (formerly Bro) sits on a tap or span port and writes structured logs — conn.log, dns.log, http.log, ssl.log, files.log — instead of raw packets. A single Zeek deployment on a mirror port provides indexed, queryable network telemetry without the storage overhead of full PCAP. For most environments, Zeek logs are the forensic record; PCAP is the escalation path.
Cloud VPC flow logs (AWS VPC Flow Logs, GCP VPC Flow Logs, Azure NSG Flow Logs) record source IP, destination IP, port, protocol, bytes, and packets at the hypervisor level. They do not capture payload, but they cover every east-west and north-south flow in the environment, including traffic between instances that never touches a physical tap.
DNS Analysis
DNS is the first signal in almost every attack chain. Malware resolves its C2 domain before the first beacon fires. Exfiltration tools encode data in DNS query labels. Compromised hosts query domains generated by Domain Generation Algorithms (DGAs) that are statistically distinguishable from human-registered names.
DNS tunneling encodes data inside query names or TXT responses. Indicators: unusually long query names (legitimate FQDNs rarely exceed 50 characters), high query volume for a single parent domain, TXT record responses with base64 or hex payloads, and clients that query the same domain thousands of times per hour.
DGA detection relies on entropy and n-gram analysis. Legitimate domain names are low-entropy English words or brand names. DGA domains are high-entropy random or pseudo-random strings. A query to xkq7r2mfpv9.top at 3 AM from a server that has no business making external DNS requests is worth investigating.
Unusual TLDs are a blunt but effective signal. Most enterprise outbound DNS touches .com, .net, .io, and a handful of country codes. Queries to .tk, .xyz, .top, .cc, or .onion proxies warrant a second look — not because they are always malicious, but because the prior probability is different.
# Extract unique queried domains from a PCAP
tshark -r capture.pcap -T fields -e dns.qry.name -Y "dns.flags.response == 0" | sort | uniq -c | sort -rn | head -50
# Find long DNS query names (potential tunneling)
tshark -r capture.pcap -T fields -e dns.qry.name -Y "dns.flags.response == 0" | awk 'length($0) > 60' | sort -u
TLS Fingerprinting with JA3 and JA3S
You cannot decrypt TLS without the session keys, but you can fingerprint the TLS client and server without decryption. JA3 hashes the TLS ClientHello fields — SSL version, cipher suites, extensions, elliptic curves, elliptic curve point formats — into a 32-character MD5. Every TLS library produces a characteristic JA3. Metasploit’s Meterpreter, Cobalt Strike, and commodity RATs each have documented JA3 hashes. JA3S does the same for the ServerHello, enabling correlation of C2 infrastructure even when domain names rotate.
Zeek computes JA3 hashes natively in ssl.log. Cross-reference collected hashes against public threat intelligence feeds — abuse.ch, Salesforce sfsal, JARM databases — to identify known-malicious TLS fingerprints without touching the payload.
# Extract JA3 hashes from a PCAP using ja3 tool
ja3 -a capture.pcap | jq '.[] | select(.ja3_digest == "72a589da586844d7f0818ce684948eea")'
# In Zeek ssl.log, pivot on a suspicious JA3
grep "72a589da586844d7f0818ce684948eea" /var/log/zeek/ssl.log | awk '{print $3, $6, $9}' | sort -u
C2 Beacon Detection
C2 frameworks beacon home on a schedule. The schedule has jitter to evade naive period-detection, but jitter is bounded — a beacon with a 60-second interval and 20% jitter fires between 48 and 72 seconds apart, every time, indefinitely. Long-running sessions to the same external IP with regular inter-packet timing are the signature.
Additional indicators: unusual User-Agent strings (default curl, Python requests, or custom strings that no browser produces); low-and-slow HTTP POST streams that transfer a few hundred bytes at fixed intervals; long-lived TCP sessions that carry almost no data (keepalive beacons); and beaconing to hosting providers (AWS, Azure, DigitalOcean, Linode) rather than named commercial services.
# Find long-duration TCP sessions in Zeek conn.log (duration > 3600s)
awk '$9 > 3600 {print $3, $5, $6, $9, $10}' /var/log/zeek/conn.log | sort -k4 -rn | head -20
# Identify connections with suspiciously regular inter-arrival timing (beacon jitter analysis)
tshark -r capture.pcap -T fields -e frame.time_epoch -e ip.dst -Y "tcp.flags.syn == 1 and tcp.flags.ack == 0" | sort -k2 | awk 'prev[$2]{diff=$1-prev[$2]; if(diff>0 && diff<120) print $2, diff} {prev[$2]=$1}'
Protocol Anomalies
Attackers frequently abuse protocol mismatches to blend in or evade inspection: SSH running on port 443, HTTP tunneling over DNS, RDP over non-standard ports. Zeek’s protocol detection is DPI-based, not port-based — it identifies the actual application protocol regardless of port. A conn.log entry where the service field reads ssh but the destination port is 443 is an immediate escalation candidate.
Other reliable signals: oversized DNS responses (legitimate responses are rarely above 512 bytes without EDNS0, and even with EDNS0 rarely above a few kilobytes); ICMP with large payloads (legitimate ICMP echo is 64 bytes — payloads in the kilobyte range suggest tunneling); outbound SMB or RDP to external IPs (almost always either lateral movement gone wrong or a misconfigured VPN).
Zeek Script Quick Wins
Zeek’s scripting layer lets you add custom detections directly in the collection pipeline, before logs hit SIEM.
# Detect DNS queries with names longer than 60 characters (tunneling indicator)
event dns_request(c: connection, msg: dns_msg, qtype: count, qclass: count) {
for (q in msg$questions) {
if (|msg$questions[q]$query| > 60) {
NOTICE([$note=DNS::Long_Query,
$conn=c,
$msg=fmt("Long DNS query: %s (%d chars)", msg$questions[q]$query, |msg$questions[q]$query|),
$identifier=cat(c$id$orig_h)]);
}
}
}
# Alert on non-standard port SSH (DPI-confirmed SSH on port != 22)
event connection_established(c: connection) {
if (c$service == "ssh" && c$id$resp_p != 22/tcp) {
NOTICE([$note=SSH::Non_Standard_Port,
$conn=c,
$msg=fmt("SSH detected on non-standard port %s", c$id$resp_p)]);
}
}
These scripts produce structured notice.log entries that feed directly into any SIEM pipeline without additional parsing.
Integrating Network Forensics into OpenArmor
OpenArmor ingests all of the above through a unified collection pipeline. Zeek logs ship via Filebeat or Fluent Bit into the OpenArmor event store, where field names are normalized to a common schema alongside host events from EDR and cloud audit logs. VPC flow logs land in the same pipeline through provider-specific ingest connectors.
The correlation layer joins network and host evidence automatically. A Zeek dns.log entry resolving a DGA domain triggers a lookup against host process trees to identify which process opened the socket. A C2 beacon pattern detected in conn.log cross-references authentication logs to determine which credential was active at the time. The result is an enriched alert with both the network and host narrative attached, rather than two separate alerts that an analyst must manually join.
JA3 and JA3S hashes from Zeek’s ssl.log are checked against OpenArmor’s threat intelligence feeds on ingest. Matches generate high-priority alerts with the full session context — source host, destination IP, certificate subject, session duration, and bytes transferred — without requiring the analyst to decrypt or replay the traffic.
Network forensics is not a separate discipline from host forensics. It is the other half of the same investigation. Attackers understand this asymmetry and exploit it. Closing the gap requires treating network telemetry as a first-class data source, ingesting it at the same fidelity as endpoint events, and correlating across both domains before an alert surfaces. That is what OpenArmor is built to do.