Деобфускація JavaScript: як розшифрувати обфускований код

CyberSecureFox 🦊

У попередній статті ми розібралися, що таке обфускація JavaScript коду і навіщо вона застосовується. Обфускований код складно читати і аналізувати, що є і плюсом для приховування логіки, і мінусом для відладки та підтримки коду. Але що робити, якщо нам все ж потрібно зрозуміти, як працює обфускований скрипт? Тут на допомогу приходить деобфускація – процес відновлення коду в читабельний вигляд.

Деобфускація потрібна в кількох випадках:

  1. Аналіз шкідливих скриптів. Хакери та автори малвару часто обфускують свій код, щоб приховати шкідливу функціональність. Щоб зрозуміти алгоритм роботи малвару, необхідно відновити код.

  2. Пошук та усунення багів. Обфускація ускладнює відладку, оскільки в мініфікованому коді складно ставити брейкпоінти і дивитися значення змінних. Деобфускація спрощує пошук помилок.

  3. Вивчення функціональності прихованих фіч. Іноді розробники обфускують код з недокументованими можливостями. Через деобфускацію можна дізнатися про ці фічі.

  4. Модифікація пропрієтарного коду. У закритому ПЗ код зазвичай обфускований. Деобфускація відкриває можливості для зміни логіки під свої завдання.

Варто пам’ятати, що деобфускація пропрієтарного коду може порушувати ліцензійну угоду. Завжди звіряйтеся з правилами використання перед реверс-інжинірингом.

Базові методи деобфускації

Існує кілька основних підходів до відновлення читабельного коду:

1. Форматування (Beautify)

Перший крок деобфускації – форматування мініфікованого коду. Це не дасть повністю читабельний результат, але спростить подальший аналіз. Для форматування можна використовувати:

  • Інструменти в браузерах, наприклад “Pretty print” в Chrome DevTools
  • Онлайн-сервіси на кшталт Prettier або JS Nice
  • Плагіни для популярних редакторів коду.

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. Форматування

Відформатуємо мініфікований код, щоб він став трохи більш структурованим:

(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'));
})();

Крок 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 коду! Пам’ятайте, що за складнощами завжди ховається елегантність початкового задуму автора.

Залишити коментар

Цей сайт використовує Akismet для зменшення спаму. Дізнайтеся, як обробляються ваші дані коментарів.