Техники загрузки драйвера без подписи в систему с помощью подписанного уязвимого драйвера
Каждый раз, когда вы пытаетесь установить драйвер без подписи в операционной системе Windows, вы получаете предупреждение о том, что это не рекомендуется и может быть опасным для вашего компьютера. Но что, если я скажу вам, что существует способ загрузки неподписанного драйвера без этих предупреждений?
Для начала нужно понимать, почему Windows требует подписанные драйверы. Это делается для обеспечения безопасности вашей системы и защиты от потенциально вредоносных программ, которые могут повредить или заразить ваш компьютер. К сожалению, это также ограничивает возможности пользователей устанавливать драйверы, которые не подписаны, например, те, которые создали сами.
Один из способов обойти это ограничение - использовать уязвимость в подписанных драйверах, которые позволяют использовать их функции для чтения/записи системной памяти.
Примеры разных техник:
Выделение системной памяти и отображение туда своего драйвера (manual map)
Использется функция ExAllocatePool или MmAllocateIndependentPages или другие функции выделения системной памяти, далее записывается в память нужный драйвер и вызывается DriverEntry.
Псевдо код:
// Выделяем память под драйвер в ядре const auto pool_base = ctx.allocate_pool(image.size(), NonPagedPool); // Копируем образ драйвера в ядро ctx.wkm(image.data(), pool_base, image.size()); // Получаем адрес точки входа драйвера auto entry_point = reinterpret_cast<std::uintptr_t>(pool_base) + image.entry_point(); // Вызываем точку входа драйвера auto status = entry_point();
Примеры: https://github.com/stuxnet147/luna-1/blob/c61b05029a83c91b50ad44747f42f93810268467/luna-1 (AMD)/luna-1/map_driver.cpp, https://github.com/estimated1337/lenovo_mapper/blob/main/lenovo_mapper/DriverMapper.cpp
Выделение usermode памяти и модификация ptes для доступа к системной памяти
В Windows Paging Tables (также известные как Page Tables) это механизм, который используется для управления виртуальной памятью. Он обеспечивает отображение виртуальных адресов процесса на физические адреса в памяти компьютера.
Когда процесс запрашивает доступ к определенному адресу в своем адресном пространстве, операционная система ищет соответствующую запись в таблице страниц. Если такая запись существует, система находит соответствующий физический адрес и перенаправляет запрос на этот адрес.
Примерная структура:
typedef union _pte { ULONG64 value; struct { ULONG64 present : 1; // Must be 1, region invalid if 0. ULONG64 ReadWrite : 1; // If 0, writes not allowed. ULONG64 user_supervisor : 1; // If 0, user-mode accesses not allowed. ULONG64 PageWriteThrough : 1; // Determines the memory type used to access the memory. ULONG64 page_cache : 1; // Determines the memory type used to access the memory. ULONG64 accessed : 1; // If 0, this entry has not been used for translation. ULONG64 Dirty : 1; // If 0, the memory backing this page has not been written to. ULONG64 PageAccessType : 1; // Determines the memory type used to access the memory. ULONG64 Global : 1; // If 1 and the PGE bit of CR4 is set, translations are global. ULONG64 Ignored2 : 3; ULONG64 pfn : 36; // The page frame number of the backing physical page. ULONG64 Reserved : 4; ULONG64 Ignored3 : 7; ULONG64 ProtectionKey : 4; // If the PKE bit of CR4 is set, determines the protection key. ULONG64 nx : 1; // If 1, instruction fetches not allowed. }; } pte, * ppte;
Псевдо код:
// Получаем PTE для базового адреса драйвера const auto [ppte, pte] = map_from.get_pte(drv_base); // Вычисляем физический адрес страницы таблицы страниц и сохраняем его в phys_addr_pt const auto phys_addr_pt = reinterpret_cast<::ppte>(((std::uintptr_t)ppte >> 12) << 12); // Отображаем физическую страницу таблицы страниц на виртуальную и получаем указатель на отображение auto virt_mapping = reinterpret_cast<::ppte>(map_from.set_page(phys_addr_pt)); // Изменяем атрибуты всех PTE для драйвера for (auto idx = virt_addr_t{ drv_base }.pt_index; idx < 512; ++idx) { // Получаем текущий PTE из отображения auto drv_pte = *(virt_mapping + idx); // Если PTE существует, изменяем его атрибуты if (drv_pte.value) { drv_pte.user_supervisor = false; drv_pte.nx = false; *(virt_mapping + idx) = drv_pte; } }
Включение TestSigning в загруженной системе
TestSigning (или Test Mode) - это режим работы операционной системы Windows, который позволяет загружать и использовать драйверы, которые не прошли цифровую подпись от Microsoft. В этом режиме проверка подписи драйверов отключается, что дает возможность загрузки и использования неподписанных драйверов.
В Windows 10 и более поздних версиях операционной системы, Test Mode может быть включен через командную строку или настройки биоса.
Обычно анти-читы запрещают TestSigning и требуют выключения, но можно это обойти с помощью патча памяти в загруженной Windows.
Псевдо код:
// Получаем RVA переменной g_CiOptions из PDB-файла ULONG ciOpt = EzPdbGetRva(&pdb, "g_CiOptions"); // Получаем базовый адрес модуля CI.dll DWORD64 ciBaseAddress = util::get_kmodule_base("CI.dll"); // Вычисляем адрес переменной g_CiOptions auto addr = ciBaseAddress + ciOpt; wkm((void*)addr, &val, sizeof(val));
Отображение драйвера в большие разделы в подписанном драйвере
Программа загружает любой подписанный драйвер с достаточно большим разделом (пример: .data, .rdata) для отображения вашего драйвера в эти разделы. Это позволяет не выделять системную память.
Пример: https://github.com/armvirus/SinMapper/blob/main/SinMapper/SinMapper/main.cpp
Отображение драйвера в RX, RWX разделы в подписанном драйвере
- загрузка легального драйвера
- нахождение раздела RX или RWX (это также используется в usermode приложениях, потому что эти разделы могут изменяться во время исполнения программы, а например .text не может)
- сделать его доступным для записи с помощью pte
- оторбажение драйвера в этот раздел
Использование слитого EV code signing сертификата с откатом времени
PastDSE - это "обход" Driver Sign Enforcement с использованием слитого в сеть EV code signing сертификата. На самом деле это не настоящий обход, поскольку он только изменяет дату на 01-01-2014 перед подписанием драйвера и восстанавливает ее после этого. Загрузчик драйверов Kernel будет принимать все образы драйверов, если код был подписан сертификатом подписи кода с расширенной проверкой, который не был отозван.
Пример: https://github.com/utoni/PastDSE
Все примеры обнаружены как минимум Easy Anti-Cheat (EAC) на момент создания статьи и не стоит их тестировать на попуярных играх.
0 Comments
Recommended Comments
There are no comments to display.