MemoryModule — техника загрузки DLL напрямую в адресное пространство процесса без вызова
При вызове
Ключевой момент: модуль привязан к файлу на диске через секцию. Это означает, что при запросе метаданных региона памяти (например, через
MemoryModule реализует загрузку PE-образа из буфера в памяти. Алгоритм:
Результат: исполняемый код DLL присутствует в памяти процесса, но модуль не зарегистрирован в
Несмотря на отсутствие записи в списке модулей, MemoryModule оставляет ряд признаков, которые защитники используют для детектирования.
При стандартной загрузке страницы модуля имеют тип
Проверка через
Если процесс выполняет код из региона, который не отображается в
MemoryModule вынужден вызывать
Современные EDR-продукты используют несколько слоёв для обнаружения MemoryModule:
Подробнее о том, как антивирусы и EDR обнаруживают вредоносный код в памяти, см. Как антивирусы и EDR обнаруживают вредоносный код: сигнатуры, поведение и анализ памяти.
MemoryModule не является универсальным решением для скрытия кода:
Ниже — упрощённый фрагмент, который демонстрирует, как защитник может найти подозрительный регион в собственном процессе (учебный сценарий):
Флаг
Если задача — обнаружить загрузку DLL из памяти в контролируемой среде:
Дополнительно о том, почему in-memory активность оставляет признаки и как их анализируют, см. Детект в памяти Windows: почему in-memory активность оставляет признаки и как их анализируют защитники.
Техника MemoryModule снижает поверхность обнаружения на уровне мониторинга загрузки модулей, но не устраняет артефакты в памяти процесса. Для защитника ключевой индикатор — исполняемые страницы без файловой привязки, а для атакующего — понимание, что полное скрытие от kernel-mode сканера этой техникой не достигается.
LoadLibrary и без создания записи в списке загруженных модулей. Библиотека существует в памяти как набор страниц, выделенных через VirtualAlloc, но не видна стандартным средствам перечисления. Это делает её привлекательной для обхода детектов, основанных на мониторинге загрузки модулей.Как работает стандартная загрузка DLL
При вызове
LoadLibrary или LoadLibraryEx загрузчик Windows выполняет последовательность действий:- Открывает файл DLL на диске и считывает PE-заголовок.
- Выделяет память через
NtMapViewOfSection— создаёт секцию, привязанную к файлу, и проецирует её в адресное пространство процесса.
- Копирует секции в адресное пространство процесса.
- Применяет релокации, если базовый адрес отличается от предпочтительного.
- Разрешает импорты: находит адреса функций в зависимых DLL.
- Вызывает
DllMainс причинойDLL_PROCESS_ATTACH.
- Добавляет модуль в список
InLoadOrderModuleListструктурыPEB_LDR_DATA.
Ключевой момент: модуль привязан к файлу на диске через секцию. Это означает, что при запросе метаданных региона памяти (например, через
NtQueryVirtualMemory с классом MemoryMappedFilenameInformation) система вернёт путь к файлу, а EnumProcessModules увидит модуль в списке.Что делает MemoryModule иначе
MemoryModule реализует загрузку PE-образа из буфера в памяти. Алгоритм:
- Парсинг PE-заголовка. Из буфера извлекаются
IMAGE_DOS_HEADER,IMAGE_NT_HEADERS, таблица секций, директории импортов и релокаций.
- Выделение памяти. Вызывается
VirtualAllocс флагомMEM_RESERVE | MEM_COMMITи защитойPAGE_EXECUTE_READWRITE(или поэтапно с изменением прав). Размер —SizeOfImageиз optional header.
- Копирование секций. Каждая секция копируется из исходного буфера в выделенную область с учётом
VirtualAddressиSizeOfRawData.
- Релокации. Если базовый адрес отличается от
ImageBase, применяются записи из директорииIMAGE_DIRECTORY_ENTRY_BASERELOC.
- Разрешение импортов. Для каждой импортируемой DLL вызывается
LoadLibrary(илиGetProcAddressдля уже загруженных), адреса функций записываются в IAT.
- Вызов точки входа.
DllMainвызывается сDLL_PROCESS_ATTACH.
Результат: исполняемый код DLL присутствует в памяти процесса, но модуль не зарегистрирован в
PEB_LDR_DATA и не привязан к файловой секции.Артефакты, которые остаются
Несмотря на отсутствие записи в списке модулей, MemoryModule оставляет ряд признаков, которые защитники используют для детектирования.
Страницы памяти без привязки к файлу
При стандартной загрузке страницы модуля имеют тип
MEM_IMAGE и связаны с файловой секцией. MemoryModule использует VirtualAlloc, поэтому страницы имеют тип MEM_PRIVATE. Защитник, сканирующий память процесса, может найти регионы с правами PAGE_EXECUTE_READWRITE или PAGE_EXECUTE_READ, которые не привязаны к какому-либо файлу.Проверка через
VirtualQueryEx:
C:
MEMORY_BASIC_INFORMATION mbi;
for (LPVOID addr = NULL; VirtualQueryEx(hProcess, addr, &mbi, sizeof(mbi)); ) {
if (mbi.State == MEM_COMMIT &&
mbi.Type == MEM_PRIVATE &&
(mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ |
PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))) {
// Подозрительный регион: исполняемая память без файловой привязки
}
addr = (LPBYTE)addr + mbi.RegionSize;
}
Отсутствие в списке модулей
Если процесс выполняет код из региона, который не отображается в
EnumProcessModules и не имеет файловой привязки при запросе метаданных памяти, это аномалия. EDR-продукты сравнивают адреса исполнения (из call stack или из перехваченных вызовов) со списком известных модулей. Адрес, попадающий в MEM_PRIVATE регион, — индикатор загруженного из памяти кода.IAT и импорты
MemoryModule вынужден вызывать
LoadLibrary и GetProcAddress для разрешения импортов. Эти вызовы видны через хуки или ETW-провайдеры. Если процесс загружает DLL, которые не соответствуют его профилю (например, ws2_32.dll в процессе, который не работает с сетью), это может быть дополнительным сигналом.Поведенческие признаки
- Вызов
VirtualAllocсPAGE_EXECUTE_READWRITEна большой регион.
- Последующий вызов
VirtualProtectдля смены прав наPAGE_EXECUTE_READ(паттерн «allocate → write → change to RX»).
- Передача управления на адрес внутри
MEM_PRIVATEрегиона (видно через call stack analysis или hardware breakpoints).
Что видит EDR и антивирус
Современные EDR-продукты используют несколько слоёв для обнаружения MemoryModule:
| Метод детектирования | Что проверяется | Ограничения |
|---|---|---|
| Сканирование памяти процесса | MEM_PRIVATE регионы с исполняемыми правами | Требует доступа к процессу; может быть обойдено через шифрование до момента исполнения |
| Call stack analysis | Адреса возврата, не принадлежащие известным модулям | Работает при перехвате API-вызовов или при срабатывании алерта |
| ETW-мониторинг | События VirtualAlloc, VirtualProtect, LoadImage | Высокий объём данных; требует корреляции |
| Поведенческий анализ | Паттерн «выделение → запись → исполнение» | Возможны ложные срабатывания на JIT-компиляторах |
Подробнее о том, как антивирусы и EDR обнаруживают вредоносный код в памяти, см. Как антивирусы и EDR обнаруживают вредоносный код: сигнатуры, поведение и анализ памяти.
Ограничения техники
MemoryModule не является универсальным решением для скрытия кода:
- Не работает с драйверами. Загрузка ядра требует подписи и проходит через
NtLoadDriver.
- Зависимость от
LoadLibraryдля импортов. Если DLL импортирует функции из других DLL, они всё равно загружаются стандартным путём и видны в списке модулей.
- Нет поддержки side-by-side сборок и manifest. Если DLL требует activation context, MemoryModule не обработает это автоматически.
- Обнаружение по паттерну памяти. Как описано выше,
MEM_PRIVATE+PAGE_EXECUTE— устойчивый индикатор.
- Не скрывает от kernel-mode детектов. Если EDR имеет драйвер, он может сканировать память любого процесса напрямую.
Практический пример: проверка региона памяти
Ниже — упрощённый фрагмент, который демонстрирует, как защитник может найти подозрительный регион в собственном процессе (учебный сценарий):
C:
#include <windows.h>
#include <stdio.h>
void scan_for_unbacked_executable_regions(void) {
MEMORY_BASIC_INFORMATION mbi;
LPVOID addr = NULL;
while (VirtualQuery(addr, &mbi, sizeof(mbi)) != 0) {
if (mbi.State == MEM_COMMIT &&
mbi.Type == MEM_PRIVATE &&
(mbi.Protect & 0xF0) != 0) { // PAGE_EXECUTE* флаги
printf("[!] Подозрительный регион: %p, размер %llu, protect 0x%lx\n",
mbi.BaseAddress,
(unsigned long long)mbi.RegionSize,
mbi.Protect);
}
addr = (LPBYTE)addr + mbi.RegionSize;
}
}
Флаг
0xF0 покрывает PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE и PAGE_EXECUTE_WRITECOPY.Что делать защитнику
Если задача — обнаружить загрузку DLL из памяти в контролируемой среде:
- Мониторить
VirtualAllocс исполняемыми правами. Через ETW-провайдерMicrosoft-Windows-Kernel-Memoryили хуки наNtAllocateVirtualMemory.
- Сканировать память процессов. Искать
MEM_PRIVATEрегионы сPAGE_EXECUTE*и проверять, содержат ли они PE-заголовок (MZсигнатура).
- Анализировать call stack. При срабатывании алерта проверять, что адреса возврата принадлежат известным модулям.
- Коррелировать с поведением. Сочетание «выделение памяти → запись → смена прав → передача управления» — сильный индикатор.
Дополнительно о том, почему in-memory активность оставляет признаки и как их анализируют, см. Детект в памяти Windows: почему in-memory активность оставляет признаки и как их анализируют защитники.
Итоговая таблица: стандартная загрузка vs MemoryModule
| Характеристика | LoadLibrary | MemoryModule |
|---|---|---|
| Файл на диске | Обязателен | Не требуется |
| Тип памяти | MEM_IMAGE | MEM_PRIVATE |
| Привязка к файлу | Да (через секцию) | Нет |
Видимость в EnumProcessModules | Да | Нет |
| Видимость при запросе метаданных памяти | Да (путь к файлу) | Нет |
| Обнаружение по паттерну памяти | Низкая вероятность | Высокая вероятность |
| Поддержка manifest/SxS | Да | Нет |
Техника MemoryModule снижает поверхность обнаружения на уровне мониторинга загрузки модулей, но не устраняет артефакты в памяти процесса. Для защитника ключевой индикатор — исполняемые страницы без файловой привязки, а для атакующего — понимание, что полное скрытие от kernel-mode сканера этой техникой не достигается.
