PICOCTF 2019 • FORENSICS • NETWORK STEGANOGRAPHY

Shark on Wire 2: Network Steganography & UDP Port Carving

By AbdoAug 31, 2026
Shark on Wire 2 Analysis
Official Challenge Prompt

“We found this packet capture. Recover the flag that was pilfered from the network.”

Category: Network Forensics / PCAP AnalysisPoints: 300 PTSFlag Format: picoCTF{...}
Provided File (Download & Practice)⬇️ Download capture.pcapSize: 115 KB • Packet CaptureType: Wireshark / libpcap
Target: UDP Port 22 Stream

💡 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:

⚠️ Trap Alert: Decoy in UDP Stream 6

In Wireshark, following UDP Stream 6 displays an apparent flag. But looking at the text carefully reveals:

ico{N0t_a_fLag}

This is a decoy designed to trick analysts who stop searching after finding the first regex match.

Decoy Flag Stream 6
METHOD A: HANDS-ON WIRESHARK INVESTIGATION

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:

  1. Open capture.pcap in Wireshark.
  2. 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).
  3. Notice the packet sequence: Packet 1 payload is start, Packets 2–33 payload is filler aaaaa, and the final packet is end.
  4. Look at the Source Port column: Port numbers range from 5049 to 5125.
  5. Subtract 5000 from each Source Port to get the decimal ASCII character code!

Complete Manual Character-by-Character Decoding Table:

Pkt #Source PortMath (Port - 5000)ASCII DecimalDecoded Character
15000Start Marker[START]
251125112 - 5000112p
351055105 - 5000105i
450995099 - 500099c
551115111 - 5000111o
650675067 - 500067C
750845084 - 500084T
850705070 - 500070F
951235123 - 5000123{
1051125112 - 5000112p
1150495049 - 5000491
1250765076 - 500076L
1350765076 - 500076L
1451025102 - 5000102f
1550515051 - 5000513
1651145114 - 5000114r
1750515051 - 5000513
1851005100 - 5000100d
1950955095 - 500095_
2051005100 - 5000100d
2150975097 - 500097a
2251165116 - 5000116t
2350975097 - 500097a
2450955095 - 500095_
2551185118 - 5000118v
2650495049 - 5000491
2750975097 - 500097a
2850955095 - 500095_
2951155115 - 5000115s
3051165116 - 5000116t
3150515051 - 5000513
3251035103 - 5000103g
3350485048 - 5000480
3451255125 - 5000125}
355000End Marker[END]
METHOD B: AUTOMATED RAW PCAP PARSER & ONELINER

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)
⚡ Terminal One-Liner (PowerShell / Bash):
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:

picoCTF{p1LLf3r3d_data_v1a_st3g0}

(Literal meaning: “pilfered data via stego” — covert channel exfiltration via network ports).

5. Network Forensic Investigation Matrix

TechniqueDetection FilterForensic Indicator
Protocol Anomalyudp.dstport == 22SSH service communicates over TCP; UDP traffic on port 22 is an intentional covert channel.
Header Steganographyudp.srcport - 5000Payload is filled with dummy characters (`aaaaa`) while real bytes are hidden in port numbers.
Decoy Trapsudp.stream eq 6Challenge 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:

STEP 1
PCAP Triage & Spotting the Decoy Trap

Loaded capture.pcap in Wireshark. Inspected UDP streams and found ico{N0t_a_fLag} in Stream 6. Recognized it as an intentional decoy troll.

STEP 2
Protocol Anomaly Hunting

Filtered for port anomalies. Spotted UDP packets going to destination port 22 (SSH is strictly TCP). Applied filter udp.dstport == 22.

STEP 3
Source Port Mathematical Analysis

Examined the source ports of the 35 anomalous packets. Discovered all ports fall between 5048 and 5125 (ASCII range 5000 + ASCII byte code).

STEP 4
Automated Binary PCAP Parsing

Wrote a standalone Python parser using struct.unpack() to extract every UDP packet destined for port 22 and subtract 5000 from each source port.

STEP 5
Flag Capture & Validation

Assembled the 33 decoded characters to submit: picoCTF{p1LLf3r3d_data_v1a_st3g0}.

Cyber Amber
#f59e0b
PresetsClick to lock