PICOCTF 2019 • FORENSICS • FILE HEADER SURGERY

c0rrupt: PNG Binary Specification & Hex Reconstruction

By Abdo•Aug 31, 2026
c0rrupt Challenge Analysis
Official Challenge Prompt

“We found this file. Recover the flag. You can also find the file in /problems/c0rrupt_0_1fcad1353b2255f250d60c14afed2100 on the shell server.”

● Category: Forensics / File Repair● Points: 250 PTS● Flag Format: picoCTF{...}
Provided File (Download & Practice)Download mysterySize: 202,887 bytesType: Corrupted PNG Binary
Dissector: HexEd.it / HxD

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.

The Golden Standard: Valid PNG Header in Bless Hex EditorBenchmark Reference • PNG-Gradient.png

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:

Valid PNG Header shown in Bless Hex Editor
Standard Healthy PNG (Reference):

Offset 0x00: 89 50 4E 47 0D 0A 1A 0A (.PNG\r\n\x1a\n)
Offset 0x0C: 49 48 44 52 (Chunk Type: IHDR)

Our Sabotaged File (`mystery`):

Offset 0x00: 89 65 4E 34 0D 0A B0 AA (.eN4...)
Offset 0x0C: 43 22 44 52 (Scrambled chunk: C"DR)

The Core Forensic Objective: By comparing our broken 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:

$ pngcheck -v mystery
mystery: not a PNG file (starts with 89 65 4e 34 0d 0a b0 aa)
ERROR: mystery is corrupted at offset 0x00000000
METHOD A: HANDS-ON MANUAL REPAIR

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:

  1. Open https://hexed.it/ in Chrome/Edge or open HxD.
  2. Click Open file and select mystery.
  3. CRITICAL RULE: Make sure the mode at the bottom says OVR (Overwrite), not INS (Insert). (Insert pushes old bytes and ruins the file size).
  4. Never press Spacebar! The editor automatically advances to the next box after every 2 characters.
Fix #1: The 8-Byte Magic Header (Offset 0x00000000)Row 1, Bytes 0-7

Why it broke: The author replaced .PNG with .eN4.

Current Corrupted Bytes:
89 65 4E 34 0D 0A B0 AA
ASCII: .eN4....
Click byte 65 & Type:
89 50 4E 47 0D 0A 1A 0A
ASCII: .PNG\r\n\x1a\n
Fix #2: First Chunk Type → IHDR (Offset 0x0000000C)Row 1, Bytes 12-15

Why it broke: Right after the 4-byte length 00 00 00 0D, the chunk type spells C"DR instead of IHDR.

Current Corrupted Bytes:
43 22 44 52
ASCII: C"DR
Click 43 & Type:
49 48 44 52
ASCII: IHDR
Fix #3: pHYs Pixel Density Data (Offset 0x00000046)Row 00000040

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.

Current Corrupted Bytes:
AA 00 16 25 00 00 16 25 01
Click AA & Type:
00 00 16 25 00 00 16 25 01
Fix #4: IDAT Compressed Image Data (Offset 0x00000053)Row 00000050

Why it broke: The actual image pixel data chunk header was scrambled from IDAT into \xabDET with an invalid length prefix.

How we calculate IDAT length: Total file size (202,887 bytes) minus all header and other chunk bytes = 202,833 bytes = 0x00031851 in hex.
Current Corrupted Bytes:
AA AA FF A5 AB 44 45 54
ASCII: ....DET
Click AA & Type 8 Bytes:
00 03 18 51 49 44 41 54
ASCII: ...QIDAT (202,833 bytes)

In HexEd.it, click Export → Save as fixed.png → Open with Windows Photo Viewer!

METHOD B: AUTOMATED FAST SOLVE & ONELINER

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.")
Terminal One-Liner (PowerShell / Bash):
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):

c0rrupt Restored Image
picoCTF{c0rrupt10n_15_n3v3r_50_b4d_534e7cb8}

6. DFIR Diagnostic Cheat Sheet

Tool / UtilitySyntaxPurpose
pngcheckpngcheck -v file.pngVerifies magic bytes, chunk sequence, CRC32 checksums, and reports offset of failure.
HexEd.it / HxDGUI Hex Editor (OVR mode)Allows manual byte-by-byte carving, patching, and binary inspection.
Python bytearraydata[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:

STEP 1
Initial Triage & Failure Mode

Downloaded mystery. Opening it failed with “Invalid image format”. Running pngcheck -v mystery flagged invalid signature bytes at offset 0x00.

STEP 2
Magic Header Surgery

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.

STEP 3
IHDR & pHYs Metadata Correction

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.

STEP 4
IDAT Payload Calculation & Restoration

Calculated exact remaining payload size (202,887 - 54 = 202,833 bytes = 0x00031851). Replaced corrupted \xabDET with 00 03 18 51 49 44 41 54.

STEP 5
Export, Verify & Capture Flag

Exported fixed.png. Double-clicked image to render the visual flag: picoCTF{c0rrupt10n_15_n3v3r_50_b4d_534e7cb8}.