Anti-debug — набор приёмов, которыми программа определяет, запущена ли она под отладчиком. Вредоносное ПО использует их, чтобы усложнить анализ; легитимные приложения — для защиты коммерческой логики. Исследователю важно распознавать эти техники на этапе статического и динамического анализа: они указывают на намеренное усложнение реверса и часто маркируют критические участки кода.
Ниже разобраны основные категории проверок с объяснением механики каждой.
Process Environment Block (PEB) — структура в адресном пространстве процесса, содержащая метаданные: флаги кучи, информацию о загрузчике, параметры отладки. Доступ к PEB на x86-64 осуществляется через
Самая простая проверка. Когда к процессу подключён отладчик пользовательского режима, ядро устанавливает
Ручная реализация без вызова API:
Для исследователя: если в дизассемблере виден доступ к
При запуске процесса под отладчиком ядро выставляет в
Смещение
Системный вызов
Для исследователя: вызов
Отладчики могут устанавливать breakpoints через регистры DR0–DR3 процессора. Программа проверяет их содержимое через
Если хотя бы один из четырёх debug-регистров содержит ненулевой адрес, программа считает, что к ней подключён отладчик с hardware breakpoint. Это особенно актуально при анализе через x64dbg, где HWBP — стандартный инструмент.
Простейший, но часто встречающийся подход — снапшот всех процессов и сравнение имён с чёрным списком:
Ограничение техники: переименование исполняемого файла отладчика обходит её. Тем не менее, в статическом анализе наличие строк
Отладчик приостанавливает выполнение процесса на точках останова. Если измерить время между двумя точками кода и оно превышает порог — вероятно, процесс был остановлен.
Более точный вариант с высоким разрешением таймера:
Порог 100000 тиков зависит от частоты таймера (обычно ~10 МГц на современных системах), что соответствует примерно 10 мс. Для исследователя: если между двумя вызовами
Функция
Логика: если
Некоторые сэмплы намеренно вызывают исключения, которые отладчик обрабатывает иначе, чем обычная программа:
Если отладчик настроен на перехват всех исключений первого шанса (first-chance), он остановится на этих инструкциях, что выдаёт его присутствие.
Неочевидная проверка, основанная на поведении API при отсутствии отладчика:
Механика:
При реверс-инжиниринге бинарного файла антиотладочные проверки распознаются по паттернам:
При запуске под отладчиком исследователь может:
Anti-debug редко используется изолированно. В реальных сэмплах он комбинируется с:
Контекст разработки и анализа вредоносного ПО как предмета защитного исследования описан в Разработка вредоносного ПО в 2026 году как предмет защитного исследования: техники, детект и безопасная лаборатория.
Ниже разобраны основные категории проверок с объяснением механики каждой.
Проверки через структуру PEB
Process Environment Block (PEB) — структура в адресном пространстве процесса, содержащая метаданные: флаги кучи, информацию о загрузчике, параметры отладки. Доступ к PEB на x86-64 осуществляется через
gs:[0x60], на x86 — через fs:[0x30].Поле BeingDebugged
Самая простая проверка. Когда к процессу подключён отладчик пользовательского режима, ядро устанавливает
PEB->BeingDebugged в 1. API-функция IsDebuggerPresent() — обёртка над чтением этого поля.
C:
if (IsDebuggerPresent()) {
// Обнаружен отладчик — запустить безопасный код или завершиться
}
Ручная реализация без вызова API:
C:
BOOL IsDebuggerPresent2() {
#ifdef _WIN64
PPEB pPeb = (PEB*)(__readgsqword(0x60));
#elif _WIN32
PPEB pPeb = (PEB*)(__readfsdword(0x30));
#endif
if (pPeb->BeingDebugged == 1)
return TRUE;
return FALSE;
}
Для исследователя: если в дизассемблере виден доступ к
gs:[0x60] с последующим сравнением байта по смещению 2 (x64) — это проверка BeingDebugged.Поле NtGlobalFlag
При запуске процесса под отладчиком ядро выставляет в
PEB->NtGlobalFlag комбинацию флагов, включающих дополнительные проверки кучи:| Флаг | Значение | Назначение |
|---|---|---|
| FLG_HEAP_ENABLE_TAIL_CHECK | 0x10 | Контроль целостности хвостов блоков кучи |
| FLG_HEAP_ENABLE_FREE_CHECK | 0x20 | Проверка освобождённых блоков |
| FLG_HEAP_VALIDATE_PARAMETERS | 0x40 | Валидация параметров при операциях с кучей |
C:
#define FLG_HEAP_ENABLE_TAIL_CHECK 0x10
#define FLG_HEAP_ENABLE_FREE_CHECK 0x20
#define FLG_HEAP_VALIDATE_PARAMETERS 0x40
BOOL IsDebuggerPresent3() {
#ifdef _WIN64
PPEB pPeb = (PEB*)(__readgsqword(0x60));
#elif _WIN32
PPEB pPeb = (PEB*)(__readfsdword(0x30));
#endif
if (pPeb->NtGlobalFlag == (FLG_HEAP_ENABLE_TAIL_CHECK |
FLG_HEAP_ENABLE_FREE_CHECK |
FLG_HEAP_VALIDATE_PARAMETERS))
return TRUE;
return FALSE;
}
Смещение
NtGlobalFlag в PEB: 0xBC для x86, 0x68 для x64. В статическом анализе паттерн mov eax, gs:[60h] / cmp dword ptr [eax+68h], 70h однозначно указывает на эту проверку.NtQueryInformationProcess
Системный вызов
NtQueryInformationProcess позволяет запросить информацию о процессе. Два класса информации релевантны для антиотладки:- ProcessDebugPort (класс 7) — возвращает ненулевое значение, если к процессу подключён отладчик.
- ProcessDebugObjectHandle (класс 30) — возвращает дескриптор объекта отладки. Если объект не создан, вызов завершается со статусом
STATUS_PORT_NOT_SET(0xC0000353).
C:
BOOL NtQIPDebuggerCheck() {
NTSTATUS STATUS = NULL;
fnNtQueryInformationProcess pNtQueryInformationProcess = NULL;
DWORD64 dwIsDebuggerPresent = NULL;
DWORD64 hProcessDebugObject = NULL;
pNtQueryInformationProcess = (fnNtQueryInformationProcess)
GetProcAddress(GetModuleHandle(TEXT("NTDLL.DLL")),
"NtQueryInformationProcess");
if (pNtQueryInformationProcess == NULL)
return FALSE;
// Проверка ProcessDebugPort
STATUS = pNtQueryInformationProcess(
GetCurrentProcess(), ProcessDebugPort,
&dwIsDebuggerPresent, sizeof(DWORD64), NULL);
if (STATUS == 0x0 && dwIsDebuggerPresent != NULL)
return TRUE; // отладчик обнаружен
// Проверка ProcessDebugObjectHandle
STATUS = pNtQueryInformationProcess(
GetCurrentProcess(), ProcessDebugObjectHandle,
&hProcessDebugObject, sizeof(DWORD64), NULL);
if (STATUS != 0x0 && STATUS != 0xC0000353)
return FALSE;
if (hProcessDebugObject != NULL)
return TRUE; // отладчик обнаружен
return FALSE;
}
Для исследователя: вызов
NtQueryInformationProcess с классом 7 или 30 — маркер антиотладки. В x64dbg и IDA такие вызовы видны в логе системных вызовов. Связь с техниками прямого вызова syscall описана в Hell's Gate и Windows syscalls: идея техники и точки наблюдения для защитного анализа.Обнаружение аппаратных точек останова
Отладчики могут устанавливать breakpoints через регистры DR0–DR3 процессора. Программа проверяет их содержимое через
GetThreadContext:
C:
BOOL HardwareBpCheck() {
CONTEXT Ctx = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
if (!GetThreadContext(GetCurrentThread(), &Ctx))
return FALSE;
if (Ctx.Dr0 != NULL || Ctx.Dr1 != NULL ||
Ctx.Dr2 != NULL || Ctx.Dr3 != NULL)
return TRUE; // обнаружена аппаратная точка останова
return FALSE;
}
Если хотя бы один из четырёх debug-регистров содержит ненулевой адрес, программа считает, что к ней подключён отладчик с hardware breakpoint. Это особенно актуально при анализе через x64dbg, где HWBP — стандартный инструмент.
Перечисление процессов отладчиков
Простейший, но часто встречающийся подход — снапшот всех процессов и сравнение имён с чёрным списком:
C:
WCHAR* g_BlackListedDebuggers[5] = {
L"x64dbg.exe",
L"ida.exe",
L"ida64.exe",
L"VsDebugConsole.exe",
L"msvsmon.exe"
};
BOOL BlackListedProcessesCheck() {
HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (hSnapShot == INVALID_HANDLE_VALUE)
return FALSE;
PROCESSENTRY32W ProcEntry = { .dwSize = sizeof(PROCESSENTRY32W) };
if (!Process32FirstW(hSnapShot, &ProcEntry)) {
CloseHandle(hSnapShot);
return FALSE;
}
BOOL bSTATE = FALSE;
do {
for (int i = 0; i < 5; i++) {
if (wcscmp(ProcEntry.szExeFile, g_BlackListedDebuggers[i]) == 0) {
bSTATE = TRUE;
break;
}
}
} while (Process32NextW(hSnapShot, &ProcEntry));
CloseHandle(hSnapShot);
return bSTATE;
}
Ограничение техники: переименование исполняемого файла отладчика обходит её. Тем не менее, в статическом анализе наличие строк
x64dbg.exe, ida64.exe и вызова CreateToolhelp32Snapshot — надёжный индикатор антиотладки.Тайминговые проверки
Отладчик приостанавливает выполнение процесса на точках останова. Если измерить время между двумя точками кода и оно превышает порог — вероятно, процесс был остановлен.
GetTickCount64
C:
DWORD64 TimeTickCheck() {
DWORD64 T0 = GetTickCount64();
// ... код, который может быть остановлен breakpoint'ом ...
DWORD64 T1 = GetTickCount64();
return T1 - T0;
}
QueryPerformanceCounter
Более точный вариант с высоким разрешением таймера:
C:
BOOL TimeTickCheck2() {
LARGE_INTEGER Time1 = { 0 }, Time2 = { 0 };
if (!QueryPerformanceCounter(&Time1))
return FALSE;
/* Код, время выполнения которого контролируется */
if (!QueryPerformanceCounter(&Time2))
return FALSE;
if ((Time2.QuadPart - Time1.QuadPart) > 100000)
return TRUE; // подозрительно долго — вероятно, отладка
return FALSE;
}
Порог 100000 тиков зависит от частоты таймера (обычно ~10 МГц на современных системах), что соответствует примерно 10 мс. Для исследователя: если между двумя вызовами
QueryPerformanceCounter находится короткий участок кода, а результат сравнивается с константой — это тайминговая антиотладка.Исключения как индикатор
DebugBreak и SEH
Функция
DebugBreak() генерирует исключение EXCEPTION_BREAKPOINT. Если отладчик подключён, он перехватывает исключение первым. Если нет — управление передаётся в SEH-обработчик процесса.
C:
BOOL DebugBreakCheck() {
__try {
DebugBreak();
}
__except (GetExceptionCode() == EXCEPTION_BREAKPOINT
? EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_SEARCH) {
// Исключение обработано нашим SEH — отладчика нет
return FALSE;
}
// Исключение не дошло до SEH — его перехватил отладчик
return TRUE;
}
Логика: если
GetExceptionCode() возвращает EXCEPTION_BREAKPOINT, выполняется EXCEPTION_EXECUTE_HANDLER и функция возвращает FALSE (отладчика нет). Если отладчик перехватил исключение и не вернул его в SEH, код после __except не выполняется, и функция возвращает TRUE.Исключения от некорректных операций
Некоторые сэмплы намеренно вызывают исключения, которые отладчик обрабатывает иначе, чем обычная программа:
- Деление на ноль
- Разыменование нулевого указателя
- Нарушение доступа к памяти (access violation)
- Недопустимая инструкция (invalid opcode)
Если отладчик настроен на перехват всех исключений первого шанса (first-chance), он остановится на этих инструкциях, что выдаёт его присутствие.
OutputDebugString
Неочевидная проверка, основанная на поведении API при отсутствии отладчика:
C:
BOOL OutputDebugStringCheck() {
SetLastError(1);
OutputDebugStringW(L"test");
if (GetLastError() == 0)
return TRUE; // отладчик подключён
return FALSE;
}
Механика:
OutputDebugString при отсутствии отладчика не изменяет код последней ошибки. Если отладчик подключён, функция успешно передаёт строку и сбрасывает GetLastError() в 0. Предварительная установка ошибки в 1 позволяет зафиксировать это изменение.Как исследователь учитывает антиотладку
Статический анализ
При реверс-инжиниринге бинарного файла антиотладочные проверки распознаются по паттернам:
| Паттерн | Техника |
|---|---|
gs:[60h] + сравнение байта по смещению 2 | PEB.BeingDebugged |
gs:[60h] + cmp [reg+68h], 70h | PEB.NtGlobalFlag |
Вызов NtQueryInformationProcess с классом 7 или 30 | Debug port / debug object |
GetThreadContext + проверка DR0–DR3 | Hardware breakpoints |
CreateToolhelp32Snapshot + сравнение строк | Чёрный список процессов |
QueryPerformanceCounter / GetTickCount64 + порог | Тайминговая проверка |
DebugBreak внутри __try/__except | SEH-based detection |
OutputDebugString + GetLastError | Проверка через last error |
Динамический анализ
При запуске под отладчиком исследователь может:
- Патчить проверки на лету — заменить условный переход после проверки на безусловный (или NOP).
- Использовать плагины — ScyllaHide для x64dbg автоматизирует обход большинства перечисленных техник.
- Контролировать исключения — настроить отладчик на пропуск first-chance исключений, которые использует сэмпл.
- Мониторить системные вызовы — логирование
NtQueryInformationProcessиNtQuerySystemInformationпоказывает, какие именно проверки выполняет процесс.
Типичные ошибки исследователя
- Игнорирование тайминговых проверок. Если сэмпл использует
QueryPerformanceCounter, пошаговая отладка (F8) гарантированно превысит порог. Нужно либо патчить проверку, либо использовать run-to-cursor.
- Перехват всех исключений. Настройка отладчика на остановку при любом first-chance exception ломает логику SEH-based проверок и может привести к ложному срабатыванию антиотладки.
- Запуск без переименования. Если сэмпл проверяет список процессов, запущенный
x64dbg.exeбудет обнаружен. Переименование исполняемого файла — минимальная мера.
Связь с другими техниками защиты
Anti-debug редко используется изолированно. В реальных сэмплах он комбинируется с:
- Anti-VM — проверки виртуального окружения (CPUID, MAC-адреса, реестр).
- Anti-disassembly — junk bytes, opaque predicates, нелинейный поток управления.
- Self-deletion — удаление собственного файла после запуска, чтобы затруднить повторный анализ.
Контекст разработки и анализа вредоносного ПО как предмета защитного исследования описан в Разработка вредоносного ПО в 2026 году как предмет защитного исследования: техники, детект и безопасная лаборатория.
Чек-лист при встрече с антиотладкой
- Определить тип проверки по паттерну в дизассемблере.
- Оценить, влияет ли проверка на поток выполнения (условный переход, вызов
ExitProcess, изменение данных).
- Выбрать метод нейтрализации: патч, плагин, конфигурация отладчика.
- Проверить, нет ли нескольких уровней антиотладки (часто проверки дублируются в разных функциях или потоках).
- Зафиксировать обнаруженные техники в отчёте — они являются индикатором намеренного противодействия анализу и повышают оценку угрозы.
