We have noticed a change of behaviour in the latest spam email campaigns used by Locky. Since its first release Locky took advantage of compromised domains to download the dropper binary, while recently Locky dropper is being delivered embedded into the loader code itself. By tracking these campaigns we have also noticed that Locky’s authors have made several attempts at embedding the dropper into the loader.

Initially we have detected emails where the dropper was stored as a chain of different arrays. Then we’ve found a base64 encoded dropper inside a javascript and then again we’ve found the same code used as part of a WSF file. In this article we will talk about the changes to Locky’s loader and more in depth about the javascripts used.


A sample email intercepted by our sensors:

Please find the invoice attached.
How about meeting on Friday?

Best regards,
Delia Whitfield
Phone +1 (312) 998-11-78
Fax +1 (312) 998-11-94
Reply-Index: f7016952ab66983b8db002c48126aa21cfe8970a
e-mail: Whitfield.29083@64townhill.net

invoice, Financial statement or sales report are the email’s subjects instead.

Attachment analysis

The most interesting change introduced in the javascript loader is of course the fact that there are no more external downloads, since the dropper is embedded into the loader. The javascript code is obfuscated as usual, but comparing it with previous samples we notice that now Locky is using one less layer. To make the analysis easier we have created 2 gists: one in which Locky dropper is embedded in different arrays of integers and one in which Locky dropper is base64 encoded.

Array version

The image below shows Locky dropper embedded in an array of integers:
26 arrays are initialized, their names are in the form: v_binx, with x ranging from 0 to 25, that are then concatenated to obtain the final array named v_bin. v_bin is an array of decimals which, through the Je function, are xor‘ed with a fixed key to obtain the final dropper. The entry point of the Locky’s loader is the following try-catch statement:

try {
    var Ri3 = Je(v_bin);
    if (Ri3[WQm8 + Uk2] < 60 * 1024 || Ri3[WQm8 + Uk2] > 300 * 1024) {
        WScript.Quit(-1226 + 1326);
    AKh(EOz, Ri3); // saves the decrypted dropper to the disk in %TEMP% folder
    Cy[PBf6](EOz + QPp2 + Ep8); // equivalent to WScript.Shell["Run"](PATH_TO_EXE + " 321");
} catch (e) {

The Je function’s code is reported below, after that we have replaced some obfuscated code.

function Je(RBi6) {
    var KRl2;
    var HOr = uheprng();
    for (var AKu9 = 0; AKu9 < RBi6["length"]; AKu9++) {
        RBi6[AKu9] ^= HOr(74 * 3 + 34);
    var Vq = RBi6[RBi6["length"] - 4] | RBi6[RBi6["legnth"] - 3] << 8 | RBi6[RBi6["length"] - 2] << (4919 - 4903) | RBi6[RBi6["length"] - 1] << 24;
    RBi6["splice"](v_bin["length"] - 4, 1022 - 1018);
    KRl2 = ONo9;
    for (var AKu9 = 6341 - 6341; AKu9 < RBi6["length"]; AKu9++) {
        KRl2 = (KRl2 + RBi6[AKu9]) % 0x100000000;
    if (KRl2 != Vq) {
        return [];
    return RBi6;

The function is straightforward: take the v_bin‘s array element and xor it with the value returned by HOr function. To understand what the HOr function does, we need to take a look at uheprng function that can be seen below after beautifying it:

function uheprng() {
    return (function() {
        var o = 48,
            c = 1,
            p = o,
            s = new Array(o);
        var i, j;
        var base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        var mash = Mash();
        for (i = 9881 - 9881; i < o; i++) s[i] = mash(VFi);
        mash = null;
        var random = function(range) {
            return Math.floor(range * (rawprng() + (rawprng() * 0x200000 | 0) * 1.1102230246251565e-16));
        function rawprng() {
            if (++p >= o) p = 3629 - 3629;
            var t = (1766358 + 2505) * s[p] + c * 2.3283064365386963e-10;
            return s[p] = t - (c = t | 0);
        return random;

random is the return value, that is a function definition:

function(range) {
    return Math.floor(range * (rawprng() + (rawprng() * 0x200000 | 0) * 1.1102230246251565e-16));

For the sample analyzed the return value is 0x8B. This return value is the xor’s key which we were looking for. To get the decrypted Locky’s dropper without executing it we can comment the last line in the try statement’s body.

Base64 encoded version

In this version we have seen that Locky’s loader embeds its dropper in base64.
Like for the previous javascript code the entry point of the script is also a try-catch statement:

try {
    SUo(v_bin, v_binpath);
    SAb(v_binpath, El);
    DBu7[SUt](El + " 321");
} catch (e) {

The body of the try statement invokes 3 functions:

  • SUo() which takes as arguments v_bin, that is the dropper base64 encoded, and the v_binpath, that is the path to a .bin file on which the function itself will write the v_bin variable content;
  • SAb() which opens the .bin file created, decodes the base64 and writes the results to El, that is the .exe file.
  • The third line executes Locky dropper passing as argument 321, as usual.

Commenting out the last line we can obtain the dropper without executing it, it will be written inside the %TEMP% folder.

Locky dropper

Droppers extracted from the 2 different loaders are virtually the same. This is the dropper’s icon:
The Locky dropper analyzed (MD5: 2943D8DA60ADAE18FB33F32B66891673) has the following PE characteristics:
In the latest email campaigns we have also noted a change in the unpacking routine. So far the payload could be obtained easily by setting a breakpoint on the RtlDecompressBuffer and dumping the output buffer:
In the current case we can dump the content of the buffer allocated by the VirtualAlloc API after all the encryption layers have been executed.

Locky unpacking

To unpack Locky dropper we have written two simple Python scripts for ImmunityDebugger. One to unpack the Locky dropper which performs the unpacking stage through RtlDecompressBuffer and the other one to unpack the Locky dropper that uses the VirtualAlloc variant to perform the unpacking stage.

RtlDecompressBuffer sample

from immlib import Debugger
imm = Debugger()
path = "C:\Users\PC\Desktop\dumped.exe"
def main(args):
	regs = imm.getRegs()
	eax_reg = regs['EDI']
	imm.log("Virtual memory address: %s" % str(hex(regs['EDI'])).strip('L'))
	dumped = imm.readMemory(eax_reg,0x22000)
	fopen = open(path, 'wb')
	return "Unpacked dumped to the disk!"

Embedded sample

from immlib import Debugger
imm = Debugger()
path = "C:\Users\PC\Desktop\dumped.exe"
def main(args):
	for i in range(1,18):
		regs = imm.getRegs()
		eax_reg = regs['EAX']
		imm.log("Virtual memory address: %s" % str(hex(eax_reg)).strip('L'))
		return_address = imm.readLong(regs['ESP'] + 4)
	dumped = imm.readMemory(eax_reg,0x22000)
	fopen = open(path, 'wb')
	return "Unpacked dumped to the disk!"

Just save the scripts inside the ImmunityDebugger’s PyCommands folder, then open the debugger and set in the Debugger Option, under the Event tab, ‘Make first pause at: System breakpoint‘. Now you can open your samples and run the scripts in the command bar: !nameofthescript
The script will dump Locky unpacked (the payload) under the same folder where the sample resides.


MD5 Hashes
Javascript (array version): 84a4b62a93985784cf580f22dae41fda
Javascript (base64 encoded version): 220cf2a81e7ed5ec5751d1e3e779ab14
Dropper (array version): 2943d8da60adae18fb33f32b66891673
Dropper (base64 encoded version): f045e840d1391538e816c55545031c53
Payload (array version): 071709f38f45d6cfaa78941800742e88
Payload (base64 encoded version): 08a9c7bcb152a54ba79c6ad0bad4180d

Affiliation ID









Join our newsletter to get the world’s latest security events and our technical analyses delivered directly to your inbox!


Close Bitnami banner