capture the :flag:

LSB steganography


It's always in the place you least expect


  • LSB

  • RGB

  • If you have found something previously, try looking again

  • Remember to get the full image

author: spamakin


The EXIF data on the image contains an interesting description.

$ exiftool flag.png

ExifTool Version Number         : 12.26
File Name                       : flag.png
Directory                       : .
File Size                       : 2.4 KiB
File Modification Date/Time     : 2021:07:31 22:05:15+08:00
File Access Date/Time           : 2021:07:31 22:06:10+08:00
File Inode Change Date/Time     : 2021:07:31 22:06:09+08:00
File Permissions                : -rw-r--r--
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 120
Image Height                    : 120
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Description                     : LSBs(Pixels[1337:])
Image Size                      : 120x120
Megapixels                      : 0.014

It says LSBs(Pixels[1337:]). Hmm... maybe it's telling us to get the LSBs of everything after the 1337th pixel.

My initial method was to go row by row, left to right (like reading English).


This did not yield any meaningful results.

Plugging it into a steganography tool, like StegOnline, helps us to figure out what's going on. In all of the 0th-bit (LSB) planes, there appeared to be some data hidden on the flag pole.

That explains! The pixels were meant to be read column-by-column instead.

Let's rearrange the pixels array to go column-by-column:

from PIL import Image

pix_val = []

with'flag.png') as secret:
    width, height = secret.size
    for x in range(width):
        for y in range(height):
            pixel = list(secret.getpixel((x, y)))

pixels = pix_val[1337:]

Now, we can read the LSBs of each pixel to get the hidden data.

result = ''
for pixel in pixels:

    for byte in pixel[:3]:
        if byte & 1:
            result += '1'
            result += '0'

        if len(result) == 8:
            if result == '0' * 8:
                result = ''

            char = chr(int(result, 2))
            print(char, end='')

            result = ''

We can see the flag right at the beginning.

Last updated