zwesty
Write-ups / Day 25 / Script
Day 25 · Sun, Oct 25 · Script

Ashes to Assets

Prey slaughtered

Script triage across 20+ languages, Elixir/Phoenix fixtures, analysis-descriptor plans, layered encodings (LYR-), ATT&CK mapping

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-25-phoenix-stage-chain (+ /time_trial, /mastery)
Solved
2026-10-25: all three modes CORRECT on the first submit, by 09:02:01 ET, 18 s after the first start request on this lane (09:01:43 ET) and 2 min 1 s after the 9 AM release. Both timed modes were ranked runs
Player
Zwesty (solo), with Claude Code (Opus 5.5): our automated morning solver (a scheduled script on our Kali VM) did all three modes, with no hands-on work needed

The challenge

"Configuration starts out in Elixir source and has changed shape by the time an inert Phoenix-hosted stage picks it up. Follow every transformation, reconcile each one with earlier campaign artifacts, and find the setting that didn't survive the rebirth."

Script is cumulative: the Single Solve asked 1,571 questions over one evidence.zip of 167 scripts. Today's new files were five Elixir scripts with 11 questions each (55 in all). For the Phoenix host:

answer_id Question Format
host-elixir-phoenix-elixir-role "Classify the inert source role." enum (benign_utility, downloader, dropper, impact, launcher, persistence_descriptor, relay, staged_chain, stager, …)
host-elixir-phoenix-elixir-attack Map the decoded ordered behaviour graph to one primary ATT&CK technique (with a stated precedence) ATT&CK ID
host-elixir-phoenix-elixir-steps "Recover the ordered behavior graph using > separators." tokens joined by >
host-elixir-phoenix-elixir-host-profile "Identify the script host or webshell source container." exact text
host-elixir-phoenix-elixir-stage-count, -stage-profile, -next-language Stage count, multi-stage relationship profile, next language in the inert handoff integer / exact text
host-elixir-phoenix-elixir-adapter, -campaign, -url, -obfuscation Modelled adapter, campaign ID, URL indicator, ordered encoding profile exact text

The four polyglots had the same 11 fields, with -file-path in place of -url for the benign utility. The Time Trial asked 1,625 questions in the timed schema (13 per polyglot file); the Mastery asked them across 9 sets (14,625).

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"Configuration starts out in Elixir source" New language: Elixir scripts (.exs) Four polyglot-elixir-* files and one host-elixir-phoenix-elixir.exs. The solver's generic plan reader covers any polyglot-* or host-* file, so nothing new was needed
"an inert Phoenix-hosted stage" A host fixture for the Phoenix web framework Its plan names host_profile = elixir_phoenix (Day 24's JSP fixture was jsp_webshell)
"has changed shape … Follow every transformation" Layered encodings obfuscation = hex_base64 for the host; the timed sets list their layers in the LYR- header (b32_hex_b64, hex_hex_hex, …)
"reconcile each one with earlier campaign artifacts" Campaign IDs and indicators across files campaign and url/file-path fields, read from each file's own plan
"find the setting that didn't survive the rebirth" A config key lost between stages No question asked for it by name; the fields were the usual plan fields
"Ashes to Assets" (title) Phoenix (rebirth from ashes), and config turned into staged assets Confirmed the reading

Safety

Static analysis only. Nothing was executed. The scripts sit in a password-protected (WinZip AES) archive and were read as text and decoded in Python; no Elixir, Erlang VM, or other interpreter was started. For this write-up only the archive's member list, the question list, and the solver's record of accepted answers were read; the Elixir 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, 147,501 bytes, 167 members: Day 24's 162 scripts plus polyglot-elixir-{benign-utility,downloader,launcher,staged-chain}.exs (1,153 to 2,298 bytes) and host-elixir-phoenix-elixir.exs (2,461 bytes).
  • The Phoenix host's plan (accepted values): role staged_chain, ATT&CK T1059, adapter operation_graph, 3 stages, steps decode_embedded>write_file>dispatch_process, host profile elixir_phoenix, obfuscation hex_base64, stage profile three_stage_decode_write_launch, next language jscript, campaign ELIXIR-9d74742f, URL hxxps://elixir-9d74742f8e[.]example[.]invalid/stage.
  • The polyglots' plans (accepted):
File Role / ATT&CK Steps Obfuscation Stage profile Next
benign-utility benign_utility / none acquire_remote>dispatch_process base64 two_stage_download_execute powershell
downloader downloader / T1105 decode_embedded>write_file>dispatch_process hex_base64 three_stage_decode_write_launch jscript
launcher launcher / T1059 profile_host>decode_configuration>dispatch_language fragmented_hex cross_language_handoff batch
staged-chain staged_chain / T1059 decode_configuration>return_configuration plain_hex single_descriptor vbscript
  • The plans disagree with their own steps, as on Day 24. The Phoenix host has the downloader's exact decode-write-launch graph but records staged_chain/T1059, and the benign utility carries a download-and-execute graph with none. The solver sends each plan's own values, and all were accepted.
  • Time Trial: evidence.zip, 164,224 bytes, 167 files in the timed schema: 6-hex stems (a7470f.exs), host files folded into the polyglot family, and a HUNTRESS_ANALYSIS_V1:LYR-… header naming the layers in order. The five Elixir files had roles launcher, uploader, downloader, staged_chain, and persistence_descriptor, with layer lists b32_hex_b64, b32_b64_b64, hex_hex_hex, b64_frag, and b64_b64_hex.
  • Mastery: 1,478,126 bytes, 9 sets in the timed schema.

So the timed variants were the same generator as Days 23 and 24, with five Elixir files per set added.

Single Solve

The solver got it in two batches, with no stop. Its core answered 1,443 questions, all 55 new ones included: it finds each polyglot-* or host-* file, parses the analysis header, checks that the header agrees with the body, and returns the plan field the question names. It flagged 60 values it had seen rejected on other questions before (none, T1105, T1059.007, delayed, linear, select_case, among them the Elixir polyglots' none and T1105) and sent them anyway, as designed; none was rejected. The add-on modules then sent the other 128.

started_at  2026-10-25T13:01:43.644Z
deadline    none (Single Solve)
answers     1,443 core (accepted, remaining 128), then 128 add-on answers
start        488 ms
artifact     264 ms
solve        353 ms
submit      2253 ms   (core 411, add-ons 1840)
TOTAL       3361 ms
result      CORRECT   flag 4fb97f82…6722

The flag is from the solver's saved submit reply; our request log's copy of the reply is cut before it.

Time Trial

CORRECT on the first attempt, ranked. The question IDs were hex stems, so the solver routed the set to its timed-schema adapter (built on Day 23), which answers each file from its own decoded plan. No change was needed for Elixir.

started_at  2026-10-25T13:01:52.539Z
deadline    2026-10-25T13:02:42.539Z   (50 s)
answers     1,625   (65 for the five Elixir files)
start        705 ms
artifact     295 ms
solve         53 ms
submit       839 ms
TOTAL       1893 ms   (submit +1.44 s after started_at)
result      CORRECT   flag 66f30ba4…197f

Mastery

CORRECT on the first attempt, ranked (9 sets, 290 s clock; Day 24 had 8 sets and 260 s).

started_at  2026-10-25T13:01:59.902Z
deadline    2026-10-25T13:06:49.902Z   (290 s)
answers     14,625 across 9 sets
start       1278 ms
artifact     489 ms   (1.5 MB)
solve        557 ms
submit      2070 ms
TOTAL       4413 ms   (submit +3.72 s after started_at)
result      CORRECT   flag 6062bbde…1286

Time to solve

All three modes were solved by 09:02:01 ET, 2 min 1 s after release (18 s from the first start request on this lane). The Time Trial and Mastery were both ranked and each went through on the first submit.

Takeaways

  • A generic plan reader keeps paying. A new language (Elixir) and a new host profile (elixir_phoenix) needed no code: the solver reads the analysis header and checks it against the body, whatever the language.
  • The plan is the answer, even when it looks wrong. Two of today's five plans had a role or ATT&CK value that doesn't match their steps; the plan's own values were accepted, as on Day 24.
  • The flagged-values list is a warning, not a veto. 60 values flagged as "rejected elsewhere before" were all accepted here.
  • The prompt promises more than the questions ask. "The setting that didn't survive the rebirth" sounded like a config diff between the Elixir source and the Phoenix stage; no field asked for it.