PICOCTF 2019 • NETWORK FORENSICS • COVERT CHANNELS

Shark on Wire 2: Network Steganography & UDP Port Carving

By Abdo•Aug 31, 2026•PicoCTF 2019
EVIDENCE PCAPcapture.pcap
TARGET SERVICEUDP Port 22 (SSH)
COVERT ENCODINGSource Port - 5000
FINAL FLAGpicoCTF{p1LLf3...}
Shark on Wire 2 Analysis
FIGURE 1: Network Header Steganography & UDP Covert Channel AnalysisWireshark Protocol Dissection
Official Challenge Prompt

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

● Category: Network Forensics / PCAP Analysis● Points: 300 PTS● Flag Format: picoCTF{...}
Provided File (Download & Practice)Download capture.pcapSize: 115 KB • Packet CaptureType: Wireshark / libpcap
Target: UDP Port 22 Stream

Protocol Forensics: UDP Source Port Covert Channel

When network payloads are monitored or normalized, covert channels can encode exfiltration data directly into protocol header fields. In this capture, the sender dispatches UDP datagrams to port 22, but modulates the 16-bit UDP source port field using an arithmetic offset: source_port = 5000 + ASCII_byte. The transmission is bounded by delimiter packets with port 5000 representing start and end sentinels.


1. Beware of the Decoy Troll Flags (Stream 6)

When you first open capture.pcap in Wireshark and begin inspecting UDP streams, the challenge author planted an intentional trap to deceive automated regex flag scrapers:

Trap Alert: Decoy in UDP Stream 6

In Wireshark, right-clicking any UDP packet and selecting Follow -> UDP Stream on Stream 6 displays what looks like a flag at first glance:

ico{N0t_a_fLag}

If you look closely, the prefix is missing the “p” and literally spells “N0t_a_fLag”. Stop right there: this is a troll decoy. The real flag is hidden elsewhere.

Decoy Flag in UDP Stream 6

2. The Discovery: Transmission Sentinels (Stream 32 & Stream 60)

When scanning through the capture's UDP conversations with Show text as: ASCII, two specific streams immediately catch our attention:

  • Stream 32: Transmits a single 5-byte payload: start
  • Stream 60: Transmits a single 3-byte payload: end
FIGURE 2: Stream 32 — The “start” Sentinel
Wireshark Follow UDP Stream 32 showing start payload

Stream 32 marks the exact beginning of the secret exfiltration channel.

FIGURE 3: Stream 60 — The “end” Sentinel
Wireshark Follow UDP Stream 60 showing end payload

Stream 60 signals that the entire exfiltrated payload has completed transmission.

Forensic Principle: In covert network communications, attackers use sentinel tokens (like “start” and “end”) to synchronize the receiver so it knows exactly when to begin and stop capturing the leaked data.


3. Cracking the Covert Channel (Anomalous UDP to Port 22)

Now comes the crucial question: What happened between Stream 32 and Stream 60?

The Protocol Anomaly:

Inspecting the destination port of both sentinels shows they are targeting port 22:

udp.dstport == 22

In standard networking, port 22 is SSH (Secure Shell), which runs exclusively over TCP. Seeing UDP packets hitting port 22 is a glaring red flag that traffic is abusing the port as a covert channel!

Applying the filter udp.dstport == 22 displays a sequence of 35 packets:

FIGURE 4: Anomalous UDP Packets to Port 22 with Encoded Source Ports
Wireshark packet list showing source ports 5112, 5105, 5099 to destination port 22

Notice the red rectangle: while every packet payload contains identical dummy text (aaaaa), the Source Ports change on every single packet!

Why Subtract 5000? (The Mathematical Logic)

In standard ASCII, printable characters live between decimal values 32 (' ') and 126 ('~'). For example:

'p' = 112
'i' = 105
'c' = 99
'o' = 111

Operating systems reserve ports 0–1023 for privileged system services. To transmit these characters as valid, non-privileged ephemeral UDP ports, the sender simply added an offset of 5000:

Source Port = 5000 + ASCII_BYTE  ->  ASCII_BYTE = Source Port - 5000

4. Decoding the Flag: Manual Converter vs. Automated Script

Subtracting 5000 from each packet between the start (Packet 1104, port 5000) and end sentinels yields 33 decimal byte values:

112 105 99 111 67 84 70 123 112 49 76 76 102 51 114 51 100 95 100 97 116 97 95 118 49 97 95 115 116 51 103 48 125
FIGURE 5: Converting Decimal Source Ports to ASCII Characters
Online Decimal to ASCII converter displaying decoded flag
The Human Transcription Trap

Notice in manual conversion: typing 33 decimal numbers by hand from Wireshark into a web converter is prone to human error! Notice that packets 1141 and 1143 are both port 5076 (5076 - 5000 = 76 = 'L'). If an analyst accidentally skips one identical number or typo-transcribes, the flag submission fails.

That is why in professional digital forensics, we write automated scripts or CLI pipelines to extract the exact bytes without human transcription error.

Complete Packet-by-Packet Decoding Table:

Pkt #Source PortMath (Port - 5000)ASCII DecimalDecoded Character
110450005000 - 5000 = 0—[START]
110651125112 - 5000112p
111851055105 - 5000105i
112250995099 - 500099c
112451115111 - 5000111o
112950675067 - 500067C
113150845084 - 500084T
113350705070 - 500070F
113551235123 - 5000123{
113751125112 - 5000112p
113950495049 - 5000491
114150765076 - 500076L
114350765076 - 500076L
114551025102 - 5000102f
114750515051 - 5000513
116251145114 - 5000114r
116450515051 - 5000513
116651005100 - 5000100d
117250955095 - 500095_
117851005100 - 5000100d
118050975097 - 500097a
118751165116 - 5000116t
118950975097 - 500097a
119250955095 - 500095_
119651185118 - 5000118v
119950495049 - 5000491
126750975097 - 500097a
127250955095 - 500095_
127451155115 - 5000115s
127651165116 - 5000116t
128450515051 - 5000513
128651035103 - 5000103g
129650485048 - 5000480
130151255125 - 5000125}
130350005000 - 5000 = 0—[END]

5. The Fast Terminal Route (tshark One-Liners)

Why do manual math when you can extract and decode all 33 characters directly in your shell in a single second?

Windows PowerShell One-Liner:
(& "C:\Program Files\Wireshark\tshark.exe" -r .\capture.pcap -Y "udp.dstport == 22 && udp.srcport > 5000" -T fields -e udp.srcport | ForEach-Object { [char]([int]$_ - 5000) }) -join ''
Linux / macOS One-Liner (awk):
tshark -r capture.pcap -Y "udp.dstport == 22 && udp.srcport > 5000" -T fields -e udp.srcport | awk '{printf "%c", $1-5000}'

6. Standalone Python PCAP Parser (`solve.py`)

If Wireshark or tshark are not available on your target machine, you can parse the raw binary PCAP structure using Python's standard library (no scapy or external dependencies required):

solve.py
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])
                
                # Filter for packets to destination port 22 with port > 5000
                if dport == 22 and sport > 5000:
                    flag += chr(sport - 5000)

print("Decoded Network Flag:", flag)

7. Forensic Takeaway: Network Steganography in DFIR

TechniqueDetection FilterForensic Significance
Protocol Anomalyudp.dstport == 22SSH communicates exclusively via TCP; UDP traffic on port 22 is an immediate red flag indicating data exfiltration.
Header Steganographyudp.srcport - 5000DPI (Deep Packet Inspection) checking only payloads sees harmless filler (`aaaaa`), while data is hidden in headers.
Transmission Sentinelsudp.stream eq 32 / 60Attackers employ explicit start/end markers to synchronize receiving listeners across noisy networks.

Captured Flag:

picoCTF{p1LLf3r3d_data_v1a_st3g0}

(Decoded meaning: “pilfered data via stego”)