PCAP triage: finding the flag in a hundred thousand packets
Network forensics challenges hand you a capture and no question. The triage order that finds the answer fast - protocol hierarchy, conversations, streams - plus the exfiltration channels people actually hide flags in: DNS, ICMP, USB, and TLS you can decrypt.
A packet capture is the least structured artifact a CTF will give you. There is no entry point, no obvious payload, and often a hundred thousand packets of which four matter. Opening it in Wireshark and scrolling is the wrong move, and it is what almost everyone does first.
The right move is to narrow before you read. Every triage step below reduces the capture from 'all packets' to 'this conversation', and only at the very end do you look at individual bytes.
Step 1: what is in here?
The protocol hierarchy is the single most informative view in network forensics. It tells you what the capture is *about* in one screen, and any protocol that looks out of place in the mix is where the challenge lives.
# Protocol breakdown by packet and byte count
tshark -r capture.pcap -q -z io,phs
# Who talked to whom, ranked by volume
tshark -r capture.pcap -q -z conv,tcp
tshark -r capture.pcap -q -z conv,udp
# How many packets, over what time span
capinfos capture.pcapRead the output like a triage nurse. Ninety-eight percent TCP with a single large HTTP conversation means the answer is a transferred file. A capture that is mostly DNS, with far more queries than any host would legitimately make, means exfiltration. A handful of ICMP packets with unusually large payloads means the same thing through a different channel. Fifty thousand USB packets means this is a peripheral capture and no networking knowledge is involved at all.
Step 2: the free wins
Before any protocol analysis, run the two checks that cost nothing and solve a surprising number of challenges outright.
# The flag may simply be sitting in a payload, unencrypted
strings capture.pcap | grep -iE 'flag\{|ctf\{|picoCTF\{'
# Export every file that crossed HTTP, then look at all of them
mkdir -p out && tshark -r capture.pcap --export-objects http,out
file out/*Object export is worth doing even when you do not think the challenge is about a file transfer. Images, archives, and scripts pulled from HTTP are common carriers, and an exported ZIP is often the *next* challenge rather than the answer - stego inside a download inside a capture is a completely standard nesting.
Step 3: follow the stream
TCP splits application data across packets, so reading individual packets is reading fragments. Reassembling a stream shows you the conversation as the application saw it - the full HTTP request and response, the entire FTP control session, the whole line-based protocol exchange.
# Reassemble stream 0 as ASCII (indices start at 0)
tshark -r capture.pcap -q -z follow,tcp,ascii,0
# Every HTTP request, one per line
tshark -r capture.pcap -Y http.request \
-T fields -e ip.src -e http.host -e http.request.uri
# Cleartext credential protocols, all at once
tshark -r capture.pcap -Y 'ftp || telnet || http.authorization || pop || imap'Cleartext protocols deserve a targeted pass because they hand over credentials verbatim: FTP USER/PASS, HTTP Basic authorization headers (base64, not encryption), Telnet typed a character at a time, SMTP AUTH LOGIN. If the challenge is 'find the password', it is almost always in one of these.
Step 4: the covert channels
DNS exfiltration
DNS is the classic exfiltration channel because it is almost never blocked and every query is logged as legitimate infrastructure traffic. Data is encoded into subdomain labels and sent to a nameserver the attacker controls. In a capture the giveaway is unmistakable: hundreds of queries to subdomains of one domain, each label a chunk of base32 or hex.
tshark -r capture.pcap -Y dns.flags.response==0 \
-T fields -e dns.qry.name | sort -u | head -40
# Typical shape:
# MFRGGZDF.exfil.example.com
# MZXW6YTB.exfil.example.com
# Strip the domain, concatenate the labels, base32-decode the result.Base32 rather than base64 is the norm here, because DNS labels are case-insensitive and base32's alphabet is uppercase-only. Watch the ordering: queries can arrive out of order, and some encodings prefix each label with a sequence number for exactly that reason.
ICMP tunnelling
An ICMP echo request carries an arbitrary payload that most networks pass without inspection. A normal ping sends a fixed pattern, frequently the bytes 0x10 through 0x37 on Linux; a payload that varies from packet to packet, or that is printable ASCII, is carrying data.
tshark -r capture.pcap -Y 'icmp.type==8' -T fields -e data.data \
| tr -d '\n' | xxd -r -pUSB captures
If the capture is full of USB URB traffic, it is a peripheral recording and the payload is keystrokes or mouse movement. HID keyboard reports are 8 bytes: byte 0 is the modifier mask (0x02 and 0x20 are the shift keys), byte 1 is reserved, and bytes 2 through 7 are up to six simultaneous keycodes - 0x04 is a, 0x05 is b, and so on up through the HID usage table.
tshark -r usb.pcap -Y 'usb.capdata' -T fields -e usb.capdata
# 00:00:0b:00:00:00:00:00 -> 'h'
# 02:00:0c:00:00:00:00:00 -> 'I' (0x02 = left shift)Step 5: encrypted traffic you can still read
TLS is not automatically the end of the road. If the challenge provides a key log file - the SSLKEYLOGFILE format that browsers and curl can write - the session keys are in it and the traffic decrypts fully. This is common in CTFs precisely because the author wants you to find the file first.
tshark -r capture.pcap -o tls.keylog_file:sslkeys.log \
-Y http2 -T fields -e http2.data.data
# In Wireshark: Preferences -> Protocols -> TLS -> (Pre)-Master-Secret log filenameTwo other cases worth recognising. An RSA private key decrypts a session only if the handshake used RSA key exchange - with any ECDHE suite, forward secrecy means the private key alone is useless, which trips people up when a challenge supplies a .key file that does not help. And a WPA handshake in a wireless capture can be cracked with a wordlist, after which the wireless payloads decrypt too.
Filters worth memorising
| Filter | Finds |
|---|---|
http.request.method == "POST" | Submitted data - logins, uploads, form contents |
frame contains "flag" | The literal string anywhere in a packet |
tcp.flags.syn == 1 && tcp.flags.ack == 0 | Connection attempts, including port scans |
tcp.analysis.retransmission | Trouble spots, often where an author truncated a stream |
dns.qry.name contains "exfil" | Suspicious lookups once you have identified the domain |
data.len > 100 && icmp | Oversized ICMP payloads |
ftp-data | The actual bytes of an FTP transfer, on its separate connection |
tcp.stream == 4 | One conversation, once you know which one you want |
The order, condensed
strings | grep flag. It works often enough to justify the five seconds.- Protocol hierarchy. What is this capture about, and what does not belong?
- Conversations by volume. The big one is usually a transfer; the odd one is usually the challenge.
- Export objects and identify every file that comes out.
- Follow the interesting streams end to end.
- Sweep the cleartext protocols for credentials.
- If the mix looked wrong: DNS labels, ICMP payloads, USB HID data.
- If TLS dominates: hunt for a key log file before you conclude it is unreadable.
Notice that reading packets appears at step five, not step one. Network forensics is a filtering discipline, and the filtering is the skill - by the time you are looking at bytes, the challenge should already be nearly solved.
Further reading
- Wireshark display filter reference - Every field of every dissector, searchable
- tshark manual - The `-z` statistics options alone are worth the read
- Malware-Traffic-Analysis.net - Real captures with exercises - the closest thing to practice PCAPs at scale