DEV Community

rudy_candy
rudy_candy

Posted on • Originally published at alsavaudomila.com

pngcheck in CTF: How to Analyze and Repair PNG Files

🔍 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%).
Enter fullscreen mode Exit fullscreen mode

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).
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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).
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Or with verbose:

  chunk IEND at offset 0x25801, length 0
additional data after IEND chunk
Enter fullscreen mode Exit fullscreen mode

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

  1. Run pngcheck -fvp challenge.png immediately — even before trying to open the file
  2. Read every line of output. Non-standard chunk names are red flags
  3. If there's a CRC error in IHDR, check the dimensions with a hex editor before doing anything else
  4. If it passes cleanly, note any tEXt, zTXt, or iTXt chunks — extract their content
  5. Check for data after IEND
  6. Only switch to steganography tools (zsteg, stegsolve) after structural analysis is complete

Installing pngcheck

# Debian/Ubuntu/Kali
sudo apt install pngcheck

macOS

brew install pngcheck

Enter fullscreen mode Exit fullscreen mode




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)