Jump to content

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.cpphttps://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;
        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;

Example: https://github.com/stuxnet147/luna-1/blob/c61b05029a83c91b50ad44747f42f93810268467/luna-1 (INTEL)/luna-1/mapper_ctx/mapper_ctx.cpp

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));

Example: https://github.com/hfiref0x/KDU/blob/bb97966abccddcaf0dc147297958d876e804aa32/Source/Hamakaze/dsefix.cpp

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

  1. load the legal driver
  2. 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)
  3. make it writable by pte
  4. to tear off the driver to this partition

Example: https://github.com/M-r-J-o-h-n/Driver-Manual-Mapper/blob/master/DriverMapper/CapcomDriverManualMapper.cpp

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.


Recommended Comments

There are no comments to display.

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...