Суть техники в одном абзаце
Module Stomping — это метод инъекции кода, при котором атакующий не создаёт новый регион памяти и не выделяет её отдельно, а перезаписывает тело уже загруженного в процесс легитимного DLL-модуля. Внешне процесс выглядит нормально: модуль присутствует в списке загруженных, его имя и путь соответствуют системному файлу. Но содержимое кодовой секции уже заменено на произвольный payload. Для защитника это создаёт специфическую проблему: стандартные проверки «есть ли подозрительный модуль» не срабатывают, потому что модуль легитимный по метаданным.
Почему атакующие выбирают именно этот путь
Классические техники инъекции —
VirtualAllocEx + WriteProcessMemory + CreateRemoteThread — оставляют характерный артефакт: регион памяти с правами PAGE_EXECUTE_READWRITE, не привязанный ни к какому модулю. Многие EDR и memory scanners ищут именно такие регионы.Module Stomping обходит эту эвристику: код выполняется внутри региона, который принадлежит загруженному образу. Права на память могут оставаться
PAGE_EXECUTE_READ после записи, а сам регион числится как часть валидного модуля. Это снижает вероятность детекта по памяти, но не устраняет артефакты полностью.Механика атаки шаг за шагом
Типичная последовательность действий при Module Stomping:
- Выбор жертвенного модуля. Атакующий подбирает DLL, который с высокой вероятностью уже загружен в целевой процесс или может быть загружен без побочных эффектов. Часто используются
setupapi.dll,version.dll,dbghelp.dllи другие модули, которые редко вызываются напрямую.
- Загрузка модуля. Если модуль ещё не загружен, атакующий вызывает
LoadLibraryAилиLoadLibraryExAв целевом процессе (напрямую или черезCreateRemoteThread). Если модуль уже загружен — используется его текущий базовый адрес.
- Определение целевой функции. Через
GetProcAddressнаходится адрес экспортируемой функции внутри модуля. Этот адрес будет точкой записи.
- Изменение прав и запись. Атакующий снимает защиту с региона (
VirtualProtect/VirtualProtectExсPAGE_READWRITE), записывает payload черезmemcpyилиWriteProcessMemory, затем восстанавливает права на исполнение (PAGE_EXECUTE_READилиPAGE_EXECUTE_READWRITE).
- Запуск. Создаётся поток (
CreateThreadилиCreateRemoteThread), стартовый адрес которого указывает на перезаписанную функцию.
Ниже — упрощённая схема записи в локальный процесс, основанная на типичной реализации:
C:
// Концептуальная схема: перезапись экспортируемой функции внутри загруженного модуля
HMODULE hModule = LoadLibraryA("setupapi.dll");
PVOID pAddress = GetProcAddress(hModule, "SetupScanFileQueueA");
DWORD dwOldProtection;
VirtualProtect(pAddress, payloadSize, PAGE_READWRITE, &dwOldProtection);
memcpy(pAddress, payload, payloadSize);
VirtualProtect(pAddress, payloadSize, PAGE_EXECUTE_READ, &dwOldProtection);
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pAddress, NULL, 0, NULL);
Для удалённого процесса используются
VirtualProtectEx и WriteProcessMemory вместо VirtualProtect и memcpy.Что именно меняется в модуле
После stomping модуль продолжает числиться в списке загруженных с корректным именем и путём. Но его внутреннее состояние отличается от оригинала:
| Элемент | До stomping | После stomping |
|---|---|---|
| Кодовая секция (.text) | Соответствует файлу на диске | Содержит произвольный payload |
| Права на память региона | PAGE_EXECUTE_READ | Может быть PAGE_EXECUTE_READWRITE или восстановлена в PAGE_EXECUTE_READ |
| Точка входа функции | Валидный пролог (mov edi, edi / push rbp и т. п.) | Может начинаться с нестандартных байтов |
| Размер перезаписанного участка | — | Обычно равен размеру shellcode, может быть меньше или больше оригинальной функции |
Артефакты для детектирования
Сравнение памяти с файлом на диске (memory-disk diff)
Наиболее прямой метод. Защитник читает содержимое кодовой секции модуля из памяти процесса и сравнивает с соответствующим участком файла на диске. Если байты не совпадают — модуль был модифицирован.
Ограничения метода:
- Некоторые модули легитимно патчатся в памяти (relocations, ASLR не влияет на сравнение, если учитывать базу, но hotpatching может).
- Атакующий может перезаписать только небольшую часть функции, что усложняет обнаружение при поверхностном сканировании.
- Для сравнения нужен доступ к процессу с правами
PROCESS_VM_READ.
Проверка прав на память регионов модуля
Нормальный загруженный модуль имеет кодовые секции с правами
PAGE_EXECUTE_READ. Если регион внутри модуля имеет PAGE_EXECUTE_READWRITE — это аномалия. Однако атакующий может восстановить права после записи, поэтому этот признак не является надёжным сам по себе.Анализ пролога функции
Стандартный пролог x86-64 функций Windows начинается с
mov edi, edi (hotpatch slot) или push rbp; mov rbp, rsp. Если по адресу экспортируемой функции находится нестандартная последовательность (например, сразу jmp, call, или байты, не соответствующие ни одному известному паттерну пролога), это повод для более глубокой проверки.Поведенческие признаки
CreateRemoteThreadс адресом, указывающим внутрь модуля, который обычно не используется как точка входа для потоков.
- Вызов
VirtualProtectExс переходом вPAGE_READWRITEна регион, принадлежащий модулю, с последующимCreateRemoteThread.
- Последовательность
LoadLibraryA→GetProcAddress→VirtualProtectEx→WriteProcessMemory→CreateRemoteThreadв одном процессе за короткий промежуток времени.
ETW-провайдеры и callback-механизмы
Ядро Windows предоставляет несколько источников информации:
PsSetCreateThreadNotifyRoutine— позволяет видеть создание потоков и их стартовые адреса.
ObRegisterCallbacks— позволяет отслеживать открытие хендлов к процессам с определёнными правами.
- ETW-провайдер
Microsoft-Windows-Kernel-Processфиксирует события загрузки модулей и создания потоков.
Если стартовый адрес нового потока попадает внутрь модуля, но при этом модуль не является известной точкой входа для потоков (например, не
DllMain, не callback), это подозрительно.Практический подход к детекту: что проверять в первую очередь
Для защитника, который строит или настраивает детект, приоритетный набор проверок:
- Сканирование модулей по memory-disk diff. Периодическое или по событию (загрузка модуля, создание потока). Сравнивать
.textсекцию с файлом на диске с учётом релокаций.
- Мониторинг прав на память. Отслеживать переходы регионов модулей в
PAGE_READWRITEилиPAGE_EXECUTE_READWRITE. В нормальном процессе такие переходы для кодовых секций модулей крайне редки.
- Корреляция событий. Сочетание
LoadLibrary+VirtualProtect+WriteProcessMemory+CreateRemoteThreadв одном процессе — сильный индикатор. По отдельности каждый из этих вызовов легитимен, но их последовательность с определёнными параметрами — нет.
- Проверка стартовых адресов потоков. Если поток стартует с адреса внутри модуля, но этот адрес не совпадает с
AddressOfEntryPointиз PE-заголовка и не является известным callback-адресом, требуется дополнительная проверка.
Ограничения техники со стороны атакующего
Module Stomping не является неуязвимой техникой:
- Размер payload ограничен. Перезаписать можно только участок, достаточный для shellcode. Если payload большой, атакующему приходится перезаписывать несколько функций или использовать всю секцию, что увеличивает заметность.
- Функциональность модуля ломается. После перезаписи экспортируемая функция перестаёт работать. Если целевой процесс вызывает эту функцию, произойдёт краш или непредсказуемое поведение. Поэтому атакующие выбирают модули, функции которых вряд ли будут вызваны.
- Memory-disk diff детектирует перезапись. Это основной и наиболее надёжный метод обнаружения.
- Права на память могут выдать. Если атакующий не восстанавливает права после записи, регион с
PAGE_EXECUTE_READWRITEвнутри модуля — прямой индикатор.
Отличие от смежных техник
| Техника | Где выполняется код | Основной артефакт |
|---|---|---|
| Classic injection (VirtualAllocEx) | В выделенном регионе без привязки к модулю | RWX-регион вне модулей |
| Module Stomping | Внутри кодовой секции загруженного модуля | Несоответствие памяти и файла на диске |
| Process Hollowing | В адресном пространстве процесса-обёртки | Несоответствие образа процесса и файла на диске |
| APC Injection | Через очередь APC целевого потока | Вызов NtQueueApcThread с подозрительным адресом |
Module Stomping занимает промежуточное положение: код выполняется в контексте валидного модуля, но содержимое этого модуля не соответствует оригиналу. Это делает детект сложнее, чем для классической инъекции, но проще, чем для техник, которые полностью подменяют образ процесса.
Что делать при обнаружении
Если memory-disk diff или поведенческий анализ выявили модифицированный модуль:
- Зафиксировать PID процесса, имя и базовый адрес модуля, размер и смещение перезаписанного участка.
- Сохранить дамп региона для последующего анализа (извлечь payload для реверса).
- Проверить, какие потоки выполнялись из этого региона и что они делали (сетевые соединения, доступ к файлам, создание процессов).
- Определить вектор загрузки: кто вызвал
LoadLibraryиCreateRemoteThread— это укажет на родительский процесс атакующего.
- Изолировать хост и запустить процедуру инцидент-реагирования.
Связь с другими материалами
Для более широкого контекста по инъекциям и детекту в памяти:
- Process Injection в Windows: механизм на уровне концепции и артефакты для детектирования
- Детект в памяти Windows: почему in-memory активность оставляет признаки и как их анализируют защитники
