Anatomy of a poisoned image: colour-coded JavaScript!


You may have read recently about a newly-discovered attack that involves injecting code into your browser using poisoned image files.

Code-carrying image files are usually a serious security concern, especially if they involve deliberately malformed content that was criminally crafted to trip up your browser, or PDF reader, RTF viewer, video player, and so forth.

A well-known example from late in 2013 was a TIFF handling vulnerability in Microsoft Windows.

This TIFF exploit was used by criminals, who lured you to open an innocent-looking document file containing a booby-trapped TIFF image; crashed Microsoft Office; grabbed control of your CPU; and tricked your computer into running executable code without any official popups or warnings.

An attack of that sort is known as a remote code execution exploit, or RCE.

RCEs usually rely not only on remotely delivered content that is deliberately and criminally unusual, but also on software behaviour that isn’t supposed to happen.

Fortunately, this latest poisoned code injection isn’t anywhere near as dangerous as the TIFF hole: it doesn’t exploit a vulnerability that allows it to take over without warning.

But it does tell an interesting, even amusing, story of subterfuge and guile, so here you are.

The start of the attack

The first stage of the attack is some unwanted JavaScript, tacked on the end of a 9000-line JavaScript file downloaded as jquery.js.

In any server logs, the presence of jquery.js is unlikely to ring any immediate alarms: it is a widely-used Javascript programming library that is free and open source.

Indeed, for the first 8981 lines, the file is jquery.js – the official, unmodified, uncompressed file from version 1.6.2.

(As you can see from the datestamp, the crooks are often many versions and several years behind, too.)

The malicious addition comes right at the end.

You’ll notice a pair of functions that stand out rather obviously, at least to a security researcher:

Even at a glance, you can make an educated guess that the loadFile() function is there to fetch an image and then to pass some part of it to JavaScript’s eval() function.

Fetching images is unexceptionable behaviour for a JavaScript function, but using an image as input to eval() is both unusual and suspicious.

→ The eval() function contributes both to the flexibility and the danger of JavaScript: it takes a text string, compiles it, and runs it as if that string had been an original part of the JavaScript code that was just downloaded. Crooks love eval() because it means that the code they send to your browser isn’t the code they ultimately intend to run: this helps them hide away their malware in the hope that it will only ever exist in its final, malevolent form inside your browser.

JavaScript-based malware often uses various text encodings (e.g. hexadecimal) and scrambling techniques (e.g. ROT13) to disguise the code it will eventually pass to eval().

String obfuscation is especially handy for disguising the presence of suspicious URLs in the malicious code.

In the “poisoned image” approach we’re studying here, however, the string isn’t encoded as another string – it’s encoded into a series of coloured pixels!

Here’s how the crooks did it.

The initial image

The image that is fetched by the loadFile() function above is 17×17 pixels.

Magnified 20 times, it looks like this:

Looking at the image doesn’t give much away.

If we open the image file in a hex editor, there’s still nothing obvious to see:

PNG files are stored as a series of data sections, the most important of which is IDAT, the actual image data.

The raw data (highlighted in blue above) in the IDAT section is compressed, disguising still further any patterns that might otherwise be obvious.

But if we convert the image to a raw RGB file, in which each pixel is represented by three bytes denoting the amount of red, green and blue it contains, things start to get interesting:

Each pixel has the same value for red, green and blue, as we’d expect for a greyscale image.

And the majority of those greyscale values map into the range in the ASCII chart reserved for digits and letters:

We can also see the stand-out values 0x20 (space, annotated in pink) and 0x0D-followed-by-0x0A (the carriage return-line feed combination used on Windows to denote the end of a line of text, annotated in green).

The hidden source code

With a little bit of colourisation and a slight tweak to the contrast to aid in clarity, the image now clearly reveals itself as a colour-coded matrix of JavaScript source code:

The darker regions in the image correspond to the lower values in the ASCII chart, namely the digits and punctuation marks.

Notice that the JavaScript code that unpacks the PNG file and extracts the colour-coded source code doesn’t need to know how to parse the PNG file format to find the IDAT section, and doesn’t need to know how to decompress the image data.

It relies on the browser to render the image into an HTML5 canvas, which transparently (if you will pardon the pun) deals with decoding and decompressing the raw image file.

The source code is then sucked back in, pixel by pixel, from the rendered version of the image.

Indeed, the malware could switch to a different file format, such as JPEG or GIF, without any additional code required in the bogus jquery.js file.

The invisible components

As you can see from the code listing above, even a keen-eyed user won’t spot the 17×17 pixel greyscale-image-that-isn’t, because it is rendered 10,000 pixels off to the left of the window: = "-10000px";

The code that’s extracted from the invisible image and submitted to eval() creates a similarly out-of-shot IFRAME: = '-1000px'; = '-1000px';

The invisible IFRAME is then populated with an HTML page that consists only of a SCRIPT tag that pulls in yet more JavaScript.

This script, in turn, produces yet another IFRAME, once again positioned invisibly:

document.write("<iframe style='position:absolute; 
                top: -200px;' src=...");

At this point, at least in our experiments, things ended in an amusingly ironic anticlimax.

The final webpage we reached, after all this jiggery-pokery, was this:


That’s an empty page, so even if it were visible, our colourful journey would end in nothing:

A final warning

Of course, the cybercriminals could quickly and easily vary the web trajectory followed by the image-rendering trick in the bogus jquery.js file.

They could change the image itself or the location and contents of any of the IFRAMEs that are subsequently fetched, so we can’t promise that things will end with the same sort of wry smile in your case…

Note. Sophos products detect and block the files in the attack described above as Troj/JSRedir-NG.

Images of image icon and gas mask courtesy of Shutterstock.