Деобфускация 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 для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.