• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья LoadPE — deprecated. Встречайте HookPE!

Mochkayshka

Пользователь
Пользователь
Регистрация
30.05.2021
Сообщения
12
Розыгрыши
0
Реакции
0
Mochkayshka не предоставил(а) никакой дополнительной информации.
Приветствую! Я создатель ботнета и рата MonsterV2 и данная статья является призывом по возможности прекратить использовать LoadPE в своих крипторах и перейти на новый, более современный подход. Но обо всём по порядку.

Ещё во времена Windows XP у многих была потребность запуска исполняемого не с диска, а из памяти. К сожалению, PE загрузчик из Ntdll не экспортирует функции для загрузки PE изображения (функции с префиксом Ldr- или Ldrp-) + эти функции активно использовали незадокументированную структуру LDRP_LOAD_CONTEXT, поэтому пришлось искать костыли. Одним из старейших таких костылей является Process Hollowing или RunPE.

Process Hollowing (RunPE)

Поскольку загрузчик Windows нужных функций нам не предоставляет, пришлось изворачиваться и заставлять его самому загрузить изображение; для этого нужно было заспавнить замороженный процесс с флагом CREATE_SUSPENDED и загрузить куда-нибудь нашу полезную нагрузку, после чего для контекста главного потока заспавненного процесса патчился адрес точки входа, который хранился в регистре Rcx для x86-64 или в регистре Eax для x86-32, и поле ImageBaseAddress структуры PEB, адрес которой хранился в регистре Rdx для x86-64 или в регистре Ebx для X86-32. После всех этих махинаций мы запускали главный поток, который уже самостоятельно подгружал и запускал нашу полезную нагрузку.

Этот метод и по сей день остаётся одним из самых распространённых. Правда, на текущий момент он не работает корректно без кое-каких патчей Ntdll на Windows 11 24H2 и выше из-за некоторых сайд-эффектов внутри Ntdll и NtOsKrnl, которые проверют атрибуты памяти и ожидают установленный MEM_IMAGE тип у региона памяти, куда загружен исполняемый модуль (подробнее читайте Для просмотра ссылки Войди или Зарегистрируйся). Поэтому в скором времени модификации RunPE по типу Process Doppelgänging, Process Ghosting, etc, которые работают по той же схеме: спавнят замороженный процесс, что-то в нём патчат и запускают, могут стать более популярными, так как многие из них грузят полезную нагрузку именно в исполняемую секцию.

RunPE и его модификации очень удобные для всяких лоадеров, поскольку они грузят и запускают полезную нагрузку в отдельном процессе, где она не может повлиять на работу резидентного модуля (причём 64-битный процесс мог спокойно запускать как 32-, так и 64-битный пейлоды). Но для крипторов такой вариант не подходил, поскольку эти сопутствующие действия по типу спавна замороженного процесса или использование транзакционного API (Process Doppelgänging) часто служили триггером для AV. Поэтому крипторы решили: а давайте-ка мы своими руками возьмём да и имплементируем всё то, что делал PE загрузчик из Ntdll. Так появился LoadPE.

Manual Map и LoadPE


Собственно LoadPE это ручная загрузка исполняемого файла, то есть мы копируем исполняемый образ, парсим и обрабатываем PE заголовки, после чего запускаем. Manual Map это то же самое, но шелл-кодом и по отношению к удалённому процессу, чаще всего применяется в читах и подразумевает загрузку именно DLL, в то время как LoadPE чаще всего подразумевает загрузку EXE, хотя различия между EXE и DLL минимальные.

Идея LoadPE была хорошей: иметь полный контроль над загружаемым образом. Ну не сказка ли? Но тут всплыли проблемы: LoadPE не может нормально запускать изображения с эксепшенами и статической инициализацией TLS. Ну то есть: мы можем обработать релоки, импорты и запустить TLS колбеки, но мы не сможем обработать исключения, так как Ntdll не экспортирует функции RtlInsertInvertedFunctionTable, которую PE загрузчик использует для внесения нового элемента в таблицу исключений LdrpInvertedFunctionTable, поэтому нам остаётся два варианта: искать нужную функцию в памяти Ntdll, сигнатура которой отличается от версии к версии, или вручную реализовать то, что она делает, но для этого придётся также рыться в памяти Ntdll в поисках LdrpInvertedFunctionTable. С функцией LdrpHandleTlsData та же петрушка: она не экспортируется и обращается к внутренним переменным Ntdll по типу LdrpTlsList и LdrpActiveThreadCount

Одна из самых полных имплементаций LoadPE, которую я видел, это китайский MemoryModulePP: Для просмотра ссылки Войди или Зарегистрируйся. Он там даже частично поддерживает ручную загрузку дотнетов без CLR hosting.

Но увы, многим крипторам до такого далеко, и у них возникают проблемы даже при обработке импортов по ординалам. Поэтому я представляю вашему вниманию PE загрузчик нового поколения — HookPE!

HookPE (LWE)


Я точно не первый, кто пишет об этом методе. Как минимум тут до меня об этом писали статью с названием "LoadLibrary Reloaded": Для просмотра ссылки Войди или Зарегистрируйся

Также мне подсказали, что первое публичное упоминание этой техники датируется аж 2011 (!) годом, а оригинальная реализация называется LWE и принадлежит пользователю Indy. За ссылки спасибо пользователю 8800: Для просмотра ссылки Войди или Зарегистрируйся Для просмотра ссылки Войди или Зарегистрируйся

(Осторожно: ASM) Для просмотра ссылки Войди или Зарегистрируйся Для просмотра ссылки Войди или Зарегистрируйся Для просмотра ссылки Войди или Зарегистрируйся

Собственно, техника уже достаточно подробно описана в ссылках выше: мы вызываем LdrLoadDll и на этапе (внутри функции LdrpLoadKnownDll), когда он будет искать DLL среди KnownDlls(32), подменяем секцию, чтоб PE загрузчик сам сделал за нас всю работу, но при этом без необходимости создавать отдельный процесс. На схеме это будет примерно так:

LdrLoadDll -> LdrpLoadDll -> LdrpLoadDllInternal -> LdrpFindOrPrepareLoadingModule -> LdrpLoadKnownDll

Вариант из статьи содержит имплементацию только под x64, а также не работает на Windows 11 24H2, так как не грузит пейлод в исполняемую секцию. Прикладываю исправленный вариант с поддержкой 32-битных систем и Windows 11 24H2 специально для вас! Сама реализация почти ничем не отличается, для поддержки Windows 11 24H2 пейлод грузится в исполняемую секцию посредством Module Overloading (берётся фейковая DLL размером больше или равная нашему пейлоду, мапится с диска как SEC_IMAGE, после чего в уже спампленное изображение грузится наш пейлод со сменой защиты секций), а для поддержки 32-битных систем я поправил регистры в VEH-обработчике.

Я не просто так упомянул Manual Map в статье, потому что реализация HookPE спокойно влезет в шелл-код. Также HookPE гораздо стабильнее LoadPE: все сложные моменты PE загрузчик парсит и обрабатывает сам. Ну и HookPE не создаёт никаких процессов, как это делал RunPE и его модификации.

У HookPE есть и несколько ограничений:

  1. если по адресам хукаемых Nt-функций будет трамплин от детура, то загрузчик, вероятно, сломается;
  2. он не может грузить UWP (AppX) приложения (какие-то вообще не хотят грузится, какие-то падают при запуске);
  3. он нормально грузит .NET, но не запускает, так как я поставил специальную заглушку:
C++: Скопировать в буфер обмена
Код:
if (peconv::is_dot_net(Image, ImageSize)) {
cerr << "[-] TODO: .NET files can be loaded, but cannot be started!" << endl;
return false;
}


А поставил я её по той причине, что для того, чтоб запустить .NET приложение, нужно проинициализировать CLR, который будет пытаться себя прочитать с диска, и вам нужно будет хукать функции (можете посмотреть на пример реализация для LoadPE в MemoryModulePP). Вывод простой: не заморачивайтесь и грузите .NET CLR хостингом, я вот нигде ещё не видел ручной загрузчик для Дотнета с поддержкой всех версий.

Заключение
Спасибо всем за прочтение статьи! Надеюсь, что данная статья поможет вам отказаться от LoadPE в пользу HookPE. Я прикладываю ссылку на свою демонстрационную реализацию HookPE, вы можете изменять её как вам вздумается. Также учтите, что моя реализация использует CMake как систему сборки, чтоб вы могли потестировать на разных компиляторах.

Готовый архив с исходным кодом(Для просмотра ссылки Войди или Зарегистрируйся): Для просмотра ссылки Войди или Зарегистрируйся
Пароль от архива: MonsterV2
Контакты / Contacts
Форумы:
 
Нигде не встречал такой нейминг технологии - HookPE
Все по сути, ранпе устарел а сделать полноценный обработчик пе - куча кода и костылей (хотя есть весьма известные персоны, которые писали что реализовали)
Причина по которой люди используют ранпе как раз в том, что он обрабатывает все

Данная тема была много раз поднята на этом форуме - кто хотел тот додумался, да и Glitch статью здесь выкладывал на эту тему с сурс кодом

Достаточно давно я пробовал использовать лоадпе, но в некоторых ситуация он не отрабатывал бинарник + для дотнета нужен другой лоадпе
Я задумался о том, как работает загрузчик винды и через некоторое время реализовал такую технологию, до сих пор использую ее в проектах

Но все равно есть ситуации где так называемый HookPe не работает, а обычный LoadPe без тлс, экскепшнов - работает
Чаще всего замечал такое с софтом написанным на паскале\делфи


Я использую другую реализацию, более простую:

NtCreateSection -> ... -> NtMapViewOfSection
В процессе загрузки создается секция, затем вызывается NtMapViewOfSection которая отвечает за мап изображения

Функция имеет следующую сигнатуру:
C: Скопировать в буфер обмена
DWORD __stdcall ZwMapViewOfSection(IN HANDLE SectionHandle, IN HANDLE ProcessHandle, IN OUT PVOID* BaseAddress, IN ULONG ZeroBits, IN ULONG CommitSize, IN OUT OPTIONAL PLARGE_INTEGER SectionOffset, IN OUT PSIZE_T ViewSize, IN SECTION_INHERIT InheritDisposition, IN ULONG AllocationType, IN ULONG Win32Protect)

BaseAddress, ViewSize
Это 2 интересующих нас параметра

Обрабатываем релоки, мапаем изображение и устанавливаем значения в выходные параметры
C: Скопировать в буфер обмена
Код:
*BaseAddress = (void*)(newImageBase);
*ViewSize = NtHeaders->OptionalHeader.SizeOfImage;

Добавляем в характеристики файла IMAGE_FILE_DLL для поддежки загрузки exe а не только длл
Зануляем точку входа - ее нужно будет вызвать вручную после загрузки длл

Возвращаем 0 из функции - успешный статус нт функции

Думаю говорить о том, что необходимо снять хук внутри хукнутой функции не нужно


LoadLibraryW -> hook NtMapViewOfSection -> relocs + map -> ... -> return

baseAddr = LoadLibraryW("cmd.exe");

entrypoint(); // exe
entrypoint(baseAddr, DLL_PROCESS_ATTACH, 0); // dll
 
Оригинальное название этого метода — LWE. Название HookPE дал этой технике я, чтоб больше запоминалось, что ли.

Да, HookPE не все бинари может загрузить: просто LoadLibrary возвращает ошибку. Возможно проблема в каких-то флагах PE, которых не должно быть в DLL, но я не смотрел.

По поводу эксепшенов: Для просмотра ссылки Войди или Зарегистрируйся и Для просмотра ссылки Войди или Зарегистрируйся
Тут есть вполне стабильные примеры реализации обработки таблицы исключений для 32 и 64 бита. Они ищут в .mrdata (.data) LdrpInvertedFunctionTable и вставляют туда новую запись. Я тестил — 64 и 32 бита работают для Windows 10 и 11.

По поводу TLS: я завтра планирую выпустить статью, посвящённую поиску LdrpHandleTlsData через дизасм ,text секции Ntdll. Так что ждите)
 
Спойлер: Loader.cpp


Это пиздец а не код
 
Старая разработка Клерка. Но пойдёт.
 
Вот статью про это было бы интересно почитать


Не забывай что есть еще вин 7, 8. А некоторым и хр нужна
 
И нафига было норм код изгаживать CRT всякими. То что он доработки требует в связи со своей старостью - те кому надо доработали давно.
 
Activity
So far there's no one here