zwesty
Write-ups / Day 26 / Script
Day 26 · Mon, Oct 26 · Script

Mixed Signals

Prey slaughtered

Script triage across 20+ languages, Erlang escripts, 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-26-beam-message-flow (+ /time_trial, /mastery)
Solved
2026-10-26: all three modes CORRECT on the first submit, by 09:01:34 ET, 20 s after the first start request on this lane (09:01:14 ET) and 1 min 34 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

"An Erlang escript passes messages between processes, and some of its indicators look suspiciously familiar. Trace the message flow, and separate the true cross-language indicators from deterministic collision decoys planted to look like old friends."

Script is cumulative: the Single Solve asked 1,615 questions over one evidence.zip of 171 scripts. Today's new files were four Erlang escripts with 11 questions each (44 in all). For the benign utility:

answer_id Question Format
polyglot-erlang-benign-utility-role "Classify the inert source role." enum (benign_utility, downloader, dropper, impact, launcher, persistence_descriptor, relay, staged_chain, stager, uploader, advanced_syntax)
polyglot-erlang-benign-utility-attack 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-erlang-benign-utility-steps "Recover the ordered behavior graph using > separators." tokens joined by >
polyglot-erlang-benign-utility-host-profile "Identify the script host or source container." exact text
-stage-count, -stage-profile, -next-language Stage count, multi-stage relationship profile, next language in the inert handoff integer / exact text
-adapter, -campaign, -file-path, -obfuscation Modelled adapter, campaign ID, file-path indicator, ordered encoding profile exact text

The other three Erlang files had the same 11 fields, with -url in place of -file-path. The Time Trial asked 1,677 questions in the timed schema (13 per polyglot file: no -adapter or -host-profile, but both -url and -file-path plus -url-host, -path-leaf, and -campaign-token); the Mastery asked them across 10 sets (16,770).

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"An Erlang escript" New language: Erlang scripts (.escript), run on the BEAM VM (the challenge ID's "beam") Four polyglot-erlang-* files. The solver's generic plan reader covers any polyglot-* file, so nothing new was needed
"passes messages between processes … Trace the message flow" Erlang's process messaging as the staging mechanism No question asked for the flow itself; the behaviour graph (steps) is the plan's own list
"some of its indicators look suspiciously familiar" Campaign IDs, URLs, and paths that resemble other languages' Each indicator was read from its own file's plan (ERLANG-… campaigns, erlang-… hosts)
"separate the true cross-language indicators from deterministic collision decoys" A trap for anyone copying values between files The solver never carries a value from one file to another, so decoys can't leak in. No field asked to label a decoy
"language-specific sub-technique" (attack format) T1059.00x where a language has one Erlang has no sub-technique; the plans' T1059 was accepted
"Mixed Signals" (title) Messages (signals) plus misleading indicators 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 Erlang runtime (escript, erl) 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 Erlang 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, 149,814 bytes, 171 members: Day 25's 167 scripts plus polyglot-erlang-{benign-utility,downloader,launcher,staged-chain}.escript (2,306, 1,814, 1,665, and 1,153 bytes). No host-erlang-* fixture today (Day 25 had a Phoenix host).
  • The Erlang plans (accepted; host profile standalone for all four):
File Role / ATT&CK Adapter Steps Obfuscation Stage profile Next
benign-utility benign_utility / none local_parser decode_embedded>write_file>dispatch_process (3) hex_base64 three_stage_decode_write_launch powershell
downloader downloader / T1105 http_client profile_host>decode_configuration>dispatch_language (3) fragmented_hex cross_language_handoff jscript
launcher launcher / T1059 process_builder decode_configuration>return_configuration (2) plain_hex single_descriptor batch
staged-chain staged_chain / T1059 operation_graph acquire_remote>dispatch_process (2) base64 two_stage_download_execute vbscript

Campaigns ERLANG-89cfcb90, ERLANG-ffd88bc0, ERLANG-f282709b, and ERLANG-d542b43e. The benign utility carries a file path, /tmp/huntress-erlang/.89cfcb90346d1031.stage; the others a URL of the form hxxps://erlang-<10 hex>[.]example[.]invalid/stage. - The plans disagree with their own steps, a third day running. The benign utility records none over a decode-write-launch graph; the staged chain records staged_chain/T1059 over a download-and-execute graph. The solver sends each plan's own values, and all were accepted. - Time Trial: evidence.zip, 169,979 bytes, 171 files in the timed schema: 6-hex stems (0640aa.escript) and a HUNTRESS_ANALYSIS_V1:LYR-… header naming the layers in order. The four Erlang files had roles staged_chain, downloader, persistence_descriptor, and dropper (T1059, T1105, T1053, T1140), layer lists b32, b64_rev_b64, b64_rev, and hex_b64_rev, and next languages awk, rust, kotlin, and beanshell. - Mastery: 1,674,533 bytes, 10 sets in the timed schema: 40 Erlang files covering all eight roles (11 persistence_descriptor, 7 uploader, 6 dropper, 5 benign_utility, 4 downloader, 4 stager, 2 launcher, 1 staged_chain).

So the timed variants were the same generator as Days 23 to 25, with four Erlang files per set added.

Single Solve

The solver got it in two batches, with no stop. Its core answered 1,489 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 65 values it had seen rejected on other questions before (none, T1105, T1059.007, delayed, linear, select_case, among them the Erlang benign utility's none and the downloader's T1105) and sent them anyway, as designed; none was rejected. The add-on modules then sent the other 126.

started_at  2026-10-26 09:01:14.239 ET
deadline    none (Single Solve)
answers     1,489 core (accepted, remaining 126), then 126 add-on answers
start        350 ms
artifact     371 ms
solve        431 ms
submit      2752 ms   (core 439, add-ons 2311)
TOTAL       3907 ms
result      CORRECT   flag 5a8e3ace…b838

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 (unique_attempts_remaining: 31, variant_fresh: true). 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 Erlang.

started_at  2026-10-26 09:01:23.472 ET
deadline    2026-10-26 09:02:13.472 ET   (50 s)
answers     1,677   (52 for the four Erlang files)
start        677 ms
artifact     352 ms
solve         75 ms
submit       928 ms
TOTAL       2036 ms   (submit +1.83 s after started_at)
result      CORRECT   flag 51376e5b…2515

Mastery

CORRECT on the first attempt, ranked (10 sets, 320 s clock; Day 25 had 9 sets and 290 s).

started_at  2026-10-26 09:01:31.722 ET
deadline    2026-10-26 09:06:51.722 ET   (320 s)
answers     16,770 across 10 sets   (520 for the 40 Erlang files)
start       1838 ms
artifact     508 ms   (1.7 MB)
solve        898 ms
submit      1742 ms
TOTAL       5012 ms   (submit +3.87 s after started_at)
result      CORRECT   flag a996c772…a82c

Time to solve

All three modes were solved by 09:01:34 ET, 1 min 34 s after release (20 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 (Erlang) needed no code, as Elixir needed none on Day 25: the solver reads the analysis header and checks it against the body, whatever the language.
  • Per-file reading is the defence against decoys. The prompt warned of indicators "planted to look like old friends"; a solver that reads every value from its own file never borrows one, so the warning cost nothing.
  • The plan is the answer, even when it looks wrong. Two of today's four plans have a role or ATT&CK value that doesn't match their steps; the plans' own values were accepted, as on Days 24 and 25.
  • The flagged-values list is a warning, not a veto. 65 values flagged as "rejected elsewhere before" were all accepted here.
  • The prompt promises more than the questions ask. "Trace the message flow" sounded like following Erlang process messages; no field asked for it.