Деобфускация JavaScript кода: разгадываем пазл

Фото автора

Kamil Akbari

В предыдущей статье мы разобрались, что такое обфускация JavaScript кода и зачем она применяется. Обфусцированный код сложно читать и анализировать, что является и плюсом для сокрытия логики, и минусом для отладки и поддержки кода. Но что делать, если нам все же нужно понять, как работает обфусцированный скрипт? Здесь на помощь приходит деобфускация — процесс восстановления кода в читаемый вид.

Зачем нужна деобфускация 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 кода! Помните, что за сложностями всегда скрывается элегантность исходной задумки автора.


Kamil Akbari

Камил Акбари — автор и редактор по кибербезопасности в CyberSecureFox. Более 5 лет работает в сфере кибербезопасности, занимается разработкой software и security-инструментов. Специализируется на AI security, анализе CVE, ransomware, malware, cloud security и практиках пентестинга. При подготовке материалов опирается на official advisories, CVE/NVD, CISA, публикации вендоров и отчёты исследователей.

Оставьте комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.