zwesty
Write-ups / Day 30 / Binary
Day 30 · Fri, Oct 30 · Binary

Campaign Trail

Prey slaughtered

Carving a config record from the tail of an unfamiliar container (magic, version, XOR key, length, CRC32), Neko bytecode recognition by magic, a JSON "campaign graph", plug-ins 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-30-multi-binary-campaign (+ /time_trial, /mastery)
Solved
2026-10-30: all three modes CORRECT, both timed modes ranked. Our automated morning solver (a scheduled script on our Kali VM) sent 410 of 420 Single Solve answers (all accepted) and left 10 open, so it didn't start the timed modes. A Claude helper agent wrote one new plug-in. Single CORRECT at 09:06:10.5 ET, Time Trial at 09:06:48.7, Mastery at 09:07:05.6. 5 min 48.8 s from the solver's first start (09:01:16.8) to the Mastery CORRECT; 7 min 5.6 s from the 9 AM release. No value was rejected on any mode
Player
Zwesty (solo), with Claude Code (Opus 5.5): the automated solver answered 410 questions and stopped; a Claude helper agent wrote the Day 30 plug-in, sent the 10, ran both timed modes, and added the plug-in to the solver

The challenge

The prompt opens with "One binary is a clue; a campaign is a story." It then asks for the loader, library, and config graph to be rebuilt across the whole set, mapping the operation end to end.

Binary is cumulative: 420 questions, 14 per day for Days 1–30, all on one evidence.zip. Day 30's 14 were three SHA-256s of the new members plus eleven analysis fields:

answer_id Question (shape) Format
d30_artifact_sha256_1 … _3 SHA-256 of each d30-- member in central-directory order 64 lowercase hex
d30_analysis_(loader\|library\|config_capsule)_format The member whose basename ends loader.n / library.n / config.n: classify the final bytes by magic and parsed container enum: elf, pe, pe_cli, jar, wasm, neko_bytecode
d30_analysis_config_(campaign\|domain\|mutex\|url) loader.n's decoded HGRF record, JSON property graph.config.<key>, "after confirming all three canonical campaign graphs match when this_role is omitted" exact text
d30_analysis_config_capsule_graph_config_(campaign\|domain\|mutex\|url) The same property from config.n's HGRF record exact text

The HGRF questions spell out the record: scan backward from the end of the Neko member for the unique CRC32-valid record, with the same 12-byte layout as the HCFG records from earlier days (byte-4 version, byte-5 XOR key, little-endian length and CRC32, XOR-encoded UTF-8 JSON).

The Time Trial asked the same 420; the Mastery asked them once per delivery (14 deliveries, 5,880 answers).

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"a campaign is a story" Several samples that only make sense together Three Neko members (loader, library, config) each carry a copy of one JSON "campaign graph"
"Reconstruct the loader, library, and config graph" Nodes and edges between the three roles The graph JSON has nodes, edges, load_order, config{…}, and this_role; the questions only asked for config strings
"across the whole set" Compare the copies, don't trust one The helper checked that the three graphs are equal apart from this_role, as the loader questions require
"map the operation end to end" A full read of the campaign Not needed beyond the config fields; no question asked for the edges or load order
"Classify final bytes by magic" (question text) A new container type Magic NEKO: the enum's neko_bytecode is the only token for it
"the same 12-byte layout as HCFG" (question text) Reuse the HCFG decoder with a new magic HGRF records decode exactly like HCFG; only the magic and the JSON shape are new

Safety

Static analysis only. Nothing was executed. The members were read as bytes: magic, the trailing HGRF record, its CRC32 check, and the XOR-decoded JSON. No Neko VM was installed and no sample was run or loaded. For this write-up only the helper's notes, the plug-in, the saved answer and timing records, the archive member lists, and the Game Master log were read. The archives stayed on our Kali VM. Indicators below are defanged.

What was in the files

  • Single Solve, Day 30 members: d30--sample-loader.n (20,880 bytes), d30--sample-library.n (20,890), and d30--sample-config.n (20,916). All three are Neko bytecode (magic NEKO).
  • One HGRF record near the end of each. The decoded JSON uses schema huntress.binary.campaign_graph.v1 with nodes, edges, load_order, config{…}, and this_role. The three graphs are equal apart from this_role.
  • The config values (the same in loader.n and config.n): campaign BR30-000, domain campaign-000[.]day30[.]example[.]invalid, mutex Global\HuntressBR30-000, and URL hxxps://campaign-000[.]day30[.]example[.]invalid/stage/BR30-000.
  • Time Trial and Mastery: the same template with different values. Every archive (1 for the Time Trial, 14 for the Mastery) has 45 members, including the same three d30--sample-*.n names. Their sizes differ only by a few bytes between archives (loader 20,921–20,926 bytes), which fits different config strings in the same layout.

Single Solve

The automated solver: 410 sent, 10 open

started_at  09:01:16.8 ET   (started by the solver)
deadline    none (Single Solve)
answers     253 core (accepted, remaining 167), then 157 plug-in answers (accepted, remaining 10)
timings     start 589 ms, artifact 3,612 ms, solve 2,186 ms, submit phase 1,748 ms
result      not CORRECT: 10 questions unanswered, no rejected values; timed modes not started

The 10 open ids were all new Day 30 fields: the loader and config-capsule formats and the eight HGRF config strings. The older Day 27 role-builds plug-in skips the loader role (another plug-in owns it) and its field pattern doesn't match config_capsule_format, so neither format was answered; nothing knew about HGRF. The library format, which that plug-in does cover, was already accepted. Leaving the 10 open was right: nothing was guessed.

The plug-in (Claude helper agent)

The helper wrote a new plug-in for days ≥ 30 and tested it on a staging copy of the solver: - Member from the question text: "basename ends X" or "Use X's decoded HGRF". - Record: the last CRC32-valid HGRF record in the member ("scanning backward from the end"), decoded with the HCFG layout. - Answer: the string at graph.<path> named in the question. The _format fields reuse the Day 27 plug-in's container classifier. - Archive choice as on earlier days: one archive given (streamed Mastery) → use it; otherwise by set or delivery N:.

Staged test: coverage 420 of 420, and every answer already accepted was reproduced.

Formats: neko_bytecode. Campaign, domain, and URL: the decoded JSON string as stored. The one judgement call was the mutex: first the decoded string with one backslash (the form accepted for every HCFG mutex since Day 3), and the JSON-escaped form with two backslashes only if that was rejected.

The helper sent the 10 on the solver's open attempt:

reconnected to the solver's attempt 09:06:04 ET
answers     10 (the plug-in's)
start        343 ms
artifact    2,488 ms
solve       3,366 ms
submit        852 ms
TOTAL       7,059 ms
result      CORRECT   flag c1d440eb…b93e   (09:06:10.465 ET)

The one-backslash mutex was accepted; the second form was never sent.

Time Trial

CORRECT on the first attempt, ranked (variant_fresh: true).

started_at  2026-10-30 09:06:46.366 ET
deadline    2026-10-30 09:08:06.366 ET   (80 s)
answers     420
artifact    +1.17 s after started_at
solve       +2.08 s
submit      +3.25 s   (TOTAL 3.8 s, solve 0.9 s)
result      CORRECT   flag 4e3decd9…f48b   (09:06:48.718 ET)

Mastery

CORRECT on the first attempt, ranked (streamed, 14 deliveries).

started_at  2026-10-30 09:06:56.679 ET
deadline    2026-10-30 09:18:01.679 ET   (665 s)
answers     5,880 (14 deliveries × 420)
downloads   14 streamed, last at +4.98 s after started_at
solve       +8.47 s
submit      +10.12 s   (TOTAL 11.4 s, solve 3.5 s)
result      CORRECT   flag 445c09e4…ad76   (09:07:05.646 ET)

Install

At 09:08 the helper added one new file, the plug-in, to the live solver. No existing file changed, so removing that file rolls it back. Live coverage afterwards: 420 of 420. The solver's fast path still has no stored shortcut for this profile, so the Binary solve stays a full solve, as since Day 24.

Time to solve

All three modes were solved by 09:07:05.6 ET, 7 min 5.6 s after release and 5 min 48.8 s after the solver's first start; most of that was writing the plug-in for the 10 open fields. Both timed modes were ranked and went through on the first submit, with no rejected value on any mode.

Takeaways

  • The question text was the spec again. It named the magic, the scan direction, the 12-byte layout, and the JSON path. The plug-in reads the member and the path from the text instead of hard-coding them.
  • A new magic over an old layout is a small change. HGRF is HCFG with a new magic and a richer JSON payload, so the existing decoder logic carried over.
  • Leaving fields open beats guessing. The solver sent the 410 it was sure of; the helper added only the 10.
  • Settle form questions before sending. The mutex had two plausible forms; ranking them, with the form accepted since Day 3 first, meant one submit and no rejected value.
  • New files only make installs cheap. One added file, no edits, so the rollback is a single delete.