Jump to content

Техники загрузки драйвера без подписи в систему с помощью подписанного уязвимого драйвера


_xvi

248 views

Каждый раз, когда вы пытаетесь установить драйвер без подписи в операционной системе 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.cpphttps://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;
    }
}

Пример: https://github.com/stuxnet147/luna-1/blob/c61b05029a83c91b50ad44747f42f93810268467/luna-1 (INTEL)/luna-1/mapper_ctx/mapper_ctx.cpp

Включение 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));

Пример:  https://github.com/hfiref0x/KDU/blob/bb97966abccddcaf0dc147297958d876e804aa32/Source/Hamakaze/dsefix.cpp

Отображение драйвера в большие разделы в подписанном драйвере

Программа загружает любой подписанный драйвер с достаточно большим разделом (пример: .data, .rdata) для отображения вашего драйвера в эти разделы. Это позволяет не выделять системную память.

image.png.536423691faf4263ea744b8d05973d07.png

Пример: https://github.com/armvirus/SinMapper/blob/main/SinMapper/SinMapper/main.cpp

Отображение драйвера в RX, RWX разделы в подписанном драйвере

  1. загрузка легального драйвера
  2. нахождение раздела RX или RWX (это также используется в usermode приложениях, потому что эти разделы могут изменяться во время исполнения программы, а например .text не может)
  3. сделать его доступным для записи с помощью pte
  4. оторбажение драйвера в этот раздел

Пример: https://github.com/M-r-J-o-h-n/Driver-Manual-Mapper/blob/master/DriverMapper/CapcomDriverManualMapper.cpp

Использование слитого 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.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...