zwesty
Write-ups / Day 3 / Binary
Day 3 · Sat, Oct 3 · Binary

Table Manners

Prey slaughtered

Reading a .NET assembly as a PE file · the CLI header and metadata root (BSJB) · decompiling with ilspycmd · finding a config stored in a static field's initial data · carrying answers over in a compounding challenge · working out a checker's vocabulary

STTMClaude Homebase Both

Flags are shortened to their first 8 and last 4 characters because they're unique per team.

Challenge ID
binary-reverse-engineering-day-03-managed-configuration (+ /time_trial, /mastery)
Solved
2026-10-07 to 2026-10-09, all three modes (Mastery as untimed practice). First start 2026-10-07 15:15:51 → last CORRECT 2026-10-09 17:28:31 UTC (2 d 2 h 13 min, almost all of it waiting on one wording question)
Player
Zwesty (solo), with Claude Code (Opus 5.5, Single Solve analysis, solver fixes, and the Mastery finish) and the Homebase agent (GPT-6 "Luna", the Day 2 config, the last format answer, and the Time Trial)

The challenge

A .NET binary is a database wearing a trench coat. Read the CLI metadata tables (methods, strings, signatures) and find where the configuration is stashed. ilspycmd and strings are your friends.

This is the first compounding Binary day. One evidence.zip (AES, password infected) holds Day 1's C ELF, Day 2's C++ ELF, and the new Day 3 .NET DLL with its runtimeconfig.json, and the questions cover all three. 24 questions in the Single Solve and Time Trial, 48 in Mastery (two sets, set1. / set2.). Every one is normalised exact; the exact-text ones say "use the literal none only when the requested field is absent".

Group answer_id Question Format
d01, the Day 1 C ELF (8) d01_artifact_sha256_1 The file's SHA-256 Exact text
d01_analysis_elf_class / machine / elf_type / endianness The ELF header fields Exact text
d01_analysis_section_names The section names Compact JSON array, exact values and order
d01_analysis_symbols The symbols Compact JSON array, exact values and order
d01_analysis_disassembly Disassemble the recover_code FUNC symbol; [] if it's absent JSON array of instruction strings (Intel or AT&T)
d02, the Day 2 C++ ELF (8) d02_artifact_sha256_1 The file's SHA-256 Exact text
d02_analysis_format "What is the exact analysis format value…" (no other hint) Exact text
d02_analysis_config_campaign / domain / mutex / url The embedded config's fields Exact text
d02_analysis_config_crc32_hex, d02_analysis_xor_key_hex The config record's CRC32 and XOR key Lowercase hex with 0x
d03, the new .NET binary (8) d03_artifact_sha256_1 / _2 The two files' SHA-256s Exact text
d03_analysis_format "its combined executable-container and managed-runtime format classification… one compound token… not just the generic container label" One container_runtime token; CLI, CLR, or .NET for the managed part; separators and case are free
d03_analysis_machine The machine Exact text
d03_analysis_metadata_version The metadata version Exact text
d03_analysis_config_campaign / domain / mutex The embedded config's fields Exact text

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"A .NET binary is a database wearing a trench coat" A PE file (the coat) wrapped around CLI metadata tables (the database) Read the PE header for the machine, then the CLI header and the metadata root
"Read the CLI metadata tables: methods, strings, signatures" The metadata streams: the tables, #Strings, #US (user strings), and #Blob Found the BSJB metadata root and read its version string straight from the bytes
"find where the configuration is stashed" A config held in the metadata, not in a resource file Decompiled, found the field that holds it, then followed the field to its initial data
"ilspycmd and strings are your friends" Decompile for the logic; strings -el for the UTF-16 #US heap Installed ilspycmd 7.2.1 (the last build for .NET 6) on our Kali VM
d03 format: "one compound token containing both parts, not just the generic container label" Container + runtime, e.g. PE + CLI Tried the most specific container name first (PE32), then the generic one (PE)
The d01 and d02 groups Earlier days' files, byte-identical Carried over the accepted ELF Aware and Mangled answers by SHA-256

Safety

Static analysis only. No binary was ever executed. The evidence was opened with 7z -pinfected inside our Kali VM (and, for the Homebase agent's runs, inside the Homebase), and nothing was copied out. The ELFs were read with readelf and objdump. The .NET DLL was read as bytes and decompiled with ilspycmd, which reads IL; it doesn't run it. Its behaviour, and the JSON trace it would print, come from the decompiled source. The timed runs used the same kind of static readers (see the Time Trial and Mastery sections).

What was in the files

The Single Solve's evidence.zip (10,238 B, SHA-256 9d7a0aee…d777, zip comment huntress:day03-single) held 4 files:

File Size What it is
d01--variant-000.elf 15,616 B Day 1's C ELF: .huntress = BR01-CATALOG-000, recover_code at 0x401126, entry 0x401040, 28 sections
d02--variant-000.elf 16,640 B Day 2's C++ ELF (SHA-256 e9ca3054…d0d7, byte-identical to Mangled's): a DiscoveryEngine class and an HCFG config record in a .hcfg section
d03--variant-000.dll 9,216 B The new one: a .NET 8 console assembly, PE32, machine 0x14c
d03--variant-000.runtimeconfig.json 98 B The .NET host's config, naming net8.0

The .NET DLL: - PE32, machine 0x14c (i386). That's normal for an "Any CPU" .NET assembly: the PE header is x86, and the IL inside runs on any CPU. - The metadata root (BSJB magic) is at 0x688, with version v4.0.30319. Even a .NET 8 assembly carries that string. The runtime version people expect (net8.0) lives in the runtimeconfig.json, not in the metadata. - Decompiled, it's namespace Huntress.Binary.Day03. ManagedHttpPlan.Evaluate(model) returns success/7 for workstation, decoy/3 for analysis, and failure/1 otherwise. The model comes from the HUNTRESS_HOST_MODEL variable, defaulting to workstation. The program would print a fixed JSON trace (huntress.binary.dynamic_trace.v1, application_layer_protocol_configuration, sinkhole_only). - Where the config is stashed: Program.Config is a 379-byte array set from a field's initial data (a FieldRVA blob at file offset 0x1898). That's how the C# compiler stores a constant byte array: the bytes sit in the file, and a metadata table row points the field at them. The blob is an HCFG record, the same layout as Day 2's: the magic HCFG, a version byte, a one-byte XOR key (0x41 here), a length (367), a CRC32, then the XOR'd JSON. The JSON holds campaign, domain, ipv4, url, mutex, windows_path, linux_path, service, and host_model. Only campaign, domain, and mutex are asked.

The timed variants used the same template: each delivery carried the same three days' files. Mastery set 2 was different: its d02 was a native Windows PE (.exe, MinGW C++), not an ELF. (The timed runs' per-variant d03 values aren't recorded in this write-up.)

Single Solve: CORRECT (17 by Claude, 6 by the Homebase agent, the last after two days)

Claude's part, 2026-10-07, 15:15:51 → 15:17:58 (17 of 24 accepted in 6 submits):

Submit Fields Result
1 d01 SHA-256, ELF64, x86-64, ET_EXEC, little, disassembly ["mov eax,0x1fa4","ret"] accepted, 18 left
2 d01 section names: without section 0 ✗, then with "" first ✓. d01 symbols: ["recover_code"] ✗, then every named .symtab entry in table order ✓ (crt1.o, __abi_tag, variant-000.c, marker, …) 2 accepted, 16 left
3 d03 sha256_1 (the .dll), sha256_2 (the .runtimeconfig.json), metadata version v4.0.30319: accepted. Format PE32_CLI ✗. Machine i386 ✓ 12 left
4 d03 format PE_CLI ✓: the container part is the generic PE, not PE32 11 left
5 d02 SHA-256 e9ca3054…d0d7 accepted, 10 left
6 d03 config: campaign BR03-000, domain node-000.day03.example[.]invalid, mutex Global\HuntressBR03_000 accepted, 7 left

The 7 open fields were all Day 2's. Six are the config fields. Claude's safety filter had stopped one step of the analysis on the same work in Mangled, and we didn't work around it, so Claude didn't attempt them here. The seventh, d02_analysis_format, was a wording question, not blocked.

The Homebase agent's part: - 2026-10-07, 17:13:06: its config analyser read the HCFG record from the C++ ELF and sent the six config fields as one batch: accepted, 1 left (23 of 24). - 2026-10-08: it sent ELF64_CXX for d02_analysis_format: incorrect. That submit was only recorded in the agent's own notes, with no time. - 2026-10-09, 15:30:21: the agent's persistent probing on Mangled found lowercase elf, a plain container name. Sent here alone: CORRECT, flag a2e1fd2a…3424. Uppercase ELF and ELF64 had both been rejected in Mangled; dozens of other spellings went through Mangled first (see the Mangled write-up).

Time Trial: CORRECT, 11.3 s on the agent's clock (Homebase)

Run by the Homebase agent, in its batch of timed runs for Days 3–9. The answers came from three pieces: our core binary solver (SHA-256s, the d03 format, machine, metadata version and config, and d02's elf), the agent's Day 1 ELF reader (wrapped), and its Day 2 config analyser.

started_at 2026-10-09T16:57:21.186Z  deadline 2026-10-09T16:57:27.186Z  (24 answer_ids, 24 answered)
  start 9937 ms · artifact 409 ms · solve 39 ms · submit 912 ms · TOTAL 11297 ms
result CORRECT  flag ca678b52…c49d

The total is longer than the 6-second window, yet it counted. The Game Master stamped the start at 16:57:21.186 and logged the submit at 16:57:22, so the server saw about one second. Most of the agent's 9.9 s "start" phase must have come before the server stamped the attempt; the agent didn't record why.

Mastery: CORRECT as untimed practice (from our Kali VM)

First attempt, Homebase, 2026-10-09 16:57:29 (deadline 16:57:35.396): stopped before submit, nothing sent. Set 2 delivered d02 as a MinGW C++ PE (.exe). The config analyser skipped the setN. answer ids and .exe files, so the six d02 config fields in both sets were unanswered, and set2.d02_analysis_format had no known value for a PE. The attempt expired.

Fixes (Claude, the same afternoon): the config analyser now accepts setN. ids and .exe files; a shared helper maps each "delivery N:" question to its own NN-evidence.zip and never answers from another set; archive reads and the config scan are cached. Re-run offline against the saved deliveries, our solvers gave the same answers as every Time Trial submit.

The PE format value: pe (lowercase, like elf and jar; the d03 question itself calls PE "the generic container label"). It was confirmed one minute earlier in Nimble's Mastery (17:27), so it went into this run as a known value, not a guess.

Finish, from our Kali VM: a retry twice said rotation_exhausted (no fresh variant left), so our runner switched to op=practice: untimed, the last delivery again, and no Hall of Fame.

practice  started_at 2026-10-09T17:28:29.221Z  deadline None  practice True
  (48 answer_ids: set1 + set2, set 2's d02 a MinGW PE; one 48-answer submit)
  retry 667 ms · practice 382 ms · artifact 380 ms · solve 46 ms · submit 1652 ms · TOTAL 3127 ms
result CORRECT  flag af41154b…8086

The confirmed rules

Field Rule
dNN_artifact_sha256_K SHA-256 of that day's files in name order (.dll = 1, .runtimeconfig.json = 2)
d01 header ELF64 · x86-64 · ET_EXEC · little
d01_analysis_section_names Every section name in header order, including section 0's ""
d01_analysis_symbols Every named .symtab entry in table order, file symbols too
d01_analysis_disassembly recover_code's instructions, e.g. ["mov eax,0x1fa4","ret"]; [] if it's absent
d02_analysis_format elf for the ELF; pe for Mastery set 2's MinGW .exe
d02 config The HCFG record's fields (read by the Homebase agent's config analyser)
d03_analysis_format PE_CLI: generic container + managed runtime (PE32_CLI rejected)
d03_analysis_machine i386 (PE machine 0x14c)
d03_analysis_metadata_version The BSJB root's version string, v4.0.30319 (not net8.0)
d03 config The HCFG record in Program.Config's FieldRVA data

Time to solve

The Single Solve was completed on 2026-10-09 at 15:30:21 UTC, 2 d 0 h 14 min after it started: 17 of 24 answers took 2 min 7 s, and the last one, d02_analysis_format, took two days. The Time Trial went through first time (11.3 s on the Homebase agent's clock, about one second on the server's); the first Mastery attempt stopped before submitting and expired, and the Mastery was finished at 17:28:31 UTC as untimed practice, so it is unranked.

Takeaways

  • A .NET binary is two formats at once. The PE header gives the machine (i386 for "Any CPU"); the CLI metadata gives everything else. The format answer named both: PE_CLI.
  • The metadata version isn't the .NET version. v4.0.30319 sits in every modern assembly; net8.0 is in the runtimeconfig.json.
  • "Stashed in the metadata" meant a field's initial data. A constant byte array becomes a FieldRVA blob; the decompiler shows the field, the metadata table shows where its bytes are.
  • Generic beats specific in this checker's vocabulary. PE32_CLI was wrong and PE_CLI right; ELF64 and ELF were wrong and elf right; later pe. When a question says "the generic container label", take it literally.
  • Compounding days reward a reusable toolkit. The d01 and d02 files were byte-identical to earlier days, so accepted answers and analysers carried straight over.
  • A timed run can stop safely and still be finished. Mastery's first attempt stopped on a file type the config analyser didn't know; after the fix, practice mode finished it untimed.
  • One blocked field can hold a challenge open for days. 17 answers took 2 minutes; the last one took two days and outside probing.