Содержание статьи
info
Подробнее про хуки ты можешь узнать из статей «Волшебные хуки. Как перехватывать управление любой программой через WinAPI» и «Мелкомягкие хуки: Microsoft Detours - честное средство для настоящего хакера».
Анхукинг позволяет снять хук, который был установлен средством защиты. Определить наличие хука несложно. Вот наглядный пример того, как он может выглядеть.
![Пример хука Пример хука](https://static.xakep.ru/images/622b77d78b67f7a2b94ab9be2a4536c7/32291/img01.png)
Здесь EDR поставил хук на NtAllocateVirtualMemory(
. Эта функция будет последней в User Mode, она вызывается лишь для инициализации системного вызова и выделения памяти путем обращения к ядру. В стоковой конфигурации, когда хука нет, никаких безусловных jmp
-переходов быть не должно. Тут мы видим иную ситуацию: переход как раз таки есть, поток управления отдается непонятно кому и непонятно куда. Поэтому нам как атакующим, да и просто чтобы уклониться от обнаружения, нужна операция анхукинга, которая снимет этот хук, и, как следствие, средство защиты потеряет контроль над потоком выполнения программы.
Отмечу лишь, что подобный способ обхода хуков — один из множества. Можно, например, совершать Direct- и Indirect-сисколы, но стоит помнить, что получится обойти только хуки, которые стоят в User Mode. Если средство защиты применяет хуки Kernel Mode (например, SSDT Hooking), то подобные методы окажутся бесполезны. На будущее: SSDT — это специальная таблица, благодаря которой сопоставляются сискол и действие ядра Windows. Есть, конечно, Kernel Patch Protection, который мешает устанавливать подобные хуки, но это уже совсем другая история.
В статье я рассмотрю наиболее популярные способы снятия хуков, от простого к сложному.
warning
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Снятие хука через чтение библиотеки с диска
Этот метод можно считать одним из самых простых. Он основан на том, что библиотека ntdll.dll подгружается в память так же, как находится на диске. Причем хуки установлены непосредственно в памяти, на диске образ девственно чист. Поэтому мы должны будем лишь считать библиотеку с диска, достать из нее PE-секцию .
(в ней находится код), а после перезаписать секцию .
хукнутой библиотеки секцией, считанной с диска.
![Алгоритм снятия хука Алгоритм снятия хука](https://static.xakep.ru/images/622b77d78b67f7a2b94ab9be2a4536c7/32290/img02.png)
Мы будем использовать функции ReadFile(
и MapViewOfFile(
, и EDR может отслеживать их, поэтому есть риск, что наша ntdll.
, загруженная с диска, будет изменена при попытке подгрузить ее содержимое в программу. Поэтому придется использовать иной способ снятия хука, например тащить ntdll.dll с некоего удаленного сервера. Этот алгоритм реализуем позже. За идею большое спасибо Ральфу.
Итак, сначала нужно считать содержимое библиотеки ntdll.dll. Начнем со стандартной функции ReadFile(
. По умолчанию ntdll.dll лежит в системной папке \
. Предлагаю создать функцию, которая будет возвращать буфер с содержимым ntdll.dll.
#define NTDLL "NTDLL.DLL"BOOL ReadNtdllFromDisk(OUT PVOID* ppNtdllBuf) { CHAR cWinPath[MAX_PATH / 2] = { 0 }; CHAR cNtdllPath[MAX_PATH] = { 0 }; HANDLE hFile = NULL; DWORD dwNumberOfBytesRead = NULL, dwFileLen = NULL; PVOID pNtdllBuffer = NULL; if (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == 0) { printf("[!] GetWindowsDirectoryA Failed With Error : %d \n", GetLastError()); goto EndOfFunc; } sprintf_s(cNtdllPath, sizeof(cNtdllPath), "%s\\System32\\%s", cWinPath, NTDLL); hFile = CreateFileA(cNtdllPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { printf("[!] CreateFileA Failed With Error : %d \n", GetLastError()); goto EndOfFunc; } dwFileLen = GetFileSize(hFile, NULL); pNtdllBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwFileLen); if (!ReadFile(hFile, pNtdllBuffer, dwFileLen, &dwNumberOfBytesRead, NULL) || dwFileLen != dwNumberOfBytesRead) { printf("[!] ReadFile Failed With Error : %d \n", GetLastError()); printf("[i] Read %d of %d Bytes \n", dwNumberOfBytesRead, dwFileLen); goto EndOfFunc; } *ppNtdllBuf = pNtdllBuffer;EndOfFunc: if (hFile) CloseHandle(hFile); if (*ppNtdllBuf == NULL) return FALSE; else return TRUE;}
Остается проверить, что наш код верно работает. Если ты пишешь в Visual Studio, то открывай пункт «Отладка → Параметры» и ставь две галочки, чтобы можно было видеть содержимое памяти.
![Включение показа содержимого памяти Включение показа содержимого памяти](https://static.xakep.ru/images/622b77d78b67f7a2b94ab9be2a4536c7/32289/img03.png)
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»