Learn · Multimedia forensics

Tampering & hidden data

Editing a picture leaves traces in places the editor did not think about: a stale thumbnail, uneven compression, bytes after the end marker, patterns in the lowest bits. None of these proves anything alone, but each is a reason to look closer.

1 Thumbnail vs. main image

The EXIF thumbnail (IFD1) is generated by the camera at the same moment as the main image. An editor that changes the pixels but copies the EXIF block unchanged leaves the old thumbnail behind: a small picture of the scene before the edit.

Thumbnail offset 866 + TIFF header 0xC = 0x36E, 3864 bytes. The tool below decodes the main image from the scan and the thumbnail from those 3864 bytes, then scales the main image down and subtracts them:

The red ball in the thumbnail is missing from the main image, and the difference image shows exactly where. Small differences elsewhere are normal: scaling and two separate JPEG compressions never give identical pixels.

What it proves, and what it does not. A mismatch shows that the main image changed after the thumbnail was made. It does not show who changed it or why: cropping, rotating or a brightness fix also leave stale thumbnails. A matching thumbnail proves nothing either, because careful editors (and anyone with exiftool) regenerate it.
Metadata of the edited file (exiftool-style listing)

2 Error level analysis (ELA)

Each time a JPEG is saved, its 8×8 blocks are rounded to the quantisation table. Saving a JPEG again at the same quality changes it only a little, because most blocks are already "rounded". A region pasted in from a different source (never compressed, or compressed with other tables, or not aligned to the same 8×8 grid) has not settled yet and changes more. ELA re-saves the image at a known quality and shows the amplified difference per pixel.

ELA(x, y) = | image(x, y) − resave(image, quality q)(x, y) | × scale

The sample was built like a typical splice: a desk photo that had been saved as JPEG before, a yellow sticker pasted in from a picture that was never compressed, and the result saved once more at quality 90. Move the sliders. At a quality near 90 the sticker stands out; far below that, everything lights up.

Error levels measured by the sample builder

What ELA can and cannot show

3 Data after the end marker

As the JPEG page showed, a viewer stops at FF D9. Bytes after it are never displayed, but they are part of the file. The simplest way to hide a file in a picture is cat picture.jpg archive.zip > out.jpg: the result still opens as a normal photo.

Try this: scroll to the last groups. After EOI the decoder found 262 bytes starting with 50 4B 03 04 ("PK"), the signature of a ZIP local file header. ZIP readers search for the archive directory at the end of a file, so unzip opens this "JPEG" directly and only warns about extra bytes at the beginning.

Signature scan (binwalk-style)
Not every trailing byte is a secret. Some cameras and phones append their own data after EOI (preview images, depth maps), and some programs pad files with zeros. Identify what is there before you interpret it, and compare with other files from the same source.

4 LSB steganography

Changing the lowest bit of a colour value alters it by 1 out of 255: invisible to the eye. A message can be stored one bit per pixel and channel. This only survives in lossless formats such as PNG or BMP; a JPEG re-save rounds the low bits away.

A bit plane shows one bit of one channel for every pixel as black or white. The high bits show the picture's shapes. The lowest bits of a natural photo look like random noise; in a synthetic image with flat colours they are flat, too, so an embedded message stands out as a band of noise.

Try this: start at bit 7 of the red channel and step down. At bit 0 the first rows of the image change into noise. The text under the planes reads those bits row by row as bytes, and at red bit 0 a sentence appears. Check green and blue bit 0 as well: nothing there.

The PNG metadata does not mention any of this. Its two tEXt chunks (see the PNG section) hold an innocent title and comment, and all CRCs are correct: the hidden data is in the pixels, not in the structure.

Real-world steganography is harder to see. Tools spread the bits pseudo-randomly with a key, encrypt the payload first, or embed in JPEG coefficients instead of pixels. Detection then relies on statistics (for example, the chi-square test on pairs of values that LSB replacement equalises), on finding the steganography tool on the same device, or on having the original cover image to compare with.

5 Try it yourself

Answer from the hex views and tools above. Numbers can be typed in decimal or hex (0x…).

In the JPEG with appended data, at which file offset does the ZIP archive start?

Find the EOI marker FF D9 after the scan. The first byte after those two bytes is the first byte a viewer never reads.

The scan ends with FF D9 at 0x3414. EOI has no length, so the image ends at 0x3414 + 2 = 0x3416.

The bytes there are 50 4B 03 04, "PK" followed by 3 and 4: a ZIP local file header.

How many bytes does the file contain after the end of the image?

File size minus the offset where the trailing data starts. The sample file is 13,596 bytes long.

13,596 = 0x351C. 0x351C − 0x3416 = 0x106 = 262 bytes.

None of them affect the picture, but all of them are in the file size and the hash.

Which file format starts right after the end marker? Name it from its signature.

Look at the first four trailing bytes, or at the signature scan below the carving tool.

The bytes are 50 4B 03 04: "PK" (Phil Katz) followed by 3 and 4, the local file header of a ZIP archive. Office documents (DOCX, XLSX), JAR and APK files are ZIP containers too, so the next step is to list the archive's contents.

The EXIF thumbnail of the edited sample shows a red ball that the main image does not. What does this mismatch establish on its own?

Think about which of the two pictures the camera made together, and which ordinary edits leave a stale thumbnail.

The camera writes thumbnail and main image at the same moment. The IFD1 entries (offset 62 03 00 00 = 866, length 18 0F 00 00 = 3864) still point to that original thumbnail, while the main image differs.

So the main image was changed after the thumbnail was made. Who did it and why is not in the bytes: cropping or a brightness fix leave the same trace.

These are all 50 bytes of the PNG's first tEXt chunk: length, type, "Title" NUL text, CRC-32. Edit them and watch the chunk list below:

  • Change the first letter of the text (4D, "M", at 0x2F) to 6D ("m"). One bit changed, and the CRC check fails. The view shows the CRC the data should have.
  • Now type that computed CRC into the last four bytes. The chunk is "valid" again: a CRC detects accidents, not deliberate edits, because anyone can recompute it.
  • Reset, then change the length 00 00 00 26 to 00 00 00 25. Every chunk after this one is now read from the wrong place.