zwesty
Write-ups / Day 20 / Script
Day 20 · Tue, Oct 20 · Script

The Butler Did It

Prey slaughtered

Multi-language script analysis (Groovy, classic ASP VBScript, Bash), Jenkins Pipeline semantics, 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-20-jenkins-pipeline-intrusion (+ /time_trial, /mastery)
Solved
2026-10-20: all three modes CORRECT on the first run, by 09:01:11 ET, 21 s after our solver's first start request (09:00:50 ET). Both timed modes were ranked runs
Player
Zwesty (solo); solved by our automated morning solver (a scheduled script on our Kali VM, core chain then add-on readers merged), built with Claude Code (Opus 5.5), with no manual work needed

The challenge

"A Jenkins Pipeline splits its Groovy stages between controller and agent, and not every step runs where it appears to. Recover the pipeline's intent, separate controller semantics from agent semantics, and trace the modeled build into its prior-language stages."

Every earlier day's script fields plus 80 new ones: 1,329 questions on the Single Solve (Day 19 had 1,249; none were dropped). Written from the solver's report, the start reply's question list, and the Game Master request log; the specimens weren't reopened.

The new specimens each carried the lane's usual question family:

answer_id (per specimen) Question Format
…-role "Classify the inert source role." enum (benign_utility, downloader, dropper, impact, launcher, persistence_descriptor, relay, staged_chain, stager, uploader, advanced_syntax)
…-attack Map the decoded ordered behaviour graph to one primary ATT&CK technique, by the stated precedence technique or sub-technique ID, or none for benign
…-adapter, …-campaign, …-url / …-file-path Recover the modelled adapter, campaign ID, and indicator exact text
…-stage-count Recover the modelled stage count base-10 integer
…-steps Recover the ordered behaviour graph tokens joined by >
…-host-profile "Identify the script host or webshell source container." exact text
…-obfuscation, …-stage-profile, …-next-language Encoding profile, multi-stage profile, next language in the handoff exact text

The two advanced Bash specimens asked role, attack, feature, primitive, campaign, stage-count, and steps.

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"A Jenkins Pipeline … Groovy stages" A new host container (jenkins_pipeline) in the Groovy language family Relied on the core chain's host-profile reader; it named the host with no change
"between controller and agent", "not every step runs where it appears to" The steps graph and the stage profile: what the build decodes, where it writes, and what it launches The core chain's decoded behaviour graph gave decode_embedded>write_file>dispatch_process, three stages
"trace the modeled build into its prior-language stages" The next-language handoff, and the compounding fields from every earlier day All 1,249 earlier fields went in with the new ones; the Jenkins specimen hands off to batch
"The Butler Did It" (title) Jenkins's logo is a butler Confirmed the Jenkins specimen was the day's centrepiece

Safety

Script specimens are hostile: static analysis only, on our Kali VM. The solver parses the text of the encrypted evidence.zip members; no specimen was executed and no build was run. No code excerpts are quoted here, only the answer values, with URLs defanged.

What was in the files

  • Single Solve: evidence.zip, 130,248 bytes, 145 encrypted members. The 80 new questions came from eight specimens:
  • host-jenkins-pipeline-groovy.groovy (11 fields): role staged_chain, attack T1059, adapter operation_graph, campaign GROOVY-49671532, URL hxxps://groovy-…example[.]invalid/stage, 3 stages, steps decode_embedded>write_file>dispatch_process, host profile jenkins_pipeline, obfuscation hex_base64, stage profile three_stage_decode_write_launch, next language batch.
  • host-classic-asp-vbscript.vbs (11): host profile classic_asp, steps acquire_remote>dispatch_process, 2 stages, obfuscation base64, stage profile two_stage_download_execute, next language jscript.
  • four Groovy polyglots, polyglot-groovy-{benign-utility,downloader,launcher,staged-chain}.groovy (44); the staged chain hands off to vbscript through fragmented_hex.
  • two advanced Bash specimens, coprocess-block.sh and mapfile-process.sh (14): role advanced_syntax, attack T1059.004, features coprocess_block and mapfile_process.
  • Time Trial: evidence.zip, 130,696 bytes, the same 1,329 questions. The Jenkins specimen had the same shape (same steps, obfuscation, stage profile, and next language); only the campaign ID and URL changed.
  • Mastery: one 649,823-byte zip, 5 sets, 6,645 questions. All five Jenkins specimens matched the Single Solve's shape with their own campaign IDs.

Single Solve

The solver solved it with no stop. It picked up today's challenge ID with its core script solver, which uses the split-header reader added on Day 19, and sent two batches:

  • Core batch, 1,200 answers → accepted, 129 remaining. All 80 new fields were in it. The solver flagged 50 values it had seen rejected on other questions before (mostly none and T1105 attack values) and sent them anyway, as it is set to; none was rejected here.
  • Merged add-on batch, 129 answers → CORRECT. These were the fields the solver's add-on readers own: 72 JScript, 16 Batch, 8 VBScript, and 11 each for the WSF, HTA, and SCT hosts.
started_at  2026-10-20T13:00:51.412Z
deadline    none (Single Solve)
answers     1,200 (core) + 129 (add-ons merged) = 1,329
start / artifact / solve timings: not recorded for the Single Solve
core submit       13:00:52 → 13:00:52   accepted, remaining 129
add-ons submit    13:00:53 → 13:00:54
TOTAL       ~3 s from the start reply to CORRECT (log precision)
result      CORRECT   flag 732ce083…3b5d

No new solver, tests, or rehearsal were written today: the code that ran was the same as on Day 19.

Time Trial

CORRECT on the first attempt, ranked (50 s clock, 1,329 answers).

started_at  2026-10-20T13:01:00.518Z
deadline    2026-10-20T13:01:50.518Z   (50 s)
answers     1329
start        598 ms
artifact     414 ms
solve         80 ms
submit      1050 ms
TOTAL       2144 ms   (submit +1.82 s after started_at)
result      CORRECT   flag 26feaa47…5a05

Mastery

CORRECT on the first attempt, ranked (5 sets, 170 s clock).

started_at  2026-10-20T13:01:07.889Z
deadline    2026-10-20T13:03:57.889Z   (170 s)
answers     6645 across 5 sets
start        930 ms
artifact     700 ms
solve        412 ms
submit      1727 ms
TOTAL       3778 ms   (submit +3.46 s after started_at)
result      CORRECT   flag 709dc3de…7a13

Time to solve

All three modes were solved by 09:01:11 ET, 21 s after the solver's first start request at 09:00:50 ET. The Single Solve took two batches about 3 s apart, and the ranked Time Trial and Mastery each went through on the first submit.

Takeaways

  • The general reader keeps paying off. A new host (Jenkins Pipeline), a new language family (Groovy), and classic ASP all went through the existing chain with no code change, for the second day running.
  • The rejected-before warning is advice, not a stop. 50 values we had seen rejected elsewhere were right for these questions; the solver sends them and lets the Game Master judge.
  • Mastery is the slow part. 6,645 answers took 1.7 s to submit; the solve itself was 412 ms.