Недавно я обнаружил образец вредоносного ПО .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.
Это было определенно очень интересно для меня, надеюсь, вам понравилось!