Недавно я обнаружил образец вредоносного ПО .JS на MalwareBazaar, который выглядел интересно. В этой статье описываются шаги, которые я предпринял, чтобы раскрыть его истинные намерения.

На первый взгляд видно, что он сильно запутан, а на месте разбросано интересное слово:

Странное слово, на которое я ссылаюсь, это «r0Tw31LeR». Меня это заинтриговало и заставило копнуть глубже.

Сценарий начинается с определения различных функций и переменных, используемых для раскрытия остальной части сценария. Я начал с того, что расшифровал этот сценарий, строчка за строчкой,

String["prototype"].proc = function() { eval(this.toString());}; 

//This creates a proc function and adds it to the String object,
// The eval() function executes JS code, this basically allows
// the execution of JS code inside a string, for example:

"var myNum = 11;".proc(); //proc() is used to execute JS inside the string

Хорошо, это начинается с довольно простого, давайте перейдем к следующей строке

String["prototype"].nan = function(one, two) {one[3] = two[0];one[4] = two[1];one[5] = two[2];};

// this adds a nan function to the String object
// this nan() function takes two arrays as input 'one' , 'two'
// this has the behaviour of shifting the entries of one & two around
// perhaps this will be used later in the code to hide the real meaning of code?

Пока довольно легко, верно?

String.\u0070\u0072\u006f\u0074\u006f\u0074\u0079\u0070\u0065.\u0074\u0075\u0072\u006E = {
  mp3: function(){var d = "";for(var i=0; i<this.toString().length; i++){d = eval("var cd = this.toString().substr(i, 1);cd;") + d;}return d;}}.mp3;

// Okay what is this: .\u0070\u0072\u006f\u0074\u006f\u0074\u0079\u0070\u0065.\u0074\u0075\u0072\u006E
// I quickly realised this was just Unicode encoding for
// .prototype.turn -> we're just making another function turn() for a string object
//I decided to whack the contents of this into JS sandbox enviroment and figured
// it was just a function to reverse a string, for example

"This is reversed".turn() // = "desrever si sihT"

Да ладно, противники, до сих пор это было легко, вы можете сделать лучше, чем это!

В этой следующей строке кода мы просто создаем массив переменных и присваиваем ему некоторые значения, используя ранее определенную функцию turn():

var \u0072\u0030\u0054\u0077\u0033\u0031\u004c\u0065\u0052 = [null,
("|tcejbOetaerC".turn()+"txeTdaeR".turn()+"|undefined|\x61\x64\x6F\x64\x62\x2E|"+"noitisoP|teSrahC".turn()).split("|").concat("epyT".turn(), "nepO".turn())];

//var r0Tw31LeR = [null, ("|tcejbOetaerC".turn()+"txeTdaeR".turn()+"|undefined|\x61\x64\x6F\x64\x62\x2E|"+"noitisoP|teSrahC".turn()).split("|").concat("epyT".turn(), "nepO".turn())];

// using the turn() function gives us
// [null, ("CreateObject|" + "ReadText" + "|undefined|adodb.|" +  "CharSet|Position").split("|").concat("epyT".turn(), "nepO".turn())]

// using the .split("|") gives us
// [null, ["CreateObject","ReadText","undefined","adodb.","CharSet","Position"].concat("Type", "Open")]

// using the .concat("Type","Open") gives us

// [null, ["CreateObject","ReadText","undefined","adodb.","CharSet","PositionTypeOpen"]]

Становится все сложнее, но все еще довольно просто пройти;)

'String["prototype"].oS3Hm = function(){return [].s0fStu;}'.proc();

// Here we see our previously defined proc() function in action/
// This function isn't that interesting, yet. At the moment
// it just defines the function oS3Hm() that'll return [].s0fStu;
// so far we haven't seen the s0fStu variable so it means nothing

Следующие несколько строк также мало что делают, но имеют решающее значение для понимания остального кода.

var exp = /{(\d+)}/g; // Sets up a regex query for numerical digits
// inside curly braces, for example {1} , {2} , {3}

var oMut1 = null; // creates a variable with null value

'Array.prototype.om0l4d3 = function() {var hYiUrF = arguments; return this[0]["replace"](exp, function(k3tTlE0, k3tTlE1) { try{ return hYiUrF[k3tTlE1];}catch(ex){ return k3tTlE0;}});}'.proc();

// The above string is appended with .proc(), which like we explained before,
// will execute the contents of the string. Let's break this down further... 

Array.prototype.om0l4d3 = function() {
    var hYiUrF = arguments;
    return this[0]["replace"](exp, function(k3tTlE0, k3tTlE1) {
        try{ return hYiUrF[k3tTlE1];
        }catch(ex){
           return k3tTlE0;}});
}

//Interesting, this function .om0l4d3() performs replacement operations
// on occurrences of {digits} placeholders with corresponding 
// values from the arguments object passed to the method 
// short example

"My {0} is {1}".om0l4d3("Name", "Ben") // = "My Name Is Ben"

Следующая строка начинается с «Array.prototype.s0fStu», за которым следует массивный раздел данных, закодированных по основанию 64, разделенных строками «{0}», «{1}» и «{2}». Вы помните ту функцию .oS3Hm(), о которой я упоминал ранее? Теперь Array.prototype.s0fStu был назначен переменной, всякий раз, когда мы используем oS3Hm(), мы будем ссылаться на замаскированную строку base64!

Когда я имею в виду, что это массивно, это почти 1 миллион символов в длину… После этой длинной строки base64 используются ранее определенные функции для раскрытия этой большой строки и, в конечном итоге, ее выполнения.

Я не буду вдаваться в подробности, но я использовал комбинацию утилиты NodeJS linux CLI и Cyberchef, чтобы найти значения после выполнения первых нескольких строк:

Вот как выглядит массив r0Tw31LeR после выполнения первой строки на скриншоте выше:

И то же самое касается второй строки ‘oMut1 = [Array …

Посмотрев последние 4 строчки, одна из них мне особенно запомнилась!

eval('var tmx = ["JuOP99".oS3Hm()].om0l4d3("d", "Q", "I")');

// oS3Hm() is just calling 
return [].s0fStu;
// However the script set .s0fStu to that big long base64 obfsucated string

// .om0l4d3() was the function that looked used a regex string to search for
// strings like '{0}', '{1}', '{2}', ect ect 
// and replaced them when the arguments, in this case "d", "Q", "I"

Теперь, когда мы знаем аргументы, которые скрипт использует для деобфускации строки base64 с помощью функции oS3Hm(), мы можем сделать это сами. Возможно, я мог бы написать это на JS, но я больше уверен в Python, и это было довольно просто. Сначала я сохранил эту большую строку base64 в файл: base64_obfuscated.

import re

def unBase64Data(self, *arguments):
    exp = re.compile(r'\{(\d+)\}')
    return re.sub(exp, lambda match: arguments[int(match.group(1))], self[0])

base64obsfucated = open("base64_obfuscated", "r").read().strip()

print(unBase64Data([base64obsfucated], "d", "Q", "I"))

Теперь у нас есть чистые данные в кодировке base64 без запутывания. Давайте расшифруем это и сохраним в файл!

Просматривая этот новый файл, я был потрясен, обнаружив, что это большой, сильно запутанный JS-скрипт!!

Хотя в этой статье я не буду подробно описывать этот деобфусцированный JS-скрипт, я кратко расскажу о некоторых интересных функциях, которые заставили меня прийти к выводу, что это почти наверняка RAT.

Захватчик паролей:

Служба RDP:

Кейлоггер:

Обратная оболочка:

Полезный ланчер?

Есть еще много интересных функций, но у меня нет места, чтобы рассмотреть их все в этой статье. Вы, возможно, заметили, что во всех этих функциях для извлечения данных используется массив TsattYW. Давайте быстро посмотрим на это!

Это массивный массив строк/данных в шестнадцатеричном кодировании. Я могу легко разобрать это на питоне с помощью модуля pprint.

pp = pprint.PrettyPrinter()

array = ["\x25\x61\x70\x70\x64\x61\x74\x61\x25", ... , ]

pp.pprint(array)

Запустив этот скрипт и перенаправив его в текстовый файл, мы можем посмотреть на текстовые команды, на которые ссылаются все функции во втором скрипте!

Это огромный массив команд и строк, используемых в сценарии JS. В моей следующей статье я попытаюсь сопоставить их с функциями и расшифровать этот клиент C2.

Это было определенно очень интересно для меня, надеюсь, вам понравилось!