The challenge
"Installers were copied, renamed, and repackaged. Recover stable product identities, distinguish revisions from different products, and find the collision."
One question about installer-001.msi inside metadata-day-13-evidence.zip.
| answer_id | Question | Format |
|---|---|---|
d13- |
"Determine msi.product_code_guid for installer-001.msi." | 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 |
|---|---|---|
| "Installers", "msi." | A Windows Installer database: an OLE compound file holding tables as streams | Read it with olefile, decoded the MSI stream names, and rebuilt the string table and the Property table |
| "product identities", "product_code" | The ProductCode property: the GUID that identifies one product release | Took the Value of the ProductCode row in the Property table, not any GUID found in the bytes |
| "distinguish revisions from different products" | Three GUIDs that sound alike: ProductCode, UpgradeCode (the product family, stable across versions), and the package code (Summary Information, new for every build of the .msi) | Kept all three apart; the question names only the ProductCode |
| "copied, renamed, and repackaged … find the collision" | Different file names that hide the same identity | Each question has one file; in the timed modes every installer carried the same ProductCode |
| "canonical GUID text in 8-4-4-4-12" | I read this as "strip the braces", as on Day 12 | Wrong: the checker wanted the text exactly as the MSI stores it, {…} included (see Single Solve) |
Safety
- The artifacts are Windows Installer packages. Everything was static: the solver parses the OLE container in memory and never installs or runs anything. The embedded cab holds one text file (
MSI proof 7E5EF87F23AC); nothing executable was found. - The artifacts stayed on our Kali VM.
What was in the files
- Single Solve:
installer-001.msi, 9,728 bytes, built bymsitools 0.103. Its Property table has seven rows: ProductCode{DD1A80D5-…-13096D633C61}, UpgradeCode{5F63B105-…-2AF6C4F4CD67}, ProductName "Huntress Metadata Evidence", ProductVersion 1.13.0, Manufacturer "Huntress Labs", ProductLanguage 1033, ALLUSERS 1. - Decoys in the same file: the UpgradeCode; the package code
{99BE1D60-…-9E465A88729D}(Summary Information, "Revision number"); a component GUID{0DF92558-…-8DC80B710C29}. Only these four GUIDs exist anywhere in the file. - Time Trial:
installer-002.msi. Mastery: an outer zip holdingmetadata-day-13-evidence-01.zipand-02.zip, withinstaller-003.msiandinstaller-004.msi. - The collision: all four installers are 9,728 bytes and carry the same ProductCode, UpgradeCode, and package code under different file names. So all four answers were the same GUID.
Single Solve
Our automated morning solver (a scheduled script on our Kali VM) downloaded the file and flagged the question as new, as intended (it didn't know what this question asks).
Today's solver wraps Day 12's solver (and through it Days 1–11). It adds:
- opening the MSI: olefile reads the compound file. Each table is a stream whose name is packed two characters per code point (0x3800–0x47FF), so the names are decoded back to !_StringPool, !Property, and so on;
- the string table: !_StringPool holds a length and a reference count per string (and the reference width: 2 bytes, or 3 when the code-page word's top bit is set); !_StringData holds the bytes back to back;
- the Property table: two string columns (Property, Value), stored column by column, so row r's key is entry r and its value is entry n + r. The MSI's own _Columns table confirms that layout;
- matching the field: an exact check for question IDs ending in msi-product-code-guid, ahead of the older days' matchers. A file that isn't an MSI, or has no single ProductCode row, stops the run;
- Mastery support: delivery N: labels, through Day 11's handling.
Three sends, two rejected:
| Time (ET) | Sent | Result | Why |
|---|---|---|---|
| 09:05:49 | dd1a80d5- |
incorrect | The same format text as Day 12, where the lowercase MVID was accepted |
| 09:10:33 | DD1A80D5- |
incorrect | The stored case, braces stripped |
| 09:29:23 | {DD1A80D5- |
CORRECT | The value exactly as stored |
Between the second and third sends, 47 teams already had full points, so the fault was ours. Checked before the third send:
- our request log: exactly those two values, under the right answer ID, on the right attempt;
- the artifact's SHA-256 matched the start reply;
- the zip has no comment, extra data, or trailing bytes;
- the OLE container has 20 directory entries, all in the normal tree, with no orphans;
- msiinfo (msitools 0.106) agreed with the parser on every table.
The ranked list of candidates was the stored form, then the UpgradeCode, then the package code. The first one answered it, so the other two were never sent.
The mistake: I normalised a value that the file stores as text. On Day 12 the MVID is 16 raw bytes, so the generator had to choose a text form (lowercase); here the generator wrote the text into the MSI itself, and the checker compares that exact text. The solver now returns the Property value verbatim.
Tests (8, all pass on our Kali VM):
- a non-OLE file gives no Property table and stops;
- the field match is exact (…-upgrade-code-guid, …-product-code-guid-hex, and …-product-code don't match);
- the decoded stream names include the string pool, string data, Property, and Summary Information;
- the real Single Solve answers exactly {DD1A80D5-…}, braces and uppercase, and it differs from the UpgradeCode;
- a delivery 1: label finds its download, and a mismatched delivery stops;
- Day 12's real question still answers its MVID.
Regression rehearsal (before the timed starts): 43 saved Metadata sets, slowest 0.04 s, per-set answers equal, 0 changed against Day 12's solver, 2 newly solved (today's).
Time Trial
CORRECT on the first attempt. The start was timed (50 s) but unranked: this route had no ranked start, and unique_attempts_remaining was 0.
started_at 2026-10-13T13:29:57.408Z
deadline 2026-10-13T13:30:47.408Z (50 s)
answers 1 {DD1A80D5-0250-4F4D-B289-13096D633C61}
submit +2.05 s after started_at (solve 1 ms)
result CORRECT flag 3e7fe936…4d33
Mastery
CORRECT on the first attempt (2 sets, 80 s, unique_attempts_remaining: 0, unranked). Both sets gave the same ProductCode: the collision.
started_at 2026-10-13T13:30:04.911Z
deadline 2026-10-13T13:31:24.911Z (80 s)
answers 2 {DD1A80D5-…-13096D633C61}, {DD1A80D5-…-13096D633C61}
submit +2.14 s after started_at (solve 3 ms)
result CORRECT flag 36a5ce84…eeef
Time to solve
All three modes were solved by 09:30:07 ET, 29 min 58 s after the Single Solve was started; the Single Solve went through on the third send at 09:29:23 ET, after two rejected forms and the checks between them. The unranked Time Trial and Mastery each went through on the first submit, and this was the last of Day 13's 15 modes to finish.
Takeaways
- Send text fields exactly as stored. If the artifact holds the answer as text, the first send is that text, braces and case included. Normalise only values the file holds as bytes.
- The same format text means nothing on its own. Day 12 and Day 13 had word-for-word the same "canonical GUID text" line and wanted different forms, because one value was bytes and the other was text.
- The wrong forms cost more than the work. Analysis and the solver were ready by 09:05; the two wrong forms and the checks around them took 24 minutes. A pre-written ranked list of forms, stored form first, would have taken one more send.
- Know the three MSI identities. ProductCode identifies one product release, UpgradeCode the product family across versions, and the package code (Summary Information) one particular build of the .msi file.