← Z0D1AK writeups
Forensics ⚡ 110 pts 👥 251 solves ✎ Praneet ✓ independently re-verified

Black Box

Reverse-engineering an undocumented 16-byte binary telemetry record format.

Challenge brief

A survey drone crashed during a test flight. Forensics recovered the flight recorder, but the telemetry module relies on an unclassified architecture. Standard analytical utilities report raw binary garbage and recorder suffered during impact.

Flag`zdk{e1EMeNt4RY_81naRY_P4rSlNg_MA573R}`

Flag: zdk{e1EMeNt4RY_81naRY_P4rSlNg_MA573R}

TL;DR

A drone's "flight recorder" dump is a flat array of fixed-size 16-byte records spanning three record types. Two types are smooth decoy telemetry; the rare, self-labeled "impact" records carry the flag, XORed with a key that is hiding in plain sight as their own trailer bytes.

Challenge

Recover the flag from blackbox.bin (3920 bytes), a drone flight recorder dump that standard forensics tools report as undocumented raw binary. Delivered both loose and inside black-box.zip (identical MD5 — the zip is just a delivery wrapper, not part of the puzzle).

Step 1 — Identify the record structure

file reports plain data; strings turns up nothing relevant. A hex dump reveals a repeating 42 58 ("BX") magic every 16 bytes:

3920 bytes / 16 = 245 records, no remainder → fixed-size record array

Per-record layout: BX | type(1B) | 00 00 | seq(1B) | payload(9B) | ...

Step 2 — Classify records by type

A small parser (parse_bb.py) buckets records by the type byte (offset 2):

Type Count Behavior
0x01 120 Steadily incrementing counter — decoy sensor channel
0x02 120 Steadily incrementing/decrementing counter — decoy sensor channel
0x03 5 Sparse; every record ends in DE AD — the "impact" marker

The 240 type 0x01/0x02 records are smooth, monotonic telemetry (altitude/ orientation-style counters) — a distraction. The five type 0x03 records are the tell: the challenge framing says the recorder "suffered during impact," and these are the only records literally trailed with DE AD.

Step 3 — Decode the payloads

Each type 0x03 record's 8-byte payload, XORed with a repeating 2-byte key DE AD, produces clean ASCII. The key is the same DE AD bytes already visible as every record's trailer:

key = bytes.fromhex("deaddeaddeaddead")
dec = bytes(b ^ k for b, k in zip(payload, key))

Step 4 — Reassemble in sequence order

Sorting the five type 0x03 records by their internal sequence byte (offset 5, values 0–4) and concatenating the decoded payloads:

seq decoded
0 zdk{e1EM
1 eNt4RY_8
2 1naRY_P4
3 rSlNg_MA
4 573R} (+ null padding)

Flag

zdk{e1EMeNt4RY_81naRY_P4rSlNg_MA573R}

(leetspeak for "elementary binary parsing master")

Key hints that led here

  1. Fixed 16-byte record size with a clean division (3920/16) signals a structured format, not garbage.
  2. BX magic + type byte → multiple record "channels" multiplexed together.
  3. Type 0x01/0x02 are arithmetic-progression red herrings.
  4. Type 0x03 is rare and self-labeled with DE AD, matching the crash/impact framing in the challenge description.
  5. The trailer bytes double as the XOR key — a common "the key sits right next to the ciphertext" CTF trick.

Tools & Files

Takeaway

When a binary dump divides cleanly into a fixed record size, stop guessing at file formats and start bucketing by a byte that looks like a type/tag field — the outlier bucket is almost always where the signal lives.

Solve scripts

parse_bb.py
⇩ Download
import sys

path = sys.argv[1] if len(sys.argv) > 1 else "blackbox.bin"
data = open(path, "rb").read()
print(f"total bytes: {len(data)}, records(16B): {len(data)//16}, remainder: {len(data)%16}")

recs = [data[i:i+16] for i in range(0, len(data), 16)]
from collections import Counter
types = Counter(r[2] for r in recs if len(r) == 16)
print("type counts:", {hex(k): v for k, v in sorted(types.items())})

for t in sorted(types):
    print(f"\n--- type 0x{t:02x} sample records ---")
    shown = 0
    for r in recs:
        if len(r) == 16 and r[2] == t:
            print(r.hex(' '))
            shown += 1
            if shown >= 6:
                break
decode_bb.py
⇩ Download
import sys

path = sys.argv[1] if len(sys.argv) > 1 else "blackbox.bin"
data = open(path, "rb").read()
recs = [data[i:i+16] for i in range(0, len(data), 16)]

type3 = [r for r in recs if len(r) == 16 and r[2] == 3]
print(f"type03 records found: {len(type3)}")

# sort by their internal sequence byte (index 5)
type3.sort(key=lambda r: r[5])

key = bytes.fromhex("deaddeaddeaddead")
out = b""
for r in type3:
    payload = r[6:14]
    dec = bytes(b ^ k for b, k in zip(payload, key))
    print(f"seq={r[5]}  raw={payload.hex()}  dec={dec!r}")
    out += dec

print("\nconcatenated raw decode:", out)
print("as text (strip nulls):", out.replace(b'\x00', b'').decode(errors='replace'))