Min beste venn — PCAP Bit Encoding
Platform: NNS CTF 2026
Category: Forensics
1. Initial inspection
The archive contains a single capture, capture.pcap. A packet count shows 3,662 TCP packets, and filtering for HTTP requests reveals that the traffic is dominated by HEAD requests to static.notion-static.com. The requests do not carry an obvious flag in their bodies.
The unusual feature is the requested path. Paths end in numeric CSS filenames, and the numeric values use only digits 0 through 7. That strongly suggests octal positions or an intentionally restricted bit index rather than ordinary asset names.
2. Finding the encoding rule
Each logical message is separated by a prefix such as d/h/0, d/h/1, d/c/0, or d/c/1. Within a group, a numeric suffix appears either once or twice. Treat the suffix as a position and the multiplicity as its bit:
- one request for a position means bit
0; - two requests for a position mean bit
1.
Sort the numeric positions before reading them. Sorting is essential because packet order is not the encoding order; the suffix itself supplies the position.
3. Turning groups into bytes
After mapping multiplicity to bits, concatenate the bits in sorted-position order and consume them eight at a time. Convert each eight-bit chunk to a byte. Decode all four streams instead of stopping at the first readable text. The d/h streams contain prompts and framing, while the d/c/1 stream contains the complete flag.
4. Reproduction
import collections, re, subprocess
raw = subprocess.check_output([
"tshark", "-r", "forensics_min-beste-venn/capture.pcap",
"-Y", "http.request", "-T", "fields",
"-E", "separator=|", "-e", "http.request.uri",
], text=True)
for prefix in ("d/h/0", "d/h/1", "d/c/0", "d/c/1"):
counts = collections.Counter()
for uri in raw.splitlines():
match = re.search(r"/" + re.escape(prefix) + r"/([0-9]+)\\.css$", uri)
if match:
counts[int(match.group(1))] += 1
positions = sorted(counts)
bits = "".join("1" if counts[p] > 1 else "0" for p in positions)
decoded = bytes(int(bits[i:i+8], 2)
for i in range(0, len(bits)-7, 8))
print(prefix, decoded)
5. Result and validation
The decoded streams include readable challenge dialogue. The d/c/1 stream yields the complete flag, including its closing brace:
NNS{1_l0v3_ch4tt1ng_w1th_m1n_b3st3_v3nn_1n_th3_cl0ud5}
This is stronger than finding a matching string in packet metadata: the result comes from a deterministic transformation of the complete capture and the other streams provide contextual confirmation.
6. Lessons
- Repeated requests can encode data through metadata rather than payloads.
- Packet order and logical position are different; always test the ordering rule.
- Decode every stream so framing and prompts can validate the interpretation.
- Keep the original PCAP and decoder together for future competitions.