Description
Find what is being sent across the network. The flag is encoded in the UDP source port numbers sent by a specific host.
Setup
Download the pcap file and open it in Wireshark.
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Isolate UDP traffic from the relevant source hostObservationI noticed the challenge description said the flag is encoded in UDP source port numbers from a specific host, which suggested filtering Wireshark to UDP traffic and scanning for the one source IP whose source ports change with every packet rather than following a typical ephemeral-port pattern.Open the pcap in Wireshark. Filter for UDP traffic and look at the source port numbers. One source IP (e.g., 10.0.0.66) sends many UDP packets where the source port is 5000 plus the ASCII value of each character. Filter by that source address with 'ip.src == <source_ip>'.bashwireshark capture.pcapbash# In Wireshark filter bar: ip.src == <source_ip> && udpWhat didn't work first
Tried: Searching the packet payloads for the flag string using 'Edit > Find Packet > String'
The UDP packets carry no meaningful payload - the flag is not in the data bytes at all. The covert channel encodes each character in the source port number field, so searching payloads returns nothing and leads you to think the pcap contains no flag.
Tried: Filtering for TCP traffic instead of UDP
The encoding uses UDP packets specifically. A TCP filter like 'tcp' returns unrelated connection traffic from the capture and hides the relevant UDP stream entirely. Switching to 'udp' and then narrowing by source IP is the correct path.
Learn more
Network covert channels encode data in protocol fields that are normally ignored. UDP source port numbers are freely chosen by the sender. In this challenge packets all target destination port 22, while the source port encodes the flag: each source port is 5000 plus the ASCII value of one character. Using source ports as a covert channel produces traffic that looks like normal application chatter to a basic traffic monitor.
Step 2
Read the ASCII values from the source port numbersObservationI noticed the source ports from the filtered host were all in the range 5032-5126 (matching 5000 plus ASCII printable values 32-126), which suggested subtracting 5000 from each port number and using tshark field extraction to pull them in order.Sort the filtered packets by time. Read each UDP source port number in order and subtract 5000 to get the ASCII character code. Extract them with tshark and process in Python.bashtshark -r capture.pcap -Y 'udp && ip.src == <source_ip>' -T fields -e udp.srcportWhat didn't work first
Tried: Extracting the destination port instead of the source port with '-e udp.dstport'
All packets in this stream share the same destination port (22), so '-e udp.dstport' produces a column of identical values that decodes to nothing. The encoding is in the source port field; only '-e udp.srcport' yields the varying values that carry the flag characters.
Tried: Using tcpdump with '-x' to dump hex payload bytes instead of tshark field extraction
tcpdump -x shows the raw packet bytes including headers, but reading the source port out of hex output by hand is error-prone and bypasses the clean field-extraction that tshark provides. The '-e udp.srcport' flag in tshark returns one decimal port number per line, which is exactly what the Python script needs.
Learn more
Look for start and stop markers in the port sequence (e.g., a port value of 'start' or a distinctive marker port). The characters between the start and end markers spell the flag.
Step 3
Assemble the flag from the port number sequenceObservationI noticed that tshark output gave a clean list of decimal port numbers in packet order, and that 5112 - 5000 = 112 = chr('p'), which confirmed the base-5000 offset and suggested a short Python loop to convert the full sequence into the flag string.Subtract 5000 from each source port number to get the ASCII character code, then concatenate in packet order.pythonpython3 << 'EOF' # Paste source port numbers from tshark output ports = [5112, 5105, 5099, 5111, 5067, 5084, 5070, ...] # example flag = '' for p in ports: code = p - 5000 # source port minus base 5000 = ASCII value if 32 <= code <= 126: flag += chr(code) print(flag) EOFExpected output
picoCTF{p1LLf3r3d_data_v1a_st3g0}What didn't work first
Tried: Subtracting a different base (e.g., 4000 or 5001) because the first port number looked off
If you guess the wrong base you get non-printable or nonsensical characters and discard the correct source. The base is 5000: ports like 5112 decode to 'p' (112), 5105 to 'i' (105). Confirming that 5112 - 5000 = 112 = chr('p') validates the offset immediately.
Tried: Including all captured UDP packets rather than filtering to the specific source IP before running the Python decode
The pcap contains UDP traffic from multiple hosts. Feeding all source ports into the decode loop mixes in unrelated port numbers and produces garbled output that does not resemble a flag. Filtering to the single source host first ensures only the covert-channel packets are decoded.
Learn more
This covert channel is difficult to detect with basic network monitoring because packet sizes and frequencies look normal. The data is hidden in the port number field rather than the payload - tools that only inspect payloads for signatures would miss it entirely.
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.
Flag
Reveal flag
picoCTF{p1LLf3r3d_data_v1a_st3g0}
The flag is encoded in the UDP source port numbers - extract source ports from the relevant source host, subtract 5000 from each, and convert each result to its ASCII character.