The challenge
"An unseen XSLT-led campaign ties together all thirty-one languages, every hosted-source profile, and every finding family you've proven. Read all of it, run none of it, and deliver the final analysis."
The Script finale. Like every Script day, one evidence.zip holds every day's members and every earlier question is asked again. The Single asked 1,846 questions. Day 30's Single had 1,802, all still present, so Day 31 added 44: 11 questions on each of four new XSLT members (no new hosted member):
- polyglot-xslt-benign-utility.xsl, polyglot-xslt-downloader.xsl, polyglot-xslt-launcher.xsl, polyglot-xslt-staged-chain.xsl
The 11 questions per member are the same as on every polyglot-* member (answer id = member stem + suffix):
| Suffix | Question | Format |
|---|---|---|
- |
"Classify the inert source role." | enum of 11 (benign_, downloader, … advanced_) |
- |
"Map the decoded ordered behavior graph to one primary MITRE ATT&CK technique using this precedence: …" (a long rule list; the parent technique is also accepted when a language sub-technique is canonical) | pattern: a technique ID, or none for the benign graph |
- |
"Recover the modeled language adapter." | exact text |
- |
"Recover the exact synthetic campaign identifier." | exact text |
- or - |
"Recover the exact url indicator." / "Recover the exact file_path indicator." | exact text |
- |
"Recover the modeled stage count." | integer |
- |
"Recover the ordered behavior graph using > separators." | tokens joined by > |
- |
"Identify the script host or source container." | exact text |
- |
"Recover the ordered encoding profile." | exact text |
- |
"Recover the multi-stage relationship profile." | exact text |
- |
"Recover the next script language in the inert handoff." | exact text |
The Time Trial asked 1,950 questions on its own zip (52 of them on XSLT members). The Mastery asked 1,950 per set over 15 sets (29,250 answers).
Reading the prompt: what it said we'd need
Nobody read the prompt on the day; the solver answered from the questions. This table is written afterwards.
| Clue | What it pointed to | How the solver met it |
|---|---|---|
| "An unseen XSLT-led campaign" | A new language: XSLT stylesheets (.) |
Four new polyglot- members, read like every polyglot- member since Day 8: from their analysis-plan header |
| "all thirty-one languages" | Cumulative: every earlier language is still asked | The 1,802 older questions were unchanged; the solver and plug-ins already covered them |
| "every hosted-source profile" | The host- members from earlier days (JSP, WSF, HTA, SCT, and others) |
Already covered; no new hosted member today |
| "every finding family you've proven" | The older per-language fields (role, ATT&CK, indicators, style questions) | Already covered by the solver and plug-ins |
| "Read all of it, run none of it" | Static analysis only | The solver only reads text; nothing is run |
| "The Full Transform" (title) | XSLT is Extensible Stylesheet Language Transformations; "full" for the final gauntlet | Confirmed the language |
Safety
Static analysis only. Nothing was executed. The solver reads each member as text: it decodes the HUNTRESS_ANALYSIS_V1 plan header and compares it with any plan found in the body. The header reader only interprets bounded literal expressions and never evaluates challenge code. No stylesheet was applied to anything. For this write-up only the solver's report, the saved Game Master replies, and the solver source were read; the evidence zips were not opened. The URL and file-path answers are synthetic (example[.]invalid hosts, /tmp/huntress-… paths) and are left out here.
What was in the files
Described from the question list and the accepted answers.
Single Solve: evidence.zip, 164,565 bytes, 1,846 questions. The four XSLT members' accepted values (all host-profile standalone):
| Member | role | attack | adapter | steps | stages | obfuscation | next-language |
|---|---|---|---|---|---|---|---|
| benign-utility | benign_ |
none |
local_ |
profile_ |
3 | fragmented_ |
powershell |
| downloader | downloader |
T1105 |
http_ |
decode_ |
2 | plain_ |
jscript |
| launcher | launcher |
T1059 |
process_ |
acquire_ |
2 | base64 |
batch |
| staged-chain | staged_ |
T1059 |
operation_ |
decode_ |
3 | hex_ |
vbscript |
Campaigns are XSLT- plus 8 hex digits. The same step lists, encodings, and stage profiles as Day 30's BeanShell members, shuffled across different roles: Day 30's launcher steps are today's downloader steps, for example. The accepted role and ATT&CK answers follow each member's header, not its step list.
- Time Trial: a fresh
evidence.zip, 190,202 bytes, 1,950 questions with a different family mix, 52 of them on XSLT members. - Mastery: one zip of 2,818,633 bytes, 15 sets of 1,950 questions, 52 XSLT questions per set.
Single Solve: the automated solver
The solver ran our Script solver's core first, as one batch, then one batch from the plug-ins.
What the core does. It is our Day 8 solver, extended through Day 23. Every polyglot-* and host-* member is answered from its HUNTRESS_ANALYSIS_V1 plan: the header is decoded (HEX, B64, HB64, FRAG, Day 18's split form, Day 19's literal expressions, Day 23's LYR- stacks) into v=3|key=value|…, and the 11 answer suffixes map to plan keys. If any plan found in the body disagrees with the header, it stops instead of answering. The older languages go through our Day 7 solver. Plug-ins run after the core and may only fill ids it left open: here the JScript files, the WSF/HTA/SCT hosts, and some Batch and VBScript wording answers.
All 44 XSLT answers came from the core batch; none needed a plug-in.
Single Solve
started_at 09:03:58.168 ET deadline none
answers 1,717 core (accepted, 129 remaining), then 129 plug-in answers
start 390 ms
artifact 245 ms (164,565 bytes)
solve 725 ms
submit 2111 ms (core batch 395 ms, plug-in batch 1714 ms)
TOTAL 3475 ms
result CORRECT flag c89983ee…52b5 (09:04:00.9 ET)
Time Trial
CORRECT on the first attempt, ranked, on a fresh variant.
Time Trial (ranked)
started_at 09:04:07.148 ET deadline 09:04:57.148 (50 s clock)
answers 1,950
start 625 ms
artifact 482 ms (190,202 bytes)
solve 66 ms
submit 1543 ms
TOTAL 2718 ms (submit +2,291 ms after started_at)
result CORRECT flag f0177344…6050 (09:04:08.4 ET; 1.205 s on the server's stamps)
Mastery
CORRECT on the first attempt, ranked.
Mastery (ranked)
started_at 09:04:15.321 ET deadline 09:14:25.321 (10 min 10 s clock)
answers 29,250 (15 sets × 1,950)
start 1415 ms
artifact 725 ms (2,818,633 bytes)
solve 1374 ms
submit 2584 ms
TOTAL 6133 ms (submit +5,542 ms after started_at)
result CORRECT flag 9060e5df…a192 (09:04:19.3 ET; 3.974 s on the server's stamps)
Why no change was needed
- The core keys on the plan header's marker, not on the language. It finds
HUNTRESS_ANALYSIS_V1in the text whatever the syntax (XML here), so a new language whose header uses a known encoding is read like every language before it. - The "gauntlet" was the same 11 fields. The 44 new ids were the usual suffixes; the diff against Day 30 was 44 new ids and 0 removed. "Every finding family" meant the families already asked, not new ones.
- The header and body agreed on every XSLT member, so the fail-closed check didn't stop anything.
- The older 1,802 were already solved by the same core and plug-ins on Day 30.
Time to solve
All three modes were solved by 09:04:19.3 ET, 4 min 19.3 s after release and 21.1 s after the Single started. Each mode went through on its first submit, and both timed modes were ranked.
Takeaways
- The Script lane ended the way it ran from Day 8: one header reader, a body cross-check, plug-ins for the gaps. XSLT was language 31 and cost no code.
- "Final gauntlet" meant volume, not novelty. 1,846 questions in the Single and 29,250 in the Mastery, but only 44 new ids, all of a known shape.
- Per-member values are shuffled, not derived. The same step lists moved between roles from Day 30 to Day 31, and the accepted role and ATT&CK followed the header each time. A solver that inferred role from steps alone would have been at risk here.
- Last in run order, still ranked: 5th and 4th on the boards.