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:
oImg.style.left = "-10000px";
The code that’s extracted from the invisible image and submitted to eval() creates a similarly out-of-shot IFRAME:
elm.style.left = '-1000px'; elm.style.top = '-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:
<html><head></head><body></body></html>
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.
This technique’s been around for several years in the demo scene community. Surprised it’s taken this long to catch on with malware authors to be honest!
The security researcher didn’t get the payload even when they knew what server to request it from. If there is an ironic anti-climax it is that they couldn’t explain why that would be the case. They assume that the response they, as security researchers, receive is the same as the response a target victim would receive. I think that is naïve.
Er, the security researcher, who was I, *did* get the payload. It was a blank page – but that page *was* served up.
There was no special security researcher magic by which I “knew what server to request it from.” I just let my browsing software follow the chain of links provided, starting with the link to the PNG file in the doctored JavaScript. Any user running that JavaScript would have the same “knowledge.”
There are many explanations why the payload might have ended up unmalevolent, and they are interesting reasons, but not directly relevant here, so I didn’t delve into them in the article.
(For example: the crooks could have given up or been between campaigns; the malware server might have been cleaned up by someone else; the server might have been set to ignore visits from my part of the world; I might have had the wrong sort of User-Agent string set; I might have cleared or never received a browser cookie from earlier on in the sequence.)
And the security researcher did *not* assume, naively or otherwise, that the result he received “is the same as the response a target victim would receive.”
In fact, he very carefully ended the article with a short section subheaded “A final warning” to advise that the response you receive might, indeed, be completely different to what he observed… 🙂
I’ve encountered these ‘blank page payloads’ before. They are often part of a cookie bomb attack. If you don’t have the corresponding cookie set you don’t get the payload. This makes it much extremely difficult to determine the type of payload that would have been delivered.
The cookie data provides info about what security hole to exploit in which browser on which platform. These tandem attacks, where the malicious code that tests the environment for exploitable weaknesses is not in the same page, or even on the same server, make it very difficult to trace all the participating websites involved.
… And another good reason not to allow your browsers to accept iFrames 🙂 .
Hey Paul, nice in-depth analysis! Maybe worth of mentioning our original Sucuri blogpost related to this iframe? Anyway, I indeed enjoyed the colours 🙂