У попередній статті ми розібралися, що таке обфускація JavaScript коду і навіщо вона застосовується. Обфускований код складно читати і аналізувати, що є і плюсом для приховування логіки, і мінусом для відладки та підтримки коду. Але що робити, якщо нам все ж потрібно зрозуміти, як працює обфускований скрипт? Тут на допомогу приходить деобфускація – процес відновлення коду в читабельний вигляд.
Навіщо потрібна деобфускація JavaScript
Деобфускація потрібна в кількох випадках:
-
Аналіз шкідливих скриптів. Хакери та автори малвару часто обфускують свій код, щоб приховати шкідливу функціональність. Щоб зрозуміти алгоритм роботи малвару, необхідно відновити код.
-
Пошук та усунення багів. Обфускація ускладнює відладку, оскільки в мініфікованому коді складно ставити брейкпоінти і дивитися значення змінних. Деобфускація спрощує пошук помилок.
-
Вивчення функціональності прихованих фіч. Іноді розробники обфускують код з недокументованими можливостями. Через деобфускацію можна дізнатися про ці фічі.
-
Модифікація пропрієтарного коду. У закритому ПЗ код зазвичай обфускований. Деобфускація відкриває можливості для зміни логіки під свої завдання.
Варто пам’ятати, що деобфускація пропрієтарного коду може порушувати ліцензійну угоду. Завжди звіряйтеся з правилами використання перед реверс-інжинірингом.
Базові методи деобфускації
Існує кілька основних підходів до відновлення читабельного коду:
1. Форматування (Beautify)
Перший крок деобфускації – форматування мініфікованого коду. Це не дасть повністю читабельний результат, але спростить подальший аналіз. Для форматування можна використовувати:
-
Інструменти в браузерах, наприклад “Pretty print” в Chrome DevTools
-
Плагіни для популярних редакторів коду.
2. Перейменування змінних
Наступний етап – дати змінним і функціям осмислені імена замість однобуквених. Хороші деобфускатори роблять це автоматично на основі контексту використання імен.
Наприклад, змінну “a” перейменують в “userName“, якщо вона використовується в рядку “Hello ” + a.
3. Видалення мертвого коду
Обфускатори часто вставляють недосяжний код, який ніколи не виконується, але сильно заважає аналізу. При деобфускації його потрібно видаляти. Наприклад:
if (false) {
x = 10;
}
4. Відновлення потоку управління
Обфускація заплутує послідовність виконання коду безглуздими умовами і переходами. Завдання деобфускатора – відновити нормальну структуру з лінійними блоками коду і мінімумом умов.
Приклад з надлишковою умовою:
// Обфусковано
function check(x) {
if (x >= 0) {
if (x > 0) {
console.log('Positive');
} else {
console.log('Zero');
}
}
}
// Деобфусковано
function check(x) {
if (x > 0) {
console.log('Positive');
} else if (x === 0) {
console.log('Zero');
}
}
5. Підстановка значень
Багато обфускаторів виносять рядки і числа в окремі масиви, а потім звертаються до них за індексом. При деобфускації ці значення потрібно вбудовувати назад у код.
// Обфусковано
const strings = ['Hello', 'world', '!'];
console.log(strings[0] + ' ' + strings[1] + strings[2]);
// Деобфусковано
console.log('Hello' + ' ' + 'world' + '!');
6. Спрощення виразів
Обфускатори ускладнюють арифметичні та логічні вирази. Наприклад, x * 1 замість x або !!x замість x. При відновленні коду такі надлишковості потрібно видаляти.
Було:
if (!!x && y !== undefined) {
result = x * 1 + y / 2;
}
Стало:
if (x && y !== undefined) {
result = x + y / 2;
}
Інструменти для деобфускації
Ручна деобфускація – копіткий процес, який може зайняти багато часу. На щастя, існують інструменти для автоматизації розплутування коду:
-
JS Nice – онлайн-сервіс, який не тільки форматує, але й перейменовує змінні, видаляє мертвий код і відновлює потік управління.
-
de4js – ще один онлайн-деобфускатор з підтримкою розпакування, підстановки рядків і спрощення управління.
-
JavaScript Deobfuscator – десктопний деобфускатор з широкими можливостями по аналізу AST (абстрактного синтаксичного дерева).
-
Розширення для браузерів, наприклад JavaScript Deobfuscator для Chrome, яке дозволяє деобфускувати код прямо в DevTools.
Однак навіть найбільш просунуті інструменти не дають 100% читабельний код, особливо при нестандартних техніках обфускації. Тому для повноцінного відновлення логіки часто потрібен ручний аналіз.
Покроковий приклад деобфускації
Розберемо на практиці відновлення читабельного коду з обфускованого скрипта.
Припустимо, у нас є такий код:
(function() {
var _0x5d2a = ['log', 'Hello\x20World'];
(function(_0x25a336, _0x5d2afa) {
var _0x41fb0f = function(_0x9fdc11) {
while (--_0x9fdc11) {
_0x25a336['push'](_0x25a336['shift']());
}
};
_0x41fb0f(++_0x5d2afa);
}(_0x5d2a, 0x78));
var _0x41fb = function(_0x25a336, _0x5d2afa) {
_0x25a336 = _0x25a336 - 0x0;
var _0x41fb0f = _0x5d2a[_0x25a336];
return _0x41fb0f;
};
console[_0x41fb('0x0')](_0x41fb('0x1'));
})();
Крок 1. Форматування
Відформатуємо мініфікований код, щоб він став трохи більш структурованим:
Крок 2. Аналіз масиву рядків
У першому рядку коду оголошується масив _0x5d2a з двома елементами: ‘log’ і ‘Hello\x20World’.
Запам’ятаємо це і продовжимо аналіз.
Крок 3. Аналіз IIFE
Наступний блок – immediately invoked function expression (IIFE).
Він приймає масив _0x5d2a і число 0x78 (120 в десятковій системі), яке потім збільшується на 1.
Всередині IIFE оголошується функція _0x41fb0f, яка приймає число _0x9fdc11 і в циклі викликає push і shift у масиву _0x25a336. По суті це перемішування масиву.
Після оголошення _0x41fb0f відразу викликається з аргументом 0x79 (121 в десятковій).
Крок 4. Аналіз функції-геттера
Далі оголошується функція _0x41fb, яка приймає 2 аргументи.
Перший аргумент _0x25a336 зменшується на 0x0 (0 в десятковій), тобто залишається без змін.
Потім з масиву _0x5d2a за індексом _0x25a336 береться елемент і повертається. Тобто це обгортка для звернення до масиву за індексом.
Крок 5. Підстановка значень
В останньому рядку у об’єкта console викликається метод з індексом ‘0x0’ (0) і аргументом ‘0x1‘ (1).
У масиві _0x5d2a за індексом 0 зберігається ‘log’, а за індексом 1 – ‘Hello\x20World’.
Підставивши ці значення, отримаємо:
console['log']('Hello\x20World');
Або, спростивши:
console.log('Hello World');
Підсумок
Початковий обфускований код просто виводив у консоль ‘Hello World‘, хоча і дуже заплутаним способом.
Зібравши всі кроки деобфускації, отримаємо такий код:
(function() {
console.log('Hello World');
})();
Поради щодо аналізу обфускованого коду
-
Починайте з форматування та аналізу оголошень змінних. Це дасть загальне уявлення про структуру коду.
-
Якщо бачите незрозумілі числа в коді, спробуйте перевести їх в інші системи числення (десяткову, ASCII).
-
Звертайте увагу на вразливі вбудовані функції на кшталт
eval()таFunction(). Через них можуть виконуватися інші частини обфускованого коду. -
Використовуйте відладчик і покрокове виконання, щоб дивитися значення змінних і послідовність операцій.
-
Якщо не розумієте логіку роботи ділянки коду, спробуйте її тимчасово видалити або замінити заглушкою. Можливо, вона не впливає на основну функціональність.
-
Шукайте патерни і повторення. Обфускатори генерують шаблонний код, тому одні й ті ж конструкції можуть використовуватися в різних частинах.
-
Запасіться терпінням. Деобфускація може забирати багато часу, особливо при розборі шкідливих скриптів.
Висновок
Деобфускація JavaScript коду – цінна навичка, яка знадобиться для аналізу шкідливих скриптів, пошуку багів, прихованих можливостей і налаштування закритого коду. Основні методи деобфускації включають форматування, перейменування змінних, видалення мертвого коду, відновлення потоку управління, підстановку значень і спрощення виразів.
Для автоматизації етапів деобфускації існують онлайн-сервіси на кшталт JS Nice та de4js, а також десктопні утиліти і браузерні розширення. Однак при складній обфускації не обійтися без ручного аналізу. Сам процес деобфускації складається з послідовного розбору ділянок коду, підстановки значень змінних і спрощення логіки. Він вимагає терпіння, знання JavaScript і дедуктивних здібностей.
Щоб ефективніше аналізувати обфускований код, починайте з форматування і розбору оголошень змінних. Звертайте увагу на підозрілі вбудовані функції, використовуйте відладчик для покрокового виконання. Шукайте шаблони і повторення в згенерованому обфускаторами коді. Вивчення деобфускації розвиває навички уважного читання коду, відладки і реверс-інжинірингу. Це суперсила для програміста, яка точно знадобиться вам у роботі або участі в CTF-змаганнях.
Бажаю успіхів у нетривіальному, але захоплюючому процесі розплутування клубків обфускованого JavaScript коду! Пам’ятайте, що за складнощами завжди ховається елегантність початкового задуму автора.