The challenge
"These VBScript specimens hide their material behind character codes, reversible transforms, and winding control flow. Unwind every representation on paper, classify each behavior, and find out which stage passes the baton to which."
231 questions about one AES-encrypted evidence.zip (password infected, 30 scripts). Days 2 and 3 came back whole, and VBScript was new: nine .vbs files with the same stems as the JScript and Batch ones (host-context, memory-stage, embedded-write, remote-write, outbound-transfer, local-dispatch, impact-plan, staged-handoff, relay-handoff).
| Group | Files | Questions | Format |
|---|---|---|---|
| PowerShell (Power Play's samples) | 3 . |
15: scenario, ATT&CK, three indicators each | enum, T####[., exact text |
| JScript (Host With the Most's) | 9 . |
72: role, ATT&CK, adapter, campaign, indicator, three style questions | as Day 2 |
| Batch (Expansion Pack's format, new values) | 9 . / . |
72: role, ATT&CK, adapter, campaign, indicator, expansion, control, graph |
role enum; the rest exact text |
| VBScript (new) | 9 . |
72: role, ATT&CK, adapter ("WSH, COM, or LOLBin"), campaign, indicator, representation, transform, control |
role enum; the rest exact text, no vocabulary given |
The three new VBScript questions were "Identify the exact VBScript material representation", "Identify the exact reversible byte transform", and "Identify the exact VBScript control-flow form". None of them lists its allowed values.
Reading the prompt: what it said we'd need
| Clue | What it pointed to | What I did about it |
|---|---|---|
| "VBScript specimens" | A new language on top of Days 1–3 | A new VBScript decoder; everything older reused from the solver chain |
| "hide their material behind character codes" | The representation question: how the encoded bytes are written |
Read from each script's Codec_ line (decimal_, hex_ …), checked against its Load code |
| "reversible transforms" | The transform question, and the key needed to undo it |
Same Codec_ line; the decode checked against the script's own Decode code |
| "winding control flow" | The control question |
The block around the Decode_ call. This was the hard part: three days of wording tests |
| "Unwind every representation on paper" | Decode statically, never run | Every script read as text from the zip in memory |
| "classify each behavior" | Role and ATT&CK per script | The decoded plan's steps give the role; Day 3's role → ATT&CK pairs held |
| "which stage passes the baton to which" | The two hand-off scripts | staged- → resource-, relay- → local-; ATT&CK is the hand-off script's own interpreter |
Safety
Static only. No script was run: the zip was read into memory and each file parsed as text. The VBScript fixtures are inert anyway (nothing calls their functions, and there is no WScript or CreateObject use), but the rule held. The artifacts stayed on our Kali VM and on the Homebase. Indicators point at the reserved .invalid domain and are defanged here.
What was in the files
- The PowerShell and JScript files are Power Play's and Host With the Most's formats with new values.
- The Batch files are Expansion Pack's format with new values, randomised adapters (
certutil.exefor the dropper,regsvr32.exefor the chain,rundll32.exefor the relay,cmd.exefor the launcher), and varying fragment names (fragment0,v_a0,h0,FrAg0). Some lines are unquoted with^|escapes, and some end in& rem HUNTRESS quote compound. Each one'sHUNTRESS-MATERIALheader decodes to exactly the fragment plan (checked with our header decoder). - Each VBScript file holds
Const Material_<id>(the plan, encoded),Const Codec_<id>=<representation>|<transform>|<key hex>, andLoad_,Decode_,Recover_, andInspect_functions. The decoded plan is the samekey=value|…format as Batch's (steps,adapter,stage_count,campaign_id, indicators).
| Script | Role | Representation / transform | Around Decode_* in Recover_* |
|---|---|---|---|
host- |
benign_utility | decimal_ / subtract_ |
Select Case 1 (inside an If True Then) |
memory- |
stager | hex_ / repeating_ |
nothing (an If (2 > 1) one-liner sits before it) |
embedded- |
dropper | hex_ / subtract_ |
Select Case 1 |
remote- |
downloader | hex_ / reverse_ |
Do … Exit Do … Loop |
outbound- |
uploader | decimal_ / repeating_ |
Do … Exit Do … Loop |
local- |
launcher | decimal_ / rolling_ |
nothing |
impact- |
impact | reversed_ / reverse_ |
While p < 1 … Wend |
staged- |
staged_chain | hex_ / single_ |
Do … Exit Do … Loop (case-mangled names like SoUrCeByTeS) |
relay- |
staged_chain (relay) | hex_ / rolling_ |
Select Case 1 |
A short excerpt of the downloader's recover step (inert, flattened to one line):
Do; item2 = Decode_1190821c(item1); Exit Do; Loop; For item5 = 0 To UBound(item2); Next
The For … Next after it only turns the decoded bytes into text, so it never decides the label.
- Time Trial and Mastery used the same template with new values, but drew their control forms from the whole Script lane. The Time Trial's VBScript included the
On Error Resume Nextform (first seen on Day 5), and the first Mastery's Batch scripts included theif defined HUNTRESS_READYwrapper (first seen on Day 3).
Single Solve
Day 1 of the attempt (2026-10-07): 139 of 231, from the solvers
- Claude, 15:59–16:04 UTC, from our Kali VM. Certain answers first, in batches, then judgement answers one at a time:
- PowerShell 15 (Power Play's solver), Batch plan fields 45, Batch ATT&CK 7 (Day 3's role → ID pairs), Batch
expansion=delayed×4, VBScript plan fields 36, and all 18 VBScriptrepresentationandtransformanswers straight from theCodec_lines. All accepted, nothing rejected: 125 of 231 after six submits. - VBScript ATT&CK: the six roles with a Batch-confirmed ID went together (none,T1105,T1140,T1105,T1041,T1486): accepted. The hand-offs alone: launcherT1059.005accepted; chainT1059.001(the next stage's PowerShell) rejected, thenT1059.005accepted; relayT1059.005accepted. Hand-off scripts take their own interpreter's ID. - VBScriptcontrol:select_caseaccepted on all threeSelect Case 1scripts (theIf Truewrapper doesn't change it). Then two spellings per form, all rejected:do_loop,do_exit(Do … Exit Do),while_wend,while_loop(While … Wend),straight_line,linear(nothing). Claude stopped there. - The VBScript hand-off lesson unlocked Day 3's two open Batch hand-offs. By the same logic they'd be the Batch interpreter's
T1059.003. Both went together at 16:25: accepted (139). - JScript: the Homebase agent (17:58 UTC). Claude's safety filter had stopped one step of the JScript analysis on Day 2, and we didn't work around it, so Claude didn't attempt them here. The Homebase agent's JScript analyser decoded the plans statically and answered all 72 in four batches: 211 of 231.
Left: 6 VBScript control and 14 Batch (5 expansion, 9 control). None of these was blocked by the safety filter. The answers are free text with no list, so the only way in was tests.
The wording tests (2026-10-08 to 2026-10-10)
Rules for every test: one answer per submit, and a rejected value never resent for that question.
VBScript, the _once labels (found on this day). The Homebase agent kept testing, and on its 2026-10-09 run it found the pattern:
| Script (form) | Rejected first | Accepted |
|---|---|---|
remote- (Do … Exit Do) |
7: do_, do_, do_, exit_, single_, early_, single_ |
do_ (then on outbound- and staged- too) |
impact- (While … Wend) |
6: while_, while_, while_, while_, bounded_, while |
while_ |
The label names what the block does (it runs once), not just its keywords. The same family later gave for_once for the For … Next wrapper on Days 6–7.
Batch expansion. The rebuild line alone didn't predict the label. Two scripts with identical for %%G in (0 1 2 3) do call set … lines got different answers: for_indirect was accepted for host-context.cmd and rejected for the dropper (Claude, 2026-10-08). The difference was the fragment variable family. The dropper uses h0–h3: delayed rejected, call_double accepted. The fragment* FOR rebuilds (benign, impact, chain) are for_indirect, and the uploader's direct call set over FrAg* is delayed (found by the Homebase agent). Claude's safety filter stopped one step of the analysis here, and we didn't work around it; the Homebase agent finished that part.
Batch control, the dispatch form. goto :HUNTRESS_DISPATCH + its label is goto_dispatch (found on Day 6; applied to this day's uploader and relay on 2026-10-09, 226 of 231). The call :HUNTRESS_RECOVER scripts are call_subroutine. Wrong guesses here were named after decoys (findstr_filter, matcher_pipe, quoted_assignment_chain, dispatch_filter …): the | findstr separators and quote-compound markers never decide the label.
The last five: nothing in the slot → direct. At 226 of 231 the open ones were the two VBScript scripts with nothing around Decode_* (memory-stage, local-dispatch) and three Batch scripts with an empty control slot (the lines between :HUNTRESS_SEPARATOR_DONE and echo(). The stager alone had 12 rejected names (direct_sequence, unwrapped, no_loop, direct_call, no_control_flow, no_wrapper …), and the launcher 5. On the evening of 2026-10-09 Zwesty and Claude worked back from the accepted answers instead:
- Each accepted label is a keyword in the code plus a purpose word.
error_guardcame from Zwesty mirroringerrorlevel_guardon Day 5. - Splitting the open scripts by what was really in the slot showed the "nothing" words had only ever been tried on scripts that did have something (an
if definedcheck). For the truly empty slot, the candidates came from the generator's own "plain" vocabulary in other fields: JScriptinvocation-styledirect, transformidentity, codecplain_*. directwas accepted on Day 5's empty Batch slot, then on this day's VBScript stager (227 of 231). The rollout: Batch benign, dropper and launcher and the VBScript launcher: Single Solve CORRECT, 231/231, at 00:50:10 UTC, flag0f1ee976…0b3a.
Day 4 was the first Script day after Day 2 to reach Single Solve CORRECT. The last Batch label, if_guard for the set "HUNTRESS_READY=1" + if defined HUNTRESS_READY wrapper, wasn't needed here (no Day 4 Single Solve script has it). It was found later that evening after 68 rejected names, from the pattern "a keyword in the code + one of five second words (subroutine / guard / dispatch / case / once)". Support was never needed.
Final control labels for this day:
| Labels | |
|---|---|
| VBScript | select_ ×3, do_ ×3, while_ ×1, direct ×2 |
| Batch | call_ ×4, goto_ ×2, direct ×3 |
Wrong answers in total: the request logs hold 46 incorrect replies (48 answers, one a batch of 3): 10 answers from Claude on our Kali VM, 38 from the Homebase agent. Every rejection narrowed the next test.
Solvers: our VBScript decoder reads a .vbs from its Codec_ line, then checks the line against the script's own Load and Decode code (the key array, the + N) Mod 256, the separator), and stops if they disagree. Rolling XOR uses the previous stored byte. A Batch reader handles the fragments (any prefix, quoted or ^|-escaped). Our Day 4 solver joins them to Power Play's solver and gives only confirmed answers (4 tests), and a small helper submits a filtered subset. Later the labels moved into shared add-ons (Batch wording rules, VBScript control, plus the Homebase agent's JScript analyser), run by our runner with the whole Script chain.
Time Trial
Run from our Kali VM with our solver once the Single Solve was CORRECT. Timings are the client's.
started_at 2026-10-10T01:13:06.101Z
coverage 231/231 (incl. error_guard and direct, both learned on other days)
submitted 01:13:07 UTC, 231 answers
TOTAL 2.24 s (client side)
result CORRECT flag 9c0f6678…d96f
Correct on the first run.
Mastery
Attempt 1 (01:13:13 UTC): stopped before submitting. Three of the four outbound-transfer / embedded-write Batch scripts across its two sets had the if defined HUNTRESS_READY wrapper, whose label was still unknown then. The solver left them out, our runner stopped instead of guessing, and the attempt expired with nothing sent.
Retry (01:39:48 UTC, after if_guard was found and folded into our Batch wording rules): CORRECT.
started_at 2026-10-10T01:39:48.370Z (retry; attempt 1 started 01:13:13 UTC and expired unsent)
coverage 462/462 (two sets of 231)
submitted 01:39:50 UTC, 462 answers
TOTAL 2.95 s (client side)
result CORRECT flag e2617d26…8f3e
Time to solve
The Single Solve was completed at 00:50:10 UTC on 2026-10-10, 56 h 51 min after it started: 211 of 231 answers were in within two hours on the first day, and the last 20 expansion and control labels took two more days of wording tests. The Time Trial went through first time in 2.24 s; the first Mastery attempt stopped before submitting and expired, and the retry passed at 01:39:50 UTC in 2.95 s, 57 h 41 min after the first start.
Takeaways
- Read the answer from the artifact when it's there. All 18 representation/transform answers were the
Codec_words, accepted first try, and our VBScript decoder checks those words against the script's own code. - Hand-off scripts take their own interpreter's ATT&CK ID (
T1059.005for VBScript). That lesson, learned here, also closed Day 3's two Batch hand-offs asT1059.003. - Name what the code does, in the generator's words.
do_onceandwhile_oncedescribe a block that runs once;directdescribes an empty slot and came from another field's vocabulary. Descriptive synonyms (single_iteration,no_wrapper) all failed. - Split by what is really in the slot. Decoys (separators, quote-compound markers,
neutral_*variables, comments, the byte-to-text loop) never decide the label. "Nothing" words had failed only because they'd been tried on scripts that had something. - Send one at a time, never resend a rejection. 48 rejections over three days, each one a test.
- A timed solver must stop rather than guess. Mastery attempt 1 met a form with no known label, sent nothing, and the retry passed once the label was found.