Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Средства защиты, в частности EDR, любят ставить хуки. Хук — это специальная инструкция, которая позволяет перехватить поток управления программы при вызове определенной функции и в результате контролировать, отслеживать и изменять данные, переданные этой функции. В этой статье я покажу, как проводить обратный процесс — анхукинг.
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-секцию .text
(в ней находится код), а после перезаписать секцию .text
хукнутой библиотеки секцией, считанной с диска.
Алгоритм снятия хука
Мы будем использовать функции ReadFile()
и MapViewOfFile()
, и EDR может отслеживать их, поэтому есть риск, что наша ntdll.dll
, загруженная с диска, будет изменена при попытке подгрузить ее содержимое в программу. Поэтому придется использовать иной способ снятия хука, например тащить ntdll.dll с некоего удаленного сервера. Этот алгоритм реализуем позже. За идею большое спасибо Ральфу.
Итак, сначала нужно считать содержимое библиотеки ntdll.dll. Начнем со стандартной функции ReadFile()
. По умолчанию ntdll.dll лежит в системной папке WindowsSystem32
. Предлагаю создать функцию, которая будет возвращать буфер с содержимым 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, то открывай пункт «Отладка → Параметры» и ставь две галочки, чтобы можно было видеть содержимое памяти.
Включение показа содержимого памяти
Источник: xakep.ru