Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
UnhookMe — Динамический резолвер и анхукер Windows API, который обнаруживает и восстанавливает перехваченные функции (IAT, EAT, inline patches) для вызова неотслеживаемых системных вызовов из вредоносного ПО Red Team. | Kitploit
Инструменты/GitHubGitHub/mgeeky/unhookme
Оборонительные ИнструментыRed TeamingРазработка Полезной Нагрузки
GitHubmgeeky/unhookme

UnhookMe

Динамический резолвер и анхукер Windows API, который обнаруживает и восстанавливает перехваченные функции (IAT, EAT, inline patches) для вызова неотслеживаемых системных вызовов из вредоносного ПО Red Team.

Репозиторий
348484 лет назадПроверено Kitploit

Популярное

Смотреть все →

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

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

UnhookMe - динамический разрешитель импортов с возможностью снятия хуков

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

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


Простейший пример использования

Вот так можно гарантированно вызвать MessageBoxW без хуков и мониторинга:

root@kitploit:~
    RESOLVE(user32, MessageBoxW);
    _MessageBoxW(0, L"Смотри, мам! Я без хука!", L"Третий — без хука", 0);

Вся магия происходит внутри макроопределения RESOLVE, которое создает объект ImportResolver<T> с именем _MessageBoxW.


Демонстрация

Анимация Unhookme

Вот как работает пример UnhookMe:

  1. Сначала показывается первый MessageBoxW, который не подвергается хукингу.
  2. Затем мы сами хукаем пролог MessageBoxW, чтобы он всегда возвращал 0, не отображая сообщение.
  3. Наконец, мы динамически разрешаем MessageBoxW с помощью разрешителя UnhookingImportResolver, который обнаружит примененные патчи пролога и восстановит оригинальные байты, эффективно снимая хук с MessageBoxW.

В процессе появления окон с сообщениями в консоль (stdout) выводятся следующие строки:

root@kitploit:~
[~] Разрешен символ kernel32.dll!CreateFileA
[~] Разрешен символ kernel32.dll!ReadProcessMemory
[~] Разрешен символ kernel32.dll!MapViewOfFile
[~] Разрешен символ kernel32.dll!VirtualProtectEx
[#] Обнаружен трамплин-хук в символе: MessageBoxW. Восстановлены исходные байты из файла.
[~] Разрешен символ user32.dll!MessageBoxW

Как это использовать?

Всего есть 5 файлов исходного кода/заголовков на C++, которые необходимо включить в ваш проект. Однако ваш основной файл программы должен включать только два обязательных заголовка, как описано ниже.

  • resolver.h — заголовок, содержащий большую часть реализации UnhookingImportResolver и удобные макроопределения.
  • resolver.cpp — исходный код с определенными глобальными опциями.
  • usings.h — большой и громоздкий файл заголовка, содержащий десятки определений типов using для часто используемых WinAPI.
  • PE.cpp — исходный код пользовательского парсера PE.
  • PE.h — заголовок пользовательского парсера PE.

Обязательные заголовки

Вашей программе потребуется включить только два заголовка:

root@kitploit:~
#include "usings.h"
#include "resolver.h"

Глобальные опции

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

Глобальные опции разрешителя:

  • globalQuietOption — установите в true, если не хотите никакого вывода.
  • globalVerboseOption — установите в true, если хотите подробный вывод.
  • globalAntiSplicingOption — снимать хуки с разрешенных функций, если они захуканы.
  • globalLogFilePath — куда перенаправлять строки вывода лога. Если пусто, выбирается stdout.
root@kitploit:~
bool globalQuietOption = false;
bool globalVerboseOption = true;
bool globalAntiSplicingOption = true;

wchar_t globalLogFilePath[MAX_PATH] = L"";

Определение пользовательского типа API

Чтобы использовать разрешитель, сначала необходимо объявить тип указателя на функцию с помощью оператора using в строгой форме:

root@kitploit:~
    using fn_FunctionName = ReturnType WINAPI (
        ParamType1 paramName1,
        ...,
        ParamTypeN paramNameN,
    );

Репозиторий поставляется с заголовочным файлом usings.h, содержащим предопределенные типы using для десятков популярных Windows API.

Имя функции будет соответствовать WinAPI, который мы хотим разрешить с помощью ImportResolver, и этот указатель на функцию должен быть помечен как имеющий соглашение вызова WINAPI (__stdcall на x86 и __fastcall на x64). ReturnType должен предшествовать модификатору типа WINAPI.

Разрешение функции и использование

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

root@kitploit:~
    RESOLVE(libraryName, FunctionName);
    ReturnType output = _FunctionName(param1, ..., paramN);

Макрос RESOLVE заботится о создании экземпляра шаблонного объекта ImportResolver и корректировке указанного имени библиотеки.

Разрешитель предоставляет несколько дополнительных макроопределений для удобного вызова конструктора в различных ситуациях:

root@kitploit:~
#define RESOLVE(mod, func)                    RESOLVE_PARAMETERIZED(mod, func, ::globalVerboseOption, ::globalAntiSplicingOption)
#define RESOLVE_NO_UNHOOK(mod, func)          RESOLVE_PARAMETERIZED(mod, func, ::globalVerboseOption, false)

#define RESOLVE_VERBOSE_UNHOOK(mod, func)     RESOLVE_PARAMETERIZED(mod, func, true, true)
#define RESOLVE_VERBOSE_NOUNHOOK(mod, func)   RESOLVE_PARAMETERIZED(mod, func, true, false)
#define RESOLVE_NOVERBOSE_UNHOOK(mod, func)   RESOLVE_PARAMETERIZED(mod, func, false, true)
#define RESOLVE_NOVERBOSE_NOUNHOOK(mod, func) RESOLVE_PARAMETERIZED(mod, func, false, false)

Конструктор разрешителя:

root@kitploit:~
    template<typename Ret, typename ...Args>
    ImportResolver<Ret WINAPI(Args...)>(
            std::string dllName,
            std::string funcName,
            bool _verbose = false,
            bool _unhook = false,
            bool *_wasItHooked = nullptr
        )

Как это работает?

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

Идея следующая:

  1. Сначала мы выдаем LoadLibrary, чтобы загрузить указанную пользователем библиотеку (ту, что указана первым параметром макроса RESOLVE), если ее не удалось получить через GetModuleHandle.

  2. Затем мы обрабатываем PE-заголовки загруженной/указанной библиотеки, сопоставляем ее экспорты, извлекаем массив адресов экспортов, а также вычисляем эти адреса самостоятельно для перекрестной проверки.

  3. Если адрес процедуры, определенный в таблице адресов экспорта DLL, не соответствует ожидаемому, экспорт считается захуканным через EAT. То же самое происходит, если запись в таблице адресов импорта (IAT) нашего исполняемого файла для этой функции была изменена и больше не указывает на правильное место в секции кода DLL — тогда функция считается захуканной через IAT.

  4. Если хуки не были обнаружены, мы извлекаем первые N байтов пролога функции и сравниваем их с тем, что хранится в DLL-файле на диске. Если есть несоответствие между байтами, полученными из памяти, и из файла, то мы считаем, что функция была пропатчена встраиванием (inline patch / hot-patch).

  5. Если функция считается захуканной, мы возвращаем исходный адрес экспорта (который мы вычислили сами) и/или снимаем хук. Если были байты патча, мы их восстанавливаем.

  6. Наконец, чтобы оптимизировать влияние разрешителя на производительность, мы кэшируем все базовые адреса загруженных модулей и адреса разрешенных функций и возвращаем их из кэша (в виде std::map) при последующих обращениях.

Среди проблем, с которыми сталкивается такой динамический разрешитель со снятием хуков, — проблемы с обходом пересылаемых API (forwarded APIs) (DLL может содержать заглушку экспорта, указывающую, что эта функция не реализована в данном модуле, а находится в другом) — хотя данная реализация поддерживает их, иногда это нарушает логику обхода.


☕ Поддержать проект ☕

Этот и другие проекты — результат бессонных ночей и немалой тяжелой работы. Если вам нравится то, что я делаю, и вы цените, что я всегда отдаю должное сообществу, Купите мне чашечку кофе (или лучше пива), чтобы просто сказать спасибо! 💪


Автор

root@kitploit:~
   Mariusz Banach / mgeeky, 21
   <mb [at] binary-offensive.com>
   (https://github.com/mgeeky)
Скачать инструмент