Description
Can you find the flag in this packet capture? Download shark2.pcapng.
Setup
Download the pcap file and open it in Wireshark.
wget <url>/shark2.pcapngSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Filter for DNS queries from the client
ObservationThe description hides the flag inside network traffic, and the name hints at repetition. That points at an exfiltration channel on a high-volume protocol, and DNS is the obvious candidate: rarely blocked, and its query names carry arbitrary text.Apply 'dns.flags.response == 0' in Wireshark to keep only outbound queries. The flag is exfiltrated character by character through DNS query subdomains, not through DNS responses.bashwireshark shark2.pcapngWhat didn't work first
Tried: Look for the flag in HTTP or TCP streams using 'Follow Stream' in Wireshark
The pcap contains no HTTP traffic carrying the flag. Focusing on TCP/HTTP streams produces empty or unrelated results because the exfiltration channel is DNS, not a transport-layer stream. The correct approach is to switch to DNS traffic and inspect query names.
Tried: Apply 'dns.flags.response == 1' to look at DNS responses for the flag
DNS responses carry resolver-generated A or NXDOMAIN records back to the client, never the attacker's data. The encoded chunks sit in the query name of outbound queries, not in any answer section, so filtering for responses shows the wrong direction entirely.
Learn more
DNS exfiltration. DNS traffic is allowed almost everywhere, so attackers encode data into subdomain names of queries to a domain they control. The authoritative name server logs the queries and reconstructs the data later.
Why
dns.flags.response == 0. DNS packets come in pairs: the query (flags.response = 0, sent by the client) and the response (flags.response = 1, sent by the resolver). The exfil data is typed by the attacker into the query name. The response carries only an A/NXDOMAIN record back. Keeping only queries halves the noise and ensures you don't double-count the same hostname when it appears in both the question and answer sections.Step 2Order, concatenate, and base64-decode the subdomains
ObservationEach filtered query name has a varying first label built from the base64 alphabet. Those labels are base64 chunks, so join them in capture order and decode.Sort queries by frame.time so they're in the order the client sent them. Pull the first label from each hostname, concatenate, and base64-decode.bash# Dump query names in capture order: tshark -r shark2.pcapng -Y 'dns.flags.response == 0' -T fields -e frame.time -e dns.qry.namebash# Strip the base domain, take the leading label, join, b64decode: tshark -r shark2.pcapng -Y 'dns.flags.response == 0' -T fields -e dns.qry.name \ | grep -v 'in-addr\|local' \ | awk -F'.' '{print $1}' \ | tr -d '\n' \ | base64 -dExpected output
picoCTF{dns_3xf1l_ftw_...}What didn't work first
Tried: Pipe the tshark output through 'sort -u' before joining to remove duplicate query names
Sorting the labels scrambles the transmission order, so the concatenated base64 is corrupted and base64 -d gives garbage or an error. The chunks have to stay in capture order, and tshark already emits packets in frame order, so stripping newlines is all that is needed.
Tried: Use 'awk -F. {print $2}' to grab the second label instead of the first
The second label is the start of the attacker's base domain, the same constant string in every packet, carrying no flag data at all. Only the first label changes per packet, and that is where the encoded chunk lives.
Learn more
Why split on the first label. Each query name looks like
cGljb0NU.attacker.com. The leading label (before the first.) is the encoded chunk; everything after is the attacker's base domain and is the same for every packet.awk -F'.' '{print $1}'isolates the chunk.Why concatenate without a separator. Each chunk is one piece of a base64-encoded message. Joining them as one continuous string gives the original base64 blob; decode it with
base64 -dto get the flag.Order matters. Use
frame.timerather thansort: the chunks were sent in transmission order, not lexicographic order.tsharkoutput is already in capture order by default, so a plaintr -d '\n'preserves it. Avoid piping throughsort -u(it scrambles order and can drop legitimate duplicates).See Wireshark for CTF for the broader pcap workflow and CTF encodings for base64 alongside the rest of the encoding zoo.
Interactive tools
- Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.
- Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
Alternate Solution
Once you concatenate the Base64 subdomain chunks, decode them with the Base64 Decoder on this site. Paste the joined string and the flag appears without needing a terminal.
Flag
Reveal flag
picoCTF{dns_3xf1l_ftw_...}
The flag was exfiltrated via DNS queries with base64-encoded subdomains, a classic covert channel that bypasses firewall restrictions on other protocols.