Module Stomping: что меняется в загруженном модуле и как это распознавать защитнику

Суть техники в одном абзаце​


Module Stomping — это метод инъекции кода, при котором атакующий не создаёт новый регион памяти и не выделяет её отдельно, а перезаписывает тело уже загруженного в процесс легитимного DLL-модуля. Внешне процесс выглядит нормально: модуль присутствует в списке загруженных, его имя и путь соответствуют системному файлу. Но содержимое кодовой секции уже заменено на произвольный payload. Для защитника это создаёт специфическую проблему: стандартные проверки «есть ли подозрительный модуль» не срабатывают, потому что модуль легитимный по метаданным.

Почему атакующие выбирают именно этот путь​


Классические техники инъекции — VirtualAllocEx + WriteProcessMemory + CreateRemoteThread — оставляют характерный артефакт: регион памяти с правами PAGE_EXECUTE_READWRITE, не привязанный ни к какому модулю. Многие EDR и memory scanners ищут именно такие регионы.

Module Stomping обходит эту эвристику: код выполняется внутри региона, который принадлежит загруженному образу. Права на память могут оставаться PAGE_EXECUTE_READ после записи, а сам регион числится как часть валидного модуля. Это снижает вероятность детекта по памяти, но не устраняет артефакты полностью.

Механика атаки шаг за шагом​


Типичная последовательность действий при Module Stomping:

  1. Выбор жертвенного модуля. Атакующий подбирает DLL, который с высокой вероятностью уже загружен в целевой процесс или может быть загружен без побочных эффектов. Часто используются setupapi.dll, version.dll, dbghelp.dll и другие модули, которые редко вызываются напрямую.
  2. Загрузка модуля. Если модуль ещё не загружен, атакующий вызывает LoadLibraryA или LoadLibraryExA в целевом процессе (напрямую или через CreateRemoteThread). Если модуль уже загружен — используется его текущий базовый адрес.
  3. Определение целевой функции. Через GetProcAddress находится адрес экспортируемой функции внутри модуля. Этот адрес будет точкой записи.
  4. Изменение прав и запись. Атакующий снимает защиту с региона (VirtualProtect / VirtualProtectEx с PAGE_READWRITE), записывает payload через memcpy или WriteProcessMemory, затем восстанавливает права на исполнение (PAGE_EXECUTE_READ или PAGE_EXECUTE_READWRITE).
  5. Запуск. Создаётся поток (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.
  • Последовательность LoadLibraryAGetProcAddressVirtualProtectExWriteProcessMemoryCreateRemoteThread в одном процессе за короткий промежуток времени.

ETW-провайдеры и callback-механизмы​


Ядро Windows предоставляет несколько источников информации:

  • PsSetCreateThreadNotifyRoutine — позволяет видеть создание потоков и их стартовые адреса.
  • ObRegisterCallbacks — позволяет отслеживать открытие хендлов к процессам с определёнными правами.
  • ETW-провайдер Microsoft-Windows-Kernel-Process фиксирует события загрузки модулей и создания потоков.

Если стартовый адрес нового потока попадает внутрь модуля, но при этом модуль не является известной точкой входа для потоков (например, не DllMain, не callback), это подозрительно.

Практический подход к детекту: что проверять в первую очередь​


Для защитника, который строит или настраивает детект, приоритетный набор проверок:

  1. Сканирование модулей по memory-disk diff. Периодическое или по событию (загрузка модуля, создание потока). Сравнивать .text секцию с файлом на диске с учётом релокаций.
  2. Мониторинг прав на память. Отслеживать переходы регионов модулей в PAGE_READWRITE или PAGE_EXECUTE_READWRITE. В нормальном процессе такие переходы для кодовых секций модулей крайне редки.
  3. Корреляция событий. Сочетание LoadLibrary + VirtualProtect + WriteProcessMemory + CreateRemoteThread в одном процессе — сильный индикатор. По отдельности каждый из этих вызовов легитимен, но их последовательность с определёнными параметрами — нет.
  4. Проверка стартовых адресов потоков. Если поток стартует с адреса внутри модуля, но этот адрес не совпадает с 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 или поведенческий анализ выявили модифицированный модуль:

  1. Зафиксировать PID процесса, имя и базовый адрес модуля, размер и смещение перезаписанного участка.
  2. Сохранить дамп региона для последующего анализа (извлечь payload для реверса).
  3. Проверить, какие потоки выполнялись из этого региона и что они делали (сетевые соединения, доступ к файлам, создание процессов).
  4. Определить вектор загрузки: кто вызвал LoadLibrary и CreateRemoteThread — это укажет на родительский процесс атакующего.
  5. Изолировать хост и запустить процедуру инцидент-реагирования.

Связь с другими материалами​


Для более широкого контекста по инъекциям и детекту в памяти:


Источники​


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