Содержание статьи
info
Подробнее про хуки ты можешь узнать из статей «Волшебные хуки. Как перехватывать управление любой программой через WinAPI» и «Мелкомягкие хуки: Microsoft Detours - честное средство для настоящего хакера».
Анхукинг позволяет снять хук, который был установлен средством защиты. Определить наличие хука несложно. Вот наглядный пример того, как он может выглядеть.
Здесь EDR поставил хук на NtAllocateVirtualMemory(
. Эта функция будет последней в User Mode, она вызывается лишь для инициализации системного вызова и выделения памяти путем обращения к ядру. В стоковой конфигурации, когда хука нет, никаких безусловных jmp
-переходов быть не должно. Тут мы видим иную ситуацию: переход как раз таки есть, поток управления отдается непонятно кому и непонятно куда. Поэтому нам как атакующим, да и просто чтобы уклониться от обнаружения, нужна операция анхукинга, которая снимет этот хук, и, как следствие, средство защиты потеряет контроль над потоком выполнения программы.
Отмечу лишь, что подобный способ обхода хуков — один из множества. Можно, например, совершать Direct- и Indirect-сисколы, но стоит помнить, что получится обойти только хуки, которые стоят в User Mode. Если средство защиты применяет хуки Kernel Mode (например, SSDT Hooking), то подобные методы окажутся бесполезны. На будущее: SSDT — это специальная таблица, благодаря которой сопоставляются сискол и действие ядра Windows. Есть, конечно, Kernel Patch Protection, который мешает устанавливать подобные хуки, но это уже совсем другая история.
В статье я рассмотрю наиболее популярные способы снятия хуков, от простого к сложному.
warning
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Снятие хука через чтение библиотеки с диска
Этот метод можно считать одним из самых простых. Он основан на том, что библиотека ntdll.dll подгружается в память так же, как находится на диске. Причем хуки установлены непосредственно в памяти, на диске образ девственно чист. Поэтому мы должны будем лишь считать библиотеку с диска, достать из нее PE-секцию .
(в ней находится код), а после перезаписать секцию .
хукнутой библиотеки секцией, считанной с диска.
Мы будем использовать функции 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, то открывай пункт «Отладка → Параметры» и ставь две галочки, чтобы можно было видеть содержимое памяти.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»