📄 Обнаружение хуков и защита от них c++

Обнаружение хуков и защита от них c++

хуки и защита

Ключевая истина безопасности:  

«Лучшая защита — понимание техник атаки. Знание методов обнаружения хуков — основа защиты ваших приложений»

? Почему обнаруживать хуки критически важно?

  • Античиты: Защита игр от модификаций (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)

// ОСТАВИТЬ КОММЕНТАРИЙ

// Ваш email не будет опубликован. Обязательные поля помечены *