The encoding step generates a square PNG image containing the length of the payload and the payload itself interleaved by the alpha channel (which is not used for storage); the decoding step extracts the payload into the browser’s memory and execute the payload without altering the DOM.
All the source code is available on GitHub.
From code to PNG
Let’s start with the encoder in
src/net.expobrain/js2png/js2png.go. First we need a buffer to store the bitmap in RGBA format:
Now we need to write the size of the payload:
The payload’s size is stored as a little-endian 64-bit unsigned integer right-aligned on 9 bytes and interleaved with a
255 byte which represent the alpha channel.
The right alignment is just for convenience and easy debugging so the first 3 pixels contains the length of the content and the content itself will start from the R channel of the 4th pixel.
The alpha channel is set to
255 so on saving the bitmap the values of the RGB channels will be same even after been multiplied by the alpha channel. For instance, with an alpha channel set to
0 the resulting RGB value will be
0x000000, with and alpha channel of
128 all the RGB values will halved, and so on.
Time to write the payload into the buffer, still interleaved by a
255 every 3 bytes:
Now that we have the buffer complete with size and payload we can copy it into a square bitmap:
The extra unused space in the final bitmap is padded with zeros. In theory the final image can have any shape you want, I just found a square image more attractive for my likes.
Last step is writing the PNG to disk:
That’s all for the encoder.
From PNG to code
html/js/loader.js is triggered on the
load event of the
payload element aka our PNG image:
We cannot read pixel data straight from the image so we render the image on a HTML5
Reading back the size of the payload:
and the payload itself:
Now that we have the original code stored into the
payload variable we can execute it by a simple:
eval() we can execute the code without leaving any trace into the DOM.
However even this technique is simple detecting hidden code in a image is hard; the only possible weak point is the loader which is the only clue of the existence of a hidden payload.