zwesty
Write-ups / Day 8 / Script
Day 8 · Thu, Oct 8 · Script

Lost in Translation

Prey slaughtered

Static triage across nine script languages, decoding a self-describing plan in four encodings, cross-checking a sample's header against its body, ATT&CK mapping, reading control structures, scripted timed runs

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-08-cross-language-iocs (+ /time_trial, /mastery)
Solved
2026-10-10 (UTC; the evening of 2026-10-09 in ET): Single Solve, Time Trial, and Mastery all CORRECT. 29 h 25 min from the first start (2026-10-08 20:16 UTC) to the Mastery CORRECT (2026-10-10 01:41 UTC). Most of that was the last four control labels
Player
Zwesty (solo), with Claude Code (Opus 5.5) and the Homebase agent (GPT-6 Luna)

The challenge

"The same indicators show up in every language the campaign speaks, each with its own quoting, escaping, and encoding accent. Normalize them to their exact form and find the one that doesn't match its siblings."

615 questions about one AES-encrypted evidence.zip (password infected). The first 483 are Days 1–7's question set with new values. The other 132 are new: 12 files with 11 questions each.

Group Files Questions Format
PowerShell (Day 1's samples) resource- / collection- / impact-sample.ps1 15: scenario, ATT&CK, three indicators each enum, T####[.###], exact text
JScript 9 .js 72: role, ATT&CK, adapter, campaign, one indicator, three style questions enum, exact text
Batch 9 .bat / .cmd 72: role, ATT&CK, adapter, campaign, one indicator, expansion, control, graph enum / exact text
VBScript 9 .vbs 72: role, ATT&CK, adapter, campaign, one indicator, representation, transform, control exact text
Python 14 126: the Day 5 set (syntax, obfuscation, carrier, chain, and the usual five) exact text
Bash 6 regular + 4 advanced 70: role, ATT&CK, campaign, stage count, steps; the advanced ones ask feature and primitive instead of adapter and indicator exact text, integer, ordered tokens
Node 8 56: the Day 7 set exact text, integer, ordered tokens
New: PHP polyglots 8 polyglot-php-*.php, one per role 88 (11 each, below) see below
New: host fixtures host-wsf-jscript-jscript.js, host-wsf-mixed-vbscript.vbs, host-hta-jscript-javascript.js, host-php-webshell-php.php 44 (11 each) see below

The 11 questions on each new file:

answer_id suffix Question Format
-role Classify the inert source role enum of 11 (benign_utility … advanced_syntax)
-attack Primary ATT&CK technique ID, or none T####[.###] or none
-adapter, -campaign Modeled language adapter; synthetic campaign ID exact text
-url or -file-path The file's indicator exact text
-stage-count Modeled stage count integer
-steps Ordered behavior graph tokens joined by >
-host-profile Script host or source container exact text
-obfuscation "Ordered encoding profile" exact text
-stage-profile Multi-stage relationship profile exact text
-next-language Next script language in the hand-off exact text

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"The same indicators show up in every language the campaign speaks" Every earlier Script language comes back (PowerShell, JScript, Batch, VBScript, Python, Bash, Node), plus PHP Ran the whole Day 1–7 solver chain first: 384 of the 483 older answers on the first submit
"each with its own quoting, escaping, and encoding accent" Per-language decoders, and new spellings of old tricks Fixed four small variants (Node & 255, Node percent_hex and unicode_escapes loaders, Bash reversed_base64); found the new files' header plan in four encodings
"Normalize them to their exact form" exact_text answers with only trim normalisation Took values straight from the decoded plan; turned the plan's comma list into the > form steps asks for
"find the one that doesn't match its siblings" A file that disagrees with itself No separate question asked this. Our header check still compares the header plan with every plan it can decode in the body, and refuses a mismatch. None disagreed
"host-profile", "stage-profile", "next-language" in the questions Fields of the new files' plan Read from the plan, not guessed from the code

Safety

Static only. No script was run. Files were read straight out of the AES zip into memory and parsed as text. The artifacts stayed on our Kali VM and on the Homebase. Indicators below are defanged.

Claude's safety filter stopped one step of the analysis, and we didn't work around it; the Homebase agent took that part on.

What was in the files

  • The older 62 files use the same templates as Days 1–7 with new values. A few loaders changed spelling: Node used & 255 where it had used % 256, two new Node loaders (percent_hex, unicode_escapes), and the Bash advanced fixtures used reversed_base64.
  • The 12 new files each carry a plan in a header comment, HUNTRESS_ANALYSIS_V1:<ENC>-<payload>, with ENC one of HEX, B64, HB64 (hex of Base64), or FRAG (dot-separated hex). It decodes to v=3|key=value|…, and the body holds the same plan again. The plan names every answer. For polyglot-php-stager.php it held:
Field Value
role / attack stager / T1105
adapter / campaign runtime_loader / PHP-3a875682
url hxxps://php-3a87568251[.]example[.]invalid/stage
stage count / steps 2 / acquire_remote>dispatch_process
host / obfuscation / stage profile standalone / base64 / two_stage_download_execute
next language jscript
  • Profiles seen: host standalone for all eight polyglots, wsf_mixed and php_webshell for the two non-JScript host files. Obfuscation plain_hex, base64, hex_base64, fragmented_hex. Stage profiles single_descriptor, two_stage_download_execute, three_stage_decode_write_launch, cross_language_handoff. The next-language answers ran across powershell, jscript, batch, vbscript, python, bash, javascript, and php.
  • Time Trial and Mastery used the same templates with new values. Mastery took 3,075 answers in one submit, five times the Single Solve's 615. No new encoding turned up in either.

Single Solve

Claude, from our Kali VM (2026-10-08, 20:16–20:18 UTC): 494 of 615, no wrong submits

  1. Older parts: 384 accepted in six language batches (Batch 54, VBScript 63, PowerShell 15, Python 126, Bash 70, Node 56), once the four loader variants were in. The Bash advanced feature / primitive pairs (for example indirect_expansion / parameter_indirection) came from Day 6's Bash solver.
  2. New files: 110 accepted (10 non-JScript files × 11): 90 plain fields, then 10 roles, then ATT&CK in three batches. The header's own ATT&CK was right, including the parent T1059 for the PHP launcher, the staged chain, and both non-JScript hosts, and T1053 for the PHP persistence descriptor.

The JScript part: the Homebase agent (2026-10-08, 20:36–20:38 UTC): 592 of 615

The Homebase agent handled the JScript files throughout this event. Its JScript add-on decoded the plans statically: 72 older JScript answers plus 22 for the two JScript host files, read from their header plans. Its Batch-wording add-on added 4 Batch control answers. All accepted, no wrong submits. 23 left, all Batch/VBScript wording.

Batch and VBScript wording (2026-10-09): 23 down to 4

These answers name a construct, not a value, so the right word had to be found by test: one answer per submit, and a rejected value never resent.

Time (UTC) Who Result on Day 8
14:09–15:50 Homebase 9 accepted: VBScript do_once ×4, while_once, for_once ×2; Batch call_subroutine ×2 (launcher, impact: for /l %%Q in (1,1,1) do call :HUNTRESS_RECOVER). 3 rejected: for_l_call and for_subroutine on the launcher before call_subroutine landed, findstr_pipe on the stager
19:25 Homebase 4 rejected: call_set_rebuild, if_defined_for_call, error_number_rethrow, straight_line_decode
20:19–20:25 Homebase Coverage reconciliation from confirmed rules: 8 accepted (Batch expansion answers, goto_dispatch on the stager). 1 rejected: delayed on batch-impact-expansion, a rule that turned out to be too broad
20:48 Homebase call_double (impact) and delayed (relay): both accepted, 611/615

The Batch expansion rule that came out of those last three tests: a set "x=!a!…" rebuild is delayed. FOR/CALL and direct call set rebuilds follow the fragment variable family: h0–h3 gives call_double, while fragment*, item*, and v_a* give delayed (or for_indirect for the FOR form). Day 8's impact script, with call set "cfg=%%h0%%%%h1%%%%h2%%%%h3%%", was the one that showed the split.

The last four control labels (Zwesty and Claude, from our Kali VM, 2026-10-10 00:35–01:37 UTC)

Late on 2026-10-09 (ET), Zwesty and Claude stopped guessing and worked back from the accepted answers. Batch: the "control slot" sits between :HUNTRESS_SEPARATOR_DONE and echo(, and it holds one of five things. VBScript: the label names the innermost block around the Decode_* call. Separator lines, the & rem HUNTRESS quote compound marker, neutral_* variables, and comments are decoys and never decide the label.

Slot contents Label
Batch call :HUNTRESS_RECOVER block call_subroutine
Batch ver >nul + if errorlevel 1 exit /b 1 errorlevel_guard
Batch goto :HUNTRESS_DISPATCH + its label goto_dispatch
Batch set "HUNTRESS_READY=1" + if defined HUNTRESS_READY … if_guard
VBScript Select Case / Do … Exit Do / While … Wend / For … To 0 select_case / do_once / while_once / for_once
VBScript On Error Resume Next around the decode error_guard
Nothing (Batch or VBScript) direct

Day 8's four open questions, each sent alone:

Question What was in the slot Answer How it was found
vbscript-stager-control On Error Resume Next / recoveredMaterial = Decode_58fefe6c(storedMaterial) / On Error GoTo 0 / If savedError <> 0 Then Err.Raise … error_guard ✅ Zwesty's idea, mirroring errorlevel_guard (first accepted on Day 5), then rolled out
batch-downloader-control Nothing. A & rem HUNTRESS quote compound decoy, then call set "cfg=%%v_a0%%%%v_a1%%%%v_a2%%%%v_a3%%" direct ✅ Splitting the open scripts by what was really in the slot, then borrowing the generator's own "plain" word from the JScript style field
vbscript-uploader-control Nothing. v_b27e = Decode_0573304b(v_a19c), then a byte-copy For … To UBound loop that doesn't enclose the decode direct ✅ Same rollout
batch-uploader-control set "HUNTRESS_READY=1", an if defined HUNTRESS_READY line that clears cfg, then for %%G in (0 1 2 3) do call set "cfg=%%cfg%%%%h%%G%%" if_guard ✅ CORRECT Every accepted label is a keyword in the code plus one of five second words (subroutine / guard / dispatch / case / once). After 68 rejected names across the 13 readiness questions, the grid's first new pair (on Day 3) was accepted

Single Solve CORRECT at 01:37:29 UTC on 2026-10-10 (615/615). Flag 8771c177…cedc. Support was never needed.

Wrong submits on Day 8: 8, all single-answer tests (the 3 morning ones, the 4 at 19:25 and the one too-broad delayed). None came from Claude's own solver.

Solvers: this day's solver became the core of the Script chain. It wraps Node by Node's solver (and through it Days 4–7 and Power Play) for the older questions, and adds the new files through a header decoder. Day 9 was solved with it, and our automated morning solver (a scheduled script on our Kali VM) uses it. The header decoder handles the four header encodings, collects every plan it can decode in the body, and flags a disagreement. The solver also checks each new-file enum answer against the question's accepted list. Its 3 tests cover the four encodings, the header-versus-body check, and "reproduces the 494 accepted answers and nothing else, with no JScript". The Homebase agent's JScript, Batch-wording, and VBScript-control add-ons run alongside it.

Time Trial

Run from our Kali VM by our solver. Timings are client side.

started_at  2026-10-10T01:41:19.308Z   (deadline not recorded in the request log)
coverage    615/615 (solver chain + JScript, Batch-wording and VBScript-control add-ons)
submit      615 answers, 01:41:21 UTC
TOTAL       2.55 s (client side)
result      CORRECT   flag 3e5e3756…c527

Correct on the first run.

Mastery

started_at  2026-10-10T01:41:25.608Z   (deadline not recorded in the request log)
submit      3,075 answers, 01:41:29 UTC
TOTAL       5.43 s (client side)
result      CORRECT   flag a51740fc…ce74

Correct on the first run. On other days, some timed runs were stopped before submitting (unknown readiness wrapper on Days 4–5, a new Bash hex_dash on Day 6). Day 8's ran after both fixes and went through cleanly.

Time to solve

The Single Solve started on 2026-10-08 at 20:16 UTC and went CORRECT on 2026-10-10 at 01:37:29 UTC, 29 h 21 min later; 494 answers were in within two minutes, and nearly all the rest of the time went on the Batch and VBScript wording. The Time Trial (2.55 s) and Mastery (5.43 s) then each went CORRECT on the first run, for 29 h 25 min from the first start to the last CORRECT.

Takeaways

  • Compounding pays off. Seven days of solvers answered 384 of the 483 older questions on the first submit. This day's solver became the chain that Day 9 and our morning solver used.
  • Trust the sample's own plan, but check it against itself. The header plan named every answer, including ATT&CK parents (T1059) that a guess would have refined. Comparing the header with the body costs nothing and catches a planted mismatch.
  • Decoders must read the code, not the template. Each day brought a respelled loader (& 255, percent_hex, reversed_base64). A check against the script's own decode line found them.
  • Name the construct, not the decoration. The control labels were all "keyword + one of five words". Once the decoys were set aside and the open scripts were split by what was really in the slot, three rules closed every open label on every day.
  • One answer per test. The 8 wrong submits here were all deliberate single tests, so every rejection taught something and none was repeated.