Shark on Wire 2: Network Steganography & UDP Port Carving

“We found this packet capture. Recover the flag that was pilfered from the network.”
picoCTF{...}💡 THE INTUITIVE ANALOGY (The Spy's Return Address)
When normal people mail letters, they write the message inside the envelope (the payload) and put their house address on the back. If a spy wants to send a secret message past government mail censors, they leave the inside of the envelope completely blank (or write generic filler like “hello”), but they intentionally write specific fake return postal codes that spell out secret numbers! This is called Network Header Steganography.
1. Beware of the Decoy Troll Flags
When you first open capture.pcap in Wireshark, the challenge author planted intentional traps to deceive automated flag scrapers:
In Wireshark, following UDP Stream 6 displays an apparent flag. But looking at the text carefully reveals:
This is a decoy designed to trick analysts who stop searching after finding the first regex match.

2. Manual Wireshark Filtering & Port Decoding
Step-by-step manual process to locate anomalous traffic and decode each byte by hand.
🔍 Step-by-Step Wireshark Workflow:
- Open
capture.pcapin Wireshark. - Apply the display filter for anomalous UDP traffic to destination port 22:udp.dstport == 22(Note: Port 22 is SSH, which is strictly TCP. Seeing UDP packets to Port 22 is an immediate red flag).
- Notice the packet sequence: Packet 1 payload is
start, Packets 2–33 payload is filleraaaaa, and the final packet isend. - Look at the Source Port column: Port numbers range from
5049to5125. - Subtract
5000from each Source Port to get the decimal ASCII character code!
Complete Manual Character-by-Character Decoding Table:
| Pkt # | Source Port | Math (Port - 5000) | ASCII Decimal | Decoded Character |
|---|---|---|---|---|
| 1 | 5000 | Start Marker | — | [START] |
| 2 | 5112 | 5112 - 5000 | 112 | p |
| 3 | 5105 | 5105 - 5000 | 105 | i |
| 4 | 5099 | 5099 - 5000 | 99 | c |
| 5 | 5111 | 5111 - 5000 | 111 | o |
| 6 | 5067 | 5067 - 5000 | 67 | C |
| 7 | 5084 | 5084 - 5000 | 84 | T |
| 8 | 5070 | 5070 - 5000 | 70 | F |
| 9 | 5123 | 5123 - 5000 | 123 | { |
| 10 | 5112 | 5112 - 5000 | 112 | p |
| 11 | 5049 | 5049 - 5000 | 49 | 1 |
| 12 | 5076 | 5076 - 5000 | 76 | L |
| 13 | 5076 | 5076 - 5000 | 76 | L |
| 14 | 5102 | 5102 - 5000 | 102 | f |
| 15 | 5051 | 5051 - 5000 | 51 | 3 |
| 16 | 5114 | 5114 - 5000 | 114 | r |
| 17 | 5051 | 5051 - 5000 | 51 | 3 |
| 18 | 5100 | 5100 - 5000 | 100 | d |
| 19 | 5095 | 5095 - 5000 | 95 | _ |
| 20 | 5100 | 5100 - 5000 | 100 | d |
| 21 | 5097 | 5097 - 5000 | 97 | a |
| 22 | 5116 | 5116 - 5000 | 116 | t |
| 23 | 5097 | 5097 - 5000 | 97 | a |
| 24 | 5095 | 5095 - 5000 | 95 | _ |
| 25 | 5118 | 5118 - 5000 | 118 | v |
| 26 | 5049 | 5049 - 5000 | 49 | 1 |
| 27 | 5097 | 5097 - 5000 | 97 | a |
| 28 | 5095 | 5095 - 5000 | 95 | _ |
| 29 | 5115 | 5115 - 5000 | 115 | s |
| 30 | 5116 | 5116 - 5000 | 116 | t |
| 31 | 5051 | 5051 - 5000 | 51 | 3 |
| 32 | 5103 | 5103 - 5000 | 103 | g |
| 33 | 5048 | 5048 - 5000 | 48 | 0 |
| 34 | 5125 | 5125 - 5000 | 125 | } |
| 35 | 5000 | End Marker | — | [END] |
3. High-Performance Python PCAP Parser (`solve.py`)
By reading the raw binary structure of the PCAP file using Python's struct module, you can parse all network frames and extract the flag without needing Wireshark installed:
import struct
# Open raw PCAP file
with open('capture.pcap', 'rb') as f:
f.read(24) # Skip 24-byte PCAP global header
flag = ''
while True:
pkt_hdr = f.read(16)
if len(pkt_hdr) < 16:
break
# Unpack PCAP packet header (timestamp, captured length)
ts_sec, ts_usec, incl_len, orig_len = struct.unpack('<IIII', pkt_hdr)
pkt_data = f.read(incl_len)
# Parse Ethernet Header (14 bytes)
eth_type = struct.unpack('>H', pkt_data[12:14])[0]
if eth_type == 0x0800: # IPv4 Protocol
ip_hdr = pkt_data[14:]
protocol = ip_hdr[9]
if protocol == 17: # UDP Protocol
udp_hdr = ip_hdr[20:28]
sport, dport = struct.unpack('>HH', udp_hdr[:4])
# Check for packets sent to destination port 22 with port > 5000
if dport == 22 and sport > 5000:
flag += chr(sport - 5000)
print("🎉 Decoded Network Flag:", flag)python -c "import struct; f=open('capture.pcap','rb'); f.read(24); pkts=[]; [pkts.append(f.read(struct.unpack('<IIII',h)[2])) for h in iter(lambda: f.read(16), b'')]; print(''.join([chr(struct.unpack('>H',p[34:36])[0]-5000) for p in pkts if len(p)>=38 and struct.unpack('>H',p[36:38])[0]==22 and struct.unpack('>H',p[34:36])[0]>5000]))"4. Decoded Flag & Verification
Extracted Secret Flag:
(Literal meaning: “pilfered data via stego” — covert channel exfiltration via network ports).
5. Network Forensic Investigation Matrix
| Technique | Detection Filter | Forensic Indicator |
|---|---|---|
| Protocol Anomaly | udp.dstport == 22 | SSH service communicates over TCP; UDP traffic on port 22 is an intentional covert channel. |
| Header Steganography | udp.srcport - 5000 | Payload is filled with dummy characters (`aaaaa`) while real bytes are hidden in port numbers. |
| Decoy Traps | udp.stream eq 6 | Challenge authors plant fake troll strings (`ico{N0t_a_fLag}`) to misdirect analysts. |
6. The Complete Investigation Path & Mental Roadmap
Here is the step-by-step roadmap from initial PCAP loading to extracting the final network stego flag:
Loaded capture.pcap in Wireshark. Inspected UDP streams and found ico{N0t_a_fLag} in Stream 6. Recognized it as an intentional decoy troll.
Filtered for port anomalies. Spotted UDP packets going to destination port 22 (SSH is strictly TCP). Applied filter udp.dstport == 22.
Examined the source ports of the 35 anomalous packets. Discovered all ports fall between 5048 and 5125 (ASCII range 5000 + ASCII byte code).
Wrote a standalone Python parser using struct.unpack() to extract every UDP packet destined for port 22 and subtract 5000 from each source port.
Assembled the 33 decoded characters to submit: picoCTF{p1LLf3r3d_data_v1a_st3g0}.