zwesty
Write-ups / Day 27 / Binary
Day 27 · Tue, Oct 27 · Binary

Compiler Says What

Prey slaughtered

Static ELF triage (header, e_machine), per-build questions on one archive, HCFG config records with CRC32, cumulative question sets, add-on modules written for day >= N

STTMClaude Homebase Both

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

Challenge ID
binary-reverse-engineering-day-27-compiler-attribution (+ /time_trial, /mastery)
Solved
2026-10-27: all three modes CORRECT on the first submit, both timed modes ranked. The solver sent 318 of 324 Single Solve answers (all accepted) and left 6 open, so it didn't start the timed modes. A helper wrote one new add-on module; the 6 went in at 09:05:29 ET (Single CORRECT), the ranked Time Trial was CORRECT at 09:05:48, and the ranked Mastery at 09:06:03. 4 min 25 s from the first start request on this lane (09:01:38 ET) to the Mastery CORRECT
Player
Zwesty (solo), with Claude Code (Opus 5.5): our automated morning solver (a scheduled script on our Kali VM) answered 318 questions and stopped; a Claude helper agent wrote the Day 27 add-on, sent the 6, ran both timed modes, and installed the add-on

The challenge

"Every compiler signs its work. Compare the runtime and linker fingerprints across builds and name the toolchain — attribute this binary to its maker."

Binary is cumulative: 324 questions, 12 per day for Days 1–27, all on one evidence.zip. Of the 12 Day 27 fields, six were familiar in form (the two members' SHA-256s and the shared HCFG config's campaign, domain, mutex, and URL, "require their config objects to be byte-for-byte equal"). Six were new, each picking one build by name:

answer_id Question Format
d27_analysis_gcc_format, d27_analysis_tcc_format "Use the unique d27-- member whose basename ends gcc.elf [tcc.elf]. Classify final bytes by magic and parsed container: elf … pe … pe_cli … jar … wasm … neko_bytecode" enum
d27_analysis_gcc_machine, d27_analysis_tcc_machine "… Read the ELF e_machine field and submit its unsigned base-10 numeric value (for example, do not substitute an architecture label)." base-10 integer
d27_analysis_gcc_config_campaign, d27_analysis_gcc_config_domain "… Scan backward from the end of the member for literal HCFG magic and accept the first CRC32-valid decoded JSON record …" exact text

The Time Trial asked the same 324; the Mastery asked them once per set (11 sets, 3,564 answers).

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"Every compiler signs its work" Toolchain fingerprints in the binary (runtime, linker, .comment-style strings) Two builds of one program, named by toolchain: d27--sample-gcc.elf and d27--sample-tcc.elf
"Compare … across builds" Questions about each build separately The six new fields pick a build by "basename ends gcc.elf / tcc.elf"; the solver's core only knew variant-NNN-<tag> names, hence the 6 left open
"name the toolchain" Identify the compiler from fingerprints No question asked for it; the member names already carry the toolchain
"attribute this binary to its maker" Tie each build to its config Per-build HCFG reads (campaign, domain), plus the shared-config fields that require both builds to agree
"Compiler Says What" (title) Compiler attribution Confirmed the reading

Safety

Static analysis only. Nothing was executed. The ELF members were read as bytes: header fields, then a backward scan for the HCFG record and its CRC32 check. No sample was run, loaded, or debugged. For this write-up only the question list, the solver's record of accepted answers, the request log, and the helper's notes were read. The archives stayed on our Kali VM. Indicators below are defanged.

What was in the files

  • Single Solve: evidence.zip, 35,900,240 bytes, password-protected members covering Days 1–27. Today's members are two ELF64 little-endian builds of one program: d27--sample-gcc.elf (108,155 bytes) and d27--sample-tcc.elf (462,379 bytes), both with e_machine 62 (x86-64).
  • One raw HCFG record near the end of each build (gcc at offset 107,776, tcc at 462,000; version 1, XOR key 0xc1), and the two configs are identical (defanged): campaign BR27-000, domain node-000[.]day27[.]example[.]invalid, mutex Global\HuntressBR27_000, URL hxxps://node-000[.]day27[.]example[.]invalid/api/BR27-000.
  • Accepted Day 27 role values: both formats elf, both machines 62, gcc campaign BR27-000, gcc domain node-000[.]day27[.]example[.]invalid.
  • Time Trial: evidence.zip, 36,035,463 bytes, the same two-build template with a randomised config (campaign BR27-2BB2…); formats and machines again elf / 62.
  • Mastery: eleven archives, 35.88 to 36.04 MB each, streamed. Every set had the same gcc/tcc pair with elf / 62 and its own config (eleven different campaigns and domains). No new toolchain appeared in any timed set.

Single Solve

The solver: 318 sent, 6 open

started_at  2026-10-27 09:01:38.531 ET   (solver)
deadline    none (Single Solve)
answers     226 core (accepted, remaining 98), then 92 add-on answers (accepted, remaining 6)
timings     start 438 ms, artifact 1767 ms, solve 1381 ms, submit phase 1740 ms
result      not CORRECT: 6 questions unanswered; timed modes not started

The solver's core already answered the two SHA-256s and the four shared config fields; it had no rule for a dNN--sample-<role>.elf member, so the six per-build fields were left. Leaving them open was right: nothing was guessed.

The add-on (helper)

The helper wrote a new add-on module (days ≥ 27), in a separate copy of the solver: - Matches dNN_analysis_<role>_(format|machine|config_<key>). The member is chosen by the question's own "basename ends X" phrase, else a -<role>.<ext> tail. No member for the role: skipped; two: raises. - Roles other modules own (vulnerable/corrected, loader/plugin, stripped/symbolized/compact/direct) are skipped, so it can't claim an older day's field. - Format from magic plus a parsed container (pe_cli needs a CLR directory and a BSJB metadata root); machine is the ELF e_machine or PE COFF Machine, in base 10; config is the last CRC32-valid HCFG record in file order (a backward scan), raw first, then Base64.

The first choices were elf; 62 (the form already confirmed for this field type); and gcc config values equal to the already-accepted shared ones, to be sent one at a time without resending a rejected value. Offline on the separate copy: coverage 324 of 324, the 318 accepted answers reproduced with 0 mismatches, unit tests 7 of 7. The helper then ran the separate copy, which reconnected to the solver's open attempt and sent only the six:

started_at  2026-10-27 09:01:38.531 ET   (solver's attempt; reconnected 09:05:24)
answers     6   (gcc/tcc format elf, gcc/tcc machine 62, gcc campaign and domain)
result      CORRECT   flag 612e1b3a…02cf   (09:05:29.561)

All six were accepted on the first try; no value was rejected.

Time Trial

CORRECT on the first attempt, ranked (unique_attempts_remaining: 63). Day 26's slip (a by-hand timed run started without the ranked clock) didn't recur.

started_at  2026-10-27 09:05:44.342 ET
deadline    2026-10-27 09:07:04.342 ET   (80 s)
answers     324
start        220 ms after started_at
artifact    2638 ms after started_at   (36.0 MB)
solve       3604 ms after started_at   (966 ms)
submit      4468 ms after started_at
result      CORRECT   flag c27e0263…01e5   (09:05:48.177)

Mastery

CORRECT on the first attempt, ranked (streamed with 3 solve workers; 11 sets, 725 s clock, unique_attempts_remaining: 15).

started_at  2026-10-27 09:05:54.545 ET
deadline    2026-10-27 09:17:59.545 ET   (725 s)
answers     3,564 (11 sets × 324)
start        414 ms after started_at
artifact    7558 ms after started_at   (11 × ~36 MB, streamed; last download done)
solve       8978 ms after started_at
submit     10593 ms after started_at
result      CORRECT   flag abd6db37…89f0   (09:06:03.918)

The helper's notes give 09:06:05, the time the reply was logged; the Game Master's solved_by.at is 09:06:03.918.

Checks and install

All after the three CORRECTs, on the separate copy: - Unit tests: 41 passed. - Re-solving the 133 saved Binary sets with the fast path off: the old and new solvers each had 1 error (Day 1's known manifest error, same message); 129 identical, 3 gained answers only (today's Single +6, TT +6, Mastery +66, all d27_analysis_ fields), no changed answer. - A regression check against every answer the Game Master had already accepted: they agree on 324 / 324 / 3,564 (apart from Day 1's known path-only error, as on Days 24 and 26). - A streamed replay of 52 saved Mastery attempts: 0 errors, identical to the whole-solve results.

The add-on went into the solver at 09:18:58 ET, with live coverage 324 of 324.

Time to solve

All three modes were solved by 09:06:03 ET, 6 min 3 s after release (4 min 25 s from the first start request on this lane). The Single Solve needed the new add-on for its last 6 answers; the Time Trial and Mastery were both ranked and each went through on the first submit.

Takeaways

  • Leaving fields open beats guessing. The solver sent the 318 it was sure of and stopped on 6 with no rule; the helper only had to add those 6, all accepted first try.
  • Pick the member the way the question says. Today's fields named their build by "basename ends gcc.elf"; the add-on uses the question's own phrase first, so a renamed or reordered member can't confuse it.
  • Writing for day >= N keeps paying, and so does skipping other days' roles. The add-on skips roles that other modules own and days that have no matching member (the Day 26 lesson, when an add-on claimed a JAR day and crashed the Single).
  • Ask for the ranked clock on every by-hand timed run. Both ranked runs were first-try and ranked today, so no ranked pass is owed for Day 27 (Day 26's slip cost a second pass).
  • The prompt promised more than the questions asked. "Name the toolchain" from runtime and linker fingerprints needed nothing: the member names gave it, and the fields asked only format, machine, and config.