Хакеры больше не пишут код — они просто указывают, где он уже есть, и Windows делает всё сама.
В новых исследованиях были продемонстрированы способы внедрения вредоносного кода в процессы Windows без использования традиционных этапов выделения и записи памяти — методов, на которых основываются почти все системы обнаружения атак. В ходе экспериментов специалисты сосредоточились исключительно на этапе исполнения и доказали, что его одного может быть достаточно для успешной инъекции.
Классическая схема атак с внедрением кода в чужой процесс предполагает последовательность шагов: сначала злоумышленник выделяет новую память в целевом процессе (например, через VirtualAllocEx), затем записывает туда полезную нагрузку (обычно с помощью WriteProcessMemory), а после этого инициирует её выполнение, например, через CreateRemoteThread или APC. Именно такую цепочку — выделение, запись, запуск — отслеживают и блокируют большинство современных EDR -решений. Однако новое исследование показало, что вся эта цепочка не обязательна.
Специалисты начали с идеи: что будет, если исключить как выделение, так и запись памяти? То есть перейти к техникам, использующим только механизмы запуска. Одним из первых методов стал «указательный» (pointer-only) вариант DLL-инъекции. В нём LoadLibraryA вызывается с указателем на уже существующую строку в памяти целевого процесса, например строку «0», которая всегда есть в ntdll.dll. Затем в файловую систему внедряется файл с именем «0.dll», и система загружает его без каких-либо операций записи в память процесса. Эта атака прошла незаметно для двух лидирующих EDR-решений.
Другой подход — использование CreateRemoteThread в связке с SetThreadContext. Новый поток создаётся в приостановленном состоянии, после чего его контекст настраивается вручную, включая регистры RCX, RDX, R8 и R9, что соответствует соглашению о вызовах функций в Windows x64. Это позволяет вызвать любую API-функцию — например, VirtualAlloc или RtlFillMemory — с любыми аргументами. Запись и выполнение происходят уже изнутри процесса-жертвы, поэтому внешние средства мониторинга не фиксируют ничего подозрительного.
Третий метод задействует NtCreateThread — малодокументированный системный вызов, позволяющий сразу передать в ядро структуру CONTEXT с нужными регистрами и указателем на стек. В отличие от CreateRemoteThread, где контекст меняется постфактум, здесь он задаётся заранее, что упрощает контроль над поведением потока и снижает заметность атаки. В одном из вариантов реализации поток запускается с ROP-гаджетом, который цепочкой инструкций вызывает сначала VirtualAlloc, затем RtlFillMemory, и в конце — shellcode.
Исследование также затронуло уязвимость архитектуры обнаружения атак. Многие решения EDR работают по принципу корреляции двух или трёх подозрительных действий: удалённого выделения памяти, изменения этой памяти и запуска кода. Если используются только механизмы запуска, а все действия происходят «локально» внутри целевого процесса, логика отслеживания нарушается. Особенно сложно отследить подмену контекста потока, если она не сопровождается прямой записью или вызовами популярных API.
Также отмечено, что создание потоков между процессами само по себе не является подозрительным поведением — этим занимаются отладчики, профилировщики, инструменты совместимости, агенты наблюдения и даже легитимные компоненты Windows. Поэтому сигналы, возникающие на этапе CreateRemoteThread, часто теряются в фоне.
Специалисты реализовали описанные техники. Они смогли выполнять DLL-инъекции или внедрять shellcode без операций записи, используя различные методы запуска: от классического CreateRemoteThread до NtQueueApcThreadEx2. Также предусмотрена возможность подмены контекста с использованием ROP-гаджетов или двухступенчатых техник.
Авторы отметили, что хотя в процессе работы они столкнулись с ограничениями — например, трудностью управления стеком или ограничением на количество аргументов — все они были успешно обойдены. Некоторые методы требуют поиска ROP-гаджетов в памяти, однако это можно заменить статическим анализом PE-файлов.
Ключевым выводом стало то, что современные средства защиты чрезмерно полагаются на шаблонную модель атак, упускающую сложные случаи, где злоумышленник умеет заставить сам процесс-жертву выполнять нужные действия. В таком сценарии фактическое управление переходит к атакующему, но телеметрия не фиксирует привычных признаков вторжения. Именно эта асимметрия и делает техники исполнения без записи особенно опасными.
Кратко говоря, EDR решает уравнение из трёх переменных: кто выделил память, кто её изменил и кто выполнил код. Но если злоумышленник заранее знает ответ — например, что достаточно лишь вызвать LoadLibraryA на заранее существующую строку — он может сыграть на опережение и остаться невидимым.