offknock
umdctf
Task: a Python service forwards a raw DNS packet over TCP to a local dnslib resolver and only returns the TXT answer when specific raw qname bytes appear in the request. Solution: forge a DNS query where the UTF-8 bytes of `ȳ` become a compression pointer, then place a zero byte at offset `0x08b3` so the parser accepts the name and reveals the flag.
$ ls tags/ techniques/
offknock — UMDCTF
Description
No organizer description was preserved in the local task files.
We are given a Python service that accepts a length-prefixed DNS packet over TCP, forwards the raw packet to a local UDP dnslib resolver, and returns the DNS response. The goal is to make the resolver return the TXT record containing the flag.
Analysis
The important behavior is in dns_server.py:
if '\x04flag\x06market\x03polȳ'.encode('utf8') in handler.request[0][12:30]: reply.add_answer(RR("flag.market.polȳ", QTYPE.TXT, rdata=TXT(flag)))
There are two different interpretations of the same bytes:
- The service performs a raw byte check on
handler.request[0][12:30]. dnslibparses the same bytes as a DNS name.
That mismatch is exploitable because the final character in polȳ is not ASCII. UTF-8 encodes ȳ as c8 b3, and in DNS name parsing any byte with the top two bits set (11xxxxxx) starts a compression pointer. So c8 b3 is interpreted as a pointer to absolute offset 0x08b3.
This means the qname bytes can satisfy the raw substring check:
04 66 6c 61 67 06 6d 61 72 6b 65 74 03 70 6f 6c c8 b3
while the DNS parser reads them as:
- label
flag - label
market - label
pol - compression pointer to offset
0x08b3
To keep the query valid, the pointer target must contain a valid DNS label sequence. The simplest choice is a zero byte, which represents the root label. So we pad the packet until absolute offset 0x08b3 and place 00 there. Then the compressed qname parses successfully, the raw byte check passes, and the resolver returns the TXT answer.
The remaining fields are normal DNS question fields:
QTYPE = TXT(16), because the server rejects any other query type.QCLASS = IN(1).
Solution
- Build a standard DNS header with
QDCOUNT = 1. - Set the qname bytes to
04 flag 06 market 03 pol c8 b3. - Append
QTYPE=TXTandQCLASS=IN. - Pad the packet so that absolute offset
0x08b3exists. - Write a zero byte at offset
0x08b3, making the compression pointer resolve to the root label. - Send the packet as a 2-byte length-prefixed TCP message to the remote service.
- Read the DNS response and extract the TXT record.
Full Solve Script
#!/usr/bin/env python3 import socket import struct HOST = "challs.umdctf.io" PORT = 32324 def build_query(): header = struct.pack("!HHHHHH", 0x1337, 0x0100, 1, 0, 0, 0) qname = b"\x04flag\x06market\x03pol\xc8\xb3" question = qname + struct.pack("!HH", 16, 1) pad = b"\x00" * (0x08B3 - len(header + question)) return header + question + pad + b"\x00" def main(): query = build_query() with socket.create_connection((HOST, PORT), timeout=10) as sock: sock.sendall(len(query).to_bytes(2, "big") + query) size = int.from_bytes(sock.recv(2), "big") data = b"" while len(data) < size: data += sock.recv(size - len(data)) print(data.decode("latin1")) if __name__ == "__main__": main()
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar
Similar writeups
- [network][free]insider-info— umdctf
- [crypto][Pro]Firewall— uoftctf2026
- [misc][free]Character— hackthebox
- [web][Pro]original_task— miptctf
- [network][free]security-breach— umdctf