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
- Fixed 16-byte record size with a clean division (3920/16) signals a structured format, not garbage.
BXmagic + type byte → multiple record "channels" multiplexed together.- Type 0x01/0x02 are arithmetic-progression red herrings.
- Type 0x03 is rare and self-labeled with
DE AD, matching the crash/impact framing in the challenge description. - The trailer bytes double as the XOR key — a common "the key sits right next to the ciphertext" CTF trick.
Tools & Files
file,xxd,stringsfor initial triageparse_bb.py— record parser / type classifierdecode_bb.py— XOR decode + reassembly
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.