Инъекции через syscalls: какие слои телеметрии остаются доступными защитнику

Почему direct syscalls усложняют детектирование​


Классическая инъекция кода в Windows строится на цепочке Win32 API → Native API → системный вызов. Когда вредонос вызывает VirtualAllocEx, WriteProcessMemory и CreateRemoteThread, EDR-агент, перехватывающий userland-функции в ntdll.dll или kernel32.dll, видит каждый шаг. Техника direct syscalls обходит этот слой: вместо вызова экспортируемой функции из ntdll.dll код напрямую выполняет инструкцию syscall, подставляя номер системного вызова в регистр eax/rax и передавая аргументы через стек или регистры по конвенции x64.

Результат: userland-хуки EDR, установленные на NtAllocateVirtualMemory, NtWriteVirtualMemory, NtProtectVirtualMemory и NtCreateThreadEx, не срабатывают, потому что исполнение не проходит через тело функции в ntdll.dll. Для защитника это означает потерю одного из самых удобных слоёв телеметрии — но не всех.

Типичная последовательность системных вызовов при инъекции​


Большинство реализаций классической инъекции через syscalls используют одну и ту же четвёрку вызовов:

ШагСистемный вызовНазначение
1NtAllocateVirtualMemoryВыделение региона памяти в целевом процессе с правами PAGE_READWRITE
2NtWriteVirtualMemoryЗапись полезной нагрузки в выделенный регион
3NtProtectVirtualMemoryСмена защиты региона на PAGE_EXECUTE_READWRITE
4NtCreateThreadExСоздание потока с точкой входа в записанный код

Каждый из этих вызовов принимает ProcessHandle — дескриптор целевого процесса. Если дескриптор не является псевдодескриптором текущего процесса ((HANDLE)-1), значит операция направлена на чужой процесс. Это ключевой артефакт, который сохраняется вне зависимости от способа вызова.

Что видит ядро: объектный менеджер и аудит​


Системный вызов обрабатывается ядром независимо от того, как он был инициирован — через ntdll.dll или напрямую инструкцией syscall. Это означает, что следующие механизмы продолжают работать:

Object Manager callbacks​


Ядро Windows предоставляет механизм ObRegisterCallbacks, позволяющий драйверам фильтровать операции с объектами (процессами, потоками, дескрипторами). Когда любой код запрашивает дескриптор процесса с правами PROCESS_VM_WRITE | PROCESS_VM_OPERATION, callback может:

  • Зафиксировать факт запроса и идентификаторы источника и цели.
  • Понизить права дескриптора (strip rights), сделав инъекцию невозможной.
  • Заблокировать операцию полностью.

Этот механизм не зависит от того, какой API использовался в usermode. Он срабатывает на уровне ядра при любой попытке открыть или использовать дескриптор.

Kernel-mode ETW​


Провайдеры ядра генерируют события на уровне системных вызовов. Например:

  • Событие создания потока (NtCreateThreadEx) фиксируется с указанием целевого процесса, адреса точки входа и флагов создания.
  • Операции с виртуальной памятью (NtAllocateVirtualMemory, NtProtectVirtualMemory) могут логироваться через соответствующие kernel ETW providers.

ETW-события ядра не зависят от userland-хуков. Даже если вызов пришёл напрямую через syscall, ядро генерирует событие до возврата в usermode.

Sysmon и драйверные источники​


Sysmon (Event ID 8 — CreateRemoteThread, Event ID 10 — ProcessAccess) работает через драйвер ядра и фиксирует:

  • Создание потока в чужом процессе с указанием адреса StartAddress.
  • Открытие процесса с подозрительными масками доступа.

Эти события не обходятся direct syscalls, потому что драйвер подписан на объектные операции и создание потоков на уровне ядра.

Что видит EDR: поведенческий анализ и память​


Поведенческие цепочки​


Современные EDR-решения не полагаются исключительно на хуки конкретных функций. Они строят поведенческие цепочки (behavioral chains):

  1. Процесс A открывает дескриптор процесса B с правами записи.
  2. В процессе B появляется новый регион памяти с правами RWX.
  3. В процессе B создаётся поток, указывающий на этот регион.

Такая последовательность подозрительна независимо от того, какие API использовались на каждом шаге. EDR может обнаружить её через:

  • Периодическое сканирование памяти процессов на наличие RWX-регионов.
  • Отслеживание изменений в VAD-дереве (Virtual Address Descriptor) через kernel callbacks.
  • Анализ стека вызовов потока при создании: если StartAddress указывает на регион, не принадлежащий ни одному загруженному модулю, это аномалия.

Сканирование памяти​


Даже если инъекция прошла успешно, записанный shellcode остаётся в памяти целевого процесса. EDR-агент с правами ядра может:

  • Перечислить регионы памяти всех процессов и найти RWX-страницы.
  • Проверить, соответствует ли содержимое региона известным паттернам (например, заголовок MZ или характерные последовательности).
  • Обнаружить, что поток выполняется из региона, не привязанного к файлу на диске (unbacked memory).

Thread Start Address​


При создании потока через NtCreateThreadEx ядро сохраняет StartRoutine в структуре ETHREAD. EDR или Sysmon может прочитать это значение и проверить:

  • Указывает ли адрес на модуль из списка загруженных (легитимно).
  • Указывает ли адрес на выделенную память без привязки к модулю (подозрительно).

Артефакты, которые остаются при любых обстоятельствах​


АртефактИсточникПочему не обходится direct syscalls
Дескриптор процесса с правами записиObject ManagerЯдро фиксирует при любом способе получения
RWX-регион в целевом процессеVAD-дерево, сканирование памятиФизическое состояние памяти не зависит от способа вызова
Поток с StartAddress вне модулейETHREAD, Sysmon Event ID 8Ядро создаёт поток и сохраняет метаданные
Изменение защиты памятиNtProtectVirtualMemory на уровне ядраСобытие генерируется ядром
Аномальный стек вызовов при создании потокаKernel stack walkПрямой syscall даёт короткий стек без фреймов ntdll

Ограничения защитника​


Несмотря на сохраняющуюся телеметрию, существуют практические сложности:

  • Объём событий. Ядро генерирует огромное количество событий. Фильтрация «шума» (легитимные аллокации, JIT-компиляция, загрузчики) требует точных правил и может давать ложные срабатывания.
  • Unbacked memory не всегда злокачественна. Легитимные приложения (браузеры, .NET CLR, Java VM) создают исполняемую память без привязки к файлу. Правило «RWX без модуля = инъекция» даёт ложные срабатывания.
  • Call stack analysis ограничен. Если атакующий использует технику «stack spoofing» (подделывает стек перед syscall), анализ стека теряет надёжность.
  • Производительность. Постоянное сканирование памяти всех процессов дорого. На практике EDR сканирует выборочно или по триггеру.

Практические правила детектирования для защитника​


На основе сохраняющихся артефактов можно строить правила:

Правило 1: подозрительный StartAddress потока​


Если NtCreateThreadEx создаёт поток в процессе, отличном от текущего, и StartAddress не попадает в диапазон адресов ни одного загруженного модуля целевого процесса — это высокорелевантный индикатор.

Правило 2: последовательность аллокация → запись → смена защиты → поток​


Корреляция четырёх событий в коротком временном окне (менее нескольких секунд) для одного и того же целевого процесса и одного региона памяти.

Правило 3: NtProtectVirtualMemory с переходом в PAGE_EXECUTE_READWRITE​


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

Правило 4: дескриптор процесса с PROCESS_VM_WRITE из неожиданного источника​


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

Что не поможет: распространённые заблуждения​


  • Хуки на ntdll.dll в usermode. Direct syscalls полностью их обходят. Полагаться на них как единственный слой — ошибка.
  • Сигнатуры на shellcode в памяти. Атакующий может шифровать payload и расшифровывать его непосредственно перед выполнением, что делает статические сигнатуры бесполезными.
  • Мониторинг только CreateRemoteThread. NtCreateThreadEx — не единственный способ запуска кода. APC-инъекция (NtQueueApcThread), hijacking существующего потока через NtSetContextThread и другие техники не создают новый поток.

Связь с MITRE ATT&CK​


Техника отображается как T1055 — Process Injection с подтехниками:

  • T1055.003 — Thread Execution Hijacking
  • T1055.012 — Process Hollowing
  • T1055.001 — Dynamic-link Library Injection

Использование direct syscalls не меняет классификацию техники, но влияет на слой детектирования: защитник должен опираться на kernel-level телеметрию, а не на userland-хуки.

Итоговая карта слоёв телеметрии​


СлойВидимость при direct syscallsНадёжность
Userland-хуки (ntdll, kernel32)Не видятНизкая
ETW kernel providersВидятСредняя–Высокая
Object Manager callbacksВидятВысокая
Sysmon (драйвер)ВидитВысокая
Сканирование памяти (RWX, unbacked)ВидитСредняя (ложные срабатывания)
Call stack analysisЧастичноСредняя (обходится stack spoofing)
Поведенческая корреляцияВидитВысокая при правильной настройке

Защитник, который строит детектирование только на userland-хуках, теряет видимость при использовании direct syscalls. Перенос фокуса на ядерные источники телеметрии — Object Manager callbacks, kernel ETW, анализ VAD-дерева и поведенческую корреляцию — позволяет сохранять обнаружение даже при обходе usermode-слоя.

Источники​


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