Unsigned driver loading techniques using a signed vulnerable driver
Every time you try to install an unsigned driver on your Windows operating system, you get a warning that it is not recommended and may be dangerous to your computer. But what if I told you that there is a way to load an unsigned driver without these warnings?
First, you need to understand why Windows requires signed drivers. This is to keep your system safe and to protect you from potentially malicious programs that could damage or infect your computer. Unfortunately, it also restricts users from installing drivers that are not signed, such as those they have created themselves.
One way around this limitation is to exploit a vulnerability in signed drivers that allow their functions to read/write system memory.
Examples of different techniques:
Allocating system memory and mapping your driver to it (manual map)
ExAllocatePool or MmAllocateIndependentPages or other functions of system memory allocation, then the required driver is written to memory and DriverEntry is called.
Pseudo code:
// Фllocate memory for the driver in the kernel const auto pool_base = ctx.allocate_pool(image.size(), NonPagedPool); // Copy the driver image into the kernel ctx.wkm(image.data(), pool_base, image.size()); // Get the address of the driver's entry point auto entry_point = reinterpret_cast<std::uintptr_t>(pool_base) + image.entry_point(); // Сall driver's entry point auto status = entry_point();
Examples: 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
Allocating usermode memory and modifying ptes to access system memory
In Windows Paging Tables (also known as Page Tables) is a mechanism used to manage virtual memory. It provides a mapping of the process's virtual addresses to physical addresses in the computer's memory.
When a process requests access to a certain address in its address space, the operating system looks for the corresponding entry in the page table. If such an entry exists, the system finds the corresponding physical address and redirects the request to that address.
An example structure:
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;
Pseudo code:
// Get PTE for the base address of the driver const auto [ppte, pte] = map_from.get_pte(drv_base); // calculate physical page address of the page table and save it in phys_addr_pt const auto phys_addr_pt = reinterpret_cast<::ppte>(((std::uintptr_t)ppte >> 12) << 12); // map the physical page of the page table onto the virtual page and get a pointer to the mapping auto virt_mapping = reinterpret_cast<::ppte>(map_from.set_page(phys_addr_pt)); // change attributes of all PTEs for the driver for (auto idx = virt_addr_t{ drv_base }.pt_index; idx < 512; ++idx) { // get the current PTE from the mapping auto drv_pte = *(virt_mapping + idx); // if a PTE exists, change its attributes if (drv_pte.value) { drv_pte.user_supervisor = false; drv_pte.nx = false; *(virt_mapping + idx) = drv_pte; } }
Enabling TestSigning on a booted system
TestSigning (or Test Mode) is a Windows operating system mode that allows you to load and use drivers that have not been digitally signed by Microsoft. In this mode, driver signature verification is disabled, allowing you to download and use unsigned drivers.
In Windows 10 and later versions of the operating system, Test Mode can be enabled through the command line or bios settings.
Usually anti-cheats disallow TestSigning and require it to be turned off, but you can get around this by using a memory patch in a booted Windows.
Pseudo code:
// Get the RVA of the g_CiOptions variable from the PDB file ULONG ciOpt = EzPdbGetRva(&pdb, "g_CiOptions"); // get the base address of the CI.dll module DWORD64 ciBaseAddress = util::get_kmodule_base("CI.dll"); // calculate the address of the g_CiOptions variable auto addr = ciBaseAddress + ciOpt; wkm((void*)addr, &val, sizeof(val));
Mapping driver to large partitions in a signed driver
The program loads any signed driver with a large enough partition (example: .data, .rdata) to map your driver to these partitions. This allows you not to allocate system memory.
Example: https://github.com/armvirus/SinMapper/blob/main/SinMapper/SinMapper/main.cpp
Mapping driver in RX, RWX partitions in the signed driver
- load the legal driver
- find the RX or RWX partition (this is also used in usermode applications because these partitions can change at runtime and for example .text cannot)
- make it writable by pte
- to tear off the driver to this partition
Using merged EV code signing certificate with time rollback
PastDSE is a "workaround" to Driver Sign Enforcement using a leaked EV code signing certificate. It's not really a real workaround, as it only changes the date to 01-01-2014 before driver signing and restores it afterwards. The Kernel driver loader will accept all driver images if the code was signed with an extended validation code signing certificate that has not been revoked.
Example: https://github.com/utoni/PastDSE
All examples are detected at least by Easy Anti-Cheat (EAC) at the time of this article's creation and should not be tested on popular games.
0 Comments
Recommended Comments
There are no comments to display.