// Содержание
Ключевая истина безопасности:
«Лучшая защита — понимание техник атаки. Знание методов обнаружения хуков — основа защиты ваших приложений»
? Почему обнаруживать хуки критически важно?
- Античиты: Защита игр от модификаций (70% взломов используют хуки)
- Финансовая безопасность: Защита банковских приложений от перехвата данных
- Конфиденциальность: Предотвращение шпионских программ
- Целостность ПО: Гарантия неизменности исполняемого кода
? Методы обнаружения хуков
1️⃣ Проверка контрольных сумм кода (CRC-сканирование)
#include <Windows.h> // Windows API функции
#include <zlib.h> // Библиотека для расчета CRC
bool IsFunctionHooked(void* function, size_t length) {
// Эталонная CRC оригинальной функции (рассчитана заранее)
const uint32_t originalCrc = 0x12345678;
// Рассчитываем текущую CRC функции
uint32_t currentCrc = crc32(0, (const Bytef*)function, length);
// Сравниваем с оригиналом - если не совпадает, есть хук
return currentCrc != originalCrc;
}
void CheckMessageBoxHook() {
// Получаем адрес функции MessageBoxA
FARPROC funcAddr = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
// Проверяем первые 32 байта функции
if (IsFunctionHooked(funcAddr, 32)) {
// Если обнаружен хук - показываем предупреждение
MessageBoxA(0, "Обнаружен хук!", "Внимание", MB_ICONWARNING);
}
}2️⃣ Анализ таблицы импорта (IAT)
#include <Windows.h>
#include <TlHelp32.h>
#include <Psapi.h>
bool IsIatHooked(const char* targetFunction) {
// Получаем базовый адрес текущего модуля
HMODULE mod = GetModuleHandleA(NULL);
ULONG size;
// Получаем таблицу импорта
PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)
ImageDirectoryEntryToData(mod, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &size);
// Если таблица не найдена - выходим
if (!importDesc) return false;
// Перебираем все импортируемые модули
for (; importDesc->Name; importDesc++) {
// Получаем имя текущего модуля
char* moduleName = (char*)((PBYTE)mod + importDesc->Name);
// Загружаем модуль для получения оригинальных адресов
HMODULE hModule = GetModuleHandleA(moduleName);
// Получаем первый thunk таблицы
PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)
((PBYTE)mod + importDesc->FirstThunk);
// Перебираем все функции в модуле
for (; thunk->u1.Function; thunk++) {
// Получаем адрес функции в IAT
FARPROC* functionAddr = (FARPROC*)&thunk->u1.Function;
// Получаем оригинальный адрес функции
FARPROC originalAddr = GetProcAddress(hModule, targetFunction);
// Сравниваем адреса
if (*functionAddr != originalAddr) {
return true; // Найден хук!
}
}
}
return false;
}3️⃣ Проверка первых инструкций (JMP/CALL детектор)
bool IsInlineHooked(void* function) {
// Преобразуем адрес функции в байтовый массив
BYTE* bytes = (BYTE*)function;
// Проверка на JMP (E9) или CALL (E8)
if (bytes[0] == 0xE9 || bytes[0] == 0xE8)
return true;
// Проверка на PUSH + RET (техника хукинга)
if (bytes[0] == 0x68 && bytes[5] == 0xC3)
return true;
// Проверка на MOV + JMP (техника Detours)
if (bytes[0] == 0xB8 && bytes[5] == 0xFF && bytes[6] == 0xE0)
return true;
return false;
}?️ Методы защиты
1️⃣ Системные вызовы напрямую (SSN)
// Получаем номер системного вызова (SSN) для NtCreateFile
DWORD GetSyscallNumber(const char* funcName) {
// Получаем адрес функции в ntdll.dll
auto addr = GetProcAddress(GetModuleHandleA("ntdll.dll"), funcName);
BYTE* bytes = (BYTE*)addr;
// Ищем инструкцию MOV EAX, SSN (B8 XX XX XX XX)
if (bytes[0] == 0xB8) {
// Извлекаем номер системного вызова
return *(DWORD*)(bytes + 1);
}
return 0;
}
// Прямой вызов через ассемблер
EXTERN_C NTSTATUS DirectNtCreateFile(
PHANDLE FileHandle, ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes, ...) {
// Получаем SSN для NtCreateFile
static DWORD ssn = GetSyscallNumber("NtCreateFile");
if (!ssn) return STATUS_NOT_FOUND; // Если не нашли
// Ассемблерная вставка для прямого системного вызова
__asm {
mov eax, ssn // Загружаем номер системного вызова
lea edx, [esp+4] // Указываем на аргументы
int 0x2E // Прерывание для системного вызова
ret // Возврат из функции
}
}2️⃣ Защита целостности (Self-Checksumming)
void SelfIntegrityCheck() {
// Получаем базовый адрес текущего модуля
HMODULE base = GetModuleHandle(NULL);
// Получаем DOS-заголовок
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base;
// Получаем NT-заголовки
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)base + dos->e_lfanew);
// Получаем первую секцию
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt);
// Перебираем все секции
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, section++) {
// Ищем секцию .text
if (memcmp(section->Name, ".text", 5) == 0) {
// Предварительно сохраненная характеристика
DWORD expected = section->Characteristics;
// Сравниваем с текущим значением
if (CalculateSectionCRC(section) != expected) {
ExitProcess(0); // Детектировано изменение - выход
}
}
}
}3️⃣ Шифрование критического кода
void EncryptSection(void* addr, size_t size, DWORD key) {
// Шифруем каждый байт в секции
for (size_t i = 0; i < size; i++) {
// Применяем XOR-шифрование
((BYTE*)addr)[i] ^= key;
}
// Очищаем кэш процессора
FlushInstructionCache(GetCurrentProcess(), addr, size);
}
void CriticalFunction() {
// Важная логика приложения
}
void ExecuteProtectedCode() {
DWORD encryptionKey = 0xDEADBEEF; // Ключ шифрования
// Расшифровываем функцию перед выполнением
EncryptSection(&CriticalFunction, sizeof(CriticalFunction), encryptionKey);
// Выполняем функцию
CriticalFunction();
// Шифруем функцию после выполнения
EncryptSection(&CriticalFunction, sizeof(CriticalFunction), encryptionKey);
}4️⃣ Контроль цепочки вызовов (Stack Walk)
bool IsCallStackValid() {
// Получаем адрес возврата текущей функции
void* retAddr = _ReturnAddress();
// Получаем указатель на текущий фрейм стека
void* framePtr = _AddressOfReturnAddress();
// Проверяем 5 предыдущих фреймов
for (int i = 0; i < 5; i++) {
// Переходим к следующему фрейму стека
void** nextFrame = (void**)*framePtr;
if (!nextFrame) break; // Если достигли конца
// Получаем адрес возврата для следующего фрейма
void* nextRet = *(framePtr + 1);
// Проверяем, находится ли адрес в пределах модуля
if ((DWORD_PTR)nextRet < 0x10000000) {
// Проверяем принадлежность адреса нашему модулю
if (!IsAddressInModule(nextRet))
return false; // Обнаружен невалидный адрес
}
framePtr = nextFrame; // Переходим к следующему фрейму
}
return true; // Все адреса валидны
}
void SecurityCriticalFunction() {
// Проверяем стек вызовов перед выполнением
if (!IsCallStackValid()) {
ExitProcess(0); // Завершаем процесс при подозрении
}
// Выполняем критическую операцию
}?️ Комбинированная защита
void SecurityCheck() {
// 1. Проверка целостности кода
if (IsCodeModified()) {
// Логируем событие
LogSecurityEvent("Обнаружено изменение кода");
TerminateProcess(); // Завершаем процесс
}
// 2. Проверка хуков в критических функциях
if (IsFunctionHooked("CreateFileA") ||
IsFunctionHooked("RegOpenKeyExA")) {
LogSecurityEvent("Обнаружены хуки API");
TerminateProcess();
}
// 3. Случайная задержка для защиты от автоматизированных атак
Sleep(rand() % 50 + 10); // Задержка 10-60 мс
// 4. Проверка стека вызовов
if (!IsCallStackValid()) {
LogSecurityEvent("Невалидная цепочка вызовов");
TerminateProcess();
}
// 5. Проверка наличия отладчика
if (IsDebuggerPresent()) {
LogSecurityEvent("Обнаружен отладчик");
TerminateProcess();
}
}? Контрольный лист защиты
- Реализована проверка целостности кода при запуске
- Используются прямые системные вызовы для критических операций
- Реализованы анти-отладочные техники
- Критический код шифруется во время простоя
- Включена аппаратная защита (HVCI/VBS)
- Регулярно обновляются сигнатуры детектирования
- Проведен пентест защиты независимыми специалистами
Помните: Полная защита невозможна, но можно сделать взлом экономически невыгодным!
P.S. Все примеры кода включают подробные комментарии для лучшего понимания механизмов защиты. Для тестирования используйте инструменты:
— Cheat Engine
— x64dbg
— Frida
— Process Hacker
Тестируйте свою защиту так же активно, как это сделали бы злоумышленники!
// КОММЕНТАРИИ (0)