The challenge
"A Dockerfile and several earlier specimens share strings, but sharing strings doesn't mean sharing parents. Attribute each specimen using build-stage, semantic, and static-handoff evidence, and expose the resemblance that's only skin-deep."
Script is cumulative: the Single Solve asked 1,659 questions over one evidence.zip of 175 scripts. Today's new files were four Dockerfiles with 11 questions each (44 in all). For the benign utility:
| answer_id | Question | Format |
|---|---|---|
polyglot- |
"Classify the inert source role." | enum (benign_, downloader, dropper, impact, launcher, persistence_, relay, staged_, stager, uploader, advanced_) |
polyglot- |
Map the decoded ordered behaviour graph to one primary ATT&CK technique (with a stated precedence) | "MITRE ATT&CK technique or language-specific sub-technique ID … or none for the benign graph" |
polyglot- |
"Recover the ordered behavior graph using > separators." | tokens joined by > |
polyglot- |
"Identify the script host or source container." | exact text |
-, -, - |
Stage count, multi-stage relationship profile, next language in the inert handoff | integer / exact text |
-, -, -, - |
Modelled adapter, campaign ID, file-path indicator, ordered encoding profile | exact text |
The other three Dockerfiles had the same 11 fields, with -url in place of -file-path. The Time Trial asked the same 1,659 questions, with the same IDs (not Days 23–26's hex-stem timed schema); the Mastery asked them across 11 sets (18,249).
Reading the prompt: what it said we'd need
| Clue | What it pointed to | What I did about it |
|---|---|---|
| "A Dockerfile" | New "language": Docker build files (.) |
Four polyglot- files. Our solver's generic plan reader covers any polyglot- file, so nothing new was needed |
| "several earlier specimens share strings" | Values reused from earlier days' files | The Dockerfiles reuse Day 26's Erlang step graphs, encodings, and stage profiles, each under a different role (below) |
| "sharing strings doesn't mean sharing parents" | A trap for anyone attributing by resemblance | The solver reads each file's own plan and never carries a value between files |
| "build-stage, semantic, and static-handoff evidence" | FROM stages, the behaviour graph, the next-language handoff |
The plan fields cover the graph and handoff; no question asked about build stages |
| "expose the resemblance that's only skin-deep" | Label the look-alikes | No field asked to label one |
| "FROM Whence It Came" (title) | A Dockerfile's FROM line: where an image comes from |
Confirmed the reading |
Safety
Static analysis only. Nothing was executed or built. The scripts sit in a password-protected (WinZip AES) archive and were read as text and decoded in Python; no Docker engine, docker build, or other interpreter was started. For this write-up only the archive's member list, the question list, the solver's record of accepted answers, and the request log were read; the Dockerfile source wasn't re-read. The archives stayed on our Kali VM. Indicators below are defanged.
What was in the files
- Single Solve:
evidence.zip, 151,106 bytes, 175 members: Day 26's 171 scripts pluspolyglot-dockerfile-{benign-utility,downloader,launcher,staged-chain}.dockerfile(1,940, 1,777, 1,225, and 2,450 bytes). Nohost-dockerfile-*fixture. - The Dockerfile plans (accepted; host profile
standalonefor all four):
| File | Role / ATT&CK | Adapter | Steps | Obfuscation | Stage profile | Next |
|---|---|---|---|---|---|---|
benign- |
benign_ / none |
local_ |
profile_ (3) |
fragmented_ |
cross_ |
powershell |
downloader |
downloader / T1105 |
http_ |
decode_ (2) |
plain_ |
single_ |
jscript |
launcher |
launcher / T1059 |
process_ |
acquire_ (2) |
base64 |
two_ |
batch |
staged- |
staged_ / T1059 |
operation_ |
decode_ (3) |
hex_ |
three_ |
vbscript |
Campaigns DOCKERFILE-23b1b5d4, DOCKERFILE-b78f0a1d, DOCKERFILE-2792f284, and DOCKERFILE-98b1a2e7. The benign utility carries a file path, /tmp/huntress-dockerfile/.23b1b5d4167400e9.stage; the others a URL of the form hxxps://dockerfile-<10 hex>[.]example[.]invalid/stage.
- The shared strings, made concrete. Each Dockerfile's step graph, encoding, and stage profile is exactly one of Day 26's four Erlang plans, moved to a different role: the Erlang downloader's triple is today's benign utility, the Erlang launcher's is today's downloader, the Erlang staged chain's is today's launcher, and the Erlang benign utility's is today's staged chain. The role-linked values (adapter, next language) stayed with the role on both days. So anyone matching "this graph looked like the downloader yesterday" would have mislabelled all four.
- The plans disagree with their own steps, a fourth day running. A benign utility with a cross-language handoff, a downloader whose graph only decodes and returns, a launcher with a download-and-execute graph. The solver sends each plan's own values, and all were accepted.
- Time Trial: evidence.zip, 153,569 bytes, 175 files with the Single's names (same Dockerfile sizes), new values. The four Dockerfiles had the same roles, steps, and encodings as the Single, with new campaigns.
- Mastery: 1,674,429 bytes, 11 sets in the same named schema: 44 Dockerfiles (484 answers), the same four roles and encodings in every set.
So the timed variants were the Single's template with different values; the hex-stem schema of Days 23–26 didn't appear.
Single Solve
The solver solved it in two batches, with no stop. Its core answered 1,529 questions, all 44 new ones included: it finds each polyglot-* file, parses the analysis header, checks that the header agrees with the body, and returns the plan field the question names. It flagged 62 values it had seen rejected on other questions before (none, T1105, T1059.007, delayed, linear, among them the Dockerfile benign utility's none and the downloader's T1105) and sent them anyway, as designed; none was rejected. The solver's plug-ins then sent the other 130.
started_at 2026-10-27 09:01:49.169 ET
deadline none (Single Solve)
answers 1,529 core (accepted, remaining 130), then 130 plug-in answers
start 616 ms
artifact 804 ms
solve 507 ms
submit 3714 ms (core 616, plug-ins 3096)
TOTAL 5647 ms
result CORRECT flag 1da06f30…8aa7 (09:01:52.522)
Time Trial
CORRECT on the first attempt, ranked (unique_attempts_remaining: 31, variant_fresh: true). The question IDs were the Single's named form, so the solver's hex-stem adapter had nothing to do and the core answered as for the Single.
started_at 2026-10-27 09:02:00.326 ET
deadline 2026-10-27 09:02:50.326 ET (50 s)
answers 1,659 (44 for the four Dockerfiles)
start 790 ms
artifact 446 ms
solve 107 ms
submit 1190 ms
TOTAL 2536 ms (submit +1.96 s after started_at)
result CORRECT flag 755e9fd7…7b39 (09:02:01.514)
Mastery
CORRECT on the first attempt, ranked (11 sets, 350 s clock; Day 26 had 10 sets and 320 s).
started_at 2026-10-27 09:02:08.352 ET
deadline 2026-10-27 09:07:58.352 ET (350 s)
answers 18,249 across 11 sets (484 for the 44 Dockerfiles)
start 1441 ms
artifact 669 ms (1.7 MB)
solve 1226 ms
submit 2724 ms
TOTAL 6093 ms (submit +5.32 s after started_at)
result CORRECT flag e440870b…0483 (09:02:11.461)
Time to solve
All three modes were solved by 09:02:11 ET, 2 min 11 s after release and 23 s after the solver's first start request on this lane (it handled four other lanes first). Each mode went through on the first submit, and both the Time Trial (+1.96 s after started_at) and the Mastery (+5.32 s) were ranked.
Takeaways
- A generic plan reader keeps paying. A new "language" (Dockerfile) needed no code, as Elixir and Erlang needed none on Days 25 and 26.
- Per-file reading is the defence against look-alikes. Today's files reused yesterday's step graphs and encodings under different roles, exactly what the prompt warned of. A solver that reads every value from its own file never borrows one, so the trap cost nothing.
- The plan is the answer, even when it looks wrong. Three of today's four plans have a role that doesn't match their steps; the plans' own values were accepted, as on Days 24 to 26.
- The flagged-values list is a warning, not a veto. 62 values flagged as "rejected elsewhere before" were all accepted here.
- The timed schema can change back. Days 23–26 used hex stems in the timed modes; today they used the Single's names. Our solver routes by the schema it sees, so both worked.
- The prompt promises more than the questions ask. "Build-stage evidence" and "expose the resemblance" had no field of their own.