📜 ⬆️ ⬇️

Hamlet's smuggling with a cat

Original image


Somehow I got the idea that it would be nice to have a way to send secret messages disguised as ordinary images. I called the result Jailbird.


Once you found yourself locked in a cell and you needed to send information to the will in order to turn one little business, so that the security didn’t notice anything? Well, you found the right solution!

Okay, okay, I'm joking, this is just an experiment.


Today I would like to show you how you can save Shakespeare's Hamlet in an image almost unnoticed. (Hehe, I think many people had the problem of smuggling Hamlet somewhere ...)


Use source, Luke


Source code is available on Github: https://github.com/ClanCatsStation/Jailbird


The size


To get started, we need to know how much space we need. (Size matters ;)).


I took Hamlet and put it in hamlet.txt . Then he created a php script that called size.php .


To remove php from the script call in the command line, install the interpreter to run:


 #!/usr/bin/env php <?php 

To get the data, read the content from STDIN :


 echo strlen(stream_get_contents(STDIN)); 

Now run the command:


 $ cat hamlet.txt | ./size.php 

We 175132 length of the string / number of bytes equal to 175132 . Let's try to archive:


 echo strlen(gzcompress(stream_get_contents(STDIN), 9)); 

And we get: 70681 bytes.


Jailbird can store 1 bit per color per pixel .


Total 565'448 bits, which means that we need 188'483 pixels. Or an image size of at least 435x435 pixels.


Add these calculations to the script size.php , which makes it easy to know what size image we need.


 #!/usr/bin/env php <?php $neededBits = (strlen(gzcompress(stream_get_contents(STDIN), 9)) + 16) * 8; $neededPixels = ceil($neededBits / 3); $neededSize = ceil(sqrt($neededPixels)); echo sprintf("bits: %s pixels: %s min-size: %sx%s \n", $neededBits, $neededPixels, $neededSize, $neededSize); 

Why did I add 16 bytes to the content length? We need to determine the end of the data by the presence of a sequence of characters, I decided that this would be the line @endOfJailbird; which contains 16 characters.


Data injection


Actually, what we gathered here for. (And then I sold something)


Binary string preparation


The easiest way to embed data in an image, I thought, is to convert it into a binary string.


 #!/usr/bin/env php <?php $content = gzcompress(stream_get_contents(STDIN), 9) . '@endOfJailbird; '; $data = ''; for($i=0; $i<strlen($content); $i++) { $data .= sprintf( "%08d", decbin(ord($content[$i]))); } 

The ord function returns a byte to its ASCII representation.


Then, using the decbin function, decbin convert the resulting number into a binary one and wrap it in sprintf to save the leading zeros.


At the output we get a fairly large string of zeros and ones.


 string(565448) "01111000110110101010110011111101110010011001001011100011010110001101001000101100000011001010111001111111001111000000010101111101110100111101110011010000111111010000000111101000000010111001011111001000001100011111110011011110110010001000100010111100000110010101000110010101010100101111110101001001001011010100000000000010001001001001000100001110000000100010110000001100110011100110010000111101011111011001101110101010100110100001110110000000011101001100111111111010101111101101101111011101001000100101010100011001"... 

Write bit


Here we need an image in which we will record. Pass the path to the image as the first argument to the inject.php script.


 //     array_shift($argv); //    $imagePath = array_shift($argv); 

Check if we have access to the image:


 if ((!file_exists($imagePath)) || (!is_readable($imagePath))) { die("The given image does not exist or is not readable.\n"); } 

The write command will be:


 $ cat hamlet.txt | ./inject.php cats.png 

The image of cats.png from (original, approx. Lane) KDPV:


image


Now run the loop:


 //     GD $image = imagecreatefrompng($imagePath); $imageWidth = imagesx($image); $imageHeight = imagesy($image); //  ,     //     $dataIndex = 0; //      Y for ($iy = 0; $iy < $imageHeight; $iy++) { //   for ($ix = 0; $ix < $imageWidth; $ix++) { $rgb = imagecolorat($image, $ix, $iy); //  rgb    $rgb = [($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF]; //     for($ic = 0; $ic < 3; $ic++) { // ,     if (!isset($data[$dataIndex])) { break 2; } $color = $rgb[$ic]; $bit = $data[$dataIndex]; //    //   } imagesetpixel($image, $ix, $iy, imagecolorallocate($image, $rgb[0], $rgb[1], $rgb[2])); } } 

This code simply passes through the pixel colors sequentially and saves the color change back to the pixel.


What?


But the most important thing:


 $negative = ($color % 2 == 0); 

This short line of code tells us about the current color of the current pixel is it even or uncent .


And $bit = $data[$dataIndex]; tells us whether the current color value should be even or odd.


Thus, we create another layer of data on top of the image simply by rounding the color values ​​to an even or odd number.


Now, all we need to do is update the color values:


 // should it be positive if ($bit == '1') { // should be positive but is negative if ($negative) { if ($color < 255) { $color++; } else { $color--; } } } // should be negative else { // should be negative but is positive if (!$negative) { if ($color < 255) { $color++; } else { $color--; } } } // set the new color $rgb[$ic] = $color; // update the index $dataIndex++; 

And actually everything! It remains only to save the image:


 imagepng($image, dirname($imagePath) . '/jailbirded_' . basename($imagePath), 0); 

Data retrieval


The reverse process - data extraction is quite simple, after we have understood the implementation.


Create a new file extract.php .


The data extraction script will also receive the path to the image as an argument.


 #!/usr/bin/env php <?php // we dont need the first argument array_shift($argv); // get image by argument $imagePath = array_shift($argv); if ((!file_exists($imagePath)) || (!is_readable($imagePath))) { die("The given image does not exist or is not readable.\n"); } 

Then we have almost the same iteration with the difference that we do not need to modify anything, just check if the value is even or not.


 // load the image with GD $image = imagecreatefrompng($imagePath); $imageWidth = imagesx($image); $imageHeight = imagesy($image); // create an empty string where our data will end up $data = ''; // and start iterating y for ($iy = 0; $iy < $imageHeight; $iy++) { // and x for ($ix = 0; $ix < $imageWidth; $ix++) { $rgb = imagecolorat($image, $ix, $iy); // split rgb to an array $rgb = [($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF]; // and for every color for($ic = 0; $ic < 3; $ic++) { $color = $rgb[$ic]; // what is the current pixel if ($color % 2 == 0) { $data .= '0'; } else { $data .= '1'; } } } } 

The $data variable contains the raw data that we need to convert to bytes:


 $content = ''; foreach(str_split($data, 8) as $char) { $content .= chr(bindec($char)); } 

The resulting content is archived, and ordered by the endOfJailbird value. We can drop everything that comes after it:


 // does the jailbird end of line exist? if (strpos($content, '@endOfJailbird;') === false) { die('Image does not contain any jailbird data.'); } // cut the compressed data out, // decompress it and print it. echo gzuncompress(substr($content, 0, strpos($content, '@endOfJailbird;'))); 

As a result, we can run the script:


 $ ./extract.php jailbirded_cats.png 

And get the beautiful Shakespearean play back.


Conclusion


If you put 2 images side by side, there’s probably no difference, although you may notice a slight blur:


image


But there are a couple of moments:


The fact is that we save the image without compression, otherwise it will lead to data loss. While the source file weighs 307 KB , after saving the data we get 759 KB . But I, unfortunately, do not see any solutions to this problem.


Also, when sending an image to, for example, facebook, reddit or twitter, the image will be compressed on the servers of these services and the data will most likely be lost.




In the end it was a pretty fun experiment, I had a good time with him for a few hours. I hope you also find this idea interesting and that you have not spent this time in vain. (Otherwise: hehe, I stole your time!)


Never gonna give you up


')

Source: https://habr.com/ru/post/306902/


All Articles