Информация Гайд Как работают метки протекторов?

Receiver

🥩 Передай meat, всё в скип, я в темпе
Автор темы
Проверенный
601
849
Приветствую вас, дорогие читатели! В этой статье мы погрузимся в изучение интересного и важного аспекта защиты программного обеспечения. Конкретно будем рассматривать принцип работы меток протекторов, таких как VMProtect, Themida и других. Я стараюсь сделать это максимально просто и понятно, так что давайте приступим.

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

Существует два распространенных типа меток: inline assembly marker и library call marker. Первый тип, inline assembly marker, является самым удобным, так как он позволяет вставить инструкции любого размера непосредственно в тело функции на этапе компиляции. Это означает, что простым протекторам не нужно будет выделять память под защитный код, который оборачивает ваш блок кода.
Однако, в настоящее время самым популярным способом является использование library call marker. Это связано с тем, что компилятор Microsoft не поддерживает inline assembly в X64. Кроме того, этот способ позволяет быть уверенным на 10 миллиардов процентов, что это именно метка протектора, а не простое совпадение паттерна в случайном месте кода.

Так давайте же посмотрим как выглядят метки разных протекторов в листинге:

1720602660917.png

Рисунок 1 — Метки Themida в листинге.

1720603030797.png

Рисунок 2 — Метки VMProtect в листинге.

На этих скриншотах видно как происходит вызов функций внешней динамической библиотеки и это действительно правда, ведь в импортах у нас появилась библиотека SDK VMProtect + 2 этих вызываемых функции.

1720604269137.png

Рисунок 3 — Метки VMProtect в таблице импортов.


Теперь на основе полученных знаний попробуем реализовать собственные метки. Будем пользоваться способом library call marker. Для начала нужно сделать 2 проекта: Marker-SDK представляет из себя динамическую библиотеку с функциями заглушками, Marker-APP занимется поиском меток нашего Marker-SDK в PE-файле и их реализацией. Именно динамическая библиотека нам необходима потому что она может на 10 миллиардов процентов гарантировать внешний вызов функции в коде и давать нам запись в таблице импорта, в том время как статическая вполне может заинлайнить код своей функции внутрь нашей.

Первым этапом будет описывание нашего 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
 
Последнее редактирование: