The challenge
Every Java class file carries its own phone book: the constant pool. Names, methods, invocations — it's all in there. Dive in and take attendance.
48 questions over one evidence.zip (AES, password infected). This is a compounding challenge: the zip carries every earlier Binary day's file as well (d01 C ELF, d02 C++ ELF, d03 .NET DLL, d04 Go ELF, d05 Rust ELF), plus the new d06 Java JAR. 40 of the 48 questions are Rust Belt's; 8 are new:
answer_id |
Question (paraphrased) | Format |
|---|---|---|
d06_ |
SHA-256 of the d06 file | Exact text |
d06_ |
"exact analysis format value" | Exact text (no hint at all) |
d06_ |
Config campaign | Exact text |
d06_ |
Config domain | Exact text |
d06_ |
Config mutex | Exact text |
d06_ |
Config URL | Exact text |
d06_ |
Config CRC32 | Lowercase hex with 0x |
d06_ |
The HCFG record's version |
Base-10 integer |
Exact text adds: "use the literal none only when the requested field is absent." There is no architecture question for d06 (a JAR has none).
Reading the prompt: what it said we'd need
| Clue | What it pointed to | What I did about it |
|---|---|---|
"Every Java class file", ID java- |
A JAR (a ZIP) of compiled . files, and a "loader" that decodes something at run time |
Listed the JAR's members and read the class files with javap - and strings. Never ran them |
| "its own phone book: the constant pool" | Every string literal, class name, and method reference in a class lives in the constant pool. The title's "pool" | Read the constant pool first, looking for a long literal that isn't a name |
| "Names, methods, invocations" | The Methodref / NameAndType entries show which library calls the code makes |
decodeConfig() invokes java., which said how the config was stored |
| "take attendance" | List what's there and answer from the list | Read each config field from the decoded record |
d06_ |
The question names the HCFG record, the same config record as Days 2–5 |
Reused our HCFG extractor and taught it to look inside Base64 |
| d01–d05 questions | Compounding: the earlier files might be identical | Checked the SHA-256s: byte-identical to Rust Belt's, so its accepted answers carried over |
d06_, "exact artifact-derived value" |
No container_runtime hint (Day 3 had one; PE_ was accepted) |
One guess, then left it open. It became the lane-wide format problem |
Safety
Static analysis only. No challenge file was ever executed, and no JVM was started on any of them. The evidence stayed on our Kali VM. The JAR was opened as a ZIP and its classes read with javap (a disassembler; it doesn't run the code) and strings. The config was decoded from the bytes with our HCFG extractor. The timed-mode tooling reads archive members in memory and never writes a script or binary member to disk.
What was in the files
The Single Solve's evidence.zip: 1,919,090 bytes, SHA-256 f3f8f329…f8b6 (matched the start reply). d01–d05 were byte-identical to Rust Belt's, so this section covers only d06.
d06--variant-000.jar (SHA-256 6571ad6e…ae6c), four ZIP entries:
META-INF/ (directory, stored)
META-INF/MANIFEST.MF Main-Class: Day06Variant000
Created-By: Huntress CTF deterministic factory
Day06Variant000.class class file major version 65 (Java 21)
Day06Variant000$Result.class
The config: the class holds a static final String CONFIG, a long Base64 literal in the constant pool starting SENGRwF…. That decodes to HCFG\x01…: the same HCFG record as Days 2–5, just Base64'd. decodeConfig() decodes it with java.util.Base64. The record (described, not decoded here) is: the magic HCFG, a version byte, a one-byte XOR key, a length, a CRC32 of the plaintext, and then the XOR'd JSON. For d06:
version 1 · key 0x71 · length 368 · CRC32 0x121bc931 (matches)
campaign BR06-000
domain node-000.day06.example[.]invalid
url https://node-000.day06.example[.]invalid/api/BR06-000
mutex Global\HuntressBR06_000
(Brackets added; the domains are non-routable .invalid names.)
The rest of the class is a harmless stand-in for a loader. evaluate(model) reads HUNTRESS_HOST_MODEL and returns success, decoy, or failure for host models like workstation, analysis, or sandbox. It prints one huntress.binary.dynamic_trace.v1 JSON line that describes itself as modeled_command_dispatch / synthetic_dispatch_only, with network none.
The ZIP entries' compression methods (directory stored = 0, files deflated = 8) came back later: Days 8 and 9 ask d06_analysis_compression_methods, and [0,8] (distinct methods, first-seen order) was accepted there.
The timed deliveries came from the same factory, and the solver answers from the bytes either way, not from the Single Solve's values. Mastery set 2 was different: it delivered d02 (MinGW C++) and d04 (Go) as native Windows PE .exe files instead of ELFs. (The timed variants' own d06 values aren't recorded here.)
Single Solve: CORRECT (two days, mostly waiting)
Claude, 2026-10-07: 38 of 48 in one submit
One batch of 38 answers went in 29 seconds after the start: accepted, 10 left. It held Rust Belt's 31 carried-over answers plus the seven certain d06 ones:
| Field | Answer | How |
|---|---|---|
d06_ |
6571ad6e…ae6c |
SHA-256 of the JAR as extracted |
config_ / domain / url / mutex |
as above | The decoded HCFG JSON |
config_ |
0x121bc931 |
The record's stored CRC32, checked against the plaintext |
hcfg_ |
1 |
The record's version byte |
How the config was found: our extractor already found the raw HCFG record in the C++, .NET, Go, and Rust binaries. It got a new mode: search the raw bytes, then every Base64 run that contains the Base64 form of the magic (SENG), decode it and search again. A hit only counts if its CRC32 matches, so a stray HCFG can't produce an answer. On the JAR it found exactly one record, inside the class file's constant pool, with a valid CRC.
Then one format guess, alone: JAR_JVM (modelled on Day 3's PE_CLI). incorrect. Claude stopped there.
What was left open (10): the seven d02 fields and the format answers for d04, d05, and d06. The d02 config fields were the blocked part: Claude's safety filter had stopped one step of the analysis on the d02 config work, and we didn't work around it. The d02 format and the other three were plain wording questions, not blocked.
The Homebase agent, 2026-10-07: d02 config
The Homebase agent's Day 2 config analyser answered the six d02 config fields in one batch at 17:13: accepted, 4 left (44 of 48). What remained was four format answers: d02, d04, d05, and d06.
The Homebase agent, 2026-10-09: the format answers
The format field was open on every ELF and JAR day. By 2026-10-09 Claude and the agent had already had ELF64, ELF, ELF_GO, elf64-x86-64, ELF_Rust, JAR_JVM, and (on Routine Check's copy of d06) JAR_Java and Java_JAR rejected. The agent kept probing, one answer per submit, each with a stated reason.
On Pool Party's attempt the Game Master rejected 26 single-answer probes between 12:51 and 15:26 UTC. The agent's log names 21 of them as d06 spellings, in roughly these families:
| Idea | Spellings rejected |
|---|---|
| Container alone | JAR, ZIP, ., JAR_, JAVA_ |
| Container + language | JAR_, JAR/, JAR-, JAR_ |
| Language / runtime | JAVA, Java, JVM, JAVA_, JAVA_, JAVA_, JAVA_ |
| Tool output | application/, the same with ; charset=binary, Java archive data (JAR), compiled Java class data, version 65. |
| Magic bytes | CAFEBABE |
(The other five rejected probes on this attempt aren't itemised in the agent's log.)
The break came on Mangled: plain lowercase elf was CORRECT for its cpp_format. The agent then sent elf to Pool Party's three ELF format fields one at a time (15:32, 15:36, 15:37: accepted, 3 → 2 → 1 left), and jar for d06_analysis_format at 15:40:13: CORRECT.
The answer was the simplest thing on the list: the lowercase file-type word, for every ELF and every JAR. Flag 4174ff27…23a3.
Time Trial: CORRECT in 2.65 s (the Homebase agent)
Run by the Homebase agent with our core binary solver plus its Day 1 ELF and Day 2 config add-ons, 48/48 covered. A 7-second window.
started_at 2026-10-09T16:59:44.402Z deadline 2026-10-09T16:59:51.402Z (48 answer_ids)
d06 format jar · config from the Base64 HCFG record · hcfg_version from its version byte
start 553 ms · artifact 599 ms · solve 774 ms · submit 724 ms · TOTAL 2651 ms
result CORRECT flag da3816f3…f583 (the agent's own log wrote …0583; the saved reply says …f583)
(From the agent's own log.) Claude later re-ran the fixed solvers offline against the saved run: the same answers as the agent's submit.
Mastery: CORRECT (untimed practice, from our Kali VM)
Attempt 1 (Homebase, 16:59:51): stopped before submitting, nothing sent
started_at 2026-10-09T16:59:51.881Z deadline 2026-10-09T16:59:58.881Z (2 sets × 48)
stopped before submit: set 1's six d02_config fields, set 2's d02_analysis_format,
six d02_config fields, d04_analysis_architecture and d04_analysis_format had no answer
result none (nothing sent; the attempt expired)
Two causes, both fixed by Claude afterwards:
- Mastery set 2 swaps d02 and d04 for native PE .exe files. PE format and architecture had no confirmed value yet.
- The Day 2 config analyser ignored setN. answer ids and .exe files, so it answered nothing in a Mastery. It now handles both; a shared helper maps each question to its own set's download and never answers from another set.
The PE values were confirmed on Nimble's Mastery at 17:27 (pe for format, x86-64 for architecture; both accepted in one submit), and were used in the next run (a native-PE add-on covers those fields).
Attempt 2 (our Kali VM, 17:28:40): rotation_exhausted, so practice
A fresh retry came back HTTP 409 rotation_exhausted (unique_attempts_remaining 0). The guidance says practice continues on the last artifact without a timer or Hall of Fame time and still completes the mode, so our runner used op=practice (a new practice option).
started_at 2026-10-09T17:28:40.371Z deadline None practice True
retry → 409 rotation_exhausted (unique_attempts_remaining 0) → practice
96 answers (2 sets × 48), set 2 with pe / x86-64 for the PE d02 and d04
TOTAL 3030 ms
result CORRECT flag 1c45d083…e43b
(Only the total was recorded for this run, not the per-phase timings.) This counts as a Mastery solve but not a ranked time.
Time to solve
The Single Solve was completed on 2026-10-09 at 15:40:13 UTC, 47 h 23 min after it started: 38 of 48 answers were accepted 29 s in, and the four format answers took two days. The Time Trial went through first time in 2.65 s; the first Mastery attempt stopped before submitting, and the Mastery was finished at 17:28:42 UTC as untimed practice, so it is unranked.
Takeaways
- The title told us where to look. "Pool" was the constant pool, and the config was a
static final Stringin it. In Java, every literal ends up there, sojavap -vshows it without running anything. - Search for the encoded magic, not just the raw one.
HCFGin Base64 always startsSENG, so one extra pass over Base64 runs found the same record a raw scan would have missed. - Validate every hit. The CRC32 check is what makes a blind scan safe to submit from.
- Compounding challenges pay off on day one. d01–d05 were byte-identical to Rust Belt's, so 31 of the first 38 answers were already known; only d06 was new work.
- A field with no format hint may want the plainest word. Twenty-one elaborate d06 spellings were rejected; lowercase
jarwas right. Try the bare, lowercase type early. - Mastery can change the file types under you. Set 2's PE swap stopped a solver that was 48/48 on the Time Trial. Add-ons must handle set ids and every file type a set can carry.
rotation_exhaustedisn't the end. Practice finishes the mode, just without a ranked time.