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

от автора

Совсем недавно озадачился защитой приложений от перехвата системных 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 — крайний случай.

ссылка на оригинал статьи http://habrahabr.ru/post/186334/


Комментарии

Добавить комментарий

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