secskills
secskills / defense / analyzing-network-traffic

analyzing-network-traffic

defense verified 2026-07-26

Analyze packet captures and network telemetry for intrusion evidence — capture and handling, the Wireshark/tshark triage funnel, Zeek log mining, Suricata rule runs, beacon and DNS-tunnel detection, TLS/JA3 fingerprinting, HTTP and file carving, exfiltration hunting, and IOC handoff. Use when a `.pcap` or `.pcapng` capture lands on your desk, when a suspected C2 beacon needs confirming, when there is data exfiltration to investigate, when malware network behaviour must be characterized from what it emitted, when a Zeek or Suricata alert needs running down, or when DNS tunneling or unusual TLS is suspected.

$ /plugin install secskills-defense $ /plugin install secskills-core

Packet capture is ground truth the endpoint can lie about but the wire cannot: every connection, DNS lookup, and byte transferred is recorded, whether or not the host's logs survived. The analysis is turning a flat capture into a story — who talked to whom, over what protocol, whether the pattern was human or automated, and what left the network. You are reconstructing intent from frames, not reading a verdict off a tool.

When to Use

When NOT to Use

undetected compromise** — use hunting-threats; this skill dissects one capture, a hunt spans data sources

analysis is one evidence stream feeding that process

come here to analyze the pcap it emitted, not to run the binary

VPC flow gaps, API calls) — use investigating-aws-incidents

it — use testing-web-applications

Capture and Handling

Get the capture right or every later step inherits the gap. A truncated snaplen or a dropped-packet capture cannot be fixed after the fact.

# Full-frame capture, no name resolution, write to disk (never parse live)
tcpdump -i eth0 -nn -s 0 -w case.pcap
# -s 0 takes full frames; a default snaplen truncates payloads and breaks carving

# Ring buffer for long-running capture: 20 files of 200 MB, oldest recycled
tcpdump -i eth0 -nn -s 0 -w case-%Y%m%d-%H%M%S.pcap -G 3600 -C 200 -W 20

# Capture without dropping under load: raise the kernel buffer, filter tightly
tcpdump -i eth0 -nn -s 0 -B 4096 'not port 22' -w case.pcap
# Confirm drops after: the summary line reports "packets dropped by kernel"

Check whether the capture is intact and what you are holding:

capinfos case.pcap        # packet count, duration, drop stats, snaplen, file type
tshark -r case.pcap -q -z io,phs   # protocol hierarchy — is the snaplen truncating?

records; classic .pcap is a flat single-link format. editcap -F libpcap in.pcapng out.pcap downgrades for a tool that only reads pcap.

editcap -c 1000000 big.pcap chunk.pcap (per packet count) or editcap -i 600 big.pcap chunk.pcap (per 600 seconds).

b.pcap` (sorts by timestamp).

"2026-07-26 06:00:00" case.pcap window.pcap`.

addresses, or editcap -s <snaplen> to truncate each packet to the first snaplen bytes (keeping headers, dropping trailing payload); TraceWrangler for deeper header/payload sanitization. Record what you changed so the recipient does not chase your rewrite as an artifact.

Hash the original and work on copies. sha256sum case.pcap goes in the case notes; the evidence file is read-only from here on.

The Triage Funnel

Start wide, narrow to the flows that matter. In Wireshark, work top-down:

proportion. Cleartext where you expected TLS, or a sliver of DNS carrying most of the bytes, is the first tell.

and the largest flows are your first two leads (beacon vs bulk transfer).

vs external at a glance.

the application saw it.

Everything Wireshark does interactively, tshark does scriptably — which is how you extract fields across a whole capture instead of clicking:

# Top talkers by bytes
tshark -r case.pcap -q -z conv,tcp

# Extract just the fields you want, tab-separated, for further processing
tshark -r case.pcap -T fields -E separator=/t \
  -e frame.time_epoch -e ip.src -e ip.dst -e tcp.dstport -e frame.len \
  -Y 'tcp.flags.syn==1 && tcp.flags.ack==0'   # every connection attempt

# Every HTTP request: host, method, URI, user-agent
tshark -r case.pcap -T fields -e http.host -e http.request.method \
  -e http.request.uri -e http.user_agent -Y http.request

Display-Filter Fluency

Display filters are the scalpel. The highest-value ones:

FilterSurfaces
http.requestEvery outbound HTTP request — URIs, hosts, user agents
dnsAll DNS; add dns.flags.rcode == 3 for NXDOMAIN (DGA tell)
tls.handshake.type == 1ClientHello only — SNI, JA3 input, offered ciphers
tls.handshake.type == 2ServerHello — chosen cipher, JA3S input
ip.addr == 10.0.0.5All traffic to or from a host (src/dst to pin direction)
tcp.flags.syn==1 && tcp.flags.ack==0Connection attempts — scan and beacon cadence
tcp.flags.reset==1RSTs — refused/closed, port-scan responses
frame contains "password"Byte-string search across payloads (cleartext creds, markers)
tcp.stream eq 7Isolate one reassembled conversation by stream index
tcp.analysis.retransmissionLoss/instability that skews timing analysis
http.response.code == 200 && http.content_type contains "octet-stream"File transfers over HTTP

Chain them: ip.dst == 185.100.87.0/24 && dns scopes DNS to one suspect netblock. dns.qry.name matches "[a-f0-9]{20,}" flags long hex labels.

Zeek: the Workhorse

Zeek turns a pcap into structured, queryable logs — the single highest-leverage move on any capture bigger than a few thousand packets.

zeek -r case.pcap
# Or add the community-id field for cross-tool pivoting:
zeek -r case.pcap policy/protocols/conn/community-id-logging
ls   # conn.log dns.log http.log ssl.log x509.log files.log notice.log weird.log ...

The log set and what each answers:

LogAnswers
conn.logEvery flow: duration, orig/resp bytes, state, service — the backbone of beacon and exfil analysis
dns.logEvery query/response — tunneling, DGA, TXT/NULL abuse
http.logHost, URI, method, user-agent, status, referrer
ssl.logTLS version, SNI, JA3/JA3S, cert chain, validation status
x509.logCertificate subject, issuer, validity, self-signed flag
files.logEvery file seen on the wire — MIME, size, MD5/SHA1, source flow
notice.logZeek's own detections (SSL::Invalid_Server_Cert, scans, etc.)
weird.logProtocol violations — protocol-on-wrong-port, malformed frames

Mine them with zeek-cut (field extraction by name, order-independent):

# Longest connections first — beacons and tunnels live at the top
cat conn.log | zeek-cut id.orig_h id.resp_h duration orig_bytes resp_bytes \
  | sort -t$'\t' -k3 -rn | head

# Every distinct destination one host reached, with hit counts
cat conn.log | zeek-cut id.orig_h id.resp_h | grep '^10\.0\.0\.5' \
  | sort | uniq -c | sort -rn

# DNS query names + types, to eyeball tunneling and DGA
cat dns.log | zeek-cut query qtype_name answers | sort | uniq -c | sort -rn | head -50

# Pivot a suspicious flow across all logs by its community-id
cat conn.log | zeek-cut community_id id.orig_h id.resp_h service

The community-id field is the same string across Zeek, Suricata, and many EDRs for the same flow — use it to line up an alert with the packets.

Suricata on a pcap

Run signatures offline against the capture to see what a rule set flags:

# Run ET Open / community rules over the pcap, write structured events
suricata -r case.pcap -S /etc/suricata/rules/suricata.rules -l ./out/
# eve.json holds alerts, plus dns/http/tls/flow records if enabled

Extract and rank the alerts:

jq -c 'select(.event_type=="alert") | {sig:.alert.signature, src:.src_ip, dst:.dest_ip}' \
  out/eve.json | sort | uniq -c | sort -rn

Read alerts as leads, not verdicts. A signature hit tells you where to look; it does not close the case, and its absence does not clear the capture.

Beacon Detection

Automated callbacks betray themselves in the timing and the shape, not the content. Look in conn.log for a source/destination pair that recurs at a regular interval, with small and roughly constant request sizes, over a long span. Human traffic is bursty and varied; a beacon is a metronome.

# Inter-arrival deltas for one src/dst pair — near-constant gaps = beacon
tshark -r case.pcap -T fields -e frame.time_epoch \
  -Y 'ip.src==10.0.0.5 && ip.dst==185.100.87.202' \
  | awk 'NR>1{print $1-prev} {prev=$1}' | sort -n | uniq -c

classic C2 signature — perfect regularity is rare malware, mild jitter is common.

polling with tasking.

hours points to a persistent channel.

Automate the scoring with RITA or a beacon-analysis tool over Zeek logs — they compute interval and size consistency across every pair so you are not eyeballing one at a time:

rita import --database case --logs ./ && rita view case beacon:'>=90'

DNS Analysis

DNS is the covert channel of choice because it is rarely blocked and often unlogged. In dns.log, two distinct patterns:

Tunneling — DNS used as a data pipe:

# Subdomain cardinality per parent domain — a tunnel spikes on one domain
cat dns.log | zeek-cut query | rev | cut -d. -f1-2 | rev \
  | sort | uniq -c | sort -rn | head

# TXT queries only — legitimate use is sparse; volume is a flag
cat dns.log | zeek-cut query qtype_name | awk -F'\t' '$2=="TXT"' | wc -l

DGA — algorithmically generated rendezvous domains:

cat dns.log | zeek-cut rcode_name query | awk -F'\t' '$1=="NXDOMAIN"' \
  | wc -l   # a burst of NXDOMAIN from one host is a DGA tell

Score label entropy or use a DGA classifier to separate cdn-3f2a.example (benign hash) from qwzjxkbvmn.info (generated). Volume plus entropy plus NXDOMAIN together make the case; any one alone has benign explanations.

TLS and Encrypted Traffic

You cannot read the plaintext without keys, but the handshake still fingerprints the client, the server, and the intent. Encryption hides content, not metadata.

parameters into a client and server fingerprint. A rare JA3 shared across unrelated hosts, or a JA3 matching a known C2 framework, is a strong lead. Zeek's ssl.log carries ja3/ja3s; correlate against public and internal known-bad lists.

match the certificate CN/SAN, absurd validity windows (1000-year or same-day certs), empty/garbage subject fields, and SNI pointing at suspicious or newly-registered domains**.

# Self-signed or validation-failed TLS, with the SNI and JA3
cat ssl.log | zeek-cut server_name validation_status ja3 ja3s \
  | grep -iv '\bok$' | sort | uniq -c | sort -rn

# SNI vs certificate subject mismatch — join ssl.log and x509.log on cert id
cat x509.log | zeek-cut certificate.subject certificate.issuer \
  certificate.not_valid_before certificate.not_valid_after

When you legitimately hold the session keys (a lab detonation with SSLKEYLOGFILE set, or an exported master secret), decrypt in Wireshark: Preferences > Protocols > TLS > (Pre)-Master-Secret log filename, or tshark -r case.pcap -o tls.keylog_file:keys.log -Y http2. Never assume you can decrypt production TLS you have no keys for — you are fingerprinting, not reading.

HTTP and File Carving

Cleartext HTTP (and decrypted TLS) exposes the whole exchange:

(python-requests, an empty UA, a typo'd browser string) on outbound traffic is a common malware tell. Stack UAs and investigate the rare ones.

strings, .php endpoints on a raw IP, or POSTs of opaque blobs.

Carve transferred files and hash them for pivoting:

# Zeek extracts files automatically when configured; otherwise from files.log:
cat files.log | zeek-cut fuid mime_type filename md5 sha1 tx_hosts rx_hosts

# Carve without Zeek:
foremost -i case.pcap -o carved/          # signature-based file recovery
tcpflow -r case.pcap -o flows/            # reassemble every TCP stream to a file
# NetworkMiner (GUI/CLI) reassembles files, images, and credentials from a pcap

Hash every carved artifact and pivot suspicious ones to analyzing-malware: sha256sum carved/* — a file that appeared on the wire and matches nothing benign is the next sample to detonate.

Exfiltration Hunting

Data leaving is the outcome that matters most. In conn.log, sort by orig_bytes descending — large outbound flows are the headline, and the direction (orig = the internal host sending) is the whole point.

# Biggest outbound transfers from internal hosts
cat conn.log | zeek-cut id.orig_h id.resp_h resp_p orig_bytes duration \
  | awk -F'\t' '$4>10000000' | sort -t$'\t' -k4 -rn

idle by day. Cross the flow timestamps against business hours.

(see DNS analysis). Small packets, high count, one destination.

*.blob.core.windows.net), paste sites (pastebin, ghostbin), and file-share domains as the destination of a large upload from a server that has no business reason to use them.

Protocol Anomalies

Attackers hide traffic on the wrong port and in the wrong protocol:

high random port. conn.log's service field is Zeek's detected protocol, independent of port number.

on 22 in conn.log (service disagrees with id.resp_p) and in weird.log.

# Detected service does not match the port — tunneling / evasion
cat conn.log | zeek-cut id.resp_p service | awk -F'\t' \
  '($1=="443" && $2!="ssl") || ($1=="53" && $2!="dns")' | sort | uniq -c

bind. frame contains "PASS " in Wireshark, or NetworkMiner's credentials tab, pulls them straight out.

Handing Off IOCs

The capture's value is what you extract for reuse. Tier indicators the way you would from any source — behaviour outlives infrastructure — and route each output:

against structure, not the URI it happened to use.

finished product — producing-threat-intelligence.

for log-based rules and engineering-detections for the broader rule pipeline (a Suricata signature for the JA3, a Zeek notice for the beacon cadence, a Sigma rule for the DNS pattern).

Record for every indicator: the flow it came from, the timestamp, and your confidence. An IP with no context is noise to whoever receives it.

Rationalizations to Reject

connection timing fingerprint encrypted traffic without decrypting it. The metadata is the analysis.

past default rule sets. Absence of a Suricata hit is not absence of C2.

You triage structured logs, not five million raw frames.

flows before concluding. C2 hides in the channels you did not sort by bytes.

A slow drip over days, or one off-hours burst, does not move the average.

domains are exactly where modern exfil and C2 hide. Confirm the flow's shape, do not wave it through on the domain name.

reassembly gaps mean payload-first misses the story. Start with the flow metadata, then read the streams that earned it.

ATT&CK Coverage

Generated from secskills-core/ttp-index.json — edit that file, then run python3 scripts/sync_attack.py --write. Re-verify IDs against the current ATT&CK release before citing them in a report.

Command and Control (TA0011)

Exfiltration (TA0010)

Detection content for any of these: engineering-detections. Proactive search: hunting-threats. Post-compromise: responding-to-incidents.

References

sources, of which network data is one

carved files from this skill for analysis

rather than packets