zwesty
Write-ups / Day 31 / Script
Day 31 · Sat, Oct 31 · Script

The Full Transform

Prey slaughtered

Cumulative static script triage (31 languages in one zip); reading an encoded analysis-plan header and checking it against the body; ATT&CK mapping by a stated precedence

STTMClaude Homebase Both

Flags are shortened to their first 8 and last 4 characters because they're unique per team.

Challenge ID
script-analysis-day-31-xslt-final-gauntlet (+ /time_trial, /mastery)
Solved
2026-10-31, the last event day: all three modes CORRECT on the first submit, both timed modes ranked, all by our automated morning solver (a scheduled script on our Kali VM) with no code change. Single CORRECT 09:04:00.9 ET, Time Trial 09:04:08.4, Mastery 09:04:19.3. 21.1 s from the Single start (09:03:58.168) to the Mastery CORRECT; 4 min 19.3 s from the 9 AM release. No answer was rejected
Speed boards
Time Trial 5th of 39 (1.205 s; fastest 0.466 s). Mastery 4th of 38 (3.974 s; fastest 1.576 s). Captured 2026-11-01
Player
Zwesty (solo), with Claude Code (Opus 5.5) tooling: the automated solver solved all three modes on its own with our existing Script solver and plug-ins. Nobody worked this challenge by hand or in a Claude chat; this write-up was put together afterwards from the records

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
-role "Classify the inert source role." enum of 11 (benign_utility, downloader, … advanced_syntax)
-attack "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
-adapter "Recover the modeled language adapter." exact text
-campaign "Recover the exact synthetic campaign identifier." exact text
-url or -file-path "Recover the exact url indicator." / "Recover the exact file_path indicator." exact text
-stage-count "Recover the modeled stage count." integer
-steps "Recover the ordered behavior graph using > separators." tokens joined by >
-host-profile "Identify the script host or source container." exact text
-obfuscation "Recover the ordered encoding profile." exact text
-stage-profile "Recover the multi-stage relationship profile." exact text
-next-language "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 (.xsl) Four new polyglot-xslt-* 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_utility none local_parser profile_host>decode_configuration>dispatch_language 3 fragmented_hex powershell
downloader downloader T1105 http_client decode_configuration>return_configuration 2 plain_hex jscript
launcher launcher T1059 process_builder acquire_remote>dispatch_process 2 base64 batch
staged-chain staged_chain T1059 operation_graph decode_embedded>write_file>dispatch_process 3 hex_base64 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_V1 in 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.