Pull to refresh

Анализ виртуальной машины на примере VMProtect. Часть 1

Level of difficultyMedium
Reading time10 min
Views4.6K

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

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

Вступление

Как понятно из названия, понятие виртуальной машины (для краткости далее ВМ) в данном контексте используется как описание механизма защиты программного обеспечения. Применение такой защиты заключается в представлении исходного алгоритма программы в виде последовательности команд, которые будут интерпретироваться соответствующими обработчиками (подробнее далее).

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

На написание данной статьи побудил образец вредоносного ПО группировки PurpleFox, в котором применялась защита VMProtect версии 2.13 (не самая свежая, но как пример описания возможной реализации ВМ вполне подойдет). В различных упоминаниях вредоносных кампаний, в которых были задействованы схожие образцы, говорилось о применении руткита в качестве следующего этапа после развертывания защищенного с помощью VMProtect вредоносного модуля, от этого и без того большой интерес к данному образцу ощутимо вырос.

Итак, есть интересный повод разобраться в работе ВМ как средства защиты от анализа, идем дальше.

PurpleFox

Анализируемый вредоносный модуль наряду с еще двумя содержится в MSI-установщике, который попадает на устройство жертвы в результате работы относительно длинной цепочки выполнения различных вредоносных компонентов. Примеры таких цепочек, а также другая информация о такой вредоносной активности довольно подробно раскрывается в разных статьях по теме (пример 1, пример 2), поэтому не буду дублировать то, что и так хорошо описано ?.

Анализируемый вредоносный модуль очень похож на данный образец (возможно, применялся в ходе той же или близкой по времени кампании). Так что если кто-то хочет повторить подходы, приведенные в статье, то можно воспользоваться указанной ссылкой.

Виртуальная машина как средство защиты от анализа

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

Если смотреть очень поверхностно на устройство виртуальной машины, то основными компонентами являются:

  • виртуальный машинный код (байткод, последовательность инструкций ВМ) - по сути это сама программа, которая будет выполняться виртуальной машиной;

  • VM dispatcher - не смог по-нормальному назвать по-русски (как вам вариант “отправитель виртуальной машины”? ?), по сути это обработчик очередной виртуальной инструкции из последовательности инструкций ВМ
    (можно его так и назвать - обработчик последовательности инструкций ВМ или обработчик байткода ВМ);

  • обработчик виртуальной инструкции - на него передается управление для выполнения очередной инструкции из байткода ВМ (или VM handler).

Если представить в виде простой схемы, то взаимодействие данных компонентов может выглядеть следующим образом:

Крупными мазками зарисовал схему работы ВМ
Крупными мазками зарисовал схему работы ВМ

При этом помимо упомянутых основных компонентов для работы виртуальной машины используются:

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

  • указатель на инструкцию ВМ в байткоде - VM_EIP
    (аналогично регистру EIP в архитектуре x86);

  • виртуальный стек и указатель на него VM_ESP (аналогично регистру ESP).

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

Как видно, у виртуальной машины есть сильные сходства с обычной архитектурой набора команд процессора (Instruction Set Architecture, ISA) - свои регистры и контекст выполнения в общем, а также сам набор команд.

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

Подробнее о работе ВМ можно прочитать в статьях ESET (постарее и посвежее), а также в большом, очень интересном и содержательном цикле статей по анализу ВМ.

Виртуальная машина в модуле PurpleFox

Так или иначе, к нам на анализ попадает указанный модуль (winupdate32), защищенный VMProtect. В идеале, как команда XZibit’а с картинки, мы надеемся разобрать эту тачку и собрать заново в доступном и очень удобном для нас представлении ?.

Первая реакция на увиденную защиту
Первая реакция на увиденную защиту

Из поверхностного анализа видно, что это библиотека, которая экспортирует функцию ServiceMain, которая фактически ничего не делает, ведь при загрузке библиотеки сначала выполняется функция DllMain (DllEntryPoint), в которой и реализована вся работа вредоносного модуля вместе с ВМ.

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

Мусорный код и запутывание в анализируемом образце
Мусорный код и запутывание в анализируемом образце

При этом если все-таки попытаться пройти по потоку управления, то вскоре можно наткнуться на то, что обычно называется MAIN_LOOP, который по сути является обработчиком последовательности инструкций ВМ (VM dispatcher). Найти MAIN_LOOP позволяет инструкция, похожая на эту:

Пример характерной для MAIN_LOOP инструкции
Пример характерной для MAIN_LOOP инструкции

То есть инструкция перемещения в регистр значения из памяти по определенному индексу.
В большинстве материалов по анализу VMProtect встречается такая инструкция, в которой по сути извлекается адрес обработчика очередной инструкции байткода ВМ.

Если перейти по адресу указанного массива, можно увидеть эти самые адреса обработчиков (возможно, IDA распознает данный участок как код, а не данные, придется преобразовать,
а потом, еще лучше, представить в виде массива). Видно, что это адреса обработчиков,
так как младшие два байта везде одинаковые (в Little Endian), то есть это адреса в загруженном образе относительно ImageBase:

Массив адресов обработчиков инструкций ВМ
Массив адресов обработчиков инструкций ВМ

В общем, если у аналитика появляется потребность или желание проанализировать работу ВМ, то первое, с чего можно начать, это поиск MAIN_LOOP с помощью обнаружения инструкции вида (для x86 архитектуры):

mov r32, m32[r32 * 4]

где r32 это какой-то регистр, а m32 это какой-то адрес в памяти.

В примере, доступном на MalwareBazaar по ссылке выше, данная инструкция расположена по адресу 0x485d99, а значит и MAIN_LOOP вокруг нее:

Фрагмент MAIN_LOOP из представленного примера вредоносного модуля
Фрагмент MAIN_LOOP из представленного примера вредоносного модуля

После данной инструкции MOV следует инструкция INC ESI, если при этом посмотреть, какие действия происходят с ESI предварительно, то видно, что в регистр AL как раз кладется значение по адресу из регистра ESI (значение в AL по пути до извлечения адреса обработчика декодируется).

Путь байта из [esi] до eax
Путь байта из [esi] до eax

Так, в EAX кладется значение по адресу ESI, по значению индекса EAX берется адрес обработчика инструкции байткода ВМ, ESI увеличивается на 1 - очень похоже, что ESI это VM_EIP, то есть указатель на очередную инструкцию ВМ

Таким образом, мы уже определили MAIN_LOOP и VM_EIP. Продолжим смотреть, что происходит после инкремента ESI. Если смотреть мимо мусорного кода, то по существу происходит “переворачивание” (BSWAP) значения в EDX (в котором хранится указатель на обработчик инструкции ВМ, VM handler), перекладывание его на стек и выполнение возврата, то есть по сути это переход по адресу VM handler и у меня он выглядит так:

Вызов очередного обработчика инструкции ВМ из байткода
Вызов очередного обработчика инструкции ВМ из байткода

Таким образом, мы рассмотрели, как работает участок обработчика байткода ВМ, конечно, это еще не все, но этого достаточно, чтобы различить хоть что‑то в обфусцированном коде
и убедиться, что хоть что‑то начинает проясняться ?.

Следующее, что приходит в голову, это попробовать описать, что делает каждый обработчик инструкции байткода ВМ, а потом, в идеале, попробовать преобразовать байткод к доступному виду (провести что-то вроде дизассемблирования).
К сожалению, на деле все немного отклонилось от ожиданий.

Возможные подходы для упрощения анализа ВМ

Когда я пытался найти решение для анализа логики работы, реализованной во вредоносном коде, мне виделись следующие способы:

  • Подход 1 - самый простой и поверхностный способ это перехват системных вызовов
    (в отладчике или, например, с помощью ApiMonitor) - очевидно, данный способ полноценно не раскроет работу вредоносного ПО. Фактически так и получилось, что работа вредоноса завершается по непонятной причине, но при этом происходит мапинг образа вредоноса - скорее всего там происходит какая-то проверка, но что именно за проверка, с помощью перехвата системных вызовов установить не получится
    (да-да, я процессе анализа я попробовал и этот подход ?). Да и в ходе анализа хотелось полноценно изучить работу ВМ, чтобы не только разобраться в ее работе,
    но и иметь какой-то выработанный подход для аналогичных случаев;

  • Подход 2 — попробовать различные инструменты для снятия VMProtect
    (или девиртуализации), примерами таких инструментов являютс vmpdump и titan. При необходимости можно найти еще несколько схожих проектов. Опять же, хотелось получше разобраться в работе такого способа защиты как ВМ, а не просто использовать готовый инструмент. К тому же среди таких инструментов серебряной пули против всех защищенных VMProtect вредоносов нет (возможно, только пока).

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

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

  • Способ 1 - использовать различные продвинутые подходы, инструменты или фреймворки, чтобы представить обфусцированные участки в форме, отражающей суть работы кода (в исходном состоянии).

  • Способ 2 - попытаться разобраться в обфусцированном коде, просмотрев каждый такой участок в обфусцированном состоянии и самостоятельно выделив существенные инструкции среди мусорных.

Скажу сразу, что на деле получился скорее второй способ, но все-таки какие-то вспомогательными инструментами кроме дизассемблера пришлось воспользоваться.

Когда решил не использовать продвинутые подходы
Когда решил не использовать продвинутые подходы

При этом под продвинутыми подходами для снятия обфускации в первом способе я думал попробовать воспользоваться такими инструментами, как  Miasm, LLVM или Triton
(больше всего для символьного представления и, возможно, для перекомпиляции
в исходное состояние).

Miasm вполне успешно использовали исследователи из ESET в уже упомянутой статье. Разница с моей проблемой заключалась в том (по крайней мере на приведенном в статье примере), что исследователи ESET применяли символьное представление к обфусцированным участкам, которые были в одном базовом блоке (то есть код был без каких-либо переходов), в случае же с моим образцом код каждого обработчика обязательно включал в себя несколько переходов, поэтому воспользоваться плагином Miasm для IDA, чтобы полноценно получить символьное представление всего кода обработчика виртуальной инструкции, не представлялось возможным.

Пример удачного использования Miasm из статьи ESET
Пример удачного использования Miasm из статьи ESET
Примеры не очень подходящих для плагина Miasm для IDA обфусцированных участков с переходами
Примеры не очень подходящих для плагина Miasm для IDA обфусцированных участков с переходами

Нужно было предварительно получить полный листинг обфусцированного обработчика виртуальной инструкции. Это можно сделать, учитывая наличие условных переходов в коде таких обработчиков, сняв трассу каждого обработчика, чтобы потом с помощью Miasm провести “лифтинг” такого кода, т.е. преобразовать в символьное представление код обработчика, полученный из трассы (или, может быть, правильное было бы сказать в IR, Intermediate Representation - промежуточное представление кода). Этот способ я так и не реализовал, потому что дошел до нужного уровня понимания работы обработчиков раньше, но если бы обфускация все-таки была бы сложнее (при соответствующих затратах
скорости ВМ), то, конечно, стоило бы все-таки рассмотреть возможность реализации
такого подхода.

LLVM (Low Level Virtual Machine) - если вкратце, то это фреймворк компиляции, в котором также применяется такое явление, как LLVM-IR - то же самое промежуточное представление. На Хабре есть подробные статьи о том, что такое LLVM (статья 1, статья 2). Применение LLVM в виде отдельного инструмента я не рассматривал, так как хотелось сначала попробовать подходы попроще, к тому же в Miasm и в Triton он и так используется. На основе LLVM, помимо прочего, реализованы как обфускаторы, так и деобфускаторы.

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

Конец первой части

Так как в этой статье текста уже много, опишу вкратце, каким именно был путь по анализу работы виртуальной машины, а в следующей части статьи детальнее расскажу
о проделанном:

  1. Чтобы понять работу обфусцированных обработчиков виртуальных инструкций,
    я попробовал использовать Miasm для символьного представления, но столкнулся
    с проблемой ограничений лифтинга в рамках только базового блока и избытка переходов внутри обработчика (уже рассказал об этом в первой части). 

  2. Далее я попробовал анализировать трассу обработчика виртуальной инструкции с помощью Triton, чтобы убрать мусорный код, при этом среди мусорных инструкций Triton не удалил те, которые работают со стеком. Тем не менее, код, частично избавленный от мусорных инструкций, дал больше ясности о работе обработчиков виртуальных инструкций.

  3. Далее я воспользовался инструментом для анализа трасс Tenet, то есть по сути решил в динамике посмотреть, как работают обработчики виртуальных инструкций, имея возможность перемещаться по трассе вперед и назад, отслеживать изменения участков памяти и прочие преимущества, которые дает анализ трассы по сравнению
    с отладкой или просто анализом в статике.

  4. Далее я решил снять трассу самой виртуальной машины (то есть выполненный
    при запуске байткод ВМ).

  5. Имея трассу ВМ и трассы работы каждого обработчика виртуальных инструкций (представляемую с помощью Tenet), я просто начал анализировать, что делает каждый обработчик, и переименовывал виртуальные инструкции в трассе ВМ в соответствии
    с их функционалом.

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

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

Помимо упомянутых выше материалов по теме не могу не отметить следующие:
https://Miasm.re/blog/2016/09/03/zeusvm_analysis.html
https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html
http://shell-storm.org/talks/DIMVA2018-deobfuscation-salwan-bardin-potet.pdf
https://yougame.biz/threads/277698/

Tags:
Hubs:
Total votes 1: ↑1 and ↓0+1
Comments5

Articles