The challenge
"The .NET assemblies share names, versions, and source ancestry—but their module identities remain distinct. Separate rebuilds from renames."
One question about managed-001.dll inside metadata-day-12-evidence.zip.
| answer_id | Question | Format |
|---|---|---|
d12- |
"Determine dotnet.mvid_guid for managed-001.dll." | pattern: "canonical GUID text in 8-4-4-4-12 hexadecimal form, including hyphens"; trimmed |
Reading the prompt: what it said we'd need
| Clue | What it pointed to | What I did about it |
|---|---|---|
| ".NET assemblies" | A managed PE: CLR header (data directory 14) and the BSJB metadata root | Walked PE → CLR header → metadata root → stream headers, instead of searching the bytes |
| "module identities remain distinct" | The Module Version ID (MVID): column 3 of the Module table's only row, an index into the #GUID heap. The compiler makes a new one on every build |
Read the Module row from the #~ tables stream, with the heap-index widths the stream's HeapSizes byte gives |
| "share names, versions, and source ancestry" | The assembly name and version can't tell builds apart | Ignored the name and version; only the MVID answers |
| "Separate rebuilds from renames" | A rebuild gets a new MVID; a renamed file keeps its MVID | No question asked about this directly: one file per set |
| "canonical GUID text in 8-4-4-4-12" | The usual GUID text, which stores the first three groups little-endian | Wrote it the way System. does: lowercase, uuid. |
Safety
- The artifacts are Windows .NET DLLs. Everything was static: the solver reads bytes with
structand never loads a challenge file. There's no .NET runtime or disassembler on our Kali VM, so nothing could have run them anyway. - The artifacts stayed on our Kali VM; none were copied to a Windows machine.
What was in the files
- Single Solve:
managed-001.dll, 5,120 bytes, a .NET 8 assembly calledHuntress.Managed.7E5EF87F23AC(file version 2.0.2.6). Its strings (ManagedArtifact,get_Module,get_ModuleVersionId,System.Console,Write) suggest one class that prints its own MVID (not run). Its#GUIDheap holds exactly one GUID, the MVID:21e410f3-…-6890ddd3085c. - Time Trial: the same template,
managed-002.dll(assembly…9C72FF5E07A0), MVID9e0e197f-…-d28da985893a. - Mastery: an outer zip holding
metadata-day-12-evidence-01.zipand-02.zip, one DLL each (managed-003.dll,managed-004.dll), each with its own assembly name and MVID.
Every file was 5,120 bytes, and every #GUID heap held just the one GUID, so there was no decoy GUID to separate.
Single Solve
The morning solver's prep step flagged it NEW ("don't know what this question asks"), as intended: no older day's matcher claimed the field. The morning run then hit HTTP 429 at 09:00:20 (after Android's Single Solve), so nothing else ran until the limit cleared.
Our solver wraps Day 11's, which wraps Days 1–10. It adds:
- finding the metadata: PE optional header → data directory 14 (CLR header) → the metadata RVA, mapped to a file offset through the section table → the BSJB root → the stream headers (#~, #Strings, #US, #GUID, #Blob);
- reading the MVID: the Module table (table 0) must exist and have exactly one row. Skip Generation (2 bytes) and Name (2 or 4 bytes, per HeapSizes bit 0). Read the Mvid index (2 or 4 bytes, per bit 1) and take that 16-byte entry of #GUID (1-based);
- matching the field: an exact check for question IDs ending in dotnet-mvid-guid, ahead of the older days' matchers. A file with no CLR header gives none (not tested on this field). A CLR header with no BSJB root, a missing stream, or an out-of-range index stops the run;
- Mastery support: delivery N: labels, through Day 11's handling.
21e410f3-c977-465c-80eb-6890ddd3085c was sent at 09:06:58, about 6.5 minutes after the 429: CORRECT (ee9ebfd8…d398).
Its tests (8, all pass):
- a native PE with no CLR header, and a non-PE, give no MVID;
- the field match is exact (…-mvid and …-mvid-guid-hex don't match);
- the real Single Solve answers exactly the expected GUID, in canonical lowercase 8-4-4-4-12 form;
- the byte order is pinned: Data1 is stored little-endian and Data4 as-is;
- a delivery 1: label finds its download, and a mismatched delivery stops;
- Day 11's real question still answers pe-base-386.exe.
Carry-over check: the Day 12 chain and Day 11's give identical answers on all 7 saved Day 10–11 sets, Masteries included.
Time Trial
CORRECT on the first attempt. A plain start was timed (this route doesn't take a ranked-clock request). It replied unique_attempts_remaining: 0 and unranked: one attempt, no ranking.
started_at 2026-10-12T13:18:28.108Z
deadline 2026-10-12T13:18:58.108Z (30 s)
answers 1 9e0e197f-d610-4ec9-bd1c-d28da985893a
start 2223 ms
artifact 402 ms
solve 1 ms
submit 890 ms
TOTAL 3.52 s
result CORRECT flag 160eac96…5bc0
Mastery
CORRECT on the first attempt (2 sets, 30 s, unique_attempts_remaining: 0). The Warmups Mastery had just expired on a single-attempt route, so before this start the Day 12 chain was checked against Day 10–11's saved Masteries.
started_at 2026-10-12T13:18:50.979Z
deadline 2026-10-12T13:19:20.979Z (30 s)
answers 2 2658bcc7-…-2bb645f8680e, ecc37f87-…-011ee0d07c7c
start 519 ms
artifact 318 ms
solve 1 ms
submit 742 ms
TOTAL 1.58 s
result CORRECT flag c04f7573…bd30
Time to solve
The morning solver started and downloaded the Single Solve at 13:00:06 UTC; after waiting out an HTTP 429 it went CORRECT at 13:07:02, 6 min 56 s from the start. Between 13:07 and 13:18 we worked the Warmups Mastery; then the unranked Time Trial (3.52 s) and Mastery (1.58 s) each went CORRECT on the first submit, the Mastery at 13:18:52, 18 min 46 s after the first start. That Mastery was Day 12's last scored mode: 14 of 15 modes done, with the Warmups Mastery expired (see the Back at You write-up).
Takeaways
- The MVID tells builds apart; names and versions don't. Every compile gets a new MVID. A renamed copy keeps it, and a rebuild with the same name and version doesn't.
- GUID byte order is a trap. The heap stores the first three groups little-endian; reading the 16 bytes as plain hex gives a different, wrong string. A test pins the order.
- The prep's NEW flag worked. The previous day's fix (exact field matching in the morning solver's prep step) made it say "new field" instead of giving a confident wrong answer.
- Single-attempt timed routes: both timed modes allowed one attempt. They were tiny solves here (1 ms), but on these routes an expiry can't be retried. From Day 13, a new solver must pass a rehearsal before any timed start.