Запускаем 64-битную библиотеку в пространстве WOW64, часть 1
Windows injection по версии GigaChat. Исключительно для привлечения внимания
Приветствую всех!
Сегодня я вам представлю свои наработки по способу инжекта 64-битных DLL библиотек в процессы WOW64, сам который считал невозможным. Любителей потрогать внутренности Windows приглашаю под кат.
Windows обеспечивает совместимость со старыми x86-приложениями с помощью прослойки WOW64. Эта технология создает 32-битное пространство, совместимые структуры PEB/TEB и предоставляет полный набор 32-битных библиотек для работы с Win32 API. Важно отметить, что эти библиотеки не всегда копируют код из нативных 32-битных библиотек Windows x86. Вместо этого, выполняется выход из эмуляции 32-битного режима, а затем передается управление в соответствующую функцию из 64-битной библиотеки. В частности, это касается ntdll.dll, поэтому в процессах WOW64 всегда находятся два экземпляра ntdll: один нативный x64 и второй — WOW64.
Это разные ntdll, но находятся в одном адресном пространстве
Выход из эмуляции можно выполнить и произвольно, для выполнения 64-битного кода прямо посреди 32-битного. Этот метод называется Heaven’s Gate и доступен в любом WOW64 процессе, но подобный код не получится выполнить в Windows x86, так как там этот механизм отсутствует. Переключение всегда выполняется для всех потоков в WOW64 процессе, если указатель на начальную функцию находится в памяти корректно загруженного 32-битного модуля (exe или dll). Однако, если адрес указывает на код в 64-битном модуле, переключение не произойдет и режим потока останется прежним, что открывает определенные возможности.
Определение типа контекста для потока выполняет wow64.dll! Run64IfContextIs64
Благодаря тому, что любом WOW64 процессе всегда есть несколько 64-битных библиотек, можно использовать свободное место в конце секции .text для выполнения шеллкода, чтобы загрузить нашу библиотеку. Также важно понимать, что DLL-библиотеки можно загружать стандартным способом (например, с помощью LoadLibrary) или вручную, выполняя все действия PE-загрузчика самостоятельно — этот метод называется Manual Map. В этой части мы рассмотрим первый способ.
Место для шеллкода
Hidden text
Предупреждая вопросы — система позволяет записать данные в RX страницы без каких-либо проблем, но такие изменения не проходят бесследно — выполняется механизм Copy-on-Write, создающий изолированную измененную копию для конкретного процесса, что можно отследить с помощью QueryWorkingSetEx (пример кода)
Загрузка DLL с помощью LoadLibrary (но не совсем)
LoadLibrary — функция Win32 API для загрузки динамических библиотек, определенная в kernel32.dll, и часто используемая как явно, так и неявно. Однако в нашем случае мы не можем её использовать, так как kernel32.dll в процессе принадлежит подсистеме WOW64 и может загружать только 32-битные библиотеки. LoadLibrary сама по себе не выполняет загрузку, а вызывает недокументированную функцию LdrLoadDll из ntdll, которая и выполняет всю работу. К счастью, LdrLoadDll, хоть и не упоминается на MSDN, доступна в отладочных символах и публично экспортируется из ntdll. Прототип вызова этой функции следующий:
NTSTATUS __stdcall LdrLoadDll(PWSTR SearchPath, PULONG LoadFlags, PUNICODE_STRING Name, PVOID *BaseAddress)
Так как 64-битная версия ntdll всегда загружается по одному и тому же адресу в памяти, мы можем найти адрес LdrLoadDll и вызвать её в пространстве процесса-цели следующим образом:
Получаем адрес LdrLoadDll с помощью GetProcAddress.
Находим свободное пространство в сегменте .text в ntdll для шеллкода.
Открываем хендл процесса-цели с помощью OpenProcess.
Выделяем страницу памяти под контекст шеллкода с помощью VirtualAllocEx (или находим свободную RW страницу памяти, убедившись, что это не нарушит работу программы).
С помощью WriteProcessMemory записываем в контекст адрес LdrLoadDll и данные о пути к загружаемой библиотеке
Записываем сам шеллкод в найденное место в ntdll.
Создаем новый поток в ntdll с помощью CreateRemoteThread.
В шеллкод вызываем LdrLoadDll, остальная работа будет выполнена без нашего участия.
Пример кода можно увидеть на github.
Hello World из x64 библиотеки в пространстве WOW64
Плюсы такого инжекта заключаются в том, что LdrLoadDll выполнит полную загрузку библиотеки, подгрузив все необходимые зависимости, включая 64-битную kernel32.dll и другие. Во-первых, это удобно, так как не требует самостоятельной подгрузки зависимостей. Во-вторых, инжектируемая библиотека не нуждается в серьезных переработках под спартанские условия.
Минусы заключаются в том, что библиотека будет явным образом видна в списке загруженных библиотек и её можно загрузить только из файла на диске, что может стать ограничением для некоторых задач. Тем не менее, для большинства 32-битных приложений такая библиотека будет незаметна. Например, это можно использовать для перехвата NT-функций в 64-битной ntdll, тогда как все проверки на предмет изменений будут направлены на 32-битную версию.
В следующей части я опишу, как выполнить тот же инжект 64-to-32, но уже загружая библиотеку вручную — иначе говоря manual map. Это освободит от упомянутых минусов LdrLoadDll-инжекта, а также даст гибкость при загрузке.
Всех благодарю за внимание.