Executive Summary
challenge.png was a PNG file that had been deliberately mangled at the byte level so that neither the OS, exiftool, nor pngcheck would recognize it as a valid image. The fix required manually repairing the file's binary structure in three passes: restoring the correct PNG magic-byte signature, correcting a zeroed-out height field in the IHDR chunk, and recomputing/patching the IHDR chunk's CRC32 checksum so PNG parsers would trust the corrected header. Once valid, the file opened cleanly and displayed the flag as plain text rendered on a solid background.
Root cause / challenge design: the PNG format's own self-describing structure (magic bytes → chunk length → chunk type → chunk data → CRC) was used as the puzzle itself — each field had to be identified, understood, and hand-repaired in sequence, with each fix only revealing the next thing that was still broken.
Flag: bronco{wh4t_ar3_mag1c_byt3s}
Initial Triage
Every standard tool refused to touch the file, which was the first clue this wasn't image corruption — it was intentional tampering:
$ file challenge.png
challenge.png: data
$ exiftool challenge.png
Error : File format error
$ pngcheck -v challenge.png
this is neither a PNG or JNG image nor a MNG stream
$ binwalk challenge.png
41 0x29 Zlib compressed data, default compression
That last line was the tell: binwalk found valid zlib-compressed data (the IDAT chunk's payload) sitting at a normal-looking offset inside the file. If the compressed image data was intact, the surrounding PNG container was what had been broken — not the image itself.
Reading the Raw Bytes
$ hexdump challenge.png
0000000 adde efbe 0000 0000 0000 0d00 4849 5244
hexdump's default output groups bytes as little-endian 16-bit words, which reads backwards from the actual byte order. Untangling it, the first 8 bytes on disk were:
DE AD BE EF 00 00 00 00
— DEADBEEF, a classic placeholder/joke value, sitting exactly where the PNG magic-byte signature belongs. The real signature is a fixed 8-byte constant every valid PNG must start with:
89 50 4E 47 0D 0A 1A 0A
Every tool's refusal to parse the file made sense immediately: this signature is the very first thing any PNG-aware library checks before reading anything else.
Fix 1 — Magic Bytes
Patched the first 8 bytes to the correct signature (via hex editor):
DE AD BE EF 00 00 00 00 → 89 50 4E 47 0D 0A 1A 0A
Re-running exiftool afterward showed real progress — the file was now recognized as a PNG, with a plausible IHDR chunk:
File Type : PNG
Image Width : 500
Image Height : 0
Recognized, but not correct — height was 0, which meant the image had no pixel rows to decode.
Fix 2 — IHDR Height Field
The IHDR chunk layout (after the 4-byte length + IHDR type tag) is:
width (4 bytes) | height (4 bytes) | bit depth | color type | compression | filter | interlace
$ xxd -g1 -l 32 challenge.png
00000010: 00 00 01 f4 00 00 00 00 08 02 00 00 00 00 00 00
└─ width=0x1F4 (500) ─┘ └─ height=0 ─┘
Width was correctly 0x000001F4 (500px), but height had been zeroed. Since the image was expected to be square (500×500, a reasonable assumption confirmed once it rendered), the height field was patched to match the width:
printf '\x00\x00\x01\xf4' | dd of=challenge.png bs=1 seek=20 conv=notrunc
$ exiftool challenge.png
Image Width : 500
Image Height : 500
Correct dimensions now — but the chunk still wouldn't validate, because editing the IHDR data invalidated its trailing CRC32 checksum.
Fix 3 — IHDR CRC32
Every PNG chunk ends with a CRC32 computed over its type tag + data (not including the length field). Recomputed it in Python to match the corrected IHDR contents:
import zlib
chunk = b'IHDR' + bytes.fromhex(
'000001f4' # width = 500
'000001f4' # height = 500
'0802000000' # bit depth, color type, compression, filter, interlace
)
crc = zlib.crc32(chunk) & 0xffffffff
print(crc.to_bytes(4, "big").hex())
# 44b448dd
Patched the 4 CRC bytes immediately following the IHDR data (offset 29):
printf '\x44\xb4\x48\xdd' | dd of=challenge.png bs=1 seek=29 conv=notrunc
At this point the file was a fully valid, standards-compliant PNG.
Result
feh challenge.png
Opened cleanly — a 500×500 image with a solid blue banner containing the flag rendered as plain white text:
bronco{wh4t_ar3_mag1c_byt3s}
Key Techniques
| # | Technique | Purpose |
|---|---|---|
| 1 | Reading raw bytes with hexdump/xxd and reasoning about endianness |
Identify that the magic-byte signature had been swapped for DEADBEEF
|
| 2 | Knowing the fixed PNG signature (89 50 4E 47 0D 0A 1A 0A) |
Restore the container so parsers would even attempt to read it |
| 3 | Understanding IHDR chunk layout (width/height/bit depth/etc.) |
Locate and correct the zeroed height field |
| 4 | Recomputing CRC32 over type + data with zlib.crc32
|
Make the corrected chunk pass integrity validation |
| 5 |
binwalk to confirm the actual image payload (zlib/IDAT) was intact |
Confirmed the fix only needed to target the container, not the pixel data |
Lessons / Takeaways
- File format "magic bytes" and per-chunk checksums exist specifically to let parsers fail fast and safely on malformed input — which also makes them a clean, minimal way to build a forensics puzzle: break the smallest number of bytes needed to make every standard tool refuse the file.
-
binwalkis a good first move on "this file won't open" challenges — it fingerprints embedded structures (like a valid zlib stream) independent of whether the outer container parses, which tells you whether you're repairing a container or recovering lost data. - PNG CRCs are computed over
chunk type + chunk data, not the 4-byte length prefix — an easy mistake to make when hand-rolling the checksum.
Attack Chain
challenge.png (fails file/exiftool/pngcheck)
│
├── binwalk ──► valid zlib/IDAT data found ──► container is broken, not the image
│
├── hexdump first bytes ──► DE AD BE EF 00 00 00 00 (fake signature)
│ │
│ └── patch ──► 89 50 4E 47 0D 0A 1A 0A (real PNG signature)
│
├── exiftool ──► Height = 0
│ │
│ └── patch IHDR height (offset 20) ──► 000001F4 (500)
│
├── pngcheck still invalid ──► IHDR CRC mismatch
│ │
│ └── recompute CRC32(type+data) ──► patch offset 29 ──► 44 B4 48 DD
│
▼
Valid PNG ──► feh challenge.png
│
▼
FLAG: bronco{wh4t_ar3_mag1c_byt3s}
Top comments (0)