Breaking QuickStego

Target: QuickCrypto’s QuickStego Version 1.2.0.1

QuickStego is a tool that can hide unencrypted text data in BMP image files.

The first look with PEiD reveals a Visual Basic executable, the crypto analyzer tells us something about CRC32. This may be used as some kind of verification of the data.

Trying the program on some small Bitmap files shows an unchanged file size, but many changed image data bytes, even for a message as short as one char (I used ‚A‘ for the test). The text is thus hidden directly in the pixel data, presumably by LSB method, with some overhead for management.

Firing up SmartCheck quickly reveals that the overhead consists of an header:

QCE_S message

Following this, the program extracts our message and an end-marker:

Aend message

Oh this is neat. Now guess what? You can trick that tool by trying to hide a message containing „end message“ :D

Hiding for example „Helloend messageWorld! you miss me :(“ and trying to show again, only the first part of the message „Hello“ is shown, the rest is thrown away :D

Okay, back to Smartcheck.

Having scrolled through the millions of messages, the line „QuickStego – Processing..“ immediately got my attention. Afterwards there’s a Picture2.Point which gets a pixel value, and a lot of RGB calls. Analyzing further one sees that there are actually 8 calls for every character of text:

first pixel RGB
next pixel RGB
next pixel only R and G

The B value of the last pixel is thrown away, the next char starts at the next pixel. Looking at the positions of the rgb calls with Olly leads to the conclusion that the program extracts the LSB from each of those values. The pixels are taken column by column from the image, starting at 0,0, incrementing the y-value by 1.

Easy as pie! Now just throw together some quick script to extract the hidden messages:

<?php

if ($argc < 2)
   die("usage: <script> <bmpfile>\n");

$img = imagecreatefrombmp($argv[1]);
$x=$y=0;
$out = '';

while (1) {
   $ch = 0;
   $col = imagecolorat($img, $x, $y);
   $ch |= (($col >> 16) & 1) << 7;
   $ch |= (($col >> 8 ) & 1) << 6;
   $ch |= (($col) & 1) << 5;

   ++$y;
   if ($y == imagesy($img)) {
       ++$x; $y = 0;
   }

   $col = imagecolorat($img, $x, $y);
   $ch |= (($col >> 16) & 1) << 4;
   $ch |= (($col >> 8 ) & 1) << 3;
   $ch |= (($col) & 1) << 2;

   ++$y;
   if ($y == imagesy($img)) {
       ++$x; $y = 0;
   }

   $col = imagecolorat($img, $x, $y);
   $ch |= (($col >> 16) & 1) << 1;
   $ch |= (($col >> 8 ) & 1);

   $out .= chr($ch);

   if (strpos($out, 'end message') !== false)
      break;

   ++$y;
   if ($y == imagesy($img)) {
       ++$x; $y = 0;
   }
}

echo 'message: '.$out."\n";
imagedestroy($img);

function imagecreatefrombmp($p_sFile) {
    $file = fopen($p_sFile, "rb");
    $read = fread($file, 10);

    while (!feof($file) && ($read <> ""))
        $read .= fread($file, 1024);
    $temp = unpack("H*", $read);
    $hex = $temp[1];
    $header = substr($hex, 0, 108);

    if (substr($header, 0, 4) == "424d") {

$header_parts = str_split($header, 2);
        $width = hexdec($header_parts[19] . $header_parts[18]);
        $height = hexdec($header_parts[23] . $header_parts[22]);
        unset($header_parts);
    }
    $x = 0; $y = 1;
    $image = imagecreatetruecolor($width, $height);
    $body = substr($hex, 108);
    $body_size = (strlen($body) / 2);
    $header_size = ($width * $height);
    $usePadding = ($body_size > ($header_size * 3) + 4);

    for ($i = 0; $i < $body_size; $i+=3) {
        if ($x >= $width) {
            if ($usePadding)
                $i += $width % 4;
            $x = 0;
            $y++;
            if ($y > $height)
                break;
        }
        $i_pos = $i * 2;
        $r = hexdec($body[$i_pos + 4] . $body[$i_pos + 5]);
        $g = hexdec($body[$i_pos + 2] . $body[$i_pos + 3]);
        $b = hexdec($body[$i_pos] . $body[$i_pos + 1]);
        $color = imagecolorallocate($image, $r, $g, $b);
        imagesetpixel($image, $x, $height - $y, $color);
        $x++;
    }

    unset($body);
    return $image;
}

?>

There we go! Hide some stuff:

And extract it with the cool new script:

$ php extract.php testbitmap.bmp
message: QCE_S messageHello World!
QuickStego uses a simple, unencrypted version of LSB stegano :)end message

Ok there’s that header and footer stuff, but I’m too lazy to code more for this stupid program ;)

Job done!

comments powered by Disqus