I attended a security conference last week and a speaker named Saumil Shah had an interesting presentation. One of the examples he gave was about using PNG images to store bad/evil JavaScript to hide it from antivirus applications. Converting the JS byte for byte to a grayscale value he saved the scripts as PNG files and used HTML5 and Canvas to decode the image back into scripts.
Some time earlier this winter I heard about this method for compressing JS, but it never crossed my mind that it could be used for hiding bad JS from antivirus applications.
Seeing as I am a geek and I like to tinker with stuff for no apparent reason I decided to make my own version, but instead of making it grayscale I would make it RGBa. Except for making it a smaller picture (in pixels, not in size) it functions in the same way. Grayscale is a better way to go for compression, the only reason why I made this is because I can.
Example outputThe first version of this script converts everything byte for byte, so "Hey" would be [72,101,121]. In the next version I will add some basic encoding/bit rotation to make it a bit more tricky for AVs to catch on if they start scanning and decoding PNGs.
The PHP script:
<?php
class Picii
{
/****************
** Picii v0.1
** Converts ASCII into byte and displays in form of a picture
** Written by: Thomas Flenstad / http://www.aroonie.com
*****************/
private $inputAsPixels = array();
private $inputLength = 0;
private $imageSize = array();
public function __construct()
{
}
// ----------------------------------------
// START: debug functions
public function D_GetBytes()
{
return $this->inputAsPixels;
}
public function D_GetDimensions()
{
return $this->imageSize;
}
// END: debug functions
// ----------------------------------------
private function CalculateDimension($input)
{
$this->inputLength = strlen($input);
$inputSqrt = sqrt($this->inputLength/3);
// Simple way to find approx size of image, feel free to improve
if ($inputSqrt == (int)$inputSqrt)
{
$this->imageSize['x'] = $this->imageSize['y'] = $inputSqrt;
}
else
{
// Out of 1000 random strings this gave me 1.3% overhead.
// Decided it was close enough since we're not doing compression anyway...
$this->imageSize['x'] = ceil($inputSqrt);
$this->imageSize['y'] = round($inputSqrt);
}
// for debugging purposes
$this->imageSize['totalPixels'] = $this->imageSize['x'] * $this->imageSize['y'];
$this->imageSize['totalBytes'] = $this->imageSize['totalPixels']*3;
$this->imageSize['actualBytes'] = $this->inputLength;
}
private function ConvertToPixelArray($input)
{
$n = 0;
$l = $this->inputLength / 3;
for ($i = 0; $i < $l; $i++)
{
for ($e = 0; $e < 3; $e++)
{
// Breaks early if there isn't enough data to store in all channels (RGB)
// Sets alpha value to indicate number of channels to look for data in
// See comment in GeneratePicture();
if ($n >= $this->inputLength)
{
if ($e != 0)
$this->inputAsPixels[$i]['alpha'] = (3 - $e);
return;
}
$this->inputAsPixels[$i]['alpha'] = 0;
$this->inputAsPixels[$i][$e] = ord($input[$n]);
$n++;
}
}
}
public function GeneratePicture($input)
{
$this->CalculateDimension($input);
$this->ConvertToPixelArray($input);
if ($this->imageSize['x'] != 0)
{
$loc_x = 0;
$loc_y = 0;
// Creates picture
$pic_o = imagecreatetruecolor($this->imageSize['x'], $this->imageSize['y']);
// Enables alpha
imagealphablending($pic_o, false);
imagesavealpha($pic_o, true);
// Clear background
imagefill($pic_o, 0, 0, imagecolorallocatealpha($pic_o, 255, 255, 255, 127));
foreach ($this->inputAsPixels as $pixel)
{
// Calculates alpha value of the last pixel and marks the end
// if alpha = 0 then R, G and B contains data
// if alpha = 1 then R and G contains data
// if alpha = 2 then R contains data
// if alpha = 127 then no data.
if( $pixel['alpha'] != 0)
{
for ($i = 0; $i < $pixel['alpha']; $i++)
{
$pixel[(2-$i)] = 255;
}
}
// Paint
imagesetpixel($pic_o, $loc_x, $loc_y, imagecolorallocatealpha($pic_o, $pixel[0], $pixel[1], $pixel[2], $pixel['alpha']));
$loc_x++;
// Time for a new line?
if ($loc_x == $this->imageSize['x'])
{
$loc_y++;
$loc_x = 0;
}
}
return $pic_o;
}
else
{
return false;
}
}
}
?>
Usage
$pic = new Picii();
imagepng($pic->GeneratePicture('Input goes here'));
Please let me know if you find any bugs or other issues with the code. The JS to decode the image will be released in a couple of days.