Всем привет, эта статья про актуальные антиотладочные трюки будет полезна всем кто разрабатывал и писал (или планирует) приватный софт на продажу, будь то стиллеры, чекеры, софт для автоматизации и т.д. Обфускаторы и пакеры усложняют реверс, но не всегда спасают софт от попадания в паблик.
Анти-отладочные трюки хоть и относятся к части вашей защиты, но они не должны рассматриваться как панацея или основной фокус защиты. Но тем не менее, нельзя недооценивать значимость грамотно реализованных анти-отладочных приемов.
Но на своем опыте я понял, что люди не хотят тратить время на подобную реализацию и используют приемы из открытых источников, которые уже давно неактуальны и не имеют какой-либо эффективности.
Если Вы уже смешарик, то можете пропустить данный парт, тут объяснения исключительно для новичков. Давайте рассмотрим такие методы:
1. IsDebuggerPresent(). Самый простой и распространенный метод - вызов функции IsDebuggerPresent() из Windows API. Эта функция проверяет флаг BeingDebugged в PEB (Process Environment Block). Однако она легко обходится простым патчем одного байта. Сам x64dbg имеет возможность подобного обхода.
2. CheckRemoteDebuggerPresent(). Точно такой же простой и распространенный метод, который проверяет, не отлаживается ли процесс удаленно. Тем не менее, не надо иметь семи пядей во лбу, чтобы обойти эту проверку.
3. Проверка времени выполнения (Timing Checks). Метод, основанный на измерении времени выполнения определенных инструкций. Предполагается, что под отладчиком код выполняется медленнее и что таким методом можно обнаружить заморозку процесса. Но как показывает практика, такие методы имеют жирный минус - ложные обнаружения, довольно много пользователей попадают под это, и поэтому приходится обрабатывать репорты с сервера вручную, чтобы система автоматом не банила пользователей.
4. Использование SEH (Structured Exception Handling). Установка обработчиков исключений и намеренное вызывание исключений для обнаружения отладчика. Под этот список попадает EXCEPTION_BREAKPOINT, EXCEPTION_SINGLE_STEP. Этот метод также легко обходится опытными реверсерами.
5. Проверка на наличие специфических окон. Супер-примитивное обнаружение, которое использовал каждый уважающий себя разработчик будучи неопытным в написании защиты. Поиск окон с заголовками, характерными для популярных отладчиков (OllyDbg, x64dbg и т.д.). Этот метод легко обойти, просто переименовав окно отладчика.
6. Обнаружение точек останова. Проверка кода на наличие инструкций INT3, которые часто используются для установки точек останова. Однако существуют методы установки точек останова без использования INT3. Обычно проверяют лишь начало функции на INT3, но можно ставить сразу в конец функции точку останову, на RET.
7. NtGlobalFlag. Проверка флага NtGlobalFlag в PEB, который часто устанавливается при запуске процесса под отладчиком. Как и в случае с полем BeingDebugged патчится разными плагинами, вроде SharpOD или ScyllaHide.
8. Проверка родительского процесса. Анализ родительского процесса на предмет известных имен отладчиков, вот только сам x64dbg давно так не делает.
9. Проверка перехваченных функций. Этот метод основан на проверке целостности важных системных функций, которые часто перехватываются отладчиками и другими инструментами анализа. Суть заключается в следующем:
Проверка первых байтов функции на наличие инструкций перехода (JMP, CALL).
Сравнение хеша или контрольной суммы кода функции с эталонным значением.
Анализ таблицы импорта на предмет изменений адресов функций.
Этот метод может быть эффективен против пользователей анти-анти-отладочных плагинов (ScyllaHide, SharpOD). Умно, но метод перехвата можно изменить на условный PageGuard Hook, который не прибегает к патчу кода.
В дальнейшем вы по чуть-чуть сами начнете отказываться от подобных плагинов, видя в них больше минусов, чем плюсов.
Другой подход к анти-отладке
Теперь, когда мы рассмотрели традиционные методы анти-отладки и их недостатки, давайте перейдем к более продвинутой и менее известной технике.
X64Debugger, несмотря на все усилия их разработчиков, оставляют множество следов в системе при подключении к процессу. Эти следы, хотя и не всегда очевидны, могут быть обнаружены при тщательном анализе процесса. Давайте рассмотрим некоторые из этих следов:
1. Изменения в памяти процесса. При подключении отладчика происходят определенные изменения в памяти отлаживаемого процесса, включая модификации PEB (Process Environment Block).
2. Изменения в библиотеках. Опять же, всему виною плагины, которые расставляют свои хуки, да бы уберечь реверс-инженера от обнаружения, но как на зло добавляют еще больше векторов обнаружения.
3. Аномалии времени выполнения. Отладка может влиять на время выполнения определенных операций, создавая заметные задержки, особенно, если вы трассируете код.
Однако, один из наиболее очевидных и в то же время часто игнорируемых следов - это дескрипторы (handles), создаваемые отладчиком.
Отладчики, для выполнения своих функций, неизбежно создают множество дескрипторов. Эти дескрипторы могут указывать на различные объекты:
Файлы плагинов отладчика
Процессы и потоки отлаживаемого приложения
Секции памяти
Объекты синхронизации
Что делает дескрипторы особенно интересными для обнаружения отладчиков?
1. Уникальность: Определенные типы дескрипторов характерны именно для процесса отладки.
2. Постоянство: В отличие от некоторых других следов, дескрипторы сохраняются на протяжении всего процесса отладки.
3. Сложность маскировки: Отладчикам трудно скрыть все создаваемые ими дескрипторы без потери функциональности.
При подключении отладчику к любому приложению на глаза сразу бросаются несколько вещей, представляю на скрине:
На этом изображении мы видим, что у отладчика уже несколько открытых хендлов для обнаружения.
1) scylla_hide.log - Лог-файл плагина ScyllaHide, если у Вас установлен этот плагин, то Вы уже обнаружены.
2) hide-from-dbg.exe - Содержит полный путь к файлу, который на данный момент отлаживается.
До этого я упоминал, что в отладчике есть открытые дескрипторы плагинов, имеющие расширение ".dp64". Это действительно так, однако далеко не все плагины могут этим похвастаться, но в свою копилку обнаружений мы ее все-равно добавим.
Анатомия обнаружения
Теперь, когда мы понимаем, почему дескрипторы являются ключом к обнаружению отладчиков, давайте рассмотрим, как мы можем использовать эту информацию на практике.
Теоретическая основа:
1. Системная информация о дескрипторах: Windows предоставляет механизм для получения информации обо всех открытых дескрипторах в системе. Мы будем использовать функцию NtQuerySystemInformation с параметром SystemHandleInformation. Эта недокументированная функция возвращает массив структур SYSTEM_HANDLE_INFORMATION, содержащих данные о каждом открытом дескрипторе в системе.
2. Анализ дескрипторов: Мы будем проходить по всем дескрипторам, открытым в системе, и анализировать их. Особое внимание будем уделять дескрипторам типа FILE_TYPE_DISK, так как они могут указывать на открытые файлы отладчика или его плагинов.
3. Дублирование дескрипторов: Для анализа дескрипторов других процессов нам потребуется их дублировать в наш процесс. Это делается с помощью функции NtDuplicateObject.
4. Получение информации о файлах: После дублирования дескриптора мы сможем получить информацию о связанном с ним файле, используя функцию GetFinalPathNameByHandleA.
5. Поиск специфических маркеров: Мы будем искать в путях файлов специфические маркеры, такие как ".dp64", "_hide", или сравнивать имя ".exe" файла с нашим.
6. Обработка найденных процессов: Все процессы, в которых мы обнаружим подозрительные дескрипторы, будут добавлены в список для дальнейшего анализа или действий.
Практическая реализация
Наш код состоит из двух основных функций в пространстве имен antidebug. Давайте рассмотрим каждую из них подробно:
1. Функция AddDetectedPid:
Эта функция добавляет идентификатор процесса (PID) в вектор detected_pids, если его там еще нет. Использование std::find гарантирует, что мы не добавим дубликаты PID в наш список.
2. Функция FindProcessIdsByOpenHandles:
Эта функция является ядром нашего метода обнаружения отладчика. Разберем ее по частям:
а) Инициализация и получение системной информации:
Здесь мы используем NtQuerySystemInformation для получения информации о системных дескрипторах. Мы начинаем с буфера размером 0x1000 байт и увеличиваем его, если информация не помещается.
б) Итерация по дескрипторам:
Мы проходим по всем дескрипторам в системе.
в) Открытие процесса и дублирование дескриптора:
Здесь мы открываем процесс, которому принадлежит дескриптор, и дублируем этот дескриптор в наш процесс для анализа.
г) Анализ дескриптора:
В этой части мы анализируем дескриптор. Если это файловый дескриптор (FILE_TYPE_DISK), мы получаем путь к файлу и проверяем его на наличие специфических маркеров (".dp64", "_hide") или на совпадение с путем нашего собственного исполняемого файла.
Кста, т.к. тут по большей части используются экспорты ntdll, то можно прикрутить к ним любую библиотеку, позволяющую напрямую вызывать syscall.
Инфектор отладчика
Вот мы получили PID отладчика, и что теперь? Простое обнаружение - это только половина дела. Настоящее искусство заключается в том, чтобы сделать отладчик полным инвалидом, который не сможет правильно декодировать инструкции и обрабатывать их, но это лишь на время его запуска. Наш подход заключается в "инфицировании" процесса отладчика, перехватывая ключевые функции Windows API и изменяя их поведение.
Суть инфектора: Наш инфектор - это DLL, которую мы внедряем в процесс отладчика. Она перехватывает несколько критических функций Windows API, используя библиотеку MinHook.
1. Перехват BitBlt:
Эта функция перехватывает операции отрисовки, меняя битовую передачу данных цвета, при этом добавляя еще свои приколы, по типу отрисовки икон в случайном месте, или закрашивая прямоугольник использую случайную операцию. Это делает интерфейс отладчика абсолютно нечитаемым.
2. Перехват ReadProcessMemory:
Т.к. через функцию ReadProcessMemory отладчик читает важную информацию, то соответственно под эту категорию попадает дизассемблер, hex-дамп и стек. Наш хук возвращает в буффере случайные байты, из-за чего дальнейшая отладка будет просто невозможной.
3. Перехват CreateProcessW:
Эта функция блокирует создания новых процессов отладчиком.
4. Перехват OpenProcess:
Эта функция всегда возвращает недействительный дескриптор, что делает невозможным присоединение отладчика к другим процессам.
Инжектор
Для статьи я не стал заморачиваться, и в качестве инжектора я взял обыкновенный LLA-инжектор. Если надумаете брать подобную технику к себе в арсенал, то настоятельно рекомендую менять метод инжекта на какой-нибудь Manual Map через NtCreateSection + NtMapViewOfSectionEx.
Код:
Запуск нашего инфектора
Теперь, когда у нас есть инфектор, важно правильно его использовать. Недостаточно один раз вызвать этот инфектор, реверс-инженер может попросту перезапустить отладчик. Поэтому наша стратегия заключается в постоянном мониторинге и повторном инфицировании при необходимости.
Пример вызова:
Время можете подбирать сами, главное не используйте это в основном потоке, иначе рискуете заработать deadlock после вызова NtQueryObject.
Анти-отладочные трюки хоть и относятся к части вашей защиты, но они не должны рассматриваться как панацея или основной фокус защиты. Но тем не менее, нельзя недооценивать значимость грамотно реализованных анти-отладочных приемов.
Но на своем опыте я понял, что люди не хотят тратить время на подобную реализацию и используют приемы из открытых источников, которые уже давно неактуальны и не имеют какой-либо эффективности.
Если Вы уже смешарик, то можете пропустить данный парт, тут объяснения исключительно для новичков. Давайте рассмотрим такие методы:
1. IsDebuggerPresent(). Самый простой и распространенный метод - вызов функции IsDebuggerPresent() из Windows API. Эта функция проверяет флаг BeingDebugged в PEB (Process Environment Block). Однако она легко обходится простым патчем одного байта. Сам x64dbg имеет возможность подобного обхода.
2. CheckRemoteDebuggerPresent(). Точно такой же простой и распространенный метод, который проверяет, не отлаживается ли процесс удаленно. Тем не менее, не надо иметь семи пядей во лбу, чтобы обойти эту проверку.
3. Проверка времени выполнения (Timing Checks). Метод, основанный на измерении времени выполнения определенных инструкций. Предполагается, что под отладчиком код выполняется медленнее и что таким методом можно обнаружить заморозку процесса. Но как показывает практика, такие методы имеют жирный минус - ложные обнаружения, довольно много пользователей попадают под это, и поэтому приходится обрабатывать репорты с сервера вручную, чтобы система автоматом не банила пользователей.
4. Использование SEH (Structured Exception Handling). Установка обработчиков исключений и намеренное вызывание исключений для обнаружения отладчика. Под этот список попадает EXCEPTION_BREAKPOINT, EXCEPTION_SINGLE_STEP. Этот метод также легко обходится опытными реверсерами.
5. Проверка на наличие специфических окон. Супер-примитивное обнаружение, которое использовал каждый уважающий себя разработчик будучи неопытным в написании защиты. Поиск окон с заголовками, характерными для популярных отладчиков (OllyDbg, x64dbg и т.д.). Этот метод легко обойти, просто переименовав окно отладчика.
6. Обнаружение точек останова. Проверка кода на наличие инструкций INT3, которые часто используются для установки точек останова. Однако существуют методы установки точек останова без использования INT3. Обычно проверяют лишь начало функции на INT3, но можно ставить сразу в конец функции точку останову, на RET.
7. NtGlobalFlag. Проверка флага NtGlobalFlag в PEB, который часто устанавливается при запуске процесса под отладчиком. Как и в случае с полем BeingDebugged патчится разными плагинами, вроде SharpOD или ScyllaHide.
8. Проверка родительского процесса. Анализ родительского процесса на предмет известных имен отладчиков, вот только сам x64dbg давно так не делает.
9. Проверка перехваченных функций. Этот метод основан на проверке целостности важных системных функций, которые часто перехватываются отладчиками и другими инструментами анализа. Суть заключается в следующем:
Проверка первых байтов функции на наличие инструкций перехода (JMP, CALL).
Сравнение хеша или контрольной суммы кода функции с эталонным значением.
Анализ таблицы импорта на предмет изменений адресов функций.
Этот метод может быть эффективен против пользователей анти-анти-отладочных плагинов (ScyllaHide, SharpOD). Умно, но метод перехвата можно изменить на условный PageGuard Hook, который не прибегает к патчу кода.
В дальнейшем вы по чуть-чуть сами начнете отказываться от подобных плагинов, видя в них больше минусов, чем плюсов.
Другой подход к анти-отладке
Теперь, когда мы рассмотрели традиционные методы анти-отладки и их недостатки, давайте перейдем к более продвинутой и менее известной технике.
X64Debugger, несмотря на все усилия их разработчиков, оставляют множество следов в системе при подключении к процессу. Эти следы, хотя и не всегда очевидны, могут быть обнаружены при тщательном анализе процесса. Давайте рассмотрим некоторые из этих следов:
1. Изменения в памяти процесса. При подключении отладчика происходят определенные изменения в памяти отлаживаемого процесса, включая модификации PEB (Process Environment Block).
2. Изменения в библиотеках. Опять же, всему виною плагины, которые расставляют свои хуки, да бы уберечь реверс-инженера от обнаружения, но как на зло добавляют еще больше векторов обнаружения.
3. Аномалии времени выполнения. Отладка может влиять на время выполнения определенных операций, создавая заметные задержки, особенно, если вы трассируете код.
Однако, один из наиболее очевидных и в то же время часто игнорируемых следов - это дескрипторы (handles), создаваемые отладчиком.
Отладчики, для выполнения своих функций, неизбежно создают множество дескрипторов. Эти дескрипторы могут указывать на различные объекты:
Файлы плагинов отладчика
Процессы и потоки отлаживаемого приложения
Секции памяти
Объекты синхронизации
Что делает дескрипторы особенно интересными для обнаружения отладчиков?
1. Уникальность: Определенные типы дескрипторов характерны именно для процесса отладки.
2. Постоянство: В отличие от некоторых других следов, дескрипторы сохраняются на протяжении всего процесса отладки.
3. Сложность маскировки: Отладчикам трудно скрыть все создаваемые ими дескрипторы без потери функциональности.
При подключении отладчику к любому приложению на глаза сразу бросаются несколько вещей, представляю на скрине:
На этом изображении мы видим, что у отладчика уже несколько открытых хендлов для обнаружения.
1) scylla_hide.log - Лог-файл плагина ScyllaHide, если у Вас установлен этот плагин, то Вы уже обнаружены.
2) hide-from-dbg.exe - Содержит полный путь к файлу, который на данный момент отлаживается.
До этого я упоминал, что в отладчике есть открытые дескрипторы плагинов, имеющие расширение ".dp64". Это действительно так, однако далеко не все плагины могут этим похвастаться, но в свою копилку обнаружений мы ее все-равно добавим.
Анатомия обнаружения
Теперь, когда мы понимаем, почему дескрипторы являются ключом к обнаружению отладчиков, давайте рассмотрим, как мы можем использовать эту информацию на практике.
Теоретическая основа:
1. Системная информация о дескрипторах: Windows предоставляет механизм для получения информации обо всех открытых дескрипторах в системе. Мы будем использовать функцию NtQuerySystemInformation с параметром SystemHandleInformation. Эта недокументированная функция возвращает массив структур SYSTEM_HANDLE_INFORMATION, содержащих данные о каждом открытом дескрипторе в системе.
2. Анализ дескрипторов: Мы будем проходить по всем дескрипторам, открытым в системе, и анализировать их. Особое внимание будем уделять дескрипторам типа FILE_TYPE_DISK, так как они могут указывать на открытые файлы отладчика или его плагинов.
3. Дублирование дескрипторов: Для анализа дескрипторов других процессов нам потребуется их дублировать в наш процесс. Это делается с помощью функции NtDuplicateObject.
4. Получение информации о файлах: После дублирования дескриптора мы сможем получить информацию о связанном с ним файле, используя функцию GetFinalPathNameByHandleA.
5. Поиск специфических маркеров: Мы будем искать в путях файлов специфические маркеры, такие как ".dp64", "_hide", или сравнивать имя ".exe" файла с нашим.
6. Обработка найденных процессов: Все процессы, в которых мы обнаружим подозрительные дескрипторы, будут добавлены в список для дальнейшего анализа или действий.
Практическая реализация
Наш код состоит из двух основных функций в пространстве имен antidebug. Давайте рассмотрим каждую из них подробно:
1. Функция AddDetectedPid:
Эта функция добавляет идентификатор процесса (PID) в вектор detected_pids, если его там еще нет. Использование std::find гарантирует, что мы не добавим дубликаты PID в наш список.
Код:
void antidebug::AddDetectedPid(std::uint32_t pid)
{
if (std::find(detected_pids.begin(), detected_pids.end(), pid) == detected_pids.end())
{
detected_pids.push_back(pid);
}
}
2. Функция FindProcessIdsByOpenHandles:
Эта функция является ядром нашего метода обнаружения отладчика. Разберем ее по частям:
а) Инициализация и получение системной информации:
Код:
std::vector<std::uint32_t> antidebug::FindProcessIdsByOpenHandles()
{
std::vector<std::uint8_t> buffer(buffer_size);
ULONG buffer_size{ 0x1000 };
NTSTATUS status{};
while ((status = NtQuerySystemInformation(
static_cast<SYSTEM_INFORMATION_CLASS>(SystemHandleInformation),
buffer.data(),
buffer_size,
&buffer_size)) == STATUS_INFO_LENGTH_MISMATCH)
{
buffer.resize(buffer_size);
}
if (!NT_SUCCESS(status))
return detected_pids;
б) Итерация по дескрипторам:
Код:
const auto handle_info = reinterpret_cast<PSYSTEM_HANDLE_INFORMATION>(buffer.data());
for (std::size_t i = 0; i < handle_info->NumberOfHandles; i++)
{
const auto& handle_entry = handle_info->Handles[i];
в) Открытие процесса и дублирование дескриптора:
Код:
CLIENT_ID client_id{};
client_id.UniqueProcess = reinterpret_cast<HANDLE>(static_cast<ULONG_PTR>(
handle_entry.UniqueProcessId));
OBJECT_ATTRIBUTES object_attributes{};
InitializeObjectAttributes(&object_attributes, NULL, 0, NULL, NULL);
HANDLE process;
status = NtOpenProcess(
&process,
PROCESS_DUP_HANDLE,
&object_attributes,
&client_id
);
if (!NT_SUCCESS(status))
continue;
HANDLE duplicate = NULL;
status = NtDuplicateObject(
process,
reinterpret_cast<HANDLE>(handle_entry.HandleValue),
GetCurrentProcess(),
&duplicate,
0,
FALSE,
DUPLICATE_SAME_ACCESS
);
г) Анализ дескриптора:
Код:
if (NT_SUCCESS(status))
{
const auto type = GetFileType(duplicate);
char path[MAX_PATH];
if (type == FILE_TYPE_DISK)
{
if (GetFinalPathNameByHandleA(duplicate, path, MAX_PATH, FILE_NAME_NORMALIZED) > 0)
{
if (util::Strstr(path, ".dp64") != NULL || util::Strstr(path, "_hide") != NULL)
{
AddDetectedPid(handle_entry.UniqueProcessId);
}
else if (util::Strstr(path, ".exe") != NULL)
{
std::unique_ptr<char[]> my_exe = std::make_unique<char[]>(MAX_PATH);
GetModuleFileNameA(NULL, my_exe.get(), MAX_PATH);
if (util::Strstr(path, my_exe.get()) != NULL)
{
AddDetectedPid(handle_entry.UniqueProcessId);
}
}
}
CloseHandle(duplicate);
}
CloseHandle(process);
}
return detected_pids;
}
Кста, т.к. тут по большей части используются экспорты ntdll, то можно прикрутить к ним любую библиотеку, позволяющую напрямую вызывать syscall.
Инфектор отладчика
Вот мы получили PID отладчика, и что теперь? Простое обнаружение - это только половина дела. Настоящее искусство заключается в том, чтобы сделать отладчик полным инвалидом, который не сможет правильно декодировать инструкции и обрабатывать их, но это лишь на время его запуска. Наш подход заключается в "инфицировании" процесса отладчика, перехватывая ключевые функции Windows API и изменяя их поведение.
Суть инфектора: Наш инфектор - это DLL, которую мы внедряем в процесс отладчика. Она перехватывает несколько критических функций Windows API, используя библиотеку MinHook.
1. Перехват BitBlt:
Код:
std::uint32_t __stdcall hkBitBlt(
HDC hdcDest,
int xDest,
int yDest,
int width,
int height,
HDC hdcSrc,
int xSrc,
int ySrc,
DWORD rop
)
{
const size_t num_raster_operations = g_raster_operations.size();
const size_t num_idi_icons = g_idi_icons.size();
int rand_raster_op_index = rand() % num_raster_operations;
int rand_icon_index = rand() % num_idi_icons;
rop = g_raster_operations[rand_raster_op_index];
StretchBlt(
hdcDest,
xDest,
yDest,
width,
height,
hdcSrc,
xSrc,
ySrc,
width,
height,
g_raster_operations[rand_raster_op_index]
);
SelectObject(
hdcDest,
CreateHatchBrush(
rand() % 6,
RGB(rand() % 256, rand() % 256, rand() % 256)
)
);
PatBlt(
hdcDest,
xDest,
yDest,
width,
height,
g_raster_operations[rand() % num_raster_operations]
);
DrawIcon(
hdcDest,
rand() % 4096,
rand() % 4096,
LoadIcon(nullptr, g_idi_icons[rand_icon_index])
);
return oBitBlt(hdcDest, xDest, yDest, width, height, hdcSrc, xSrc, ySrc, rop);
}
2. Перехват ReadProcessMemory:
Код:
std::uint32_t __stdcall hkReadProcessMemory( HANDLE hProcess, LPCVOID
lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesRead )
{
std::uint8_t *buffer = static_cast<std::uint8_t *>( lpBuffer );
for ( std::size_t i = 0; i < nSize; ++i )
buffer[ i ] = static_cast<std::uint8_t>( rand() % 256 );
if ( lpNumberOfBytesRead )
*lpNumberOfBytesRead = nSize;
return 1;
}
3. Перехват CreateProcessW:
Код:
std::uint32_t __stdcall hkCreateProcessW(
LPWSTR lpApplicationName,
LPWSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPWSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
)
{
return STATUS_ACCESS_VIOLATION;
}
4. Перехват OpenProcess:
Код:
HANDLE __stdcall hkOpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId )
{
return INVALID_HANDLE_VALUE;
}
Инжектор
Для статьи я не стал заморачиваться, и в качестве инжектора я взял обыкновенный LLA-инжектор. Если надумаете брать подобную технику к себе в арсенал, то настоятельно рекомендую менять метод инжекта на какой-нибудь Manual Map через NtCreateSection + NtMapViewOfSectionEx.
Код:
Код:
std::uint32_t infector::Run(DWORD process_id, const wchar_t* dll_path)
{
auto process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process_id);
if (process == NULL)
return 0;
const auto path_size = util::Wcslen(dll_path) * sizeof(wchar_t);
const auto remote_path = VirtualAllocEx(
process,
NULL,
path_size,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
if (remote_path == NULL)
{
CloseHandle(process);
return 0;
}
if (!WriteProcessMemory(process, remote_path, dll_path, path_size, NULL))
{
VirtualFreeEx(process, remote_path, 0, MEM_RELEASE);
CloseHandle(process);
return 0;
}
const auto kernel = GetModuleHandle(L"kernel32.dll");
if (kernel == NULL)
{
VirtualFreeEx(process, remote_path, 0, MEM_RELEASE);
CloseHandle(process);
return 0;
}
const auto address = GetProcAddress(kernel, "LoadLibraryW");
if (address == NULL)
{
VirtualFreeEx(process, remote_path, 0, MEM_RELEASE);
CloseHandle(process);
return 0;
}
const auto thread = CreateRemoteThread(
process,
NULL,
0,
(LPTHREAD_START_ROUTINE)address,
remote_path,
0,
NULL
);
if (thread == NULL)
{
VirtualFreeEx(process, remote_path, 0, MEM_RELEASE);
CloseHandle(process);
return 0;
}
WaitForSingleObject(thread, INFINITE);
DWORD exit_code{};
GetExitCodeThread(thread, &exit_code);
CloseHandle(thread);
VirtualFreeEx(process, remote_path, 0, MEM_RELEASE);
CloseHandle(process);
return exit_code != 0;
}
Теперь, когда у нас есть инфектор, важно правильно его использовать. Недостаточно один раз вызвать этот инфектор, реверс-инженер может попросту перезапустить отладчик. Поэтому наша стратегия заключается в постоянном мониторинге и повторном инфицировании при необходимости.
Код:
void TryToInfectDebugger(DWORD ms)
{
while (true)
{
antidebug::FindProcessIdsByOpenHandles();
if (!antidebug::detected_pids.empty())
{
std::printf(
"Found x64dbg processes: %u -> ",
antidebug::detected_pids.size()
);
for (const auto& pid : antidebug::detected_pids)
{
const auto infected = infector::Run(pid, infector::path);
std::printf(
"%u %s\n",
pid,
infected ? "(INFECTED)" : "(NOT INFECTED)"
);
}
antidebug::detected_pids.clear();
antidebug::detected_pids.shrink_to_fit();
}
else
{
std::printf("No x64dbg processes found.\n");
}
util::Sleep(ms);
}
}
Пример вызова:
Код:
int main()
{
TryToInfectDebugger( 30000 );
return 0;
}
Время можете подбирать сами, главное не используйте это в основном потоке, иначе рискуете заработать deadlock после вызова NtQueryObject.
Последнее редактирование: