c0rrupt: PNG Binary Specification & Hex Reconstruction

“We found this file. Recover the flag. You can also find the file in /problems/c0rrupt_0_1fcad1353b2255f250d60c14afed2100 on the shell server.”
picoCTF{...}Forensic Triage: PNG Chunk Structure Corruption
When an image viewer fails to render a file, the parser is encountering invalid signatures or checksum mismatches during chunk header validation. In mystery, the file has deliberate byte corruption across the 8-byte file signature, the critical IHDR header length and chunk name, and downstream chunk metadata (pHYs and IDAT), causing parsers like libpng to reject the file immediately.
1. The W3C PNG Binary Standard
To repair any damaged image file, you must master the binary architecture of the PNG (Portable Network Graphics) standard. Every valid PNG has two components:
1. The 8-Byte Magic Header (Official Signature):
Hex: 89 50 4E 47 0D 0A 1A 0A
ASCII: \x89 P N G \r \n \x1a \n
2. Sequential Data Chunks (Each chunk follows this exact 4-field rule):
┌──────────────────┬──────────────────┬──────────────────────────┬──────────────────┐
│ Length (4 Bytes) │ Type (4 Bytes) │ Data (Length Bytes) │ CRC32 (4 Bytes) │
├──────────────────┼──────────────────┼──────────────────────────┼──────────────────┤
│ 00 00 00 0D │ 49 48 44 52 │ Width, Height, Bit Depth │ 7C 8B AB 78 │
│ (13 bytes data) │ ("IHDR" ASCII) │ Color Type, Compression │ (Checksum test) │
└──────────────────┴──────────────────┴──────────────────────────┴──────────────────┘If the chunk name is misspelled or the Length doesn't match the number of data bytes, the CRC32 check fails, and the image viewer aborts rendering.
Below is what a healthy, uncorrupted PNG file looks like when inspected in a hex editor. Notice how the first 8 bytes contain the universal magic signature, immediately followed by the IHDR chunk header:

Offset 0x00: 89 50 4E 47 0D 0A 1A 0A (.PNG\r\n\x1a\n)
Offset 0x0C: 49 48 44 52 (Chunk Type: IHDR)
Offset 0x00: 89 65 4E 34 0D 0A B0 AA (.eN4...)
Offset 0x0C: 43 22 44 52 (Scrambled chunk: C"DR)
mystery file directly against this golden hex template, our goal is clear: change the corrupted bytes back to the specification standard, save the repaired file as fixed.png, and the visual image along with the secret flag will immediately render!2. Diagnostic Reconnaissance with `pngcheck`
Before touching any bytes, we run the automated diagnostic utility pngcheck to pinpoint the exact failing offsets:
3. Manual Hex Surgery (Click-by-Click via Hex Editor)
Learn how to edit raw bytes manually using HexEd.it (in your browser) or HxD on Windows.
Manual Setup Instructions:
- Open https://hexed.it/ in Chrome/Edge or open HxD.
- Click Open file and select
mystery. - CRITICAL RULE: Make sure the mode at the bottom says OVR (Overwrite), not INS (Insert). (Insert pushes old bytes and ruins the file size).
- Never press Spacebar! The editor automatically advances to the next box after every 2 characters.
Why it broke: The author replaced .PNG with .eN4.
Why it broke: Right after the 4-byte length 00 00 00 0D, the chunk type spells C"DR instead of IHDR.
Why it broke: The pHYs chunk defines physical pixels per meter ($5669 \times 5669$). An extra corrupted byte AA threw off the chunk length and CRC checksum.
Why it broke: The actual image pixel data chunk header was scrambled from IDAT into \xabDET with an invalid length prefix.
0x00031851 in hex.In HexEd.it, click Export → Save as fixed.png → Open with Windows Photo Viewer!
4. The 0.1-Second Python Fixer (`solve.py`)
In competition environments, manually typing hex bytes is too slow. Once you know what chunks are broken, you write an automated Python script using bytearray to slice and replace the corrupted segments in memory in 1 millisecond:
# Open the corrupted mystery file as raw bytes
with open("mystery", "rb") as f:
data = bytearray(f.read())
# Fix 1: PNG Magic Header (Bytes 0 to 7)
data[0:8] = b"\x89PNG\r\n\x1a\n"
# Fix 2: First chunk name -> IHDR (Bytes 12 to 15)
data[12:16] = b"IHDR"
# Fix 3: pHYs chunk data (Offset 0x46)
data[0x46:0x4A] = b"\x00\x00\x16\x25"
# Fix 4: IDAT chunk length and name (Offset 0x53)
data[0x53:0x5B] = b"\x00\x03\x18\x51IDAT"
# Save the fully repaired image
with open("fixed.png", "wb") as f:
f.write(data)
print("[+] Successfully repaired! fixed.png generated.")python -c "d=bytearray(open('mystery','rb').read()); d[0:8]=b'\x89PNG\r\n\x1a\n'; d[12:16]=b'IHDR'; d[0x46:0x4A]=b'\x00\x00\x16\x25'; d[0x53:0x5B]=b'\x00\x03\x18\x51IDAT'; open('fixed.png','wb').write(d); print('fixed.png generated!')"5. Restored Evidence & Extracted Flag
Visual Verification of Repaired Image (fixed.png):

6. DFIR Diagnostic Cheat Sheet
| Tool / Utility | Syntax | Purpose |
|---|---|---|
| pngcheck | pngcheck -v file.png | Verifies magic bytes, chunk sequence, CRC32 checksums, and reports offset of failure. |
| HexEd.it / HxD | GUI Hex Editor (OVR mode) | Allows manual byte-by-byte carving, patching, and binary inspection. |
| Python bytearray | data[start:end] = b"..." | Instant programmatic byte patching in memory without installing external tools. |
7. The Complete Investigation Path & Mental Roadmap
Here is the complete chronological path and thought process from the initial broken file to the final extracted flag:
Downloaded mystery. Opening it failed with “Invalid image format”. Running pngcheck -v mystery flagged invalid signature bytes at offset 0x00.
Opened file in HexEd.it. Discovered .eN4 instead of .PNG. Replaced bytes 0..7 with official PNG signature 89 50 4E 47 0D 0A 1A 0A.
Fixed corrupted chunk name C"DR back to IHDR. Removed extra stray byte AA from pHYs chunk to fix pixel resolution and restore CRC32 checksum.
Calculated exact remaining payload size (202,887 - 54 = 202,833 bytes = 0x00031851). Replaced corrupted \xabDET with 00 03 18 51 49 44 41 54.
Exported fixed.png. Double-clicked image to render the visual flag: picoCTF{c0rrupt10n_15_n3v3r_50_b4d_534e7cb8}.