D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Перевод статьи западного ресёрчера Hasherezade: https://hshrzd.wordpress.com/2025/01/27/process-hollowing-on-windows-11-24h2/. Я также добавил несколько уточнений и немного изменил некоторые формулировки на, по моему мнению, более точные. Приятного чтения!
После того, как PE был имплантирован¹ в недавно созданный приостановленный процесс, мы возобновляем главный поток, который после подмены адресов сам загрузит наш PE через обычный загрузчик Windows. Однако, когда мы возобновляем 64-битный процесс в Windows 11 24H2, загрузка прерывается с ошибкой
Эта проблема возникает из-за изменений в коде 64-битного PE загрузчика Windows.
Реализация Run PE включает запись полезной нагрузки в недавно выделенную память. В зависимости от варианта техники, она может быть реализована двумя способами:
Windows 11 24H2 добавила нативную поддержку Hotpatching (подробности см. здесь). Это вызвало некоторые изменения при инициализации процесса: например, была добавлена новая функция RtlpInsertOrRemoveScpCfgFunctionTable (см. в разделе «extras»). Стек её вызова выглядит примерно так:
Функция
Видео с демонстрацией: . После этого загрузка прерывается через вызов
Есть два способа, с помощью которых мы можем решить эту проблему:
Хотя RunPE по-прежнему остаётся самой известной и популярной техникой запуска исполняемого файла из памяти в новом процессе, существует некоторое множество альтернатив, с помощью которых мы можем сопоставить наш имплант¹ как
Есть группа техник, которые сначала создают секцию (используя
Со временем появилось больше возможностей для запуска полезной нагрузки из памяти в новом процессе. Process Doppelgänging и Process Ghosting вдохновили гибридные методы, которые по своей реализации ближе к Process Hollowing, но при этом отображают PE как
Позже я придумала ещё один вариант загрузчика, который отображал полезную нагрузку как именованный
Согласно моим последним тестам, Transacted/Ghostly Hollowing, а также Process Overwriting успешно загрузили PE в Windows 11 24H2 без необходимости дополнительных изменений или исправлений.
Демонстрация (Process Overwriting в Windows 11 24H2):
Если по какой-либо причине мы настаиваем на использовании оригинального RunPE и запускаем нашу полезную нагрузку из
Сначала мы проверяем, будет ли запущена наша полезная нагрузка на Windows 11 24H2 или выше, поскольку в более старых версиях этой проблемы нет. Кроме того, этот патч нужен только для 64-битных процессов.
Функциональность патча можно описать следующим псевдокодом:
В результате загрузка нашего импланта¹ не будет прервана, и мы сможем наслаждаться Process Hollowing в Windows 11 24H2!
Тем не менее, даже после того, как мы решили первую проблему с помощью патча
Мы столкнёмся с ней в Windows 11 24H2 с включеной проверкой целостности памяти:
Это также приводит к прекращению загрузки с наблюдаемой ошибкой.
Как и в предыдущем случае, альтернативные методы являются наилучшим вариантом — они работают из коробки, без применения каких-либо исправлений. Однако, если мы хотим придерживаться классического Process Injection², то нам необходим ещё один патч для NTDLL: на этот раз к функции
Кстати, тут нам нужен патч как для 32-, так и для 64-битных приложений.
32-бита: https://github.com/hasherezade/libpeconv/blob/master/run_pe/patch_ntdll.cpp#L4
64-бита: https://github.com/hasherezade/libpeconv/blob/master/run_pe/patch_ntdll.cpp#L43
Функция
После этого Process Hollowing должен работать, даже если включена проверка целостности памяти!
https://github.com/hasherezade/libpeconv/tree/master/run_pe
Process Hollowing (aka RunPE) — это, вероятно, самая старая и популярная техника запуска исполняемого файла Windows из памяти под прикрытием безвредного процесса. Она используется в различных загрузчиках PE, PoC и наступательных инструментах. Она также использовалась для демонстрации возможностей моей библиотеки LibPeConv. 13 октября 2024 года в репозитории LibPeConv на Github была открыта issue, в которой было продемонстрировано, что RunPE больше не работает на последней версии Windows 11 24H2. Этот релиз Windows был опубликован 1 октября 2024 года, и, несмотря на различные проблемы с обновлениями, он постепенно набирает популярность. В поисках решения было установлено, что многие люди сталкивались с той же проблемой с различными реализациями RunPE, то есть это была проблема самой техники. Также в комментариях той issue были представлены решения проблемы, но они боролись лишь с симптомами, а не с корнем проблемы, поэтому мною было принято решение исследовать её глубже. В этом коротком блоге я описываю свои выводы в надежде, что это поможет другим людям, столкнувшимся с той же проблемой.Несколько пояснений:
¹ Hasherezade называет "имплантом" загружаемый через Process Hollowing (или его альтернативы) PE файл. По сути имплант — полезная нагрузка.
² Тут имеется в виду — RunPE.
Нажмите, чтобы раскрыть...
Наблюдаемая ошибка: 0xc0000141 (STATUS_INVALID_ADDRESS)
После того, как PE был имплантирован¹ в недавно созданный приостановленный процесс, мы возобновляем главный поток, который после подмены адресов сам загрузит наш PE через обычный загрузчик Windows. Однако, когда мы возобновляем 64-битный процесс в Windows 11 24H2, загрузка прерывается с ошибкой
STATUS_INVALID_ADDRESS
.Корень проблемы
Эта проблема возникает из-за изменений в коде 64-битного PE загрузчика Windows.
Реализация Run PE включает запись полезной нагрузки в недавно выделенную память. В зависимости от варианта техники, она может быть реализована двумя способами:
- Анмапинг отображения исходного PE, выделение памяти по тому же адресу и запись туда импланта¹;
- Выделение новой области памяти, запись туда импланта¹, затем установка новой области в качестве базового адреса в структуре PEB.
MEM_PRIVATE
памяти, в отличие от обычно отображаемого PE, который будет сохранён как образ (MEM_IMAGE
). Это будет иметь важное значение в дальнейшем.Windows 11 24H2 добавила нативную поддержку Hotpatching (подробности см. здесь). Это вызвало некоторые изменения при инициализации процесса: например, была добавлена новая функция RtlpInsertOrRemoveScpCfgFunctionTable (см. в разделе «extras»). Стек её вызова выглядит примерно так:
LdrInitializeThunk
-> LdrpInitialize
-> LdrpInitializeInternal
-> _LdrpInitialize
-> LdrpInitializeProcess
-> LdrpProcessMappedModule
-> RtlpInsertOrRemoveScpCfgFunctionTable
-> NtQueryVirtualMemory
Функция
NtQueryVirtualMemory
предназначена для получения различных свойств региона памяти. Она вызывается с новым аргументом MemoryImageExtensionInformation, который может использоваться только для испоняемых файлов (MEM_IMAGE
). Поскольку имплантированный¹ PE загружен в MEM_PRIVATE
регион, то системный вызов завершается ошибкой STATUS_INVALID_ADDRESS
.Видео с демонстрацией: . После этого загрузка прерывается через вызов
NtRaiseHardError
Решение
Есть два способа, с помощью которых мы можем решить эту проблему:
- Использовать альтернативную технику, которая сохраняет имплант¹ как MEM_IMAGE вместо MEM_PRIVATE;
- Пропатчить NTDLL, чтобы обойти проверку.
Альтернативные техники
Хотя RunPE по-прежнему остаётся самой известной и популярной техникой запуска исполняемого файла из памяти в новом процессе, существует некоторое множество альтернатив, с помощью которых мы можем сопоставить наш имплант¹ как
MEM_IMAGE
вместо MEM_PRIVATE
.Есть группа техник, которые сначала создают секцию (используя
NtCreateSection
), а затем вручную создают процесс и поток из неё, используя незадокументированные системные вызовы NtCreateProcessEx
и NtCreateThreadEx
:- Process Doppelgänging (PoC)
- Process Ghosting (PoC)
- Process Herpaderping (PoC)
GetProcessImageFileName
возвращает пустую строку — в случае RunPE этого не происходит). Поэтому, хотя они и являются приятным дополнением к арсеналу методов, они не являются идеальной заменой классическому.Со временем появилось больше возможностей для запуска полезной нагрузки из памяти в новом процессе. Process Doppelgänging и Process Ghosting вдохновили гибридные методы, которые по своей реализации ближе к Process Hollowing, но при этом отображают PE как
MEM_IMAGE
:- Transacted Hollowing (PoC)
- Ghostly Hollowing (PoC)
- Herpaderply Hollowing (PoC)
GetProcessImageFileName
возвращает путь до целевого процесса, и сам процесс больше напоминает нормально созданный. Полезная нагрузка отображается как неименованный MEM_IMAGE
.Позже я придумала ещё один вариант загрузчика, который отображал полезную нагрузку как именованный
MEM_IMAGE
, делая его еще более похожим на законно загруженный PE. Подробности реализации и сравнение с другими методами можно найти в репозитории:Согласно моим последним тестам, Transacted/Ghostly Hollowing, а также Process Overwriting успешно загрузили PE в Windows 11 24H2 без необходимости дополнительных изменений или исправлений.
Демонстрация (Process Overwriting в Windows 11 24H2):
Патчинг NTDLL
Если по какой-либо причине мы настаиваем на использовании оригинального RunPE и запускаем нашу полезную нагрузку из
MEM_PRIVATE
, то это всё ещё реализуемо! Однако для этого потребуется пропатчить функцию, вызывающую ошибку (NtQueryVirtualMemory
). Конечно, мы хотим, чтобы патчинг оказал минимальное влияние на остальную часть рантайма, поэтому он должно фильтровать только один конкретный случай, когда мы делаем запрос для конкретной области памяти, содержащей нашу полезную нагрузку.Сначала мы проверяем, будет ли запущена наша полезная нагрузка на Windows 11 24H2 или выше, поскольку в более старых версиях этой проблемы нет. Кроме того, этот патч нужен только для 64-битных процессов.
Функциональность патча можно описать следующим псевдокодом:
- if MEMORY_INFORMATION_CLASS != MemoryImageExtensionInformation -> вызвать оригинальный NtQueryVirtualMemory
- if ImageBase != implant_ptr¹ -> вызвать оригинальный NtQueryVirtualMemory
- в противном случае — вернуться с безобидной ошибкой: STATUS_NOT_SUPPORTED
В результате загрузка нашего импланта¹ не будет прервана, и мы сможем наслаждаться Process Hollowing в Windows 11 24H2!
Наблюдаемая ошибка: 0xC00004AC (STATUS_PATCH_CONFLICT)
Тем не менее, даже после того, как мы решили первую проблему с помощью патча
NtQueryVirtualMemory
, на некоторых системах может возникнуть другая ошибка с другим кодом. На этот раз это STATUS_PATCH_CONFLICT
.Мы столкнёмся с ней в Windows 11 24H2 с включеной проверкой целостности памяти:
Корень проблемы
- При инициплизации импланта¹ вызывается LdrpQueryCurrentPatch (адрес выделен красным):
- Внутри этой функции системный вызов NtManageHotPatch возвращает ошибку STATUS_CONFLICTING_ADDRESSES:
Это также приводит к прекращению загрузки с наблюдаемой ошибкой.
Решение
Как и в предыдущем случае, альтернативные методы являются наилучшим вариантом — они работают из коробки, без применения каких-либо исправлений. Однако, если мы хотим придерживаться классического Process Injection², то нам необходим ещё один патч для NTDLL: на этот раз к функции
NtManageHotPatch
. На сей раз я решила вообще удалить функцию. Вызов немедленно завершается с безобидной ошибкой: STATUS_NOT_SUPPORTED
.Кстати, тут нам нужен патч как для 32-, так и для 64-битных приложений.
32-бита: https://github.com/hasherezade/libpeconv/blob/master/run_pe/patch_ntdll.cpp#L4
64-бита: https://github.com/hasherezade/libpeconv/blob/master/run_pe/patch_ntdll.cpp#L43
Функция
NtManageHotPatch
используется с ранних стадий инициализации процесса. Вот почему лучше всего патчить её сразу после создания процесса. Если мы сделаем это позже, мы должны убедиться, что кэш инструкций был очищен (FlushInstructionCache
), в противном случае вместо него может быть выполнена кэшированная версия функции.После этого Process Hollowing должен работать, даже если включена проверка целостности памяти!
Полный PoC
https://github.com/hasherezade/libpeconv/tree/master/run_pe