Привет, хабровчане. Для будущих студентов курса «Reverse-Engineering. Basic» Александр Колесников подготовил полезную статью.

Также приглашаем всех желающих посетить открытый вебинар на тему «Эксплуатация уязвимостей в драйвере». В первой части вебинара будет пример классической уязвимости переполнения буфера, а также уязвимости переполнения целочисленного типа. Также участники вместе с экспертом разберут особенности разработки эксплойтов в режиме ядра.


Статья будет разбита на 2 части — теоретический минимум для понимания основных элементов навесных защит исполняемых файлов, и вторая, которая покажет несколько примеров разбора файлов. Все данные не претендуют на полноту. Для полного понимания темы и проведения распаковки файлов, которые были защищены рассматриваемыми защитами, нужно терпение и достаточный бэкграунд в ОС Internals.

Disclamer: Вся информация предоставляется исключительно для обучающих целей.

Навесная защита

Используемый термин «навесная защита» на самом деле используется только для обозначения класса пакеров, основная цель которых — не дать реверс инженеру или начинающему исследователю быстро разобраться с алгоритмом и модифицировать его или скопировать. Ключевое слово здесь — быстро. Способы достижения этой цели — любые, вплоть до блокирования и обнаружения любых инструментов для динамической инструментации и отладки. Давайте попробуем описать спектр возможностей:

  • Охранные процедуры, которые детектируют присутствие отладчика в системе;

  • Охранные процедуры для детектирования песочницы;

  • Охранные процедуры для детектирования динамической инструментации;

  • Блокирование возможностей отладки охраняемого процесса;

  • Виртуальная машина с собственным набором команд;

  • Методы обфускации и запутывания кода «имитовставки», «наномиты».

Список внушителен, все это заставить работать для защищаемого приложения достаточно сложная задача, но тем не менее не невыполнимая. На сегодняшний день такие защиты существуют и довольно успешно применяются. Примерами может служить WinLicense, Themida, Enigma Protector.

Все перечисленные системы коммерческие и применяются для лицензирования и защиты приложений. Эти пакеры безусловно созданы для благих целей, чтобы труд разработчиков программного обеспечения был надежно защищен, но технологии подобного рода так же могут быть использованы для упаковки вредоносного программного обеспечения. Причем, могут применяться именно перечисленные пакеры, а могут быть частичные собственные имплементации. Одним из последних и ярких примеров может быть FinSpy RAT, который как раз был защищен своей собственной имплементацией «навесной защиты».

Краеугольный камень всех перечисленных выше защит — кастомная виртуальная машина. Трюк, который математически можно описать как модифицированный конечный автомат. Именно этот элемент всегда вызывает головную боль у начинающих реверс инженеров. Как анализировать подобные механизмы? Какие инструменты применять? Попробуем разобраться.

Методы исследования

Что такое виртуальная машина для защиты кода? Это алгоритм, который оперирует своими собственными версиями команд, которые могут отличаться от команд процессора, на котором запускается приложение. Существуют 2 типа виртуальных машин:

  • Виртуальная машина, которая преобразует команды исполняемого файла, сохраняет их в файл. При запуске такого приложения команды расшифровываются в команды процессора и приложение работает как обычно.

  • Виртуальная машина содержит набор команд, которые используются для выполнения алгоритма защищаемого приложения. Защищаемое приложение преобразуется в последовательность команд, которые может разобрать только виртуальная машина, и эти команды используются для выполнения всех операций защищаемого приложения. То есть нет совершенно момента времени, когда команды виртуальной машины снова становятся обычными командами процессора. Такие виртуальные машины иногда называют «виртуальными обфускаторами».

Обычная архитектура виртуальной машины со своим набором команд имеет следующие отличительные черты:

  • Алгоритм инициализации виртуальной машины;

  • Огромный switch блок, который имплементирует каждую команду виртуальной машины;

  • Большой блок данных, который содержится в файле и не поддается декомпиляции. Обычно это байткод, который используется виртуальной машиной. Themida, к примеру, может использовать 4 разных вида байткода. При упаковке приложения пользователь может выбрать, какой вариант байткода будет использоваться.

Все действия, которые могло бы выполнять защищаемое приложение на самом низком уровне — выполняются за счет вызова команд из switch блока виртуальной машины. Как эти блоки могут выглядеть в декомпиллированной версии, можно видеть на снимках ниже:

Картинка позаимствована отсюда.

Как такое анализировать? Общий алгоритм анализа сводится к:

  • Снятие всех антиотладочных приемов;

  • Идентификация места сохранения всех регистров в структуру для начала работы виртуальной машины;

  • Идентификация используемого набора команд;

  • Идентификация используемого байткода;

  • Идентификация типа виртуальной машины (расшифровывается ли код до native в памяти и затем работает как обычно или нет);

  • Создание декодировщика для работы с идентифицированным байткодом.

Проведем небольшую практику.

Начало пути

В качестве примера будем использовать древний VMProtect 1.1 версии. Соберем простое приложение и обфусцируем его. Исходный код приложения:

#include <iostream>

int main
{
  std::cout<< "Hello World!" <<std::endl;
  system("pause");
  return 0;
}

Приложение соберем в release варианте и декомпилируем. В итоге Main функция будет выглядеть так:

А после преобразования через VMProtect:

Так как команд вообщем-то не так много, то и на графе мы видим не огромный switch блок, а всего лишь несколько дополнительных блоков.

Пройдемся по каждому из них:

Первые два блока выполняют функцию первых шагов инициализации. В этих шагах сохраняются регистры общего назначения в общую струтуру для более удобного использования и так же производится инициализация указателя на набор байткода, который будет использоваться для работы алгоритма приложения.

Последующие два блока созданы для запутывания алгоритма, это и есть слабая обфускация, которая просто замыливает глаз. Основные действия выполняются дальше, в последнем блоке этой функции:

Последний блок позволяет инициализировать edx регистр адресом функции обработчика и алгоритм вытягивается через выполнение виртуальной машиной байткода.

Кстати, байткод выглядит так:

В следующей статье познакомимся с тем как раскодировать команды и провести отладку такого приложения.


Узнать подробнее о курсе «Reverse-Engineering. Basic».

Смотреть открытый вебинар на тему «Эксплуатация уязвимостей в драйвере».