TraceBash 2026 — The Silent Echo Writeup
A tiny capture that looks like DNS noise, a decoy ICMP flood and a fake SSH session. The real message is smuggled in two header fields nobody reads: the IP identification field of ICMP echoes and the TCP window size of an HTTP flow.
TraceBash 2026 — The Silent Echo
A network-forensics challenge about covert channels: everything visible is a decoy, and the flag lives in header fields you normally ignore.
- Event: TraceBash 2026
- Category: Forensics
- Artifact:
the_silent_echo.zip→challenge.pcap - Flag format:
TBCTF{...}
1. Three conversations, all suspicious
The capture is only 106 packets. Sorting it by protocol gives three independent stories:
DNS 25 packets lookups for ubuntu.com, github.com, stackoverflow.com, microsoft.com, google.com
ICMP 32 packets 192.168.1.111 <-> 10.0.0.222, echo request/reply, payload "AAAA..."
TCP 49 packets two HTTP flows and one "SSH" flowEach one is a red herring dressed as normal traffic. The ICMP payload is 32 bytes of the letter A in every single packet — a "silent echo" that carries nothing. The DNS queries resolve nothing and use transaction id 0. And the "SSH" flow on port 22 is not SSH at all:
SSH-2.0-OpenSSH_8.2p1\r\nTm90aGluZyBoZXJlLCBrZWVwIGxvb2tpbmcu\nThat trailing line is base64:
>>> base64.b64decode("Tm90aGluZyBoZXJlLCBrZWVwIGxvb2tpbmcu")
b'Nothing here, keep looking.'A decoy that literally tells you to keep looking. So the payloads are empty — the data must be in the headers.
2. Two HTTP flows that are not what they seem
GET /api/v1/status HTTP/1.1 -> 10.0.0.80:80
User-Agent: Mozilla/5.0 ID-Check/1.0
X-Window-Trace: enabled
GET /downloads/ubuntu-22.04.iso -> 10.0.0.88:80
(response: 2304 bytes of pure zeros)The ubuntu-22.04.iso download is 18 segments of zeroed bytes — no filesystem, no data, another decoy. But its request stream is not: the client 192.168.1.122 and the header X-Window-Trace: enabled are the hint. Both custom headers name the covert channels: the IP ID and the TCP window.
3. Channel one — the IP identification field
Every ICMP echo request has a random-looking IP id. It is not random. The high byte of the 16-bit id is a character:
from scapy.all import rdpcap, IP, ICMP
pk = rdpcap("challenge.pcap")
part1 = "".join(chr(p[IP].id >> 8) for p in pk
if ICMP in p and p[ICMP].type == 8)
# 'TBCTF{h1dd3n_1n_'Sixteen requests, sixteen characters, each hidden in the upper byte of a field most tooling displays only as a decimal squiggle.
4. Channel two — the TCP window size
The HTTP client in the second flow never sends its advertised window at a "normal" value. Its ACKs all sit just above the default 8192; subtract that base and the remainder is an ASCII code:
part2 = "".join(chr(p[TCP].window - 8192) for p in pk
if IP in p and TCP in p and p[IP].src == "192.168.1.122"
and p[TCP].dport == 80 and p[TCP].window != 8192)
# 'w1nd0w_4nd_1p_1d!}'The server side answers with a constant window of 65535, so only the client's varying window matters. Stitching the two channels together closes the flag.
5. Reassembling the message
print(part1 + part2)
# TBCTF{h1dd3n_1n_w1nd0w_4nd_1p_1d!}6. Flag
TBCTF{h1dd3n_1n_w1nd0w_4nd_1p_1d!}The whole point of a covert channel is that the header is metadata, so nobody looks at it. Here the packet bodies were a deliberate parade of noise, and the answer was split across two fields — the IP id of the ICMP echoes and the window of the HTTP client — that most capture tools summarise into a single column you never scroll to.
What do you think?
React to show your appreciation