Steganography in svenomenal's steganoV2

by

There are many different tools and algorithms used to hide secrets in other documents. Unfortunately, not all of them are open and documented. But this doesn't seem to harm the reputation of Windows only tools like Stegano.Net in the context of recreational activities like Geocaching. The overview about the principle of this specific software should allow the user to extract its previously stored data without depending on closed software and make it possible for the interested reader to decide whether this is really such a good "privacy" tool.

The basic routines in Stegano.Net 2.0.1.0 are extreme simple. It takes an image, encodes the input data and saves this encoded buffer in the lower bit of the RGB component of each RGBA32 pixel. It only supports to save the result in PNG files. Thus extracting the encoded stream from an PNG image is trivial in python. The only "unexpected" part is the region where the data is stored. Usually the whole size of the image is used by such a program but Stegano.Net 2.0.1.0 skips the bottom row and right column.

#! /usr/bin/python
# -*- coding: utf-8 -*-

from PIL import Image
import sys

# generator for 8 bit junks
def bytechunks(l):
      for i in xrange(0, len(l), 8):
              yield l[i:i+8]

# open INPUT.png as RGBA
im = Image.open("INPUT.png").convert(mode="RGBA")
width, height = im.size

# go through the image and extract the lowest bit
# for each RGB component to a list
bits = []
for x in range(0, width - 1):
      for y in range(0, height - 1):
              rgba = im.getpixel((x, y))
              bits.append("%u" % (rgba[0] & 1))
              bits.append("%u" % (rgba[1] & 1))
              bits.append("%u" % (rgba[2] & 1))

# convert 8 bit (reversed) to a character
bytestring = []
for b in bytechunks(bits):
      byte = "".join(b)[::-1]
      bytestring.append(chr(int(byte, 2)))

# find highest byte != 0
highest_info = 0
for i in range(len(bytestring), 0, -1):
      if ord(bytestring[i - 1]) != 0:
              highest_info = i
              break

# remove 0 bytes from the end
cleanedstring = bytestring[0:highest_info]

# write all bytes to stdout
# start python with '-u' to switch stdin to binary mode
sys.stdout.write("".join(cleanedstring))

These extracted bytes can either be plaintext or encrypted using Rijndael (see the history of AES). The decrypted bytes will look exactly like the extracted bytes from the image when no encryption was used.

Interestingly the password entered by the user is not given to Rijndael in a strong way and instead the password is hashed using the weak MD5 algorithm and given to Rijndael as the only secret and initialization vector for the CBC mode. This may be an important weakness but a proper cryptanalysis is necessary to verify this speculation. At least, it is already known that this invalid chosen initialization vector makes the encryption of Stegano.Net vulnerable to common prefix detection when the user decides to re-use the secret key in at least one other encrypted message.

Also important is the missing encryption of the non used parts of the image (and the padding should be something different than 0). So at least the rough length of messages is leaked when using the encryption of Stegano.Net 2.0.1.0.

Decrypting the extracted bytes can be done quite easily with a small python snippet.

#! /usr/bin/python
# -*- coding: utf-8 -*-

import sys, hashlib
from Crypto.Cipher import AES

# start python with '-u' to switch stdin to binary mode
buf = sys.stdin.read()

# create MD5 of the secret
m = hashlib.md5()
m.update("SECRETKEY")

# create new 128 bit blocksize Rijndael... now called AES128 CBC
crypt = AES.new(m.digest(), AES.MODE_CBC, m.digest())

# write all decoded bytes to stdout
# start python with '-u' to switch stdin to binary mode
sys.stdout.write(crypt.decrypt(buf))

The extracted (+decrypted) data itself is also structured data in Stegano.Net 2.x. It seems to have two different versions identified by the first byte.

Version-Byte: 1

It is a simple plaintext stream followed by two bytes (0xffff) marking the end

Version-Byte: 2

It is a simple zip file (including comment) followed by two bytes (0xffff) marking the end

Stegano.Net 2.0.1.0 is not a really complex type of software and it is incomprehensible for me why the cryptographic/steganographic algorithms aren't documented by the author. This is required to avoid "false security through obscurity" situations. The above documented findings doesn't let me to believe that the claim of the author "Unschlagbaren Werkzeug für Ihre Privatssphäre" (engl.: "unbeatable tool for your privacy") holds true in reality. I would still recommend to encrypt data with tools like GPG. And if I really need to use steganography to hide my previously encrypted data then I would not use Stegano.Net 2.0.1.0. The algorithm is rather trivial and easy to detect because uneven RGB color values can only be found at the beginning of the image. It has a characteristic bottom row and right column.

example for the characteristic rows/columns