The challenge
"A GDScript resource loader builds its strings in layers, one power-up at a time. Undo the construction, recover the exact indicators, and correlate its stage relationships with the rest of the corpus to find the level that doesn't belong."
Script is cumulative: the Single Solve asked 1,461 questions over one evidence.zip of 157 scripts. Today's new files were four GDScript polyglots (polyglot-gdscript-*.gd), with 44 questions such as:
| answer_id | Question | Format |
|---|---|---|
polyglot- |
"Classify the inert source role." | enum (benign_, downloader, dropper, …) |
polyglot- |
Map the decoded behaviour graph to one primary ATT&CK technique (with a stated precedence) | ATT&CK ID |
polyglot- |
"Recover the exact url indicator." | exact text |
polyglot- |
"Recover the ordered behavior graph using > separators." | tokens joined by > |
polyglot- |
"Recover the ordered encoding profile." | exact text |
The timed modes used a different question schema (below): 1,495 questions in the Time Trial, 10,465 in the Mastery (7 sets).
Reading the prompt: what it said we'd need
| Clue | What it pointed to | What I did about it |
|---|---|---|
| "A GDScript resource loader" | New language: Godot's GDScript, as a polyglot | Our existing polyglot readers already covered it; the Single Solve passed |
| "builds its strings in layers, one power-up at a time" | Stacked encodings | In the timed modes, the LYR- header names the layers in the order applied |
| "Undo the construction" | Decode those layers last-first | A new LYR decoder in our header parsers |
| "recover the exact indicators" | URLs, paths, hosts, file names | New indicator, url-, and path- fields in the timed schema |
| "correlate its stage relationships with the rest of the corpus" | Cross-file links between stages | The Python handoff- comes from the . precursor whose $NextStage names the file |
| "the level that doesn't belong" | An outlier in the corpus | No question asked for it by that name; the cross-file answers covered the relationships asked |
Safety
Static analysis only. Nothing was executed. Every script was read as text and decoded in Python; no interpreter, shell, or host for any of the languages was started. The archives stayed on our Kali VM. Indicators below are defanged.
What was in the files
- Single Solve:
evidence.zip, 139,343 bytes, 157 scripts with descriptive names (polyglot-gdscript-downloader.gd,batch-stager.cmd, …) and question IDs built from those names. - Time Trial:
evidence.zip, 154,625 bytes (ranked) and 156,576 bytes (retry), also 157 files. Fresh content (no SHA-256 matches with the Single) and a new schema: - every file renamed to a 6-hex stem (for example
34c1d9.cmd), and question IDs built from it (batch-34c1d9-role); - a new field set: for role scripts
role,attack,campaign,campaign-token,indicator,steps, plus per-languagestyle(batch, VBScript, JScript: three labels joined by|) and Pythoncarrierandobfuscationpairs; for the 80 polyglot and host filesrole,attack,steps,stage-count,stage-profile,obfuscation,next-language,campaign,url,file-path,url-host,path-leaf,campaign-token; Bash advancedfeature,primitive,campaign,campaign-token; PowerShellurl,secondary,tertiary; - roles re-drawn (for example four batch uploaders and no batch stager), so it is not a renamed Single;
- the polyglot and host files carry a new header,
HUNTRESS_ANALYSIS_V1:LYR-<layer>_<layer>_…-<payload>, with layers fromb64,b32,hex,rev, andfraglisted in the order applied. All 80 decode last-first to av=3|…plan. - Mastery: 1,088,904 bytes, 7 sets in the timed schema. A defanged PowerShell example from set 1: URL
hxxps://e0776da97f[.]example[.]invalid/6805a0c5cd20[.]dat, secondaryupdate-e0776da97f.dat, tertiaryMozilla/5.0 HuntressLab/4.9.
So the timed variants were the same generator as the Single Solve (157 files each) with a different naming scheme, field set, and header.
Single Solve
The solver got it in two batches. The core sent 1,333 answers (accepted, 128 remaining), then the add-on modules sent 128 (CORRECT). The core noted that some values had been rejected on other questions before; it sent them anyway, as designed, and they were accepted here.
started_at 2026-10-23T13:01:18.581Z
deadline none (Single Solve)
answers 1,333 core (accepted, remaining 128), then 128 add-on answers
start 370 ms
artifact 304 ms
solve 335 ms
submit 2503 ms (core 372, add-ons 2129)
TOTAL 3515 ms
result CORRECT flag 2f83cf81…c84c (server stamp 13:01:20.910Z)
Time Trial
Ranked attempt: solver crash, nothing sent
started_at 2026-10-23T13:01:27.781Z
deadline 2026-10-23T13:02:17.781Z (50 s)
answers none
result solver crash: KeyError '34c1d9' in the Day 4 solver's step table (a hex stem it couldn't look up).
The solver stopped this lane (it doesn't retry); the attempt expired at the deadline.
Why it crashed. Our solver's stem tables (from Day 4, Day 7, …) are keyed on the descriptive file names, and a hex stem can't be looked up. Matching each TT file to its "Single twin" doesn't work either, because the roles were re-drawn. Each file has to be answered from its own decoded plan.
The fix (helper)
The helper built and tested the fix separately, then put it into the solver at 09:14 ET:
- A new adapter answers the hex-stem schema file by file. The plans come from our existing per-language parsers (batch, VBScript, Python, Bash, Node, JScript, and the header decoders). Role uses the Time Trial rubric from the steps, cross-checked against the solver's confirmed step tables; ATT&CK comes from the same tables (polyglots: the plan's own values). The field rules:
- campaign-token = the campaign after its first hyphen; steps = the plan's steps joined by >.
- indicator = the first present of url, upload_url, next_stage, file_path, schedule_path, note_name, host_id.
- style: batch graph|control|expansion, VBScript representation|transform|control, JScript function|invocation|property.
- Python carrier = carrier|windows_chain, obfuscation = syntax|obfuscation, handoff-id = the $HandoffId of the .ps1 precursor whose $NextStage names the file (same inner zip).
- Polyglot url-host = the URL's host, path-leaf = its last path segment; obfuscation = the LYR layer list (it equals the plan's obfuscation_profile on all 80).
- PowerShell secondary and tertiary = wallet and mutex, file and user agent, or note and extension, by the confirmed scenario.
- Polyglot role and steps disagree inside the plans (the Single's too). The Single accepted the plan's own role and ATT&CK, and the TT question says "role recorded in the decoded analysis descriptor", so the plan values are used.
- Routing: a script question set goes to the adapter only when about 90% of the IDs are hex stems, so older sets are unchanged.
- Header parsers: gained the LYR decoder; the classic headers are unchanged.
- New tests: LYR round trips, ID parsing (old IDs never match), the role rubric.
Checks before sending: - Unit tests: 25 passed. - Every derived field checked against the Day 22 and Day 23 Single answers: 1,035 and 999 fields, 0 mismatches. - Coverage: Single 1,461 of 1,461, Time Trial 1,495 of 1,495, Mastery 10,465 of 10,465. - A regression check against every answer the Game Master had already accepted for Script on Days 20–23 (Single, Time Trial, and Mastery): 0 mismatches.
Untimed retry: CORRECT
We allowed ourselves one untimed retry of a failed first timed run, so the helper used retry in untimed (unranked) mode. All 1,495 answers went in one submit.
started_at 2026-10-23T13:13:17.049Z (retry, untimed)
deadline none
answers 1,495
retry 262 ms after started_at
artifact 319 ms
solve 60 ms
submit 1600 ms
TOTAL 1980 ms from the retry reply (submit +2.24 s after started_at)
result CORRECT flag f8888802…b5c2
Mastery
CORRECT on the first attempt, ranked (230 s clock), but not on the first pass. The first pass stopped in the solver before any submit: the handoff-id lookup pooled the .ps1 files from all 7 sets, so a stem could match a precursor from the wrong set. The helper restricted it to the same inner zip, and the same attempt was resumed (the start request returned the same attempt), so no retry was used and nothing wrong was sent.
started_at 2026-10-23T13:13:29.631Z
deadline 2026-10-23T13:17:19.631Z (230 s)
answers 10,465 (7 sets)
first pass stopped in the solver before submitting; fixed; resumed at 13:13:47
resume +19.40 s after started_at
artifact 581 ms
solve 426 ms
submit 1764 ms
TOTAL 2784 ms for the resumed pass (submit +22.18 s after started_at)
result CORRECT flag 8212188a…3995 (server stamp 13:13:50.529Z)
The ranked time includes the fix. About 19 s of the 22.2 s was the first pass, the fix, and the resume.
Time to solve
All three modes were solved by 09:13:51 ET, 13 min 51 s after release (12 min 33 s from the first start request). The Single Solve and the ranked Mastery went through on the first attempt; the ranked Time Trial crashed and expired, and the Time Trial was then solved on an untimed (unranked) retry at 09:13:19 ET after about 8 minutes of fixing.
Takeaways
- Timed sets can change format from the Single Solve. Same generator, but hex names, a new field set, and a
LYR-header. A Single CORRECT didn't prove the timed shape. - Answer from each file's own plan, not from a name table. Tables keyed on file names break the moment names are randomised; the decoded plan is the same either way.
- Route on the question shape, narrowly. Sending a set to the adapter only when ~90% of IDs are hex stems kept every older day's answers unchanged (0 mismatches over Days 20–23).
- Scope cross-file lookups to the set. Pooling
.ps1files across 7 Mastery sets was a bug only Mastery could show. - A clean stop inside a ranked attempt can still be saved. The solver stopped before submitting, so the same attempt could resume; the cost was ~19 s on the clock, not the ranking.