D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
В этой статье я расскажу, как самостоятельно обходить обфускацию JavaScript в тех случаях, когда не помогают даже нестандартные деобфускаторы. Мы рассмотрим метод исследования запутанного кода и напишем свой собственный деобфускатор.
Среди читателей наверняка есть те, кому тема скриптовой обфускации гораздо милее ассемблера и прочего низкоуровневого колдунства. Именно этому вопросу была посвящена, например, моя недавняя статья «JSFuck. Разбираем уникальный метод обфускации JS-кода» или упоминаемая в ней обзорная статья про обфускаторы на Хабре. Но что делать, если перечисленные в этой статье кастомные деобфускаторы не помогают? В таком случае обфускацию придется обходить самостоятельно, и сейчас я расскажу, как это делается.
А заканчивается этот код вот так.
Если ты уже успел ознакомиться с упомянутой выше статьей, характерные имена идентификаторов (_0x58cd18, _0x2f8935_0x321d33, _0x1e0595) должны были натолкнуть тебя на мысль, что код запутан обфускатором obfuscator.io. Однако попытка деобфускации его стандартным онлайн‑деобфускатором при любых настройках не приносит положительного результата: читаемый код в правом окне просто не появляется.
Точно так же не приносят результатов и попытки деобфускации другими упомянутыми в статье инструментами. Например, универсальный деобфускатор de4js выдает совершенно неинформативный результат.
Похоже, надежды на автоматические деобфускаторы мало и нам придется учиться работать руками.
JavaScript: Скопировать в буфер обмена
Логично предположить, что таким образом зашифрованы константы, которые первым делом надо перевести в нормальный читаемый вид. Как обычно, начнем с конца. Код заканчивается следующим фрагментом:
JavaScript: Скопировать в буфер обмена
Если мыслить логически, это console.log(_0x321d33), то есть _0x1e0595(0x4f3, 0x854, 0x1210, 0x19e1, 0x3ca, 0x992, 0x665, 0xf98, 0x185b, 0x1073) == "log". Попробуем провернуть этот фарш назад: ищем в коде строку function _0x1e0595:
JavaScript: Скопировать в буфер обмена
Как видишь, эта хрень ссылается на другую субхрень по имени _0x340121. Ищем и ее тоже:
JavaScript: Скопировать в буфер обмена
Эта субхрень, в свою очередь, ссылается на протохрень под именем _0x3a86, которая, по счастью, последняя (точнее, первая) в этой цепочке:
JavaScript: Скопировать в буфер обмена
Пока что все просто. Осталось найти массив строковых констант, возвращаемый _0x5e2d:
JavaScript: Скопировать в буфер обмена
Итак, похоже, мы вычленили минимальный фрагмент кода, отвечающий за генерацию обфусцированных строк через функцию _0x1e0595:
JavaScript: Скопировать в буфер обмена
В дальнейшем нам предстоит автоматизировать поиски кода для каждой аналогичной функции (хоть их и много, но они однотипные). А пока что мы просто попытаемся убедиться, что всё сделали правильно. Жмем в браузере F12 и вставляем найденный фрагмент кода в консоль, пробуя вычислить выражение _0x1e0595(0x4f3, 0x854, 0x1210, 0x19e1, 0x3ca, 0x992, 0x665, 0xf98, 0x185b, 0x1073).
В этот момент мы убеждаемся, что возвращаемая строка не log, а, наоборот, awal, хотя строка log в исходном массиве тоже присутствует. Значит, мы где‑то облажались в расчетах или авторы обфускатора нас хитро обдурили, хотя счастье было так близко...
Посмотрим на код более внимательно. Верхний фрагмент кода, начиная с комментария IT IS NOT SAFE TO MAKE CHANGES IN THE CODE BELOW, хитро перемешивает массив строковых констант _0x552e21 после его инициализации.
Занятно, что в условии while мы обнаруживаем знакомую нам по JSFuck конструкцию (!![])==true. При внимательном рассмотрении отмечаем, что такие константы вместе с обратным вариантом (![])==false щедро раскиданы по обфусцированному коду. Делаем себе заметку на будущее поменять их в коде глобальной заменой, после чего вставляем фрагмент, показанный на предыдущем скриншоте, в начало нашего «ядерного кода» и снова делаем тест. На этот раз все сходится, результат правильный: _0x1e0595(0x4f3, 0x854, 0x1210, 0x19e1, 0x3ca, 0x992, 0x665, 0xf98, 0x185b, 0x1073) == "log".
На этом интересная и увлекательная исследовательская часть заканчивается и начинается кодинг, хоть и несложный, но довольно рутинный.
По образу и подобию описанного выше процесса препарирования функции _0x1e0595 полностью формируем «ядро» функций, которые будут декодировать строковые константы. Для этого ищем все функции, соответствующие вот такому шаблону:
JavaScript: Скопировать в буфер обмена
Примерная реализация этого действия через регулярные выражения на JavaScript выглядит так:
JavaScript: Скопировать в буфер обмена
Здесь string — исходный код, на выходе получаем functions — массив кода функций, которые надо добавить к «ядерному» коду и одновременно убрать из кода исходного; names — список имен этих функций.
Ищем функции вида
JavaScript: Скопировать в буфер обмена
Здесь Name1 — имя функции, из списка names, полученного в пункте 1. Код выглядит следующим образом:
JavaScript: Скопировать в буфер обмена
Получаем новый список функций functions1 и их имен names1, которые тоже добавляем в ядро и убираем из исходного кода.
Повторяем поиск функции, каждый раз подставляя вместо списка names полученный на предыдущем этапе список names1 до тех пор, пока на очередном шаге список не опустеет. Итоговый код выглядит примерно так:
JavaScript: Скопировать в буфер обмена
На выходе functions — это все паразитические функции, сгенерированные обфускатором, а names — их имена.
Теперь, когда «ядро» сформировано, мы просто перебираем все исчисляемые выражения вида
JavaScript: Скопировать в буфер обмена
Name1 — имя функции из полученного на предыдущих шагах списка names. Их мы вычисляем при помощи вот такого кода:
JavaScript: Скопировать в буфер обмена
На выходе мы получаем массив expressions, в котором содержатся исчисляемые выражения вида _0x4a0111(0xa0c, 0xc0b, 0x13ef, 0x1e3e, 0x15e2, 0x1a29, 0x1b08, 0x94b, 0x968, 0x753) и соответствующие им константы. Нам остается просто заменить в обфусцированном коде первые вторыми обычной глобальной заменой.
В итоге мы получаем частично деобфусцированный код, в котором хотя бы строковые константы и имена стандартных методов будут представлены в явном виде. Этот код уже вполне можно анализировать, править, можно кормить им по частям другие деобфускаторы для приведения в полностью читаемый вид.
Разумеется, это далеко не полная деобфускация исходного приложения. В стремлении к совершенству можно свернуть выражения вида Class["MethodName"] в Class.MethodName. Вот чуть более продвинутый вариант кода в Object.MethodName:
JavaScript: Скопировать в буфер обмена
Напоследок можно выполнить несколько словарных преобразований следующего вида:
JavaScript: Скопировать в буфер обмена
В итоге мы получим близкий к читабельному код, в котором только исходные имена переменных и функций будут безвозвратно потеряны.
Взято: ТУТ
Среди читателей наверняка есть те, кому тема скриптовой обфускации гораздо милее ассемблера и прочего низкоуровневого колдунства. Именно этому вопросу была посвящена, например, моя недавняя статья «JSFuck. Разбираем уникальный метод обфускации JS-кода» или упоминаемая в ней обзорная статья про обфускаторы на Хабре. Но что делать, если перечисленные в этой статье кастомные деобфускаторы не помогают? В таком случае обфускацию придется обходить самостоятельно, и сейчас я расскажу, как это делается.
Используем автоматические деобфускаторы
Для примера возьмем некое браузерное JavaScript-приложение. Объем его составляет около трех мегабайт, примерно три четверти из которых занимает жестко обфусцированный код, начинающийся так, как показано на следующем скриншоте.А заканчивается этот код вот так.
Если ты уже успел ознакомиться с упомянутой выше статьей, характерные имена идентификаторов (_0x58cd18, _0x2f8935_0x321d33, _0x1e0595) должны были натолкнуть тебя на мысль, что код запутан обфускатором obfuscator.io. Однако попытка деобфускации его стандартным онлайн‑деобфускатором при любых настройках не приносит положительного результата: читаемый код в правом окне просто не появляется.
Точно так же не приносят результатов и попытки деобфускации другими упомянутыми в статье инструментами. Например, универсальный деобфускатор de4js выдает совершенно неинформативный результат.
Похоже, надежды на автоматические деобфускаторы мало и нам придется учиться работать руками.
INFO
Забегая вперед, скажу, что перечисленными выше инструментами мы не исчерпали все средства автоматической деобфускации. Например, можно попытаться оптимизировать код через нейросеть Llama. Автоматически деобфусцировать подобный код умеет проект webcrack, но давай все‑таки сделаем вид, что с использованием автоматических средств у нас ничего не вышло, — так намного интереснее!
Деобфусцируем код вручную
Для начала натравим на сырой код JS Beautifier, дабы придать ему читабельность. Пробежавшись по теперь уже структурированному коду, обращаем внимание на многочисленные вызовы функций с десятью шестнадцатеричными константами в виде параметров:JavaScript: Скопировать в буфер обмена
Код:
_0x4a0111(0xa0c, 0xc0b, 0x13ef, 0x1e3e, 0x15e2, 0x1a29, 0x1b08, 0x94b, 0x968, 0x753)
_0x114a88(-0x6d, 0x126c, 0x621, 0xa59, -0x5f2, 0x72a, 0x6cf, 0x8a, 0xbf9, -0x4b4)
_0x27e22f(0xbf2, 0x670, 0xe4, 0x132e, 0x1267, 0xbf9, -0xb6, 0x697, 0x51f, 0x6da)
_0x1e51ce(0xe58, 0x1b5e, 0x2457, 0x191a, 0x224a, 0x133c, 0xf61, 0x1c11, 0x128d, 0xc77)
_0x33055f(0x16f4, 0x1704, 0xbaf, 0x231d, 0x163e, 0x161a, 0xca1, 0x15ba, 0x1c3f, 0x1649)
_0x1b164c(0x485, -0x398, 0x1e0, 0xf51, 0xcdd, 0x2de, 0xfea, 0x82f, -0x54a, 0x37)
...
JavaScript: Скопировать в буфер обмена
Код:
} catch (_0x321d33) {
console[_0x1e0595(0x4f3, 0x854, 0x1210, 0x19e1, 0x3ca, 0x992, 0x665, 0xf98, 0x185b, 0x1073)](_0x321d33);
}
});
JavaScript: Скопировать в буфер обмена
Код:
function _0x1e0595(_0x4bc581, _0x4ecbba, _0x1d5a39, _0x50dcae, _0x403da8, _0x4ad34e, _0x2b446e, _0x3b51da, _0x44854e, _0x1491c6) {
return _0x340121(_0x1491c6 - 0x5ff, _0x4ecbba - 0xb8, _0x1d5a39 - 0xb2, _0x50dcae - 0x81, _0x403da8 - 0x80, _0x4ad34e - 0x1b, _0x2b446e - 0x99, _0x44854e, _0x44854e - 0x72, _0x1491c6 - 0x10f);
JavaScript: Скопировать в буфер обмена
Код:
function _0x340121(_0x5ae465, _0x101079, _0x1d662f, _0x55f16c, _0x4029db, _0x3a7a06, _0x1d53e1, _0x5b0eb3, _0x4c47fe, _0x445726) {
return _0x3a86(_0x5ae465 - -0x26c, _0x5b0eb3);
}
JavaScript: Скопировать в буфер обмена
Код:
function _0x3a86(_0x37610f, _0x5cbb3a) {
const _0x1214fd = _0x5e2d();
return _0x3a86 = function(_0x3aa59b, _0x1ad7b1) {
_0x3aa59b = _0x3aa59b - (-0x10 * -0x80 + 0x69 * -0x1 + 0xa * -0xb5);
_0x4072cc = _0x1214fd[_0x3aa59b];
return _0x4072cc;
}, _0x3a86(_0x37610f, _0x5cbb3a);
}
JavaScript: Скопировать в буфер обмена
Код:
function _0x5e2d(){
const _0x552e21=['t?id=','mjSUq','wcYjB','pljgg','ct:\x20<','accou','nlMqS',
...
'se,\x22s','xESCJ'];
_0x5e2d=function(){
return _0x552e21;
};
return _0x5e2d();
}
JavaScript: Скопировать в буфер обмена
Код:
function _0x5e2d(){
const _0x552e21=['t?id=','mjSUq','wcYjB','pljgg','ct:\x20<','accou','nlMqS',
...
'se,\x22s','xESCJ'];
_0x5e2d=function(){
return _0x552e21;
};
return _0x5e2d();
}
function _0x3a86(_0x37610f, _0x5cbb3a) {
const _0x1214fd = _0x5e2d();
return _0x3a86 = function(_0x3aa59b, _0x1ad7b1) {
_0x3aa59b = _0x3aa59b - (-0x10 * -0x80 + 0x69 * -0x1 + 0xa * -0xb5);
_0x4072cc = _0x1214fd[_0x3aa59b];
return _0x4072cc;
}, _0x3a86(_0x37610f, _0x5cbb3a);
}
function _0x340121(_0x5ae465, _0x101079, _0x1d662f, _0x55f16c, _0x4029db, _0x3a7a06, _0x1d53e1, _0x5b0eb3, _0x4c47fe, _0x445726) {
return _0x3a86(_0x5ae465 - -0x26c, _0x5b0eb3);
}
function _0x1e0595(_0x4bc581, _0x4ecbba, _0x1d5a39, _0x50dcae, _0x403da8, _0x4ad34e, _0x2b446e, _0x3b51da, _0x44854e, _0x1491c6) {
return _0x340121(_0x1491c6 - 0x5ff, _0x4ecbba - 0xb8, _0x1d5a39 - 0xb2, _0x50dcae - 0x81, _0x403da8 - 0x80, _0x4ad34e - 0x1b, _0x2b446e - 0x99, _0x44854e, _0x44854e - 0x72, _0x1491c6 - 0x10f);
}
В этот момент мы убеждаемся, что возвращаемая строка не log, а, наоборот, awal, хотя строка log в исходном массиве тоже присутствует. Значит, мы где‑то облажались в расчетах или авторы обфускатора нас хитро обдурили, хотя счастье было так близко...
Посмотрим на код более внимательно. Верхний фрагмент кода, начиная с комментария IT IS NOT SAFE TO MAKE CHANGES IN THE CODE BELOW, хитро перемешивает массив строковых констант _0x552e21 после его инициализации.
Занятно, что в условии while мы обнаруживаем знакомую нам по JSFuck конструкцию (!![])==true. При внимательном рассмотрении отмечаем, что такие константы вместе с обратным вариантом (![])==false щедро раскиданы по обфусцированному коду. Делаем себе заметку на будущее поменять их в коде глобальной заменой, после чего вставляем фрагмент, показанный на предыдущем скриншоте, в начало нашего «ядерного кода» и снова делаем тест. На этот раз все сходится, результат правильный: _0x1e0595(0x4f3, 0x854, 0x1210, 0x19e1, 0x3ca, 0x992, 0x665, 0xf98, 0x185b, 0x1073) == "log".
На этом интересная и увлекательная исследовательская часть заканчивается и начинается кодинг, хоть и несложный, но довольно рутинный.
Пишем деобфускатор
План построения деобфускатора у нас будет следующий.По образу и подобию описанного выше процесса препарирования функции _0x1e0595 полностью формируем «ядро» функций, которые будут декодировать строковые константы. Для этого ищем все функции, соответствующие вот такому шаблону:
JavaScript: Скопировать в буфер обмена
Код:
function _0x??????(_0x??????, _0x??????, _0x??????, _0x??????, _0x??????, _0x??????, _0x??????, _0x??????, _0x??????, _0x??????) {
return _0x3a86(??????, ??????);
}
JavaScript: Скопировать в буфер обмена
Код:
var reg = /function (_0x[a-f0-9]*)\(_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*\)\{return _0x3a86\([^)]*\);\}/g;
var functions = [], found,names=[];
while (found = reg.exec(string)) {
functions.push(found[0]);
names.push(found[1]);
}
Ищем функции вида
JavaScript: Скопировать в буфер обмена
Код:
function _0x??????(_0x??????, _0x??????, _0x??????, _0x??????, _0x??????, _0x??????, _0x??????, _0x??????, _0x??????, _0x??????) {
return <Name1>(??????, ??????,??????, ??????,??????, ??????,??????, ??????,??????, ??????);
}
JavaScript: Скопировать в буфер обмена
Код:
var reg = /function (_0x[a-f0-9]*)\(_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*\)\{return _0x3a86\([^)]*\);\}/g;
var functions = [], found,names=[];
while (found = reg.exec(string)) {
functions.push(found[0]);
names.push(found[1]);
}
var functions1 = [], names1=[];
for (var i=0;i<names.length;i++)
{
var reg1 = new RegExp("function (_0x[a-f0-9]*)\\(_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*\\)\\{return "+names[i]+"\\([^)]*\\);\\}", "g");
while (found = reg1.exec(string)) {
functions1.push(found[0]);
names1.push(found[1]);
}
}
Повторяем поиск функции, каждый раз подставляя вместо списка names полученный на предыдущем этапе список names1 до тех пор, пока на очередном шаге список не опустеет. Итоговый код выглядит примерно так:
JavaScript: Скопировать в буфер обмена
Код:
var reg = /function (_0x[a-f0-9]*)\(_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*\)\{return _0x3a86\([^)]*\);\}/g;
var functions = [], found,names=[];
while (found = reg.exec(string)) {
functions.push(found[0]);
names.push(found[1]);
}
var names2=names.slice();
while (true)
{
var functions1 = [], names1=[];
for (var i=0;i<names2.length;i++)
{
var reg1 = new RegExp("function (_0x[a-f0-9]*)\\(_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*,_0x[a-f0-9]*\\)\\{return "+names2[i]+"\\([^)]*\\);\\}", "g");
while (found = reg1.exec(string)) {
functions1.push(found[0]);
names1.push(found[1]);
functions.push(found[0]);
names.push(found[1]);
}
}
if (names1.length==0) break;
names2=names1.slice();
}
Теперь, когда «ядро» сформировано, мы просто перебираем все исчисляемые выражения вида
JavaScript: Скопировать в буфер обмена
<Name1>(??????,??????,??????,??????,??????,??????,??????,??????,??????,??????)
Name1 — имя функции из полученного на предыдущих шагах списка names. Их мы вычисляем при помощи вот такого кода:
JavaScript: Скопировать в буфер обмена
Код:
var expressions=[];
var values=[];
for (var i=0;i<names.length;i++)
{
var reg1 = new RegExp(names[i]+"\\([^)]*,[^)]*,[^)]*,[^)]*,[^)]*,[^)]*[^)]*,[^)]*,[^)]*,[^)]*\\)", "g");
while (found = reg1.exec(string)) {
var test=found[0];
var value=undefined;
try
{
value=eval(test);
expressions.push(found[0]);
values.push(value);
} catch (err) {}
}
}
В итоге мы получаем частично деобфусцированный код, в котором хотя бы строковые константы и имена стандартных методов будут представлены в явном виде. Этот код уже вполне можно анализировать, править, можно кормить им по частям другие деобфускаторы для приведения в полностью читаемый вид.
Разумеется, это далеко не полная деобфускация исходного приложения. В стремлении к совершенству можно свернуть выражения вида Class["MethodName"] в Class.MethodName. Вот чуть более продвинутый вариант кода в Object.MethodName:
JavaScript: Скопировать в буфер обмена
Код:
const _0x17ef7d = {};
_0x17ef7d[MethodName]
JavaScript: Скопировать в буфер обмена
Код:
const _0x45618a = {
'aJqDi': function(_0x2b031d, _0x1fc5a3) {
return _0x2b031d(_0x1fc5a3);
},
'FDTmk': function(_0x542b4c, _0x55bd01) {
return _0x542b4c(_0x55bd01);
},
Выводы
По своей любимой привычке я слегка слукавил и описал всего лишь самый простой и быстрый способ частичной деобфускации JavaScript-кода. Для написания серьезного полного деобфускатора простым поиском‑заменой регулярных выражений, разумеется, не обойтись. Для этого по‑хорошему надо писать свой собственный эмулятор JavaScript-машины, как, например, это реализовано в упомянутом мною деобфускаторе webcrack. Возможно, я когда‑нибудь расскажу об этом подробнее, а пока интересующиеся и нетерпеливые могут самостоятельно покопаться в исходном коде этого проекта.Взято: ТУТ