zwesty
Write-ups / Day 6 / Metadata
Day 6 · Tue, Oct 6 · Metadata

Portable Deception

Prey slaughtered

PDF structure (header, catalog, trailer) · telling a real PDF from an impostor · separating what a file promises from what it contains

STTMClaude Homebase Both

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

Challenge ID
metadata-forensics-day-06-portable-deception (+ /time_trial, /mastery)
Solved
2026-10-07, all three modes, first attempt each, in 1 min 10 s from the first start (15:47:07 → 15:48:17 UTC)
Player
Zwesty (solo), with Claude Code (Opus 5.5)

The challenge

A document bundle mixes real PDFs, renamed impostors, and multiple declared versions. Determine what the files promise, what they contain, and which promise is false.

One question per file: "Determine pdf.header_version for document-NNN.pdf." Exact text, trimmed, with none allowed when the field is absent. The Single Solve asks about document-001.pdf, the Time Trial document-002.pdf, and Mastery document-003.pdf and document-004.pdf (two zips).

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"Real PDFs, renamed impostors" Some .pdf files may not be PDFs at all, so check the content, not the name (Day 2's magic-bytes lesson again) An impostor has no %PDF- header, so the answer is none
"Multiple declared versions" A PDF can declare its version twice: the %PDF-x.y header, and a /Version entry in the document catalog that overrides it (added when a file is updated) Answer the header only, as the field name says; report /Version separately
"What the files promise … which promise is false" Name versus content, header versus catalog The inventory shows all three: header, catalog version, true type
Same zip layout as Days 1–5 The Metadata lane compounds Build on Chunk Norris's solver

Where a PDF says its version

%PDF-1.4                     <- header: must be the first bytes (readers tolerate up to 1,024)
%âãÏÓ                        <- binary marker, so tools treat the file as binary
1 0 obj << /Type /Catalog /Pages 2 0 R /Version /1.7 >> endobj   <- optional override
…
trailer << /Root 1 0 R >>
%%EOF                        <- one per save; several mean incremental updates

When both exist, the catalog's /Version wins for a reader, if it's higher. That's how a file can "promise" 1.4 on its first line but really be 1.7.

What was in the files

Every document was the same minimal, real PDF (1,552 bytes): a catalog, a page tree, one page, a content stream printing "Metadata exercise d06-pdf-NNN" and a case number, and a Helvetica font. Only the header version and the text changed:

Mode File Header Catalog /Version %%EOF Answer
Single Solve document-001.pdf %PDF-1.4 none 1 1.4
Time Trial document-002.pdf %PDF-1.5 none 1 1.5
Mastery set 1 document-003.pdf %PDF-1.6 none 1 1.6
Mastery set 2 document-004.pdf %PDF-1.7 none 1 1.7

No impostor and no second version turned up in our four. As with Picture This and Chunk Norris, the prompt describes the whole pool, and each mode drew from its plain end. The versions simply count up, 1.4 to 1.7.

Single Solve: CORRECT in 14 seconds

Started at 15:47:07 UTC. The zip's SHA-256 matched, and the header read %PDF-1.4. The format wasn't stated, so two spellings were ready: 1.4, then %PDF-1.4. 1.4 was CORRECT at 15:47:21, flag d06b7964…e67f.

The solver

Our Day 6 solver loads Chunk Norris's solver by path (five days of Metadata zip handling) and adds the field …-pdf-header-version:

The file Answer
%PDF-x.y at byte 0 x.y
No %PDF- anywhere (an impostor) none
%PDF- present, but not at byte 0 Stop. Readers accept it there, but whether the Game Master counts it is unknown, so no guessing

6 tests, all passing before any timed run: - the real file; - versions 1.0, 1.7, and 2.0; - a 1.4 header with a catalog /Version /1.7: the answer stays 1.4, and 1.7 is reported; - impostors (a PNG and plain text named .pdf) giving none; - a header after some junk, which stops; - the Mastery shape with a PNG impostor.

One test needed fixing first: my fake "zip impostor" (bytes starting PK\x03\x04 but not a real zip) made Day 1's nested-zip handling throw an error. A member that looks like a zip is opened as one. I swapped the test's impostor for a PNG and noted the behaviour.

Time Trial: 1.71 s of a 6-second clock

started_at 2026-10-07T15:48:09.552Z  deadline 2026-10-07T15:48:15.552Z
  artifact metadata-day-06-evidence.zip 788 bytes sha256 3dbdcc5e0729767e… (checked)
  d06-pdf-002-pdf-header-version: 1.5
  start         529 ms
  artifact      393 ms
  solve           1 ms
  submit        786 ms
  TOTAL        1708 ms
result CORRECT  flag 06cab562…261f

Mastery: 1.49 s of a 6-second clock, first attempt

started_at 2026-10-07T15:48:15.114Z  deadline 2026-10-07T15:48:21.114Z
  artifact metadata-forensics-day-06-portable-deception-mastery.zip 1871 bytes sha256 275284f4a84beb0d… (checked)
  set1.d06-pdf-003-pdf-header-version: 1.6
  set2.d06-pdf-004-pdf-header-version: 1.7
  start         448 ms
  artifact      375 ms
  solve           1 ms
  submit        664 ms
  TOTAL        1488 ms
result CORRECT  flag 0626acf3…f88f

Time to solve

All three modes were solved on 2026-10-07 by 15:48:17 UTC, 1 min 10 s after the first start. The Single Solve took 14 s, the solver was extended in under a minute, and the Time Trial (1.71 s of 6 s) and the Mastery (1.49 s of 6 s) each went through on the first submit.

Takeaways

  • Answer the field that's asked. pdf.header_version means the header, even though a reader would honour a higher catalog /Version. Report the other, but don't mix them up.
  • Plan for the absent case. The format said none is allowed, so the solver answers none for a file with no PDF header, rather than failing or guessing.
  • Stop on the genuinely ambiguous case. A header after junk bytes is valid to a reader but might not count here. Stopping costs a retry; guessing could cost a wrong answer.
  • The Metadata lane is now six formats deep: zip names, magic bytes, text encodings, JPEG, PNG, and PDF, all on one shared base.