zwesty
Write-ups / Day 20 / Metadata
Day 20 · Tue, Oct 20 · Metadata

Universal Appeal

Prey slaughtered

Mach-O header parsing (thin, fat/universal, ar archives), answer-form choice, extending a compounding solver

STTMClaude Homebase Both

Flags are shortened to their first 8 and last 4 characters because they're unique per team.

Challenge ID
metadata-forensics-day-20-universal-appeal (+ /time_trial, /mastery)
Solved
2026-10-20: all three modes CORRECT, by 09:05:50 ET, 5 min 40 s after our solver's first start request (09:00:10 ET). Single Solve on the fourth candidate; both timed modes CORRECT on the first run, ranked
Player
Zwesty (solo), with Claude Code (Opus 5.5): our automated morning solver (a scheduled script on our Kali VM) started the Single Solve and stopped; Claude found the answer form, built the solver, and ran the timed modes

The challenge

"A macOS deployment includes thin and universal binaries mixed with archives. Enumerate every CPU type and identify what truly supports the advertised fleet."

answer_id Question Format
d20-macho-001-macho-cpu-type-set "Determine macho.cpu_type_set for macos-001.bin." (artifact metadata-day-20-evidence.zip -> macos-001.bin) JSON: "compact valid JSON array with exact values and order; no Markdown or explanatory text"; trimmed

The timed modes asked this field alongside the earlier days' Metadata fields: 25 questions in the Time Trial, 125 across 5 sets in the Mastery.

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"thin and universal binaries" Mach-O comes in two shapes: a thin file with one header, and a fat (universal) file with a table of slices, one per CPU The solver reads the thin header's cputype, or every slice's cputype in a fat file (checking each slice header against its table entry)
"mixed with archives" Static libraries (ar, !<arch>) whose members are Mach-O objects The solver walks BSD and GNU ar members, skips symbol tables, and reads each Mach-O member
"Enumerate every CPU type" The answer is a list of cputype values, not one name A JSON array, one entry per CPU type
cpu_type_set A set: no duplicates, and an order the question calls "exact" Duplicates removed and sorted ascending (unconfirmed, see below)
"what truly supports" Trust the header bytes, not the file name Identified files by their magic bytes

The format was the hard part. "cpu_type" could mean the name (arm64), the number (16777228), or a hex constant. Day 19's ELF field had been accepted as 0x003e (0x plus the field's full width in hex), so that form was on the list from the start.

What was in the files

  • Single Solve: macos-001.bin, 56 bytes, in metadata-day-20-evidence.zip: a thin 64-bit little-endian Mach-O (magic bytes cf fa ed fe), cputype 0x0100000c (CPU_TYPE_ARM64), cpusubtype 0, MH_EXECUTE, two load commands including LC_UUID.
  • Time Trial and Mastery: the same template with the earlier days' fields. Every Mach-O in the six timed sets was thin arm64 too, so the fat and ar paths were never exercised by a real answer.

Single Solve

The solver started it and stopped cleanly. It flagged the field as new ("don't know what this question asks"), and its Metadata candidate search had no candidate list for cpu-type-set, so it sent nothing.

Claude read the header from the bytes (the solver had parsed Mach-O since Day 17; only this field name was missing), drew up a ranked candidate list, and sent the candidates one at a time, stopping at the first CORRECT:

# Candidate Why it was on the list Result
1 ["arm64"] The solver's CPU name, as lipo -archs prints it incorrect (13:01:38 UTC)
2 [16777228] The raw cputype as a JSON number incorrect (13:02:11)
3 ["ARM64"] The upper-case name some libraries use incorrect (13:02:25)
4 ["0x0100000c"] 0x plus 8 hex digits, the same form as Day 19's accepted 0x003e CORRECT (13:02:38)

Not sent: ["CPU_TYPE_ARM64"] and ["16777228"].

Rule: macho.cpu_type_set is the file's Mach-O cputype values, each as a JSON string 0x%08x.

started_at  2026-10-20T13:00:10.410Z   (started by the solver)
deadline    none (Single Solve)
answers     1, sent four times (three rejected, see above)
start / artifact / solve timings: not recorded (hand-sent candidates)
last submit 13:02:37 → 13:02:38
TOTAL       2 min 28 s from the start reply to CORRECT
result      CORRECT   flag 404d29fd…ed61

Our solver: builds on the Metadata solver we had prepared the evening before and adds one field, macho-cpu-type-set, for every question-ID prefix. It collects every cputype from a thin Mach-O, every slice of a fat one, and every Mach-O member of an ar archive; formats each as 0x%08x; removes duplicates; sorts ascending; and writes compact JSON. It refuses a file that isn't Mach-O. Bytes are parsed, never executed.

Tests (8, all pass): the accepted Single value; the saved Single through the whole chain; a fat file; i386; BSD and GNU ar; a non-Mach-O refused; the field name kept; timing.

Rehearsal (before any timed start): a regression rehearsal over 89 saved Metadata attempts, slowest 0.08 s, per-set answers identical: 88 identical, 1 newly solved (today's), 0 changed. It passed, and the new solver became the one the scheduled job uses.

Time Trial

CORRECT on the first attempt, ranked (50 s clock, 25 answers).

started_at  2026-10-20T13:05:40.457Z
deadline    2026-10-20T13:06:30.457Z   (50 s)
answers     25   (Mach-O field: ["0x0100000c"])
start        797 ms
artifact     460 ms
solve         11 ms
submit      1214 ms
TOTAL       2482 ms   (submit +1.82 s after started_at)
result      CORRECT   flag 46dc75bd…cc7c

Mastery

CORRECT on the first attempt, ranked (5 sets, 170 s clock, 125 answers).

started_at  2026-10-20T13:05:48.402Z
deadline    2026-10-20T13:08:38.402Z   (170 s)
answers     125 across 5 sets   (every Mach-O field: ["0x0100000c"])
start       ~606 ms  (TOTAL minus the logged phases)
artifact     778 ms
solve         72 ms
submit       776 ms
TOTAL       2233 ms   (submit +1.79 s after started_at)
result      CORRECT   flag 0f967e66…6bd6

Time to solve

All three modes were solved by 09:05:50 ET, 5 min 40 s after the solver's first start request at 09:00:10 ET. The Single Solve went through on the fourth candidate at 09:02:38; about three minutes of solver work, tests, and rehearsal followed, and the ranked Time Trial and Mastery each went through on the first submit.

Takeaways

  • Answer forms carry over between days. Day 19's 0x003e taught that header fields want 0x plus the field's full width in hex. Ranking that form first would have saved three sends and about a minute.
  • The fat and ar paths are unconfirmed. Every timed set was thin arm64, so the ordering of a multi-CPU set (sorted ascending, duplicates removed) has never been checked by the Game Master. If a later set holds a universal binary and is rejected, slice order is the first thing to try.
  • The rehearsal held. No timed start happened until 89 saved attempts replayed unchanged.