Capture · Traffic analysis
Traffic analysis workflows
Routing through a Kali VM doesn't just isolate the sample — it gives you a privileged vantage point on the wire. Every byte the malware sends or receives is yours to capture, decode, modify, replay. Here are the workflows that actually find things.
On this page
- Why the router vantage matters
- First-look triage (5 minutes)
- DNS-based C2 & exfiltration
- FakeNet reveals: making malware confess
- TLS fingerprinting & SNI analysis (JA3/JA4)
- Beaconing & periodicity detection
- Finding hidden C2 infrastructure
- Inline TLS interception with mitmproxy
- Offline analysis: Zeek & Suricata
- PCAP replay & re-analysis
- Tool matrix: when to use what
Why the router vantage matters
A host-based capture sees what reaches the host. A router-based capture sees what the malware thinks it sent. Those aren't the same:
- Pre-NAT visibility. The malware's source port assignments, its retry patterns, its real connection attempts — all visible before MASQUERADE rewrites them.
- Pre-DNS-resolution visibility. Failed lookups, fast-flux behaviour, hostnames the malware never actually reaches — you see them.
- Inline modification. iptables, mitmproxy, sslsplit can transform the traffic in-flight. Malware analysis is much more than passive sniffing.
- Replay potential. Captured traffic can be re-injected into a fresh detonation to test hypothesis-driven scenarios.
- No host noise. Your Mac's iCloud, Spotlight, App Store, browsers, and brew updates aren't on the wire here — the only traffic is what the lab generates.
First-look triage (5 minutes)
You just detonated something. Before anything else, get fast answers to three questions: where is it talking, on what ports, and how loud?
# 1. On the router. Start a rotating capture so you don't lose anything.
ssh root@10.0.100.1
sudo secvf-capture start # writes /var/captures/*.pcap
# 2. While the sample runs, get live deltas:
sudo tcpdump -i eth0 -nn -q -t -l \
| tee /tmp/live.txt # human-readable summary
# 3. After 60s, hit Ctrl-C on tcpdump and snapshot what you've seen:
secvf-monitor # router status + conntrack
Now answer the three questions:
# Hosts the sample has reached
awk '/IP/ {print $3, "->", $5}' /tmp/live.txt | sort -u
# Top destinations by packet count
awk '/IP/ {print $5}' /tmp/live.txt | sort | uniq -c | sort -rn | head
# Ports it's hammering
awk '/IP/ {n=split($5,a,"."); print a[n]}' /tmp/live.txt | sort | uniq -c | sort -rn
If you see nothing after 60 seconds, the sample is probably sandbox-detecting. That's a finding. Move to FakeNet (below) to coax it out of dormancy.
DNS-based C2 & exfiltration
DNS is the single most useful protocol for tracking malware. Even encrypted payloads need to resolve names first.
Watch every DNS query live
# On the router — listen for DNS, decode names, ignore everything else
sudo tshark -i eth0 -Y "dns" -T fields \
-e ip.src -e dns.qry.name -e dns.qry.type -e dns.resp.name -e dns.a
# Filter out chatter — only outbound queries from lab clients
sudo tshark -i eth0 -Y "dns.flags.response == 0 and ip.src != 10.0.100.1" \
-T fields -e ip.src -e dns.qry.name
Look for DNS tunneling (exfiltration)
DNS-tunneled C2 has tells: long random-looking subdomains, high query rates, TXT/NULL record types, or many different subdomains under one apex.
# Long subdomains (often base32/base64 encoded data)
sudo tshark -i eth0 -Y "dns.qry.name matches \"[a-z0-9]{20,}\""
# TXT and NULL record types — unusual for normal browsing
sudo tshark -i eth0 -Y "dns.qry.type == 16 or dns.qry.type == 10" \
-T fields -e dns.qry.name
# Many subdomains under one apex (entropy + variance)
sudo tcpdump -i eth0 -nn -A 'port 53' \
| grep -oE '[a-z0-9.-]+\.example\.com' | sort -u | wc -l
Pin DNS-tunneling to a specific process inside the guest
On the lab guest itself:
# Linux guest
sudo ss -tunap | grep ":53"
sudo lsof -i UDP:53 -n -P
# Or use the auditd hooks the router setup script installs:
auditctl -l | grep dns
ausearch -k dns-watch | tail -20
FakeNet reveals: making malware confess
This is where SecVF earns its keep. FakeNet sinkholes every DNS query and answers every port with a plausible service. The malware behaves as if the internet exists — and reveals everything it would have done.
The reveal workflow
- Switch to FakeNet mode. See the FakeNet page for setup.
- Pre-detonation: start tcpdump on the router capturing everything:
sudo tcpdump -i eth0 -nn -w /var/captures/$(date +%F-%H%M)-fakenet.pcap \ -s 0 'not port 22' # exclude your SSH - Detonate the sample. Run it inside the analysis VM.
- Watch FakeNet logs in real time across three terminals:
# Terminal 1: every DNS query the sample makes tail -f /var/log/fakenet/dns.log # Terminal 2: every HTTP/HTTPS request hitting nginx tail -f /var/log/fakenet/http.log # Terminal 3: TLS connections that hit sslsplit tail -f /var/log/fakenet/sslsplit.connect.log - After 5–15 minutes, stop and aggregate.
What you'll typically see
Real malware behaviour observed in this exact workflow:
- Hardcoded C2 domains in DNS queries — including ones the malware would never have reached through real DNS (because the operator hasn't activated them yet).
- User-Agent strings that imitate real browsers but get subtle details wrong — perfect fingerprinting input.
- URI patterns like
/gate.php?id=ABC&v=1.2— the unique part is often a campaign identifier or version tag. - Beacon timing with jitter — see Beaconing detection.
- Multiple staging hosts — the first request reveals the dropper; later requests reveal the post-exploitation payload server.
- Encrypted POST bodies over HTTP — the sample knows TLS is unreliable so it brings its own crypto. The shape of the body (length, byte distribution) is fingerprintable even when the content isn't.
Extract a clean IOC list from a FakeNet session
# Domains seen
awk '/query=/ {print $4}' /var/log/fakenet/dns.log \
| sed 's/query=//' | sort -u > /tmp/iocs-domains.txt
# Request paths
awk -F'"' '/"GET|"POST/ {print $2}' /var/log/fakenet/http.log \
| awk '{print $2}' | sort -u > /tmp/iocs-paths.txt
# User-Agents
awk -F'"' '{print $(NF-1)}' /var/log/fakenet/http.log \
| sort -u > /tmp/iocs-uagents.txt
# TLS SNI values (pre-decryption)
awk '/SNI=/ {print $4}' /var/log/fakenet/sslsplit.connect.log \
| sort -u > /tmp/iocs-sni.txt
These four files are the IOCs you'd hand to a threat-intel pipeline or stuff into a YARA rule.
TLS fingerprinting & SNI analysis (JA3/JA4)
Even without decrypting, TLS Client Hellos identify the calling library uniquely. Mass-produced malware reuses TLS stacks across campaigns; the fingerprint is often more durable than the C2 domain.
SNI (cleartext)
# Every SNI value seen on the wire — pre-encryption
sudo tshark -i eth0 -Y "tls.handshake.extensions_server_name" \
-T fields -e tls.handshake.extensions_server_name | sort -u
JA3 fingerprints
# Suricata or Zeek both compute JA3. Run a JA3-aware sensor against
# either live traffic or a saved PCAP:
sudo suricata -r /var/captures/sample.pcap -k none \
--runmode=offline -l /tmp/suricata
# JA3 hashes will appear in /tmp/suricata/eve.json
jq -r 'select(.event_type=="tls") | .tls.ja3.hash' /tmp/suricata/eve.json \
| sort | uniq -c | sort -rn
What a JA3 hash tells you
Cross-reference against community databases — abuse.ch/sslbl, ja3er.com, your own internal corpus. Common fingerprints map to specific libraries (Cobalt Strike beacon, Sliver implant, Empire, etc.). When a malware sample's JA3 matches a known beacon framework's, that's high-confidence attribution before you've decoded a single byte of payload.
Beaconing & periodicity detection
C2 channels usually have a sleep-then-call pattern. The interval is often jittered (e.g. "every 60 seconds, ±15s") but the average is stable enough to detect.
# Extract timestamps + destination IP from a saved PCAP for one suspected C2
tshark -r /var/captures/sample.pcap \
-Y "ip.dst == 192.0.2.10" \
-T fields -e frame.time_epoch -e ip.dst > /tmp/beacons.txt
# Compute inter-packet deltas
awk 'NR>1{print $1-prev} {prev=$1}' /tmp/beacons.txt | sort -n
# Quick histogram
awk 'NR>1{print int($1-prev)} {prev=$1}' /tmp/beacons.txt \
| sort | uniq -c | sort -rn | head -10
A consistent delta of, say, 58–62 seconds is a beacon. The standard deviation tells you how much jitter the framework was configured with.
Visual beacon detection
# RITA (Real Intelligence Threat Analytics) is purpose-built for this.
# It runs offline against Zeek logs.
zeek -r /var/captures/sample.pcap # produces logs in cwd
rita import-zeek-logs ./ secvf-session
rita show-beacons secvf-session
Finding hidden C2 infrastructure
Malware often has primary, fallback, and "domain generation algorithm" (DGA) tiers. FakeNet exposes them all because the malware tries every tier when the primary doesn't respond correctly. In the real world, that infrastructure stays hidden until activation.
DGA detection
# Domains with high lexical entropy
python3 -c "
import math, sys, collections, re
for line in sys.stdin:
name = re.search(r'query=([^ ]+)', line)
if not name: continue
s = name.group(1).split('.')[0]
if len(s) < 8: continue
p = collections.Counter(s)
H = -sum((c/len(s)) * math.log2(c/len(s)) for c in p.values())
if H > 3.5: print(f'{H:.2f} {name.group(1)}')
" < /var/log/fakenet/dns.log | sort -rn | head
Domain fronting / CDN abuse
Some samples connect to a benign-looking CDN (CloudFront, Fastly, Cloudflare) and use the SNI to specify a different hidden backend. The TLS SNI doesn't match the actual application traffic:
# Look for traffic to known CDN ASN ranges where the SNI is generic
# (e.g. amazonaws.com, fastly.net) but the HTTP Host header — if seen —
# disagrees. Since we sinkhole, the Host header inside the request is
# the actual target.
awk '/Host:/ {print $0}' /var/log/fakenet/http.log | sort -u
Inline TLS interception with mitmproxy
For samples that don't certificate-pin, running mitmproxy in transparent mode on the router gives you decrypted application-layer traffic.
# One-time setup: generate the CA, install it inside the analysis VMs
# (NOT in the malware sample VM — we want failures to be visible).
mitmproxy --mode transparent --certs /root/.mitmproxy/
# Redirect outbound 80/443 from the lab to mitmproxy
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 \
-j REDIRECT --to-ports 8080
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 \
-j REDIRECT --to-ports 8080
# Now run mitmproxy
mitmproxy --mode transparent
For non-interactive use, swap to mitmdump with a script that logs decrypted bodies:
mitmdump --mode transparent -s /opt/secvf/log_decrypted.py
(The router setup script writes a sample log_decrypted.py to /opt/secvf/ on first install.)
Offline analysis: Zeek & Suricata
Once you have a PCAP, run it through both engines. They give different views.
Zeek — protocol-aware log generation
cd /tmp/zeek-analysis
zeek -r /var/captures/sample.pcap
# Output: dozens of log files
ls -la
# conn.log — every connection summary (5-tuple, duration, bytes)
# dns.log — every DNS query + response
# http.log — every HTTP request + response
# ssl.log — every TLS handshake (JA3 included if version supports)
# files.log — every file transferred (with extracted MD5/SHA1)
# weird.log — protocol anomalies (Zeek's instinct)
# notice.log — Zeek's high-level observations
Read weird.log and notice.log first. Zeek often flags malware behaviour that you'd otherwise miss.
Suricata — signature-driven IDS
# Run against the same PCAP with the Emerging Threats ruleset
sudo suricata -r /var/captures/sample.pcap -l /tmp/suricata \
-k none --runmode=offline
# Alerts in eve.json
jq -r 'select(.event_type=="alert") | "\(.timestamp) \(.alert.signature)"' \
/tmp/suricata/eve.json | sort -u
A clean run with zero alerts is itself a signal — either the sample is using novel infrastructure (rare) or you don't have rules covering it (common). Add SIDs as you discover patterns.
PCAP replay & re-analysis
You captured something interesting. Now you want to test how a different defensive product, or a different malware version, would behave against the same traffic.
# tcpreplay — inject the saved PCAP back onto an interface
sudo tcpreplay -i eth0 --topspeed /var/captures/sample.pcap
# Mid-stream slowdown / loop
sudo tcpreplay -i eth0 --pps 100 --loop=3 /var/captures/sample.pcap
# Modify the PCAP before replay (e.g. rewrite destination IP)
tcprewrite --infile=in.pcap --outfile=out.pcap \
--dstipmap=192.0.2.10:10.0.100.50
Combine with FakeNet for a fully offline regression workflow: replay → FakeNet responds → log diff against prior session.
Tool matrix: when to use what
| Need | Reach for | Why |
|---|---|---|
| "What's happening right now?" | tcpdump | Lightest. Live, on the wire, no parsing overhead. |
| "Show me decoded protocols" | tshark -Y | Wireshark grammar; can filter on application fields. |
| "I want to click around" | Wireshark (offline, post-PCAP) | GUI for deep-dive once you have a capture. |
| "Find the C2 in a 10 GB capture" | Zeek | Protocol logs are structured; grep through conn.log. |
| "Match against known threat patterns" | Suricata + ET rules | Signature-based detection at line rate. |
| "Show me beaconing" | RITA (over Zeek logs) | Statistical periodicity built in. |
| "Decrypt non-pinning TLS" | mitmproxy / sslsplit | Inline MITM. Won't beat pinning. |
| "What's the calling library?" | JA3 hash via Suricata | Cross-reference against community feeds. |
| "Replay this traffic differently" | tcpreplay + tcprewrite | Modify + re-inject for regression. |
| "DNS exfil hunting" | tshark + entropy script | Manual; tooling is sparse here. |
| "File extraction from traffic" | Zeek files.log + extract | Pulls payloads with hash for further analysis. |