Антихукинг — теория

в 15:13, , рубрики: c++

Совсем недавно озадачился защитой приложений от перехвата системных api, решил поделится и обсудить то, к чему пришел. Многие из вас знают, что перехват системных api сводится к перенаправлению оригинальной функции в нужное место, благодаря этому можно модифицировать параметры функции, возвращать результат отличный от оригинала, хранить оригинальный вызов с параметрами и многое другое. Так как это теоретическая часть, примеры в статье будут сопровождаться псевдокодом.

Обычно используются следующие методы перехвата:

  1. Вставка безусловного прыжка на месте функции (х86 — 5 байт jmp addr, x64 — 12 байт, mov rax addr, jmp rax)
  2. Вставка инструкции вызова (call) на месте вызова оригинальной функции (hooked_function_entry: call my_function)
  3. Модификация таблицы импорта приложения, внедрение прокси DLL.
  4. Перехват без модификации кода средствами аппаратных точек останова (hardware breakpoints).
  5. Перехват через kernel драйвер Zw/Nt функций.

Можно перечислить следующие методы детектирования перехвата из usermode:
1. Сравнение начала функции перед ее вызовом с опкодами машинных инструкций:

//0x90 - nop
//0xE9 - jmp
//0xE8 - call
if (*mainFunc == 0xE9 || mainFunc == 0x90 || mainFunc == 0xE8 ...) 
printf("Hook detected");

Метод статичный и легко обходится, более умными переходами.
2. Перечисленные методы перехвата — это модификация памяти, и они могут быть обнаружены по средствам crc checksum проверок, но api для чтения памяти тоже могут быть перехвачены и тогда будет возвращен ложный результат.
3. Порождать контролируемый процесс (с активной отладкой самого себя), с восстановлением важных api.
4. Перебор важных для нас api, используя таблицу экспорта в системных библиотеках, сравнивать дизассемблированную длину функции с оригинальной.
5. Восстановление модифицированных api по средствам копирования байтов из библиотеки, примерная схема такая:
— получение базы определенного модуля
— итерация экспорта
— получение RVA необходимой функции.
— преобразование RVA->FileOffset.
— чтение оригинала и запись в память.
— в некоторых случаях не забываем про релоки.
6. Просмотр таблицы импорта библиотеки и для каждой импортированной функции из другой системной библиотеки делать проверку на соответствие, если не совпадает так же читаем с диска и записываем в память.
7. Имитирование системных функций Nt / Zw по средствам syscall-ов, int2e в (старых версиях windows), таблица syscall-ов для всех систем, легко находится в гугле.

Остановимся на 7 методе так как Nt / Zw функции так же могут быть перехвачены, этот метод тоже далек от совершенства, но по моему мнению лучше из всего выше перечисленного.
Посмотрим на реализацию функции NtCreateFile в windows 7 sp1

image

происходит следующее:
— в еах лежит номер syscall-a.
— обнуляется ecx, (wow64 index?).
— в edx содержится указатель на параметры.
— далее вызов, корректировка стэка и возврат.

x64 вызов в win7 и win8
image

При помощи следующего x86 псевдокода «можно создать файл на диске».

//прототип функции NtCreateFile
typedef NTSTATUS  (NTAPI * NTCREATEFILE) (OUT PHANDLE FileHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes,
    OUT PIO_STATUS_BLOCK IoStatusBlock,
    IN PLARGE_INTEGER AllocationSize OPTIONAL,
    IN ULONG FileAttributes,
    IN ULONG ShareAccess,
    IN ULONG CreateDisposition,
    IN ULONG CreateOptions,
    IN PVOID EaBuffer OPTIONAL,
    IN ULONG EaLength);

unsigned char dNtCreateFile[] = 
{0xb8,0x52,0x00,0x00,0x00,0x33,0xc9,0x8d,0x54,0x24,0x04,0x64,
0xff,0x15,0xc0,0x00,0x00,0x00,0x83,0xc4,0x04,0xc2,0x2c,0x00};
...
InitUnicodeString
InitializeObjectAttributes
...
DWORD oldp;    
VirtualProtect(&dNtCreateFile, sizeof(dNtCreateFile), PAGE_EXECUTE_READ, &oldp); 
auto func = (NTCREATEFILE) ((void*)dNtCreateFile);
Ntstatus = (func)(&fileHandle, DesiredAccess, ObjectAttritubes, ioStatusBlock, 0, FileAttributes, ShareAccess, CreateDisposition ,CreateOptions, Optional_Buffer, 0); 

Данный псевдокод будет работать только на windows 7 sp1, однако dNtCreateFile можно формировать «динамично», используя метод из пункта 5, и тогда код с небольшими правками будет работать на всех системах начиная с winxp. Приветствуются любые размышления в комментариях, kernelmode — крайний случай.

Автор: Dinisoid

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js