The challenge
"It crashed. That's the good news — the dump is a confession. Unwind the stack, find the faulting instruction, and triage what went wrong from the wreckage."
Binary is cumulative: 312 questions, 12 per day for Days 1–26, all on one evidence.zip. Nine of the 12 Day 26 fields are familiar (the member's SHA-256, its format, the HCFG config's campaign, domain, mutex, and URL, its CRC32, version, and XOR key). Three are new:
| answer_id | Question | Format |
|---|---|---|
d26_ |
"Parse crash/trace.json and submit the exact first string in its frames array." | exact text |
d26_ |
"true exactly when every string in crash/trace.json frames is present in the primary classfile method-name set" | enum true / false |
d26_ |
"For Day 7 use the manifest Main-Class classfile; for Day 26 use the primary classfile named by that unit. Submit its classfile method-table count in base 10." | base-10 integer |
The _hex fields (d26_analysis_xor_key_hex, d26_analysis_config_crc32_hex, and Day 2's d02_analysis_xor_key_hex) all say "lowercase hexadecimal with a 0x prefix; preserve the field's required zero padding". That line mattered today. The Time Trial asked the same 312; the Mastery asked them once per set (10 sets, 3,120 answers).
Reading the prompt: what it said we'd need
| Clue | What it pointed to | What I did about it |
|---|---|---|
| "It crashed … the dump is a confession" | A crash record shipped with the sample | crash/ inside the JAR (huntress.) |
| "Unwind the stack" | The record's frame list, top frame first | frames is the call stack, innermost first |
| "find the faulting instruction" | Where it failed | The question asks only for the method (the first frame), not an instruction |
| "triage what went wrong" | Check the dump belongs to this build | The frames-match predicate: every frame must be a method in the primary class's method table |
| "Crash Course" (title) | Crash triage | Confirmed the reading |
Safety
Static analysis only. Nothing was executed. The JAR was opened as a ZIP file; its manifest, trace JSON, and class files were read as bytes (constant pool, method table, Base64 runs, then the HCFG scan). The method count was cross-checked with javap -p, a class-file disassembler that reads the file and runs none of it; no Java runtime was started. For this write-up the JAR's member list, its manifest, and crash/trace.json were printed, plus the question list, the solver's record of accepted answers, and the request log. The archives stayed on our Kali VM. Indicators below are defanged.
What was in the files
- Single Solve:
evidence.zip, 35,703,913 bytes, password-protected members covering Days 1–26. Today's member isd26--sample.jar(4,508 bytes; SHA-256cb614301…2d05), a Groovy JAR:Main-Class: Day26Variant000("Created-By: Huntress deterministic Groovy factory"),Day26Variant000.class(5,110 bytes),Day26Variant000$_main_closure1.class, andcrash/trace.json:{"schema":"huntress.synthetic_crash.v1","kind":"IllegalStateException","frames":["stageThree","stageTwo","stageOne"]}. - The HCFG record is a Base64 constant in the primary class file, as on Days 7 and 24 (defanged): campaign
BR26-000, domainnode-000[.]day26[.]example[.]invalid, mutexGlobal\HuntressBR26_000, URLhxxps://node-000[.]day26[.]example[.]invalid/api/BR26-000; version1, XOR key0xb1, CRC320xfd5b…48eb. - Accepted Day 26 values: format
jar; fault methodstageThree; frames matchtrue; method count10(javap -pshows 10 methods and no<clinit>). - Time Trial:
evidence.zip, 35,705,526 bytes. Same JAR template with a randomised config (Day 26 XOR key0x37); the three crash fields were againstageThree,true,10. - Mastery: ten archives, 35.68 to 35.84 MB each, streamed. Every set's crash fields were
stageThree,true,10; the configs varied. Set 5's Day 2 XOR key was0x08, the only one below0x10among all of today's Day 2 keys (Single0x31, TT0xfb, the other nine sets0x16to0xd4).
What broke in the solver
The solver's Single Solve died before any submit:
ValueError: Day 26 expects one primary managed assembly in 01-evidence.zip, found 0
The Day 22 managed-DLL add-on matches dNN_analysis_format for every day from 22 on and assumed each such day ships a dNN--sample.dll. Day 26 ships a JAR, so it raised, and the whole run stopped. Fix (built in a separate copy): a day with no dNN--sample.dll is skipped (other modules answer it); more than one still raises.
Single Solve
The helper wrote a new Day 26 add-on module (days ≥ 26, safe for a single archive or a streamed Mastery set):
- dNN_analysis_crash_fault_method: the first string of frames in the JAR's crash/trace.json.
- dNN_analysis_crash_frames_match_methods: true exactly when every frame string is a method name in the primary class file's method table.
- dNN_analysis_method_count: the Main-Class class file's methods_count.
The first choices were fault method stageThree (with Day26Variant000.stageThree as a fallback), frames match true, and method count 10 (with 11 and 9 as fallbacks), to be sent one at a time without resending a rejected value. The fixed copy covered 312 of 312 questions. The helper then ran it, and it reconnected to the solver's open attempt:
started_at 2026-10-26 09:01:05.771 ET (solver's attempt; reconnected 09:04:29)
deadline none (Single Solve)
answers 220 core (accepted, remaining 92), then 92 add-on answers from nine add-on modules
(today's new module answered 3 of them)
result CORRECT flag 31cf7f0f…add8 (09:04:35.045)
All three new values were accepted on the first try; no value was rejected.
Time Trial
CORRECT, but untimed. The helper started the Time Trial without asking for a timed run. Both timed routes support an optional ranked clock, so a plain start is untimed: the reply showed no deadline and an unranked attempt. (The scheduled solver asks for the ranked clock itself; the by-hand run didn't.)
started_at 2026-10-26 09:04:55.317 ET
deadline none (unranked)
answers 312
start 472 ms after started_at
artifact 3566 ms after started_at (35.7 MB)
solve 4144 ms after started_at (578 ms)
submit 4853 ms after started_at
result CORRECT flag c017d44c…6bc5 (09:04:59.599)
Mastery
First submit: incorrect
The same slip: started without the ranked clock, so untimed (10 sets).
started_at 2026-10-26 09:05:28.935 ET
deadline none (unranked)
answers 3,120 (10 sets × 312)
start 438 ms after started_at
artifact 5484 ms after started_at (10 × ~35.8 MB, streamed)
solve 7393 ms after started_at
submit 8641 ms after started_at
result incorrect (09:05:37; no per-set detail)
An offline replay with the fast path off gave byte-identical answers, so it was a shared solver bug, not a streaming one.
Finding the bad field with partial batches
Because the attempt was untimed, it stayed open and took partial batches.
- Per set (09:09:37 → 09:09:56): sets 1–4 and 6–10 accepted; set 5 incorrect.
- Set 5 per day (09:10:08 → 09:10:49): 25 days accepted; Day 2 incorrect.
- The bug: the Day 2 config add-on wrote d02_analysis_xor_key_hex as 0x{key:x}, so set 5's key 0x08 went out as 0x8. The format asks to preserve the zero padding (the core's own reader already uses :02x). Fixed to :02x on both Day 2 analysis paths; Day 2's older cpp_xor_key field was left alone.
- Resend of set 5's Day 2 batch (12 answers) with 0x08 at 09:11:27: CORRECT 09:11:28.870. That confirms the fix; no value was rejected on its own. 37 submits after the first, between 09:09:37 and 09:11:28.
The Mastery flag is not on file. Our request log cuts replies at about 4,000 characters, before the flag, and the by-hand resend didn't save its reply.
Ranked runs: not done
At 09:11:45 the helper tried a ranked Time Trial start. The Game Master answered with the already-solved attempt (a start only reconnects; no questions were returned), so nothing was sent. Once a mode has an untimed CORRECT, a ranked run needs an explicit retry with the ranked clock. We didn't make one that morning.
Checks and install
All on the fixed copy: - Unit tests: 23 passed after both fixes. - Re-solving the 125 saved Binary sets: the old solver had 4 errors (Day 1's known manifest error plus the 3 Day 26 sets the old Day 22 add-on crashes on); the fixed copy had 1 (Day 1 only). No older set's answer changed. - A streamed replay of 49 saved Mastery sets: 0 errors, identical to the whole-solve results, Day 26's 3,120 included. - A regression check against every answer the Game Master had already accepted: accepted and fixed agree on 312 / 3,120 / 312, 0 differ (apart from Day 1's known path-only error and the 3 Day 26 sets the old copy can't solve, as on Day 24). - A cache rebuild for the solver's fast path ran out of memory three times and was skipped; today's sets don't use the fast path, so it doesn't change timed runs.
The three add-on modules (the new Day 26 one and the two fixed ones) went into the solver at 09:39 ET.
Time to solve
All three modes were solved by 09:11:28 ET, 11 min 28 s after release (10 min 23 s from the first start request on this lane). None of the timed modes was ranked: the Time Trial and Mastery were started untimed by mistake, and the Mastery needed partial batches to find one badly padded field before it went through.
Takeaways
- A by-hand timed run must ask for the ranked clock. Without it both timed modes ran untimed, and an untimed CORRECT then blocks a plain ranked start: getting a ranked time takes an explicit ranked retry.
- Render every
_hexfield the way the question pins it. "Preserve the field's required zero padding" was in the format all along;0x{k:x}dropped it. Every saved set had a Day 2 key of0x10or more, so no snapshot could catch it. Synthetic test values below0x10(and CRC32 values below0x10000000) belong in the tests of any hex field. - The untimed attempt had one upside: it took partial batches. 10 per-set batches, then 26 per-day ones, found the one bad field of 3,120 in 37 submits.
- An add-on matched on a shared field name must skip days that aren't its own. The Day 22 managed-DLL add-on claimed
d26_analysis_formaton a JAR day and took the whole Single down with it. - Writing for
day >= Nkeeps paying. The Day 24 add-on answered its 12 fields again with no change, and today's add-on is written for every day from 26 on. - The prompt promised more than the questions asked. "Find the faulting instruction" needed only the first frame's method name; the class's method table and the trace were enough.