Деобфускация JS-вредоноса

Дата публикации 3 янв 2018 | Редактировалось 10 янв 2018
Доброго времени суток. Некоторое время назад один из участников нашего форума обратился к сообществу за помощью в исследовании найденного на просторах интернета вредоносного java-script'а. Сам скрипт сильно обфусцирован и в том виде, в котором он был представлен, проанализировать его активность невозможно. В связи с этим я хотел бы рассказать как же посмотреть что он делает.

Каких-то особых спецсредств для работы нам не потребуется. Понадобится лишь текстовый редактор. Я обычно использую Notepad++.

Исходный файл вредоноса - Main.js.txt. (см. аттач)

Как видно из листинга скрипт состоит из одной функции с именем I58mjY6n3, она принимает единственный аргумент с именем Gn84wOfOJ. Все, что находится за ее границами - это код, который будет исполнен при загрузке скрипта. В нашем случае произойдет вызов I58mjY6n3 с довольно большой константой в качестве аргумента. В теле функции I58mjY6n3 содержится такая строка:

Код (Text):
  1. eval(M6Nnm6jY0);
Для тех кто не в курсе, функция eval(code) позволяет интерпритировать js-код, переданный ей в виде строки. То есть перед вызовом eval переменная M6Nnm6jY0 должна содержать js-код. Сразу необходимо сделать важное замечание: всегда старайтесь избегать исполнения функции eval, когда содержимое ее аргумента неочевидно, как сейчас. Это может привести к нежелательным последствиям, инфицированию в том числе.

Что бы избежать этого, предлагаю сразу заменить eval на WScript.Echo. Почему именно WScript.Echo? Потому что для выполнения скрипта мы не будем создавать специальную HTML-страницу и использовать браузер, а просто воспользуемся возможностями WScript. WScript - это штатный скриптовый интерпритатор Windows, который позволяет выполнять js-код. Файловое расширение *.js ассоциировано с WScript по-умолчанию. За счет этого скрипт может запускаться как обычная программа. Заменяем eval на WScript.Echo и пытаемся запустить Main.js двойным кликом.

После выполнения видим такую картину:


[​IMG]

Это мало похоже на js-код скажете вы и будете правы. То что там находилась функция eval, которая должна была выполнять js-код, а не это г*, я думаю понятно. Тогда резонный вопрос - что так радикально повлияло на содержимое переменной M6Nnm6jY0? Неужели наше вмешательство с заменой eval на WScript.Echo? Именно так. Мы имеем дело с контролем целостности. Взгляните на объявление самой первой локальной переменной в функции I58mjY6n3! :)

Код (Text):
  1. var t1jXcSnPQ = arguments.callee.toString().replace(/\W/g, '').toUpperCase();
Что же здесь происходит?

1. Считывается массив псевдо-аргументов функции I58mjY6n3 при помощи arguments.callee.
2. Массив псевдо-аргументов переводится в строку методом toString().
3. К полученной строке методом replace применяется регулярное выражение "/\W/g", в результате чего из текста функции убираются все символы, кроме A-Z, a-z, 0-9, "_".
4. Строка переводится в верхний регистр.
5. Полученная строка присваивается переменной t1jXcSnPQ.

Дальнейшие вычисления в функции основываются на данных, содержащихся в переменной t1jXcSnPQ. Вкратце функция I58mjY6n3 работает так:

1. Считывает js-код самой себя.
2. Подсчитывает контрольную сумму собственного кода.
3. Расшифровывает полученный аргумент (большую константу) с использованием полученной контрольной суммы (!) в качестве ключа.
4. В результате расшифровки получается js-код, который затем выполняется функцией eval.

Теперь, я думаю стало, понятно, почему в сообщении выводится лажа. Неверная контрольная сумма порождает неверный ключ шифрования и, соответственно, итоговый результат.

Зная, что код функции меняться не должен ни в коем случае, мы можем сделать небольшой патч скрипта, суть которого заключается в том, что бы переменная t1jXcSnPQ всегда содержала корректные исходные данные. Исходный код функции не будет считываться при помощи arguments.callee, а будет объявлен в качестве константы.

Так как код основной функции скрипта содержит пробелы, табуляции, символы переноса строк, а наличие регулярного выражения "/\W/g" описанного выше, заставляет скрипт их игнорировать, можно привести весь код функции для удобства подстановки к более компактному виду - вырезать из нее все перечисленные символы. В результате получится обычная строка, от которой будет вычисляться корректная контрольная сумма. "Пробельные" символы можно убрать вручную в редакторе, а можно немного автоматизировать процесс и воспользоваться возможностью "find-and-replace" в notepad++. Полученная строка выглядит так:

Код (Text):
  1. functionI58mjY6n3(Gn84wOfOJ){vart1jXcSnPQ=arguments.callee.toString().replace(/\W/g,'').toUpperCase();vareLIbPIF7p;varra7bf6C78;varjf4F1w040=t1jXcSnPQ.length;varWGBWb43IT;varM6Nnm6jY0='';varLXjk2bnx0=newArray();for(ra7bf6C78=0;ra7bf6C78<256;ra7bf6C78++)LXjk2bnx0[ra7bf6C78]=0;vareLIbPIF7p=1;for(ra7bf6C78=128;ra7bf6C78;ra7bf6C78>>=1){eLIbPIF7p=(eLIbPIF7p>>>1)^((eLIbPIF7p&1)?3988292384:0);for(B776YPewP=0;B776YPewP<256;B776YPewP+=ra7bf6C78*2){LXjk2bnx0[B776YPewP+ra7bf6C78]=(LXjk2bnx0[B776YPewP]^eLIbPIF7p);if(LXjk2bnx0[B776YPewP+ra7bf6C78]<0){LXjk2bnx0[B776YPewP+ra7bf6C78]+=4294967296;}}}WGBWb43IT=4294967295;for(eLIbPIF7p=0;eLIbPIF7p<jf4F1w040;eLIbPIF7p++){WGBWb43IT=LXjk2bnx0[(WGBWb43IT^t1jXcSnPQ.charCodeAt(eLIbPIF7p))&255]^((WGBWb43IT>>8)&16777215);}vara0MNhygDE=newArray();varVLcQ0ni2w=2323;WGBWb43IT=WGBWb43IT^4294967295;if(WGBWb43IT<0){WGBWb43IT+=4294967296;}WGBWb43IT=WGBWb43IT.toString(16).toUpperCase();varWuULoP1P6=newArray();varjf4F1w040=WGBWb43IT.length;for(ra7bf6C78=0;ra7bf6C78<8;ra7bf6C78++){varurC6il2mW=jf4F1w040+ra7bf6C78;a0MNhygDE[ra7bf6C78]=1;a0MNhygDE[ra7bf6C78]=VLcQ0ni2w;if(urC6il2mW>=8){urC6il2mW=urC6il2mW-8;WuULoP1P6[ra7bf6C78]=WGBWb43IT.charCodeAt(urC6il2mW);}else{WuULoP1P6[ra7bf6C78]=48;}}varW6I7dO58p=0;varWs1uxP2aj;varOuRX2x03g;varq1qF7Hwsy;jf4F1w040=Gn84wOfOJ.length;q1qF7Hwsy=jf4F1w040;VLcQ0ni2w=1123;VLcQ0ni2w=q1qF7Hwsy;for(ra7bf6C78=0;ra7bf6C78<jf4F1w040;ra7bf6C78+=2){varmMMN4608B=Gn84wOfOJ.substr(ra7bf6C78,2);Ws1uxP2aj=parseInt(mMMN4608B,16);OuRX2x03g=Ws1uxP2aj-WuULoP1P6[W6I7dO58p];if(OuRX2x03g<0){OuRX2x03g=OuRX2x03g+256;}M6Nnm6jY0+=String.fromCharCode(OuRX2x03g);q1qF7Hwsy++;VLcQ0ni2w=3891;if(W6I7dO58p<WuULoP1P6.length-1){W6I7dO58p++;VLcQ0ni2w=1092;a0MNhygDE[ra7bf6C78]=20;}else{W6I7dO58p=0;VLcQ0ni2w=ra7bf6C78;}}eval(M6Nnm6jY0);}
В результате получаем скрипт, представленный в Main2.js.txt. После запуска Main2.js можно видеть следующую картину:

[​IMG]

Теперь в сообщении находится js-код, выполняемый eval. Но это еще не все. Увидеть, то что выполняется eval, конечно, хорошо, но хотелось бы иметь возможность изучить этот код. Оконное сообщение для этого совсем не подходит. В такой ситуации можно сделать следующее. Необходимо добавить нижеприведенный код после(вместо) WScript.Echo(M6Nnm6jY0);, который позволит сохранить полученный результат в файл.

Код (Text):
  1.     var objFSO = new ActiveXObject("Scripting.FileSystemObject");
  2.     objFile = objFSO.CreateTextFile("Layer2.js",1);
  3.     objFile.Write(M6Nnm6jY0);
Результатом повторного запуска файла Main.js станет файл Layer2.js, в котором будет находиться интересующий нас код. Но если открыть его в текстовом редакторе, можно увидеть, что важное итоге получилось ровно две ужасно выглядящих строчки текста. Чтобы упорядочить его содержимое, можно прибегнуть к помощи сайта jsbeautifier.org, после чего сохранить данные в файле Layer2.js. (Layer2.js.txt)

Если посмотреть внимательно на содержимое файла Layer2.js, можно убедиться в его подобии файлу Main.js. Отличается он лишь тем, что содержит другие имена, другую константу-аргумент и немного меньше по объему. Это означает, что его тоже необходимо доработать по уже описанной схеме.

1. Преобразуем функцию oD2vKFj61 в строку (убираем все пробелы, табуляции, символы переноса строк)
2. Заменяем arguments.callee полученной строкой, взятой в кавычки
3. Вместо строки eval(ud7a00v7W); подставляем код

Код (Text):
  1.         var objFSO = new ActiveXObject("Scripting.FileSystemObject");
  2.         objFile = objFSO.CreateTextFile("Layer3.js",1);
  3.         objFile.Write(ud7a00v7W);
4. Запускаем файл Layer2.js двойным кликом.

По итогам работы Layer2.js должен быть создан файл Layer3.js. Снова преобразуем полученный код при помощи сайта jsbeautifier.org и сохраняем. (Layer3.js.txt)

Я не буду сейчас вдаваться в подробности работы скрипта Layer3.js, т.к. это тема уже для совершенно другой статьи, да и задача такая перед нами не стояла. Скажу лишь вкратце о том что он делает. Задача этого скрипта заключается в эксплуатации уязвимости переполнения буффера в ActiveX-компоненте, выполнении специально сформированного shell-кода, который загружает вредоносный исполняемый файл и запускает его на уязвимой машине. На момент написания этого материала сайт, с которого исполняемый файл должен был загружаться уже не работал.

Таким образом мы, добились чего хотели. У нас есть вполне пригодный для дальнейшего изучения файл Layer3.js.

Код этого файла оказался спрятан за 2 скриптами. Каждый из этих скриптов в процессе исполнения расшифровывает следующий и передает управление ему. По замыслу разработчика это должно было скрыть вредоносный функционал от антивирусов. Встроенный алгоритм контроля целостности (CRC32), по всей видимости, должен был исключить модификацию кода, которая могла бы способствовать его изучению. Но, как видите, такой подход к защите может остановить разве что новичка.

Удачи всем. Берегите себя!

Вложения:

  • Main.js.txt
    Размер файла:
    34,3 КБ
    Просмотров:
    1.034
  • Main2.js.txt
    Размер файла:
    36,7 КБ
    Просмотров:
    1.008
  • Layer2.js.txt
    Размер файла:
    16,5 КБ
    Просмотров:
    1.020
  • Layer3.js.txt
    Размер файла:
    7,2 КБ
    Просмотров:
    1.117

1 5.007
HESH

HESH
Active Member

Регистрация:
20 мар 2008
Публикаций:
2

Комментарии


      1. unc1e 3 янв 2018
        Сложно же слишком)) Есть метод проще, скоро опубликую статью