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- |
"Classify the inert source role." | enum (benign_, downloader, dropper, impact, launcher, persistence_, relay, staged_, stager, uploader, advanced_) |
polyglot- |
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- |
"Recover the ordered behavior graph using > separators." | tokens joined by > |
polyglot- |
"Identify the script host or source container." | exact text |
-, -, - |
Stage count, multi-stage relationship profile, next language in the inert handoff | integer / exact text |
-, -, -, - |
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 (.), run on the BEAM VM (the challenge ID's "beam") |
Four polyglot- 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. 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 pluspolyglot-erlang-{benign-utility,downloader,launcher,staged-chain}.escript(2,306, 1,814, 1,665, and 1,153 bytes). Nohost-erlang-*fixture today (Day 25 had a Phoenix host). - The Erlang plans (accepted; host profile
standalonefor all four):
| File | Role / ATT&CK | Adapter | Steps | Obfuscation | Stage profile | Next |
|---|---|---|---|---|---|---|
benign- |
benign_ / none |
local_ |
decode_ (3) |
hex_ |
three_ |
powershell |
downloader |
downloader / T1105 |
http_ |
profile_ (3) |
fragmented_ |
cross_ |
jscript |
launcher |
launcher / T1059 |
process_ |
decode_ (2) |
plain_ |
single_ |
batch |
staged- |
staged_ / T1059 |
operation_ |
acquire_ (2) |
base64 |
two_ |
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.