The challenge
The prompt, paraphrased (the exact wording wasn't kept): cluster the binaries by behaviour, and watch out for false friends.
Binary is cumulative: 392 questions, 14 per day for Days 1–28, all on one evidence.zip. Day 28 did two things at once:
- Seven Day 28 role fields on three builds: false_friend, family_a, and family_b, each with a format and a machine, plus the false friend's config campaign.
- 37 new field ids on older days' samples. Each restated bytes the solver had already seen (Days 1–26) with a new question whose procedure is spelled out in the question text.
| answer_id (pattern) | Question (shape) | Format |
|---|---|---|
d28_ |
The build whose basename ends family- (etc.): classify by magic and parsed container; read the machine field |
enum / base-10 integer |
d28_ |
The false friend's HCFG record, CRC32-checked | exact text |
d01_, d01_ |
The d01- members in archive order: count, and a SHA-256 over their digests |
integer / hex |
dNN_, d18_ |
Count (or hash) names in "the parsed symbol tables used by the ELF parser", sometimes filtered by a / |
integer / hex |
dNN_, dNN_ |
"true exactly when the final bytes contain X or Y" | true / false |
d10_ |
"Read ELF e_shnum" | integer |
| Others | vtable symbol, CLI runtime version, JAR class count / name / methods / access flags, PK19 packing keys, overlay size, plug-in CRC32 and HCFG offset, Day 24/25 vulnerable-build configs, config host_ / ipv4 |
as asked |
The Time Trial asked the same 392; the Mastery asked them once per set (12 sets, 4,704 answers).
Reading the prompt: what it said we'd need
| Clue | What it pointed to | What I did about it |
|---|---|---|
| "cluster … by behaviour" | Group builds by what they share | Three ELF builds of one program; family A and B carry the same config as the shared Day 28 one |
| "false friends" | A build that looks like the family but isn't | The false friend is the same size and layout (one byte longer) with its own config: campaign FF28-, its own XOR key, and CRC |
| "Family Resemblance" (title) | Look-alikes that need a deeper check | Confirmed the reading: format and machine are identical across all three; only the decoded config tells them apart |
| The 37 old-day questions | Not mentioned by the prompt at all | The question text carried the procedure (which table, which carrier, which predicate), so the plug-in reads it from there |
Safety
Static analysis only. Nothing was executed. Members were read as bytes: ELF and PE headers, section and symbol tables, HCFG records and their CRC32 checks, JAR class files. No sample was run, loaded, or debugged. For this write-up only the helper's notes, the plug-in and its tests, and our own records of the day were read. The archives stayed on our Kali VM. Indicators below are defanged.
What was in the files
- Single Solve, Day 28 members:
d28--sample-family-a.elfandd28--sample-family-b.elf(22,922 bytes each, 1 byte apart) andd28--sample-false-friend.elf(22,923 bytes). All ELF64 little-endian x86-64 (e_machine62), no.symtab. - One raw HCFG record in each, at offset 22,536. Family A and B: campaign
BR28-000, equal to the already-accepted shared Day 28 config. False friend: campaignFF28-000, domaindecoy-000[.]day28…, its own XOR key0xd9, and CRC0xdbaacf1c. - New JSON keys in this year's configs:
ipv4,host_model,windows_path,linux_path,service. - The other 37 fields restate older days' bytes; no new member was needed for them.
- Time Trial: the same 392 fields; the Single's rules answered every one.
- Mastery: 12 sets of the same 392 fields. In sets 2–5, 7–9, and 12 (8 of 12), Day 10 is a native PE (
d10--sample.exe), though its question still says "Read ELF e_shnum". Everything else was answered by the Single's rules.
Single Solve
The automated solver: 348 sent, 44 open
deadline none (Single Solve)
answers 244 core (accepted), then 104 plug-in answers (accepted)
result not CORRECT: 44 questions unanswered, no rejected values; timed modes not started
None of the 44 ids had appeared on any earlier day. Leaving them open was right: nothing was guessed.
The plug-in (Claude helper agent)
The helper wrote a new plug-in for days ≥ 28 and tested it on a staging copy of the solver:
- Field kind from the id. Plain kinds (section_count, symbol_count, handler_symbols_sha256, …) are tried before <role>_<kind>, so handler_symbol_count isn't read as role handler.
- Member from the question text: "basename ends X", the primary managed .dll via its runtimeconfig, or the one dNN-- member. Carrier from the text too: .hcfg section, first literal record, backward scan, or JAR constant-pool Base64.
- How it decides what it owns: roles with an underscore (family_a, false_friend) are its own. For single-word roles that older plug-ins answer (vulnerable, loader, gcc, tcc, …) it takes only the kinds new on Day 28 (config_crc32_hex, hcfg_offset, config keys outside campaign/domain/mutex/url, and the Day 24/25 vulnerable configs that were never answered). It leaves Day 4's Go and Day 7's coroutine predicates, Day 6's methods/access flags, and Day 19's overlay size to the plug-ins that already own them.
- A question it can't parse is left open (logged to stderr), never a crash and never a guess.
- Batches and ranked values: a symbol-table field whose answer changes with the table choice is sent alone, with its values ranked.
Every answer came from the plug-in run on the saved bytes (one value each, except d13). Some examples: d01 artifact count 1, d10 section count 27, d19 packing keys 0x21 / 0xa4, the three Day 28 formats elf and machines 62, false-friend campaign FF28-000.
The one judgement call: d13 crystal_symbol_count. "The parsed symbol tables" is plural, and a pyelftools-style parser reads both .symtab and .dynsym, so the ranking was 1255 (both tables) first, 771 (.symtab only) second. On Days 5, 11, and 18 the two readings give the same number, so only d13 depended on it.
Offline on the staging copy: coverage 392 of 392, the 348 accepted answers reproduced with 0 mismatches. The helper then ran the staged solver, which reconnected to the open Single Solve attempt:
submit 1 all 44 merged -> incorrect (nothing burned)
submit 2 43 without d13 -> accepted
submit 3 d13 crystal_symbol_count 1255 (.symtab + .dynsym) -> incorrect
submit 4 d13 crystal_symbol_count 771 (.symtab only) -> CORRECT
result CORRECT flag d0e56ef7…41af (09:11:40.3 ET, solved_by.at 13:11:40.302Z)
Rejected value: d13_analysis_crystal_symbol_count 1255 (not to be resent). The rule it taught: symbol counts use .symtab (SHT_SYMTAB) only. The plug-in's default table ranking was flipped to .symtab first before the timed runs, and the staged re-check gave 392/392 accepted reproduced, 0 mismatches.
Time Trial
CORRECT on the first attempt, ranked.
answers 392
total 4.0 s
result CORRECT flag 927a4482…5c8a (09:12:23.9 ET)
Mastery
CORRECT, ranked, in one attempt, but not in one run.
First run (ranked and streamed, started 09:12:32 ET, 9.5 min clock): stopped before submit, nothing sent. The solver's coverage check found d10_analysis_section_count uncovered in 8 of 12 sets, where Day 10 is a native PE. Fix: for a PE, answer the COFF NumberOfSections (a PE has no null section, as with the Go PE section_names earlier). Offline on the saved attempt, each set alone: 392/392 in all 12 sets (d10 = 27 for ELF sets, 9 for PE sets).
Resuming: an untimed retry was refused locally by the solver ("status None and the last submit wasn't CORRECT; nothing sent"), because the first attempt was still open. Rerunning the ranked, streamed run without the retry option resumed the same ranked attempt inside its clock; no new attempt was started.
answers 4,704 (12 sets × 392) (same ranked attempt, resumed)
resumed run 7.95 s
result CORRECT flag 58cbe8af…2e5c (09:14:21.9 ET), ranked
The CORRECT confirms the PE NumberOfSections form.
Checks and install
All after the three CORRECTs, on the staging copy:
- Unit tests for the plug-in: 12 passed (synthetic members: hyphenated roles and the false-friend backward scan, .symtab-only counts and handler hash, PE NumberOfSections, predicates including UTF-16LE, central-directory order, .hcfg section vs literal, small-CRC padding, JAR fields and constant-pool Base64, PK19 key padding, primary .dll by runtimeconfig, a COFF long name after an aux record, owned kinds left unclaimed, streamed delivery). Staged suite with the Binary tests and Day 27: 53 passed.
- A regression check against every answer the Game Master had already accepted (fast path off): live 136 sets with 1 error, staged 136 with 1 (Day 1's known path-only manifest error); 132 identical, 3 gained-only (today's Single +44, TT +44, Mastery +528), no changed answer; accepted vs staged agree on 392 / 392 / 4,704. The check reported a failure only for Day 1's path-only error, as on Days 24, 26, and 27.
- Streamed-mode snapshot (staged): 53 Mastery attempts, 0 errors, identical to the whole-solve snapshot.
- The plug-in and its tests were then installed into the live solver, with 7 protected files unchanged. Live tests 12 passed, live coverage 392 of 392, and a regression rehearsal against the previous days' puzzles passed 11 of 11.
Time to solve
The Mastery was CORRECT at 09:14:21.9 ET, 14 min 21.9 s after release; the Single Solve came at 09:11:40.3 once the new plug-in was written, and the ranked Time Trial at 09:12:23.9. Both timed modes were ranked, and the Mastery's first run stopped before submit and was resumed inside the same clock, 1 min 50 s after it started.
Takeaways
- Leaving fields open beats guessing. The solver sent the 348 it was sure of and stopped on 44 with no rule; the helper added only those.
- The question text was the spec. The 37 old-day fields named their own procedure (which table, which carrier, which predicate); reading it from the text is what made one plug-in cover them all.
- "Symbol tables", plural, still meant
.symtabonly. Ranking the two readings and sending d13 alone cost one incorrect submit and nothing else. - A timed set can break a question's own wording. "Read ELF e_shnum" on a PE needed COFF
NumberOfSections. The coverage check stopped the run before anything was sent. - An open ranked attempt resumes; it doesn't retry. The solver refuses a retry while the attempt is open; rerunning without it kept the Mastery ranked and inside its clock.
- Clear ownership rules keep plug-ins from fighting. The new plug-in claims only kinds no older solver owns, so installing it changed no existing answer.