API Hooking в Windows: механизм перехвата вызовов и признаки для защитного анализа

Что такое 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).

Последовательность установки хука вручную​


  1. Сохранить оригинальные байты целевой функции (размер = TRAMPOLINE_SIZE).
  2. Изменить защиту страницы на PAGE_EXECUTE_READWRITE через VirtualProtect.
  3. Записать трамплин в начало функции через memcpy.
  4. Восстановить исходную защиту страницы.

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

Структура для управления хуком​


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 E2mov r10, imm64; jmp r10Классический x64-трамплин
B8 ?? ?? ?? ?? FF E0mov 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) не позволяют внешним модулям внедрять код или модифицировать память.

Практический чек-лист для защитника​


  1. Дамп первых 16 байт критичных функций (NtCreateFile, NtOpenProcess, CreateRemoteThread, WriteProcessMemory) — искать паттерны трамплинов.
  2. Сравнить .text секции загруженных ntdll.dll и kernel32.dll с файлами на диске.
  3. Проверить IAT подозрительных процессов на несоответствие адресов.
  4. Мониторить вызовы VirtualProtect с PAGE_EXECUTE_READWRITE на адресах кодовых секций.
  5. Отслеживать вызовы SetWindowsHookExW с типами WH_KEYBOARD_LL, WH_MOUSE_LL.
  6. Проверять цепочки загрузки DLL: глобальные хуки требуют загрузки модуля в чужие процессы.
  7. Обращать внимание на процессы, в которых одновременно присутствуют отладочные привилегии (SeDebugPrivilege) и подозрительные модули.

Источники​


 

Похожие темы

Назад
Верх Низ