Athena Wiki
SteganographyImage Steganography

PNG Analysis

steganographyintermediate

Deep-dive PNG structure analysis - chunk inspection, CRC manipulation, and hidden data extraction.

steganographypngchunkscrctEXtiTXtidatbinwalkpython

PNG Analysis

PNG files have a rich internal structure that CTF challenge authors exploit heavily. Flags can be hidden in chunk metadata, appended after IEND, embedded as different image dimensions, or encoded in color palette manipulation. Understanding PNG structure lets you find data that automated tools miss.


PNG File Structure

Mermaid diagram

Every PNG consists of a magic header followed by chunks. Each chunk has the format: [4-byte length][4-byte type][data][4-byte CRC].

ChunkDescription
IHDRImage header — width, height, bit depth, color type
PLTEColor palette (for indexed images)
IDATCompressed image data
IENDEnd marker (length=0, data=empty)
tEXtText metadata — keyword\0value
iTXtInternational text metadata
zTXtCompressed text metadata
bKGDBackground color
tIMELast modification time

Chunk types starting with a lowercase first letter are "ancillary" (optional). CTF challenges often insert custom ancillary chunks with hidden data.


Systematic Analysis

Validate the PNG structure

pngcheck -v suspicious.png

This immediately flags CRC errors, truncated chunks, or invalid chunk sequences.

List all chunks

import struct

data = open('suspicious.png','rb').read()
pos = 8  # skip magic
while pos < len(data):
    length = struct.unpack('>I', data[pos:pos+4])[0]
    ctype  = data[pos+4:pos+8].decode()
    cdata  = data[pos+8:pos+8+length]
    crc    = data[pos+8+length:pos+12+length].hex()
    print(f'{ctype:8s}  len={length:6d}  offset=0x{pos:06x}  crc={crc}')
    pos += 12 + length

Check text chunks for embedded flags

Look specifically for tEXt, iTXt, zTXt chunks — these often contain the flag directly.

Check for data after IEND

data = open('suspicious.png','rb').read()
iend_pos = data.rfind(b'IEND')
after_iend = data[iend_pos + 8:]
if after_iend:
    print(f"Data after IEND: {len(after_iend)} bytes")
    open('after_iend.bin','wb').write(after_iend)

Verify IHDR CRC — detect dimension manipulation

If pngcheck reports a CRC error in IHDR, the width/height may have been tampered with to hide rows.

LSB analysis

zsteg suspicious.png
zsteg -a suspicious.png

Common CTF Hiding Places

Text metadata chunks often contain flags directly:

import struct, zlib

data = open('suspicious.png','rb').read()
pos = 8
while pos < len(data):
    length = struct.unpack('>I', data[pos:pos+4])[0]
    ctype  = data[pos+4:pos+8].decode()
    cdata  = data[pos+8:pos+8+length]

    if ctype in ('tEXt', 'iTXt'):
        print(f"[{ctype}] {cdata[:200]}")
    elif ctype == 'zTXt':
        null = cdata.index(b'\x00')
        keyword = cdata[:null].decode()
        compressed = cdata[null+2:]
        decompressed = zlib.decompress(compressed)
        print(f"[zTXt] key={keyword}: {decompressed[:200]}")

    pos += 12 + length

Extracting and Decompressing IDAT

import struct, zlib

data = open('suspicious.png','rb').read()

idat_data = b''
pos = 8
while pos < len(data):
    length = struct.unpack('>I', data[pos:pos+4])[0]
    ctype  = data[pos+4:pos+8].decode()
    if ctype == 'IDAT':
        idat_data += data[pos+8:pos+8+length]
    pos += 12 + length

raw_pixels = zlib.decompress(idat_data)

Stegsolve Equivalent in Python

from PIL import Image
import numpy as np

img = Image.open('suspicious.png').convert('RGB')
pixels = np.array(img)

# Extract all 24 bit planes
for channel, name in enumerate(['R','G','B']):
    for bit in range(8):
        plane = ((pixels[:,:,channel] >> bit) & 1) * 255
        Image.fromarray(plane.astype('uint8')).save(f'{name}_bit{bit}.png')

print("Saved all 24 bit planes: R_bit0.png through B_bit7.png")

Checklist

  • pngcheck -v suspicious.png
  • Parse all chunks — look for tEXt/iTXt/zTXt
  • Check for data after IEND
  • Verify IHDR CRC — if wrong, brute-force true width/height
  • Open in Stegsolve / Aperisolve
  • Check LSB in all channels with Python
  • binwalk -e for embedded files
  • Compare file size vs expected size for given dimensions

Last updated on

On this page