Steganography Challenge: My Solution

Published: 2025-05-10. Last Updated: 2025-05-10 10:33:58 UTC
by Didier Stevens (Version: 1)
0 comment(s)

When I tried to solve "Steganography Challenge" with the same method as I used in "Steganography Analysis With pngdump.py: Bitstreams", I couldn't recover the text message.

So I looked into the source code of the encoding function EncodeNRGBA, and noticed this:

To encode each of the pixels, there are 2 nested for loops: "for x" and "for y". This means that first the column is processed (y).

While a raw bitmap is one line after the other (and not one column after the other). Thus we need to transpose the raw bitmap (rows and columns need to be swapped):

And as 8-bit RGB encoding is used for pixels, each pixel is encoded with 3 bytes, that need to be transposed correctly:

This transposition can be done with my tool translate.py and the necessary Python function. I wrote this one to do the transposition:

def Transpose(data, size, width, height):
    result = []
    for x in range(width):
        for y in range(height):
            i = y * width + x
            result.append(data[i*size:(i + 1)*size])
    return b''.join(result)

So let's decode this.

First we need the dimensions of the image:

1195 pixels wide and 642 pixels high.

With this information, I can do the transposition with translate.py (3 is the number of bytes per pixel): Transpose(data, 3, 1195, 642)

Then I use the following command to decode the size of the message. It's the same command as I used in diary entry "Steganography Analysis With pngdump.py: Bitstreams", except that this time there's an extra step (translate) to do the transposition:

pngdump.py -R -d encoded_stegosaurus.png | translate.py -f -s transpose.py "lambda data: Transpose(data, 3, 1195, 642)" | cut-bytes.py 0:32l | format-bytes.py -d -f "bitstream=f:B,b:0,j:>" | format-bytes.py

The message is 547 bytes long. Let's decode this:

pngdump.py -R -d encoded_stegosaurus.png | translate.py -f -s transpose.py "lambda data: Transpose(data, 3, 1195, 642)" | cut-bytes.py 32:4376l | format-bytes.py -d -f "bitstream=f:B,b:0,j:>"

We were able to extract the text message (a partial copy from the Wikipedia article on Stegosaurus).

But what surprised me is the lack of space characters ... I though that this could hardly be due to an error in the decoding, so I took a look at the test encoder source code. Line 19 contains the message encoded as decimal bytes, and I noticed that there are no values equal to 32 (that's the space character). Decoding this line with numbers-to-string.py does indeed reveal that there are no space characters in the source code:

The solution to this challenge is identical to the one described in diary entry "Steganography Analysis With pngdump.py: Bitstreams", with one important difference: we need to transpose lines and columns.

Finally, if you would want to write this command as a one-liner without a file containing the source code for the Transpose Python function, you can to this with nested list comprehensions, but it's less readable:

pngdump.py -R -d encoded_stegosaurus.png | translate.py -f "lambda data: b''.join([b''.join([data[(y*1195+x)*3:(y*1195+x+1)*3] for y in range(642)]) for x in range(1195)])" | cut-bytes.py 32:4376l | format-bytes.py -d -f "bitstream=f:B,b:0,j:>"

Didier Stevens
Senior handler
blog.DidierStevens.com

Keywords:
0 comment(s)

Comments


Diary Archives