zwesty
Write-ups / Day 24 / Binary
Day 24 · Sat, Oct 24 · Binary

Spot the Difference

Prey slaughtered

Static JAR triage (ZIP container, class files), Base64 constants, HCFG config record with CRC32, cumulative question sets, add-on modules

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-24-patch-diffing (+ /time_trial, /mastery)
Solved
2026-10-24: all three modes CORRECT. The solver's two Single Solve batches were accepted but left 6 questions uncovered (09:01:14 ET); Claude sent the 6 (09:02:38), then the ranked Time Trial (09:03:25) and the ranked Mastery (09:03:38), both on the first attempt. 2 min 29 s from the first start request (09:01:09 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) started the Single Solve and sent the first two batches; Claude worked out the last 6 answers, sent them, built the new add-on module, and ran both timed modes

The challenge

"Two builds, one fix. Diff the vulnerable function against its corrected twin and pinpoint exactly what changed — then prove the old one was really broken."

Binary is cumulative: 288 questions, 12 per day for Days 1–24, all on one evidence.zip. The 12 Day 24 fields:

answer_id Question (short) Format
d24_artifact_sha256_1, _2 SHA-256 of d24-- members 1 and 2, in ZIP central-directory order 64 lowercase hex
d24_analysis_vulnerable_format, d24_analysis_corrected_format Classify the member ending vulnerable.jar / corrected.jar by magic and container enum: elf, pe, pe_cli, jar, wasm, neko_bytecode
d24_analysis_config_campaign, _domain, _mutex, _url "Decode both d24-- role members and require their config objects to be byte-for-byte equal", then the HCFG JSON fields exact text
d24_analysis_corrected_config_campaign, _domain, _mutex, _url The same HCFG fields, from the corrected.jar member only exact text

The HCFG rule in the question text: parse every class file's constant pool; Base64-decode every Base64-alphabet UTF-8 constant of at least 80 characters; take the first CRC32-valid HCFG JSON record (HCFG, version byte, XOR key byte, payload length, CRC32). Time Trial asked the same 288; Mastery asked them once per set (8 sets, 2,304 answers).

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"Two builds" Two members: d24--sample-vulnerable.jar and d24--sample-corrected.jar Both are JARs: format jar for each
"one fix … Diff the vulnerable function against its corrected twin" A bytecode diff of one method None of the 12 Day 24 questions asked for the diff. The answers come from the containers and the embedded config
"prove the old one was really broken" Show the vulnerable behaviour Not asked either. Nothing to run
"require their config objects to be byte-for-byte equal" (question text) Both builds carry the same HCFG config The solver's core already decodes both and checks they match; those 4 fields were covered
"Use the unique d24-- member whose basename ends corrected.jar" (question text) The same config from one member The corrected build's config must equal the accepted common one, so the common values were the first choice
"jar for a ZIP/JAR with classfiles" (format text) Format by container A ZIP whose entries include .class files → jar

Safety

Static analysis only. Nothing was executed. The JARs were opened as ZIP files and their class files read as bytes (Base64 runs, then the HCFG scan); no Java runtime was started. The archives stayed on our Kali VM.

What was in the files

  • Single Solve: evidence.zip, 35,684,100 bytes, password-protected members covering Days 1–24. Today's members are d24--sample-vulnerable.jar (4,054 bytes, member 1) and d24--sample-corrected.jar (4,422 bytes, member 2). SHA-256: 7a4706e9…d922 and 38723bc7…7863.
  • The HCFG record decoded to a synthetic config (defanged), the same in both builds: campaign BR24-000, domain node-000[.]day24[.]example[.]invalid, mutex Global\HuntressBR24_000, URL hxxps://node-000[.]day24[.]example[.]invalid/api/BR24-000.
  • Time Trial: one evidence.zip of 35,818,671 bytes. Mastery: eight, 35.66 to 35.79 MB each. Same template with different values; the whole solve ran unchanged on all of them once the add-on was in.

Single Solve

The solver's batches: accepted, 6 remaining

The solver reported coverage 282 of 288: the two *_format fields and the four corrected_config_* fields had no answer. It sent the core batch (205, accepted, 83 remaining) and the add-on batch (77, accepted, 6 remaining). With questions it couldn't answer, it stopped there and left Binary, timed modes included, for Claude.

started_at  2026-10-24T13:01:09.954Z   (solver)
deadline    none (Single Solve)
answers     205 core (accepted, remaining 83), then 77 add-on (accepted, remaining 6)
start        383 ms
artifact    2124 ms   (35.7 MB)
solve       1037 ms
submit      1749 ms   (core 459, add-ons 1288)
TOTAL       5293 ms
result      accepted, remaining 6; solver stopped Binary; timed modes not started

The last 6 (Claude)

The accepted d24_analysis_config_* values already required both builds' configs to be byte-for-byte equal. So the corrected build's config had to be the same. The answers chosen: - both *_format: jar (both members are ZIPs with class files); - corrected_config_campaign BR24-000, _domain node-000.day24.example.invalid, _mutex Global\HuntressBR24_000, _url the accepted common URL.

All were accepted on the first try, so no other value was sent.

started_at  2026-10-24T13:01:09.954Z   (same attempt, finished by Claude at 13:02:37)
answers     6
result      CORRECT   (13:02:38)   flag not recorded: see below

The Single Solve flag isn't on file. Our request log keeps only the first ~4,000 characters of each reply, and this reply's flag came after the cut; the submit wasn't saved to a file. The CORRECT result itself is in the log.

The add-on (Claude)

A new add-on module answers the six fields for every Binary day from 24 on: - dNN_analysis_{vulnerable,corrected}_format: jar when the member is a ZIP whose entries include .class files; anything else raises. - dNN_analysis_corrected_config_*: from the corrected JAR only. It reads every class file, Base64-decodes every Base64-alphabet run of at least 80 characters, and asks the shared HCFG reader for CRC32-valid records; the first one's JSON config supplies the value. - Safe for streamed Masteries: when a Mastery archive is handed over on its own, it uses that one archive; otherwise it picks by set or by the delivery N: name.

One honest difference: the question says "parse every constant pool", while the add-on scans the class bytes for Base64 runs. That finds the same constants here (a Java constant-pool string is stored as plain UTF-8 bytes), and every set's corrected-config answers were accepted alongside the core's common config.

It was built in a separate copy (the solver wasn't touched while it was running), and both timed modes ran from there.

Time Trial

CORRECT on the first attempt, ranked (80 s clock), from the separate copy.

started_at  2026-10-24T13:03:21.520Z
deadline    2026-10-24T13:04:41.520Z   (80 s)
answers     288
start        154 ms after started_at
artifact    2117 ms after started_at   (35.8 MB)
solve       2854 ms after started_at
submit      3590 ms after started_at
result      CORRECT   flag 03b153da…bc48

Mastery

CORRECT on the first attempt, ranked (530 s clock). The eight archives were streamed; all were in by +4,688 ms.

started_at  2026-10-24T13:03:32.103Z
deadline    2026-10-24T13:12:22.103Z   (530 s)
answers     2,304 (8 sets × 288)
start        374 ms after started_at
artifact    4688 ms after started_at   (8 × ~35.7 MB, streamed; first in at +2108 ms)
solve       5219 ms after started_at
submit      6585 ms after started_at
result      CORRECT   flag dd57b601…f657

Around 09:18 ET the add-on went into the solver for the following days. Before that it was checked: re-solving 117 saved Binary sets gave 113 identical, the 3 Day 24 sets gaining answers only, and Day 1 a known gap; a replay of 47 saved Masteries was clean; the Day 24 accepted answers matched (288, 288, 2,304).

Time to solve

All three modes were solved by 09:03:38 ET, 3 min 38 s after release (2 min 29 s from the first start request). The Single Solve needed Claude to finish the last 6 answers; the Time Trial and Mastery were both ranked and went through on the first submit.

Takeaways

  • A field restricted to one member inherits the shared answer. When the accepted config already required both builds to match, the corrected-only config could only be the same values. It was the first choice and it was right.
  • Accepted-with-remaining is a clean stop. The solver sent 282 answers it could cover and left 6, so nothing was burned and both ranked runs were still first attempts.
  • The prompt's theme didn't change the questions, again. "Diff the function" and "prove it was broken" sounded like bytecode work; the 12 fields were hashes, formats, and the usual config. Read the question list before planning around the prompt (as on Day 23).
  • Write add-ons for day >= N and for streaming from the start. Today's add-on was both, so the streamed Mastery needed no second version.
  • Keep every reply that carries a flag. The Single Solve flag was lost to the log's cut; the timed runs, which save their submit replies, kept theirs.