🔍 pngcheck CTF Tutorial: How to Analyze Corrupted PNG Files and Find Hidden Chunks
Searching for "pngcheck CTF" or "how to fix corrupted PNG forensics" usually returns tool documentation or terse Writeups that skip the thinking. This article is different: it's a walkthrough of how I actually use pngcheck in CTF PNG forensics challenges — including the 30 minutes I wasted on steganography tools before I learned to validate structure first. If you're stuck on a corrupted PNG challenge and wondering what pngcheck is showing you, this guide will get you unstuck.
This Article at a Glance
pngcheck is a command-line PNG validation tool that reads a PNG file's internal chunk structure and reports exactly what's wrong — or what's hidden — at the byte level. In CTF forensics, it's the fastest way to diagnose a corrupted PNG, find non-standard chunks, and decide whether you're dealing with a structural fix challenge or a steganography challenge. By the end of this article, you'll know when to run it, what its output means, and — just as importantly — when to put it down and switch tools.
Introduction: The PNG Challenge Where I Used Every Tool Except the Right One
CTF forensics challenges love PNG files. They're binary, they have a well-documented structure, and there are a dozen ways to hide data inside them without visually changing the image. The problem for beginners is that a corrupted or manipulated PNG doesn't announce itself — it just fails to open, or opens fine while hiding something in the chunk data you never look at.
pngcheck is the tool that makes the invisible visible. It reads the raw PNG chunk stream and validates every piece of the structure: the magic header bytes, the IHDR dimensions, each IDAT chunk's CRC, the IEND terminator, and anything else lurking in between. It won't decrypt anything or extract hidden images — but it will tell you precisely where the file is broken, where extra data is hiding, and what every chunk in the file actually contains.
The challenge that taught me this was picoCTF's Corrupted File — a PNG that wouldn't open, a description that said "I tried to open this image but something seems off," and me spending 30 minutes going down the wrong path completely. My first instinct was steganography. I ran zsteg challenge.png, got output I didn't understand, tried every channel combination. Nothing. I tried stegsolve and clicked through every filter. Still nothing. I even tried strings and grepped for picoCTF{. The flag wasn't there because the image wasn't a steganography challenge. It was a broken CRC challenge. One pngcheck -v would have shown me the answer in 2 seconds. The reason I didn't run it first: I associated PNG challenges with hidden pixel data and never considered that the file structure itself was the puzzle.
What is pngcheck? (And What It Isn't)
What pngcheck actually does
A PNG file is a sequence of chunks. Each chunk has a type (4-byte name like IHDR, IDAT, tEXt), a length, data, and a CRC checksum. pngcheck reads every chunk in order, validates the CRC, checks that required chunks are present in the right order, and reports anything unexpected.
The basic output looks like this:
$ pngcheck challenge.png
OK: challenge.png (800x600, 24-bit RGB, non-interlaced, 92.3%).
And verbose output — which is what you actually want in CTF — looks like this:
$ pngcheck -v challenge.png
File: challenge.png (153847 bytes)
chunk IHDR at offset 0x0000c, length 13
800 x 600 image, 24-bit RGB, non-interlaced
chunk tEXt at offset 0x00025, length 36, keyword: Comment
chunk IDAT at offset 0x00057, length 8192 (OK)
chunk IDAT at offset 0x02065, length 8192 (OK)
chunk IEND at offset 0x25819, length 0
No errors detected in challenge.png (5 chunks, 92.3% compression).
What pngcheck cannot do
This is the part beginners miss. pngcheck is a validator and inspector — it is not an extractor or a decoder. It will tell you that a tEXt chunk exists with keyword "Comment," but it won't show you the content of that comment in basic mode. It will tell you there's data after the IEND chunk, but it won't extract it. It validates structure; everything else needs another tool.
| Task | pngcheck can do it? | Use this instead |
|---|---|---|
| Find broken CRC | ✅ Yes | — |
| List all chunks and offsets | ✅ Yes | — |
| Detect extra data after IEND | ✅ Yes | — |
| Extract hidden text from tEXt chunks | ⚠️ Partial (shows keyword only) |
strings, exiftool
|
| Detect LSB steganography | ❌ No | zsteg |
| Extract embedded files | ❌ No | binwalk -e |
| Fix broken CRC | ❌ No | hex editor + manual CRC calculation |
| Repair corrupted IHDR dimensions | ❌ No | hex editor + pngcheck to verify fix |
When to Use pngcheck in CTF
Problem description keywords that should trigger pngcheck
I've developed a reflex: if a PNG challenge mentions any of these, pngcheck runs first before anything else:
- "The image is corrupted" or "won't open"
- "Something is wrong with the file"
- "Check the structure" or "check the chunks"
- "The file passes validation but something is off"
- Any hint involving CRC, chunk, header, or IHDR
pngcheck vs zsteg vs binwalk — When to Use Which
The trap I fell into on Corrupted File — and then again on two other challenges before I finally learned — was running steganography tools on a PNG that had a structural problem. My reasoning at the time: "It's a PNG challenge, so it's probably steganography." That assumption is wrong about half the time. Here's the decision logic I've built since then:
Use pngcheck when: the file won't open, the challenge mentions "corruption," "chunks," or "structure," or you want to enumerate what chunks exist before doing anything else. pngcheck answers the question: is this file structurally valid?
Use zsteg when: pngcheck reports no errors and the image opens normally. zsteg checks for LSB-encoded data hidden in pixel channels — it operates entirely at the pixel level and doesn't care about chunk structure. If pngcheck says the file is clean, zsteg is your next move.
Use binwalk when: you suspect an entirely different file is embedded somewhere inside the PNG, or when pngcheck reports extra data after IEND and you want to extract it cleanly. binwalk signature-scans the raw bytes regardless of format.
The decision order that works for me: pngcheck -fvp first, always. If it passes → zsteg. If it fails with extra data → binwalk -e. If it fails with CRC → hex editor to patch. This sequence alone has saved me from countless Rabbit Holes. (For a deeper look at binwalk, see CTF Forensics: How to Use binwalk to Extract Hidden Files.)
Basic Usage With Thinking
Step 1 — Basic validation: is it broken at all?
$ pngcheck challenge.png
CRC error in chunk IHDR (computed 4a3f2c1b, expected 00000000)
ERRORS DETECTED in challenge.png
If this returns an error, you have a structural problem. The chunk name tells you where to look. IHDR error = broken header. IDAT error = broken image data. CRC mismatch = someone modified a byte somewhere. The next question is whether it was intentional (challenge design) or accidental (corrupted file).
Step 2 — Verbose output: read every chunk
$ pngcheck -v challenge.png
This is the command I run on every PNG challenge now, even before checking if it opens. The verbose output shows chunk names, offsets, lengths, and CRC status. I'm looking for:
- Any chunk with a CRC error
- Non-standard chunk names (anything that isn't IHDR/IDAT/IEND/tEXt/zTXt/gAMA/etc.)
- Chunks in wrong order (IDAT before IHDR is invalid)
- Content after IEND
Step 3 — Maximum detail: zlib and compression info
$ pngcheck -fvp challenge.png
The -f flag forces pngcheck to continue checking even after errors (useful when multiple chunks are broken). The -p flag prints the contents of non-critical chunks including text. This is how I found a base64-encoded flag sitting in a tEXt chunk with keyword "Author" — the image opened perfectly, the flag was in plain sight in the chunk data, and I'd wasted 20 minutes on pixel-level steg before checking this.
The Three Most Common CTF Scenarios
Scenario 1: Broken CRC — The Most Common Trap
A challenge author modifies a chunk's data without recalculating the CRC. pngcheck catches it immediately:
$ pngcheck -v challenge.png
chunk IHDR at offset 0x0000c, length 13
800 x 600 image, 24-bit RGB, non-interlaced
CRC error in chunk IHDR (computed 4a3f2c1b, expected 1a2b3c4d)
ERRORS DETECTED in challenge.png
The CRC mismatch means the IHDR data was modified after the CRC was set. In CTF, this almost always means the image dimensions were changed — the real dimensions were replaced with smaller values to crop out the hidden content. The flag is in the part of the image that was "hidden" by reducing the reported height or width.
To fix it: find the correct CRC value for the real IHDR data, then patch the file. Here's the Python approach I use:
import struct, zlib
with open("challenge.png", "rb") as f:
data = f.read()
# IHDR chunk data is at bytes 12-28 (after 8-byte signature + 4-byte length + 4-byte type)
ihdr_data = data[12:29] # 4 (length) + 4 (IHDR) + 13 (data) = offset 12 to 28
chunk_type_and_data = data[16:29] # just "IHDR" + 13 bytes of data
correct_crc = zlib.crc32(chunk_type_and_data) & 0xFFFFFFFF
print(f"Correct CRC: {correct_crc:#010x}")
# Patch: replace bytes 29-33 with correct CRC
patched = data[:29] + struct.pack(">I", correct_crc) + data[33:]
with open("fixed.png", "wb") as f:
f.write(patched)
After patching, run pngcheck fixed.png to confirm it passes, then open the image. If the dimensions were manipulated, the fixed file will render at the real size and reveal the hidden area. This pattern is so common in picoCTF and beginner CTFs that I now check for dimension mismatches as the first instinct whenever I see an IHDR CRC error.
Scenario 2: Hidden Custom Chunks
PNG allows custom (ancillary) chunks. They're valid PNG — most image viewers ignore unknown chunks silently. pngcheck lists them:
$ pngcheck -v challenge.png
chunk IHDR at offset 0x0000c, length 13
chunk IDAT at offset 0x00025, length 8192 (OK)
chunk IEND at offset 0x25801, length 0
chunk flAg at offset 0x25815, length 42
(unknown ancillary chunk)
No errors detected in challenge.png (4 chunks).
That flAg chunk after IEND is not standard. The data inside it is the flag. pngcheck found it in one command. Without it, I'd be running steganography tools on an image that wasn't hiding anything in its pixels at all.
Scenario 3: Data Appended After IEND
The IEND chunk is supposed to be the last chunk in a PNG. Data after it is technically invalid, but most image viewers load the image anyway and ignore the trailing bytes. pngcheck flags it:
$ pngcheck challenge.png
invalid chunk name "" (00 00 00 00)
ERRORS DETECTED in challenge.png
Or with verbose:
chunk IEND at offset 0x25801, length 0
additional data after IEND chunk
From the offset of IEND, you can calculate exactly where the appended data starts and extract it with dd or binwalk.
Common Mistakes and Rabbit Holes
The Corrupted File mistake was the first one. The second was a different challenge where pngcheck reported "No errors detected" — so I assumed I'd checked everything and moved to pixel-level steganography. I spent another 20 minutes on zsteg before going back and running pngcheck -fvp. The -p flag I'd skipped revealed a tEXt chunk with keyword "flag" containing a base64 string. It was sitting there in plain text the whole time. The file was clean structurally — the data was just hidden in a chunk I hadn't looked at.
The third mistake: I saw an IHDR CRC error, correctly identified that dimensions had been manipulated, patched the CRC — and then stopped. The image rendered at the new larger size, but I didn't look at what was in the newly revealed area carefully enough. The flag was written in white text on a white background in the bottom 50 pixels that had been hidden. I would have seen it immediately if I'd opened the image in a tool that let me invert colors. Lesson: when you fix a CRC and the image changes size, the newly revealed area is where to look first.
| Mistake | What happens | How to avoid it |
|---|---|---|
| Running steganography tools on a structurally broken PNG | Tools either fail or give garbage output | Always run pngcheck -v first; fix structure before any steg analysis |
Stopping at "No errors detected" without -p
|
Miss flag stored in tEXt chunk content | Always use -fvp — "no errors" doesn't mean "nothing hidden" |
| Fixing CRC without examining what changed | Fix the structure but miss the actual clue in the revealed area | After patching, look at the newly visible area of the image first |
| Ignoring ancillary chunk names | Miss flag stored in custom chunk like flAg
|
Read every chunk name in verbose output; unusual names are the challenge |
Full Trial Process Table
| Step | Action | Command | Result | Decision |
|---|---|---|---|---|
| 1 | Open in image viewer | — | ❌ Won't open / blank | Structural problem suspected |
| 2 | Basic pngcheck | pngcheck challenge.png |
❌ CRC error in IHDR | IHDR was modified |
| 3 | Verbose pngcheck | pngcheck -v challenge.png |
âś… Offset + expected CRC shown | Dimensions likely manipulated |
| 4 | Check IHDR bytes in hex editor | hex editor at offset 0x10 | âś… Width/height values confirmed wrong | Restore real dimensions |
| 5 | Recalculate CRC and patch | Python CRC32 + hex editor | âś… pngcheck now passes | Image opens at correct dimensions |
| 6 | Full verbose check on fixed file | pngcheck -fvp fixed.png |
âś… Flag visible in tEXt chunk | Challenge solved |
Command Reference
| Command | Purpose | When to Use | Notes |
|---|---|---|---|
pngcheck file.png |
Basic validation | Quick first check | Shows pass/fail only |
pngcheck -v file.png |
Verbose chunk listing | Always, after basic check | Shows all chunk names, offsets, CRC status |
pngcheck -fvp file.png |
Full detail including text chunk contents | When looking for hidden data in chunks | -f forces continuation past errors |
pngcheck -c file.png |
Show chunk names only | Quick structural scan | Less detail than -v |
pngcheck -t file.png |
Print tEXt/zTXt chunk content | When verbose output shows text chunks | Useful for reading hidden comments |
Beginner Tips
My personal pngcheck workflow for every PNG challenge
- Run
pngcheck -fvp challenge.pngimmediately — even before trying to open the file - Read every line of output. Non-standard chunk names are red flags
- If there's a CRC error in IHDR, check the dimensions with a hex editor before doing anything else
- If it passes cleanly, note any
tEXt,zTXt, oriTXtchunks — extract their content - Check for data after IEND
- Only switch to steganography tools (
zsteg,stegsolve) after structural analysis is complete
Installing pngcheck
What You Learn From Using pngcheck
If you want to go further with the pattern this article teaches — validate structure before running analysis tools — the same mindset applies to disk images with mount and mmls, to ZIP archives with zip2john, and to PDFs with pdfdumper. Every binary format has a structure validator. Find it before running extraction tools.
Using pngcheck teaches you to read binary file structure before running tools. The PNG chunk format is one of the clearest examples of how a file format works internally — fixed headers, typed chunks, CRC integrity checks, a defined terminator. Once you understand why pngcheck exists and what it's validating, you start applying the same thinking to every binary challenge: what does the spec say this file should contain, and what does this specific file actually contain?
That gap between spec and reality is where CTF flags live. pngcheck is the tool that measures that gap for PNG files.
In real-world forensics, the same principle applies. Investigators validate file format integrity to detect tampering — a modified PNG with a recalculated CRC might look valid to an image viewer but will show the modification timestamp inconsistency at the chunk level. CTF PNG challenges are teaching you actual forensic thinking, not just CTF tricks.
Further Reading
This article is part of the Forensics Tools series. You can see the other tools covered in the series here: CTF Forensics Tools: The Ultimate Guide for Beginners. Introducing the pngcheck command, how to use it in CTF, and common patterns used in problems.
Here are related articles from alsavaudomila.com that complement what you've learned here about pngcheck:
Once pngcheck confirms that a PNG file is structurally clean and you still suspect hidden data, the next tool to reach for is zsteg. It operates entirely at the pixel level, scanning LSB-encoded channels that pngcheck cannot see. zsteg in CTF: Detect and Extract Hidden Data from Images walks through exactly when and how to use it, including the patterns that distinguish a clean image from one carrying hidden payloads.
When pngcheck reports extra data after the IEND chunk, the right tool to extract it is binwalk. Rather than manually calculating offsets with dd, binwalk signature-scans the raw bytes and pulls out embedded files automatically — regardless of filesystem or format boundaries. binwalk in CTF: How to Analyze Binaries and Extract Hidden Files explains how to read its output and avoid the common mistake of extracting too aggressively.
If pngcheck reveals a tEXt or iTXt chunk with suspicious content, exiftool can read the full metadata embedded across all chunk types — including GPS data, creation timestamps, and author fields that pngcheck shows as keywords but doesn't display in full. exiftool in CTF: How to Analyze Metadata and Find Hidden Data covers how metadata fields become hiding places in CTF challenges and what to look for.
Top comments (0)