Что такое API Hooking и зачем он нужен
API Hooking — техника перехвата вызовов функций Windows API до того, как управление достигнет оригинальной реализации. Перехватчик получает доступ к аргументам вызова, может их изменить, заблокировать вызов или передать управление собственной функции. Легитимные применения: отладчики, профайлеры, антивирусные мониторы, системы совместимости. Вредоносные: сокрытие процессов, перехват учётных данных, обход защитных механизмов.
Для защитника важно понимать механику на уровне байтов, чтобы отличать нормальное поведение от подозрительного.
Классификация методов перехвата
| Метод | Уровень | Область действия | Сложность обнаружения |
|---|---|---|---|
| Inline hooking (трамплин) | Машинный код функции | Конкретная функция в процессе | Средняя |
| IAT hooking | Таблица импорта | Все вызовы через IAT модуля | Низкая |
| SetWindowsHookEx | Механизм сообщений Windows | Глобально или по потоку | Низкая |
| SSDT hooking | Таблица системных сервисов ядра | Все процессы | Высокая (требует Ring 0) |
Далее разберём три первых метода, которые работают в пользовательском режиме и наиболее часто встречаются как в легитимном, так и во вредоносном ПО.
Inline Hooking: патч первых байтов функции
Суть метода: первые несколько байтов целевой функции перезаписываются инструкцией перехода (jmp) на код перехватчика. Оригинальные байты сохраняются для возможного восстановления.
Трамплин на x86-64
На 64-битной архитектуре адрес перехода не помещается в 32-битный операнд, поэтому используется двухинструкционная последовательность:
Код:
mov r10, <адрес_перехватчика> ; 10 байт: 49 BA + 8 байт адреса
jmp r10 ; 3 байта: 41 FF E2
Итого 13 байт (константа
TRAMPOLINE_SIZE для x64).Трамплин на x86
На 32-битной архитектуре адрес помещается в 32-битный операнд:
Код:
mov eax, <адрес_перехватчика> ; 5 байт: B8 + 4 байта адреса
jmp eax ; 2 байта: FF E0
Итого 7 байт (
TRAMPOLINE_SIZE для x86).Последовательность установки хука вручную
- Сохранить оригинальные байты целевой функции (размер = TRAMPOLINE_SIZE).
- Изменить защиту страницы на
PAGE_EXECUTE_READWRITEчерезVirtualProtect.
- Записать трамплин в начало функции через
memcpy.
- Восстановить исходную защиту страницы.
Для снятия хука выполняется обратная операция: оригинальные байты копируются назад, защита восстанавливается.
Структура для управления хуком
C:
typedef struct _HookSt {
PVOID pFunctionToHook;
PVOID pFunctionToRun;
BYTE pOriginalBytes[TRAMPOLINE_SIZE];
DWORD dwOldProtection;
} HookSt, *PHookSt;
Поле
pOriginalBytes хранит перезаписанные байты — без них корректное снятие хука невозможно. Поле dwOldProtection фиксирует исходные права страницы, чтобы после патча вернуть их.Библиотека Microsoft Detours
Detours — легитимная библиотека от Microsoft Research для инструментирования API. Использует транзакционную модель: изменения не применяются, пока транзакция не подтверждена.
Ключевые функции
| Функция | Назначение |
|---|---|
DetourTransactionBegin | Открыть транзакцию |
DetourUpdateThread | Включить поток в транзакцию |
DetourAttach | Зарегистрировать хук (не применяется до commit) |
DetourDetach | Зарегистрировать снятие хука |
DetourTransactionCommit | Применить все изменения транзакции |
Все функции возвращают
LONG; значение NO_ERROR (0) означает успех.Пример установки хука на MessageBoxA
C:
typedef INT (WINAPI* fnMessageBoxA)(HWND, LPCSTR, LPCSTR, UINT);
fnMessageBoxA g_pMessageBoxA = MessageBoxA;
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText,
LPCSTR lpCaption, UINT uType) {
// Перехватчик видит оригинальные аргументы
// и может вызвать оригинал через g_pMessageBoxA
return g_pMessageBoxA(hWnd, "modified", lpCaption, uType);
}
BOOL InstallHook(void) {
LONG err;
if ((err = DetourTransactionBegin()) != NO_ERROR) return FALSE;
if ((err = DetourUpdateThread(GetCurrentThread())) != NO_ERROR) return FALSE;
if ((err = DetourAttach((PVOID*)&g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) return FALSE;
if ((err = DetourTransactionCommit()) != NO_ERROR) return FALSE;
return TRUE;
}
После
DetourAttach переменная g_pMessageBoxA автоматически обновляется библиотекой и указывает на оригинальный код функции (через трамплин Detours). Вызов g_pMessageBoxA внутри перехватчика — стандартный способ передать управление оригиналу.Снятие хука через Detours
C:
BOOL Unhook(void) {
LONG err;
if ((err = DetourTransactionBegin()) != NO_ERROR) return FALSE;
if ((err = DetourUpdateThread(GetCurrentThread())) != NO_ERROR) return FALSE;
if ((err = DetourDetach((PVOID*)&g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) return FALSE;
if ((err = DetourTransactionCommit()) != NO_ERROR) return FALSE;
return TRUE;
}
Библиотека MinHook
MinHook — компактная альтернатива с более простым API. Не требует транзакций.
Последовательность вызовов
C:
MH_Initialize();
MH_CreateHook(&MessageBoxA, &MyMessageBoxA, (LPVOID*)&g_pMessageBoxA);
MH_EnableHook(&MessageBoxA);
// ... работа с перехватом ...
MH_DisableHook(&MessageBoxA);
MH_Uninitialize();
MH_CreateHook принимает три аргумента: адрес цели, адрес перехватчика и указатель на переменную, куда будет записан адрес оригинала (trampoline). Возвращаемые значения сравниваются с константой MH_OK.IAT Hooking: подмена указателя в таблице импорта
Каждый PE-модуль содержит Import Address Table — массив указателей на функции импортированных DLL. При загрузке загрузчик заполняет IAT реальными адресами. Подмена записи в IAT перенаправляет все вызовы через эту таблицу.
Преимущество для атакующего: не нужно модифицировать код функции, достаточно заменить один указатель. Недостаток: легко обнаруживается сравнением IAT с ожидаемыми адресами из EAT (Export Address Table) соответствующей DLL.
SetWindowsHookEx: перехват через механизм сообщений
C:
HHOOK SetWindowsHookExW(
int idHook, // тип хука: WH_KEYBOARD, WH_MOUSE, WH_CALLWNDPROC и др.
HOOKPROC lpfn, // указатель на callback-функцию
HINSTANCE hmod, // модуль с callback (NULL для текущего процесса)
DWORD dwThreadId // целевой поток (0 = все потоки текущего десктопа)
);
Callback-функция должна иметь сигнатуру:
C:
LRESULT CALLBACK HookCallbackFunc(int nCode, WPARAM wParam, LPARAM lParam);
Внутри callback обязательно вызывается
CallNextHookEx, чтобы не нарушить цепочку хуков. Этот метод часто используется кейлоггерами (тип WH_KEYBOARD_LL), поэтому его мониторинг — стандартная задача EDR.Признаки хуков для защитного анализа
Проверка первых байтов функции (inline hook detection)
Самый прямой метод: прочитать первые 13 байт (x64) или 7 байт (x86) критичных API-функций и сравнить с ожидаемым паттерном.
Подозрительные сигнатуры в начале функции:
| Байты | Инструкция | Что означает |
|---|---|---|
49 BA ?? ?? ?? ?? ?? ?? ?? ?? 41 FF E2 | mov r10, imm64; jmp r10 | Классический x64-трамплин |
B8 ?? ?? ?? ?? FF E0 | mov eax, imm32; jmp eax | Классический x86-трамплин |
E9 ?? ?? ?? ?? | jmp rel32 | Относительный переход (менее типично для хуков, но возможно) |
FF 25 ?? ?? ?? ?? | jmp [rip+disp32] | Косвенный переход через память |
Если функция из
ntdll.dll или kernel32.dll начинается с одной из этих последовательностей — с высокой вероятностью установлен inline-хук.Сравнение с образом на диске
Загрузить DLL с диска (например,
ntdll.dll из C:\Windows\System32), спроецировать в память и побайтово сравнить секцию .text с загруженной в процесс. Расхождения указывают на патчи. Этот подход используют многие EDR-продукты.Проверка IAT
Пройти по
IMAGE_IMPORT_DESCRIPTOR целевого модуля, для каждой импортированной функции сравнить адрес в IAT с адресом в EAT модуля-экспортёра. Несоответствие — признак IAT-хука.Мониторинг VirtualProtect
Установка inline-хука невозможна без временного изменения защиты страницы кода на
PAGE_EXECUTE_READWRITE. Перехват или логирование вызовов VirtualProtect / VirtualProtectEx с флагом PAGE_EXECUTE_READWRITE на адресах из секций .text системных DLL — надёжный индикатор.Перечисление хуков SetWindowsHookEx
Прямой API для перечисления не существует, но можно:
- Проверять загруженные модули: глобальный хук требует, чтобы DLL с callback была загружена в целевые процессы.
- Использовать
Get-ItemPropertyили реестр для поиска подозрительных DLL, загруженных через механизм хуков.
- Мониторить вызовы
SetWindowsHookExWчерез ETW или callback-механизмы ядра.
Типичные ошибки при реализации хуков
- Рекурсивный вызов: перехватчик вызывает ту же функцию, на которую установлен хук, без использования сохранённого указателя на оригинал. Результат — бесконечная рекурсия и переполнение стека.
- Неправильный размер трамплина: если перезаписано меньше байт, чем нужно для полного трамплина, оставшиеся байты оригинальной инструкции интерпретируются как мусор.
- Отсутствие синхронизации: патч кода без приостановки других потоков может привести к выполнению частично записанной инструкции.
- Не восстановлена защита страницы: страница остаётся
PAGE_EXECUTE_READWRITE, что само по себе является индикатором для защитных продуктов.
Ограничения и нюансы
- PatchGuard (Kernel Patch Protection): на x64-версиях Windows начиная с Vista SSDT hooking из ядра блокируется PatchGuard. Это ограничивает kernel-mode hooking, но не затрагивает user-mode методы.
- Control Flow Guard (CFG): может блокировать косвенные переходы на непроверенные адреса, что затрудняет некоторые варианты хуков в защищённых процессах.
- Protected Processes: системные процессы с флагом Protected Process Light (PPL) не позволяют внешним модулям внедрять код или модифицировать память.
Практический чек-лист для защитника
- Дамп первых 16 байт критичных функций (
NtCreateFile,NtOpenProcess,CreateRemoteThread,WriteProcessMemory) — искать паттерны трамплинов.
- Сравнить
.textсекции загруженныхntdll.dllиkernel32.dllс файлами на диске.
- Проверить IAT подозрительных процессов на несоответствие адресов.
- Мониторить вызовы
VirtualProtectсPAGE_EXECUTE_READWRITEна адресах кодовых секций.
- Отслеживать вызовы
SetWindowsHookExWс типамиWH_KEYBOARD_LL,WH_MOUSE_LL.
- Проверять цепочки загрузки DLL: глобальные хуки требуют загрузки модуля в чужие процессы.
- Обращать внимание на процессы, в которых одновременно присутствуют отладочные привилегии (
SeDebugPrivilege) и подозрительные модули.
