Защита приложения от отладки

Отладка вирус иконка

Способы защиты от отладки используют как программисты, которые хотят уберечь свой софт от конкурентов, так и те которые ищут способ противостоять вирусным аналитикам и автоматическим системам распознавания вредоноса.

Несмотря на то, что наиболее популярной архитектурой сейчас является x86, но и x64 тоже пока еще пользуется спросом. Поэтому в сегодняшней статье я расскажу о методах антиотладки, которые подойдут как для архитектуры x86, так и для архитектуры x64.

Еще по теме: Деобфускация вредоносного приложения

IsDebuggerPresent() и структура PEB

Рассматривать детект отладчика стоит начать с универсальной функции IsDebuggerPresent(), которая работает на разных архитектурах и очень проста в использовании. Для определения отладки, достаточно использовать всего одну строку кода: if (IsDebuggerPresent()).

Рассмотрим работу функции WinAPI IsDebuggerPresent, которая обращается к структуре PEB.

Process Environment Block

Process Environment Block  (блок окружения процесса) – это структура процесса в windows , которая заполняется загрузчиком операционной системы на этапе создания процесса. Она содержит информацию об окружении, загруженных модулях (LDR_DATA) и другие данные необходимые для функционирования процесса. Он находится в адресном пространстве процесса и может быть модифицирован из режима usermode. Он содержит много полей: например, отсюда можно узнать информацию о текущем модуле, окружении и загруженных модулях. Получить структуру PEB можно, обратившись к ней напрямую по адресу fs:[30h] для x86 и gs:[60h] для x64.

Соответственно, если загрузить в отладчик функцию IsDebuggerPresent(), на x86-системе мы увидим:

А на x64 код будет таким:

Что значит byte ptr [rax+2]? По этому смещению находится поле BeingDebugged в структуре PEB, которое и сигнализирует нам о факте отладки. Как еще можно использовать PEB для обнаружения отладки?

NtGlobalFlag

Во время отладки система выставляет флаги FLG_HEAP_VALIDATE_PARAMETERS, FLG_HEAP_ENABLE_TAIL_CHECK, FLG_HEAP_ENABLE_FREE_CHECK, в поле NtGlobalFlag, которое находится в структуре PEB. Отладчик использует эти флаги для контроля разрушения кучи посредством переполнения. Битовая маска флагов — 0x70. Смещение NtGlobalFlag в PEB для x86 составляет 0x68, для x64 — 0xBC. Чтобы показать пример кода детекта отладчика по NtGlobalFlag, воспользуемся функциями intrinsics, а чтобы код был более универсальным, используем директивы препроцессора:

Flags и ForceFlags

PEB также содержит указатель на структуру _HEAP, в которой есть поля Flags и ForceFlags. Когда отладчик подсоединен к приложению, поля Flags и ForceFlags содержат признаки отладки. ForceFlags при отладке не должно быть равно нулю, поле Flags не должно быть равно 0x00000002`:

CheckRemoteDebuggerPresent() и NtQueryInformationProcess

Функция CheckRemoteDebuggerPresent, как и IsDebuggerPresent, кросс-платформенная и проверяет наличие отладчика. Ее отличие от IsDebuggerPresent в том, что она умеет проверять не только свой процесс, но и другие по их хендлу. Прототип функции выглядит следующим образом:

где hProcess — хендл процесса, который проверяем на предмет подключения отладчика, pbDebuggerPresent — результат выполнения функции (соответственно, TRUE или FALSE). Но самое важное отличие в работе этой функции заключается в том, что она не берет информацию из PEB, как IsDebuggerPresent, а использует функцию WinAPI NtQueryInformationProcess. Прототип функции выглядит так:

Поле, которое поможет нам понять, как работает CheckRemoteDebuggerPresent, — это ProcessInformationClass, который представляет собой большую структуру (enum) PROCESSINFOCLASS с параметрами. Функция CheckRemoteDebuggerPresent передает в это поле значение 7, которое указывает на ProcessDebugPort. Дело в том, что при подключении отладчика к процессу в структуре EPROCESS заполняется поле ProcessInformation, которое в коде названо DebugPort.

Структура EPROCESS, или блок процесса, содержит много информации о процессе, указатели на несколько структур данных, в том числе и на PEB. Заполняется исполнительной системой ОС, находится в системном адресном пространстве (kernelmode), как и все связанные структуры, кроме PEB. Все процессы имеют эту структуру.

Если поле заполнено и порт отладки назначен, то принимается решение о том, что идет отладка. Код для CheckRemoteDebuggerPresent:

Код передачи параметра ProcessDebugPort напрямую в функцию NtQueryInformationProcess:

Переменная Status имеет тип NTSTATUS и сигнализирует нам об успехе или неуспехе выполнения функции; в DbgPort проверяем, назначен порт или поле нулевое. Если функция отработала без ошибок и вернула статус 0 и DbgPort имеет ненулевое значение, то порт назначен и идет отладка.

Тонкости NtQueryInfoProcess

Документация MSDN говорит нам, что использовать NtQueryInfoProcess следует при помощи динамической линковки, получая ее адрес из ntdll.dll напрямую, через функции LoadLibrary и GetProcAddress, и определяя прототип функции вручную при помощи typedef:

Но функция NtQueryInformationProcess может показать несколько признаков отладки, и ProcessDebugPort — только один из них.

DebugObject

При отладке приложения создается DebugObject, объект отладки. Если NtQueryInformationProcess в поле ProcessInformationClass передать значение 0x1E, то оно укажет на элемент ProcessDebugObjectHandle и при отработке функции нам будет возвращен хендл объекта отладки. Код похож на предыдущий с тем отличием, что вместо 7 в поле ProcessInformationClass передается значение 0x1E и меняется условие проверки:

где hDebObj — поле ProcessInformation с результатом. Здесь все так же: функция отработала правильно и вернула 0, hDebObj ненулевой. Значит, объект отладки создан.

ProcessDebugFlags

Следующий признак отладки, который нам покажет функция NtQueryInfoProcess, — это поле ProcessDebugFlags, имеющее номер 0x1F. Передавая значение 0x1F, мы заставляем функцию NtQueryInfoProcess показать нам поле NoDebugInherit, которое находится в структуре EPROCESS. Если поле равно нулю, это значит, что в данный момент приложение отлаживается. Код вызова NtQueryInfoProcess идентичен, меняем только номер ProcessInformationClass и проверку:

Проверка родительского процесса

Суть этого антиотладочного метода заключается в том, что мы должны проверить, кем именно было запущено приложение, которое мы защищаем: пользователем или отладчиком. Этот способ можно реализовать разными путями — проверить, является ли parent-процессом explorer.exe либо не выступает ли в этой роли ollydbg.exe, x64dbg.exe, x32dbg и так далее. Если попытаться развить логику этого метода обнаружения отладки, то приходит в голову еще один простой метод — получить снапшот всех процессов в системе и сравнить название каждого со списком известных отладчиков.

Проверять родительский процесс мы будем при помощи уже известной нам функции NtQueryInformationProcess и структуры PROCESS_BASIC_INFORMATION (поле InheritedFromUniqueProcessId), а получать список всех запущенных процессов в системе можно при помощи CreateToolhelp32Snapshot/Process32First/Process32Next. Чтобы не писать не относящийся к делу код парсинга всех процессов в системе, напишем только основной код получения ID родительского процесса и основную проверку:

Итак, в baseInf.InheritedFromUniqueProcessId находится ID процесса, который порождает наш. Его можно использовать как угодно: например, получить из него имя файла, название процесса и сравнить с именами отладчиков или проверять, не explorer.exe ли это.

TLS Callbacks

Этот нетривиальный метод антиотладки заключается в том, что мы встраиваем антиотладочные приемы в TLS Callbacks, которые выполняются до входной точки программы. Внутри самого приложения могут быть установлены точки останова, да и внимание будет сконцентрировано на основном коде приложения, но этот прием завершит отладку, даже толком ее не начав. Кто-то считает этот способ весьма могучим, но сейчас при правильной настройке отладчика процесс отладки может останавливаться при входе в TLS Callbacks. То есть против матерых реверсеров это не спасет, зато отсеет много школьников, которые не будут понимать, что происходит. Чтобы реализовать этот метод обнаружения, необходимо сказать компилятору создать секцию TLS таким кодом:

Секция должна иметь имя CRT$XLY:

Сам код имплементации:

Отладочные регистры

Если в отладочных регистрах есть какие-то данные, то это еще один признак. Но дело в том, что отладочные регистры — привилегированный ресурс и получить к ним доступ напрямую можно только в режиме ядра. Но мы попробуем получить контекст потока при помощи функции GetThreadContext и таким образом прочитать данные отладочных регистров. Всего отладочных регистров восемь, DR0–DR7. Первые четыре регистра DR0–DR3 содержат информацию о точках останова, регистры DR4–DR5 — зарезервированные, регистр DR6 заполняется, когда сработал брейк-пойнт отладчика, и содержит информацию об этом событии. Регистр DR7 содержит биты управления отладкой. Итак, нам интересно, какая информация содержится в первых четырех регистрах.

NtSetInformationThread

Еще один нетривиальный метод антиотладки основан на передаче флага HideFromDebugger (находится в структуре _ETHREAD за номером 0x11) в функцию NtSetInformationThread. Вот как выглядит прототип функции:

Этот прием спрячет наш поток от отладчика, переставая отправлять ему отладочные события, например такие, как срабатывание точек останова. Особенность этого метода в том, что он универсален и работает благодаря штатным возможностям ОС. Вот код, который реализует отсоединение главного потока программы от отладчика:

NtCreateThreadEx

Подобно предыдущей работает и функция NtCreateThreadEx. Она появилась в Windows начиная с Vista. Ее тоже можно использовать в качестве готового инструмента для препятствия отладке. Принцип действия схож с NtSetInformationThread — при передаче параметра THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER в поле CreateFlags процесс будет невидим для дебаггера. Прототип функции:

Код отключения отладчика:

После этого начинает работать функция next() из WinAPI, которая находится в отдельном невидимом для отладчика треде.

SeDebugPrivilege

Один из признаков отладки приложения — получение приложением привилегии SeDebugPrivilege. Чтобы понять, есть ли такая привилегия у нашего процесса, можно, например, попытаться открыть какой-нибудь системный процесс. По традиции пробуем открыть csrss.exe. Для этого используем функцию WinAPI OpenProcess с параметром PROCESS_ALL_ACCESS. Вот как реализуется этот метод (в переменной Id_From_csrss находится ID csrss.exe):

SetHandleInformation

Функция SetHandleInformation применяется для установки свойств дескриптора объектов, на который указывает hObject. Прототип функции выглядит следующим образом:

Типы объектов различны — например, это может быть задание, отображение файла или мьютекс. Мы можем этим воспользоваться: создадим мьютекс с флагом HANDLE_FLAG_PROTECT_FROM_CLOSE и попробуем его закрыть, попутно пытаясь поймать исключение. Если исключение будет поймано, то процесс отлаживается.

Заключение

Мы рассмотрели несколько способов защиты приложения от отладки. Я старался показать разные методы отладки и рассказать, как они работают на низком уровне. Чтобы лучше разбираться в том, что происходит, вы должны понимать, как работает ОС, как приложение взаимодействует с разными структурами окружения потока и процесса.

Надеюсь, моя статья поможет вам в этом и научит более эффективно защищать приложения от любопытных реверсеров и автоматических систем распаковки и анализа.

Дима (Kozhuh)

Эксперт в кибербезопасности. Работал в ведущих компаниях занимающихся аналитикой компьютерных угроз. Анонсы новых статей в Телеграме.

Добавить комментарий

  1. Евгений

    куда вставлять код?

    Ответить