Приветствую вас, дорогие читатели! В этой статье мы погрузимся в изучение интересного и важного аспекта защиты программного обеспечения. Конкретно будем рассматривать принцип работы меток протекторов, таких как VMProtect, Themida и других. Я стараюсь сделать это максимально просто и понятно, так что давайте приступим.
Метки играют ключевую роль в работе протекторов и без них просто не обойтись. Они необходимы для обозначения блоков кода, которые подлежат защите, будь то виртуализация, мутация, шифрование или сжатие. Метки не только облегчают протектору поиск нужного места в коде, но также обеспечивают code cave для прыжка и задают границы блока для защиты.
Существует два распространенных типа меток: inline assembly marker и library call marker. Первый тип, inline assembly marker, является самым удобным, так как он позволяет вставить инструкции любого размера непосредственно в тело функции на этапе компиляции. Это означает, что простым протекторам не нужно будет выделять память под защитный код, который оборачивает ваш блок кода.
Однако, в настоящее время самым популярным способом является использование library call marker. Это связано с тем, что компилятор Microsoft не поддерживает inline assembly в X64. Кроме того, этот способ позволяет быть уверенным на 10 миллиардов процентов, что это именно метка протектора, а не простое совпадение паттерна в случайном месте кода.
Так давайте же посмотрим как выглядят метки разных протекторов в листинге:
Метки играют ключевую роль в работе протекторов и без них просто не обойтись. Они необходимы для обозначения блоков кода, которые подлежат защите, будь то виртуализация, мутация, шифрование или сжатие. Метки не только облегчают протектору поиск нужного места в коде, но также обеспечивают code cave для прыжка и задают границы блока для защиты.
Существует два распространенных типа меток: inline assembly marker и library call marker. Первый тип, inline assembly marker, является самым удобным, так как он позволяет вставить инструкции любого размера непосредственно в тело функции на этапе компиляции. Это означает, что простым протекторам не нужно будет выделять память под защитный код, который оборачивает ваш блок кода.
Однако, в настоящее время самым популярным способом является использование library call marker. Это связано с тем, что компилятор Microsoft не поддерживает inline assembly в X64. Кроме того, этот способ позволяет быть уверенным на 10 миллиардов процентов, что это именно метка протектора, а не простое совпадение паттерна в случайном месте кода.
Так давайте же посмотрим как выглядят метки разных протекторов в листинге:
Рисунок 1 — Метки Themida в листинге.
Рисунок 2 — Метки VMProtect в листинге.
На этих скриншотах видно как происходит вызов функций внешней динамической библиотеки и это действительно правда, ведь в импортах у нас появилась библиотека SDK VMProtect + 2 этих вызываемых функции.
Рисунок 3 — Метки VMProtect в таблице импортов.
Теперь на основе полученных знаний попробуем реализовать собственные метки. Будем пользоваться способом library call marker. Для начала нужно сделать 2 проекта: Marker-SDK — представляет из себя динамическую библиотеку с функциями заглушками, Marker-APP — занимется поиском меток нашего Marker-SDK в PE-файле и их реализацией. Именно динамическая библиотека нам необходима потому что она может на 10 миллиардов процентов гарантировать внешний вызов функции в коде и давать нам запись в таблице импорта, в том время как статическая вполне может заинлайнить код своей функции внутрь нашей.
Первым этапом будет описывание нашего Marker-SDK, для этого создадим sdk.h и вставим туда следующий код:
Первым этапом будет описывание нашего Marker-SDK, для этого создадим sdk.h и вставим туда следующий код:
C++:
#ifndef MARKER_SDK_H
#define MARKER_SDK_H
#ifdef MARKER_SDK_EXPORTS
#define MARKER_API __declspec(dllexport)
#else
#define MARKER_API __declspec(dllimport)
#endif // MARKER_SDK_EXPORTS
extern "C" {
MARKER_API void begin_encrypted(void);
MARKER_API void end_encrypted(void);
}
#endif // MARKER_SDK_H
Так же для корректной компиляции нам понадобиться файл заглушка sdk.cpp, в котором будет храниться пустая имплементация этих 2-х функций.
Следующим этапом у нас идёт реализация Marker-APP. Тут нам понадобиться какой-нибудь PELib, PEBliss, либо же можно просто использовать структуры Windows, но я буду пользоваться кроссплатформенным linux-pe. Весь код выкладывать нет смысла, так что рассмотрим исключительно функцию поиска метки:
C++:
auto find_sdk_imports(std::error_code& ec) {
auto result = std::pair<std::uintptr_t, std::uintptr_t>{};
// Получаем директории таблицы импортов
auto imports_boundary = image_->get_directory(win::directory_entry_import);
if (!imports_boundary) {
ec = std::make_error_code(std::errc::no_such_file_or_directory);
return result;
}
// Перебираем все импортируемые библиотеки
for (auto library = reinterpret_cast<win::import_directory_t*>(
image_->rva_to_ptr(imports_boundary->rva));
library->rva_name; ++library) {
// Получаем имя библиотеки
auto library_name = std::string{
reinterpret_cast<const char*>(image_->rva_to_ptr(library->rva_name))};
if (library_name != "Marker-SDK.dll") continue;
// Перебираем все импортируемые функции для библиотеки
for (auto function = reinterpret_cast<win::image_thunk_data_x64_t*>(
image_->rva_to_ptr(library->rva_first_thunk));
function->address; ++function) {
// Получаем имя функции
auto function_name =
std::string{reinterpret_cast<win::image_named_import_t*>(
image_->rva_to_ptr(function->address))
->name};
// Получаем смещение до функции в IAT
// Так же я конвертирую в смещение относительно начала буффера
auto iat_address = image_->ptr_to_raw(function);
if (function_name == "begin_encrypted") {
result.first = iat_address;
} else if (function_name == "end_encrypted") {
result.second = iat_address;
}
}
}
return result;
}
На выходе эта функция вернёт нам пару [начало, конец] адресов меток на IAT. Теперь мы можем подключить наш Marker-SDK к любому проекту и чтобы заменить их на полезную нагрузку остаётся прогнать PE-файл любым дизассемблером и сравнить расчитанные адреса для call/jmp с адресами меток.
Текст статьи написан с помощью mistral.ai