July 3, 2026

Image Metadata and EXIF Forensics for CTF

EXIF metadata forensics for CTF: the flag hides in metadata, not pixels. Use exiftool, PNG text chunks, thumbnails, and strings to pull flags from EXIF and XMP.

The 30-second answer

You were handed an image. The flag is not in the picture you can see; it is in the data wrapped around the picture. That data is the metadata, and the single command that pulls almost all of it out is:

exiftool image.jpg
# see everything, including duplicate and unknown tags, grouped by family:
exiftool -a -u -g1 image.jpg

Read every line of the output. Flags love to sit in the Comment, Artist, UserComment, ImageDescription, or Copyright fields, and in the GPS block. If exiftool comes up empty, drop to strings image.jpg | grep -i pico and, for PNG specifically, look at the text chunks (tEXt, zTXt, iTXt). The rest of this post is the why and the edge cases, but those three moves solve the large majority of beginner metadata challenges.

Tip: If you only remember one flag, remember -a -u -g1. The -a keeps duplicate tags, -u shows tags exiftool would normally hide as unknown, and -g1 groups by the location the tag lives in so you can see whether a value came from EXIF, XMP, or IPTC. Authors hide flags exactly where the default view would skip them.

What metadata actually is, and where it lives

An image file is not just pixels. It is a container, and alongside the compressed pixel data it carries labelled records describing the photo: when it was taken, what camera took it, the GPS coordinates, a copyright string, a caption, a thumbnail. Those records are the metadata. A CTF author can type anything they like into one of those fields, which is why metadata is one of the first places a flag goes.

There are three common metadata standards you will meet, often in the same file:

  • EXIF (Exchangeable Image File Format) is the camera-centric one: exposure, ISO, timestamps, GPS, camera make and model, and free-text fields like UserComment and ImageDescription. It is stored in the APP1 segment of a JPEG. The spec is published by the CIPA EXIF standard.
  • XMP (Extensible Metadata Platform) is an XML blob, so it is human-readable if you open the file in a text editor. It holds titles, descriptions, ratings, and arbitrary custom namespaces. A flag dropped into XMP is often visible with plain strings because it is just text.
  • IPTC is the news and stock-photo standard: caption, keywords, byline, credit, headline. Editorial tools write here, so do CTF authors who want a slightly less obvious field than Comment.
The picture is the decoy. The metadata is the message. Most forensics newcomers stare at the pixels for an hour before they read the wrapper.

PNG files do not use EXIF the same way. Instead they carry text in dedicated chunks, which we cover below. The mental model that carries across all formats: a flag in metadata is a labelled string sitting next to the image, not woven into it.

exiftool is always the first move

ExifTool by Phil Harvey reads and writes metadata for hundreds of formats. It is the single most useful tool in image forensics and it should be the first thing you run on any image, every time, before you think about anything cleverer.

# install (Debian / Ubuntu / Kali)
sudo apt install libimage-exiftool-perl
# the default dump: clean, human-grouped
exiftool image.jpg
# the thorough dump: ALL tags, unknown tags, grouped by family 1
exiftool -a -u -g1 image.jpg
# pull one field by name (case-insensitive, fuzzy)
exiftool -Comment image.jpg
exiftool -UserComment image.jpg
# machine-readable output if you want to grep hard
exiftool -j image.jpg # JSON
exiftool -s -G image.jpg # short tag names with group prefix

The default exiftool image.jpg is enough to win many challenges on its own. When it is not, -a -u -g1 is the escalation: it refuses to hide anything. Authors who know players run the default sometimes tuck the flag into a duplicate tag or a vendor-specific MakerNote that only shows up with -u.

Note: exiftool is also a directory walker. Pointed at a folder it prints metadata for every file inside, which is handy when the challenge ships a zip of fifty near-identical photos and exactly one has a non-empty Comment: exiftool -Comment -r ./photos/ | grep -i pico.

The fields where flags actually hide

Once you have the full dump, you are scanning for a value that does not belong. Camera fields contain camera-shaped data; a flag stands out because it reads like English, like base64, or literally like picoCTF{...}. These are the usual homes:

  • Comment and UserComment are the classics. A flag pasted straight in, or base64-encoded, lives here more often than anywhere else.
  • Artist, Copyright, ImageDescription, and IPTC Caption-Abstract are the second tier: free-text fields a normal photo would leave blank or fill with a name.
  • GPS coordinates can be a flag by themselves. Sometimes the latitude and longitude point at a landmark whose name is the answer; sometimes the numbers decode to ASCII. Always read GPSLatitude and GPSLongitude, and try the coordinates on a map.
  • Software, DocumentName, and the XMP dc:description namespace catch the overflow when an author wants something less obvious.
Tip: When a field holds gibberish, suspect an encoding rather than encryption. Run it through base64, hex, or ROT13 before assuming it is junk: exiftool -Comment -b image.jpg | base64 -d. The -b flag outputs the raw binary value with no formatting, which is what you want to pipe into a decoder.

PNG text chunks: tEXt, zTXt, and iTXt

PNG stores text differently from JPEG. Instead of an EXIF block it uses dedicated text chunks defined in the PNG specification. There are three, and a flag can sit in any of them:

  • tEXt is uncompressed Latin-1 text, a keyword plus a value. Visible to strings and to exiftool.
  • zTXt is the same idea but zlib-compressed, so strings will not see it. exiftool decompresses it for you.
  • iTXt is international UTF-8 text, optionally compressed. Same story: exiftool reads it, raw strings may not.

The reliable way to read all three at once is exiftool, but it helps to confirm the chunks exist:

# exiftool decodes every text chunk type, compressed or not
exiftool -a -u -g1 image.png
# list the raw chunk structure to SEE which chunks are present
pngcheck -t -v image.png
# zTXt is compressed, so plain strings misses it; exiftool catches it
exiftool -Comment -Description -Title image.png
Warning: A zTXt chunk is the single most common reason a PNG challenge looks empty to strings but is not. If strings finds nothing and the challenge is clearly about metadata, you almost certainly have a compressed text chunk. Reach for exiftool or pngcheck -t -v immediately.

The embedded thumbnail trick

Many cameras and editors store a small thumbnail copy of the image inside the EXIF block. Here is the trap: when someone crops or paints over the main image, the embedded thumbnail is often left untouched. The visible photo hides the flag with a black box; the thumbnail still shows it. This is a genuine, recurring CTF move.

Extract the thumbnail as a separate file and open it:

# pull the embedded thumbnail out to its own file
exiftool -b -ThumbnailImage image.jpg > thumb.jpg
# some files store a larger preview instead
exiftool -b -PreviewImage image.jpg > preview.jpg
exiftool -b -JpgFromRaw image.cr2 > full.jpg
# then look at it
xdg-open thumb.jpg

Compare the thumbnail to the main image side by side. If they differ, the difference is almost always the answer. This is the metadata equivalent of finding an earlier draft of a document in its revision history.

Key insight: Cropping changes the pixels you can see; it does not regenerate the embedded thumbnail unless the editing software is careful to. That asymmetry is the whole trick. Whenever a photo looks deliberately censored, check the thumbnail before anything else.

strings and binwalk: the backstop

When exiftool does not surface a flag, the data may not be in a labelled field at all. It might be appended after the image data, stuffed into a comment marker exiftool does not parse, or hidden in a second file concatenated onto the first. Two blunt tools cover this:

# every printable run of 4+ chars; grep for the flag format
strings -n 6 image.jpg | grep -i 'pico\|flag\|ctf'
# include the byte offset so you know WHERE the string sits
strings -t x image.jpg | grep -i pico
# is there a second file hidden inside or appended?
binwalk image.jpg
binwalk -e image.jpg # extract anything it recognises

strings is the catch-all that finds plain-text flags wherever they sit. binwalk is for the case where the image is actually a carrier: a ZIP, a second JPEG, or a script appended after the real image ends. If binwalk reports an embedded archive, that is usually the real puzzle and the metadata was a misdirection.

Note: strings only finds printable, uncompressed text. A base64 blob shows up; a zlib-compressed zTXt chunk or genuinely encrypted data does not. A negative strings result never proves the flag is not there; it only proves it is not sitting in plaintext.

Metadata is not the same as pixel steganography

This is the distinction that decides which tools you reach for. Metadata lives in the container around the image, in labelled fields you can read with exiftool. Pixel-level steganography hides data inside the pixel values themselves, typically in the least significant bits, where no field name exists and exiftool sees nothing unusual.

Metadata (this post)

Flag sits in EXIF, XMP, IPTC, or a PNG text chunk. Tools: exiftool, strings, pngcheck. Fast to check, always check first.

Pixel stego (other post)

Flag is encoded in the pixel bits. Tools: zsteg, steghide, stegsolve, LSB extractors. exiftool will not find it.

The workflow is sequential: run exiftool and strings first because they are seconds of effort, and only escalate to pixel analysis when the metadata is clean. For the pixel-level half of the picture, see the CTF Steganography guide. When the image turns out to be a carrier for an entirely different file, the Hex Dumps for CTF and Disk Forensics posts cover reading and carving raw bytes.

Key insight: If you find yourself running zsteg and steghide before you have run exiftool, you are skipping the cheap check for the expensive one. Metadata first, pixels second. Always.

Writing and adding metadata yourself

You occasionally need to write metadata, not just read it: to confirm how a field is stored, to craft a test image, or because a challenge asks you to round-trip a value. exiftool writes as easily as it reads.

# set a comment (exiftool keeps a _original backup by default)
exiftool -Comment='picoCTF{test}' image.jpg
# overwrite in place with no backup file
exiftool -overwrite_original -Artist='me' image.jpg
# write a PNG text chunk
exiftool -PNG:Comment='hello' image.png
# strip ALL metadata (useful to prove a field is gone)
exiftool -all= image.jpg
# copy every tag from one file to another
exiftool -tagsFromFile source.jpg -all:all dest.jpg

The practical reason to learn the write side is verification. If you are not sure whether a value belongs in EXIF or XMP, write it both ways into a scratch file and re-run exiftool -a -u -g1 to see exactly where it lands. The tool that hides the flag is the same tool that reveals it.

If you would rather not install anything, this site has a browser-based metadata viewer that reads EXIF and common tags client-side, which is handy for a quick first look before you drop to the command line.

picoCTF challenges that are pure metadata

picoCTF leans on metadata for its entry-level forensics, which makes these two the ideal place to drill the workflow above:

  • picoCTF 2021 information hands you an image whose flag is reachable through its metadata. It is the canonical "run exiftool, read the fields, decode the value" challenge, and it rewards the -a -u -g1 habit.
  • picoCTF 2019 Glory of the Garden is the strings backstop in challenge form: the flag is appended to the image rather than living in a named field, so exiftool alone is not enough and strings closes it out.

Do them in that order. The first teaches you to read labelled fields; the second teaches you that metadata tools have a blind spot and strings covers it.

Quick reference

exiftool cheat sheet

exiftool image.jpg # default dump, always start here
exiftool -a -u -g1 image.jpg # ALL tags, unknown included, grouped
exiftool -Comment -b image.jpg | base64 -d # raw field value into a decoder
exiftool -b -ThumbnailImage f.jpg > t.jpg # extract embedded thumbnail
exiftool -b -PreviewImage f.jpg > p.jpg # extract larger preview if present
exiftool -Comment -r ./photos/ # walk a folder of images
exiftool -j image.jpg # JSON output for scripting
exiftool -all= image.jpg # strip every tag
exiftool -Comment='picoCTF{x}' image.jpg # write a field
pngcheck -t -v image.png # list PNG chunks (tEXt/zTXt/iTXt)
strings -t x image.jpg | grep -i pico # plaintext backstop with offsets
binwalk -e image.jpg # carve an appended/embedded file

Decision order for a metadata challenge

  1. exiftool image.jpg, then exiftool -a -u -g1 image.jpg. Read every line.
  2. Scan Comment, UserComment, Artist, Copyright, IPTC caption, and the GPS block for anything English-shaped or base64-shaped.
  3. PNG? Read the text chunks (exiftool, pngcheck -t -v). Suspect zTXt if strings is empty.
  4. Extract the embedded thumbnail and compare it to the visible image.
  5. strings and binwalk for appended or embedded files.
  6. Still nothing? It is probably pixel stego, not metadata. Switch to the steganography toolkit.
Read the wrapper before you study the picture: in a metadata challenge the flag is a labelled string sitting right next to the pixels, and exiftool -a -u -g1 shows you all of them.