How malicious actors use signed drivers to capture infrastructure. BYOVD technique selection.

Introduction to BYOVD

BYOVD (Bring Your Own Vulnerable Driver) is an advanced technique that allows attackers to use signed, legitimate drivers to gain unauthorized access to the operating system’s kernel mode. More precisely, BYOVD is an umbrella term for a set of techniques that exploit vulnerable primitives in operating-system drivers.

When exploiting vulnerable drivers, attackers achieve the following goals:

  • Privilege Escalation Privilege Escalation: obtaining NT AUTHORITY\SYSTEM-level privileges.

  • Defense Evasion Defense Evasion: neutralizing or bypassing security controls.

  • Persistence Persistence: implanting hidden mechanisms to maintain a foothold in the system.

  • Data Manipulation Impact: performing unauthorized operations to read, modify, or delete mission-critical data in a corporate information infrastructure.

  • Malicious Code Execution Execution: executing arbitrary code in kernel mode (Ring 0), which provides full control over the system and enables direct manipulation of system structures, objects, and memory.

Instead of looking for vulnerabilities in the kernel itself, attackers look for vulnerabilities in third-party drivers that are already allowed to execute in kernel mode. This significantly simplifies the task, since the drivers are already signed and therefore do not raise suspicion from the system. BYOVD is considered one of the most sophisticated techniques because it lets an attacker gain full control over the system while remaining largely unnoticed.

The key idea behind BYOVD is that attackers “bring their own vulnerable driver”, load it into the kernel (or leverage one that is already loaded), and then exploit the vulnerable primitives found in it using their own exploits.

In this article, we focus on techniques for manipulating process privileges and access rights in the context of BYOVD. The key idea of this material is to help readers understand how the basic security mechanisms in the operating system Windows, and what can happen if requests passed to a driver are not sufficiently controlled by the driver’s code.

Disclaimer

This material is for informational purposes only and is intended for information security specialists and systems programmers interested in protecting information in operating-system kernels. LLC “PARANOID SECURITY” bears no responsibility for any potential damage caused by the use of the information presented. Any activity related to creating, distributing, or using computer programs or other computer information knowingly intended for unauthorized destruction, blocking, modification, copying of computer information, or neutralization of computer information protection tools entails liability under the legislation of the Russian Federation.

A Brief Overview of the Windows Security Subsystem

Understanding how the Windows security subsystem works is critical to understanding techniques for manipulating privileges and access rights.

In operating systems, the term “program” refers to a static executable file stored on a storage device. In contrast, a running program is called a process. A process is an abstraction created by the operating-system kernel to manage executing code. This abstraction encapsulates allocated resources and includes a set of kernel data structures such as virtual memory tables, open-file descriptors, and scheduled threads. The operating system’s job is to isolate, schedule, and manage many parallel processes, ensuring safe and efficient sharing of computing resources.

Thus, a process is an operating-system kernel object that represents a running instance of a program. The key structure describing a process is _EPROCESS. It is a very large structure containing more than 200 fields and pointers to other helper objects. It stores all information about the process, including its identifier, priority, state, memory usage, and—most importantly (in the context of this material)—a pointer to the access token (Access Token or Token).

Every process in Windows has an associated Access Token. It is a kind of passport that the process presents whenever it performs operations on resources (reading files, accessing other processes, writing registry keys, etc.). The Access Token contains comprehensive information about the account under which the process is running, the groups it belongs to, and the privileges it holds. Thus, one can say that each process executes in the context of some account.

The Token field in _EPROCESS is a pointer to the _TOKEN structure, which contains information about a process’s privileges and access rights.

The _TOKEN structure has more than 40 fields and nested structures. Practically every field in this structure describes the security context of a process depending on the Windows kernel’s protection mechanisms. We will consider only some of them.

The Privileges field is a pointer to the _SEP_TOKEN_PRIVILEGES structure, which contains all process privileges and their states. We will examine it in detail in the next section.

The SessionId field indicates that the process is running in a user session.

UserAndGroups defines the access level in the context of the groups the account belongs to. The UserAndGroupCount field contains the number of such groups.

TokenType describes the type of token. If this field has the value TokenPrimary, it indicates use of a primary token; TokenImpersonation indicates use of another process’s access token (a phenomenon known as impersonation). This mechanism is used in multi-user services, where the main process (a web server or SMB server) runs under a service account, but to correctly enforce access rights to resources (web pages, database records, or files), the process spawns a new thread that runs under the account of the user requesting access.

Even with a single account on the system, multiple different logon sessions may be registered. To distinguish them, the field LogonSession is a pointer to the _SEP_LOGON_SESSION_REFERENCES structure, which contains information about the logon time, logon type, and authenticating package.

We now have a brief overview of the key kernel structures that describe processes, their access rights, and their privileges. Next, we will look at how attackers can manipulate the fields of these structures.

BYOVD Exploitation Example: Process Privilege Manipulation

Kernel-level manipulations of access tokens allow attackers to gain full control over all processes running on the system, including protected ones (Protected Process Light). Having privileges enables a process to perform a set of specific system (sensitive) operations. For example, if a process has the privileges SeBackupPrivilege and SeRestorePrivilege, this gives it the ability to read and write arbitrary files regardless of access permissions. This means that even if a user (more precisely, an account) has an explicit deny on writing (or reading) a particular file, those privileges can be used to bypass the deny. With the privilege SeLoadDriverPrivilege a user can load (or unload) drivers, and with SeDebugPrivilege —debug a process and gain full access to its memory.

As we saw earlier, the presence (or absence) of privileges in a process is determined by the _SEP_TOKEN_PRIVILEGES structure (the Privileges field of _TOKEN).

This structure consists of three fields:

The Present field is a bitmask of privileges that exist in the token. If a bit is set to 1, the privilege is present and can be used if needed; if it is 0, the privilege is absent and cannot be used.

The presence of a particular privilege in a process token does not yet mean it is “enabled”. Before requesting a sensitive operation, the privilege must be enabled. This is done using the special function AdjustTokenPrivileges. The current state of privileges in a process token is determined by the field Enabled. This is another bitmask in the _SEP_TOKEN_PRIVILEGES structure that specifies whether a particular privilege is enabled or disabled.

EnabledByDefault — a bitmask of privileges that are enabled by default when the token is created.

Let’s see how this works for a process started without administrative privileges (more precisely, a process with Medium Integrity Level):

We can see that the process has 5 privileges listed in the field Present (0x602880000). At the same time, only one of them is enabled—the field Enabled (value 0x800000).

The image above shows the bitwise equivalents of each field in the _SEP_TOKEN_PRIVILEGES structure.

Let’s manipulate the field Present and look at the result.

Using the debugger command eq (edit qword), we changed the value of the Present field, after which the number of privileges expanded. Similarly, the Enabled field can be manipulated to change the state of privileges.

Thus, if an attacker gains the ability to manipulate this data structure via a vulnerable driver, they automatically gain the ability to elevate their privileges and perform administrative operations.

The BYOVD give attackers the ability not only to elevate their own privileges, but also another critically important capability— manipulate the privileges of other processes.

This aspect is especially dangerous in the context of bypassing security controls such as antivirus (AV) and endpoint detection and response solutions (EDR).

Using a vulnerable driver, an attacker can deliberately strip AV/EDR processes of a set of critical privileges required for their normal operation. As a result, parts of the security tooling may stop working correctly. For example, an AV process may lose the ability to scan files with the signature engine or to send/read events from the ETW (Event Tracing for Windows) subsystem.

In effect, the attacker uses a legitimate, signed driver as a “Trojan horse” to create ideal conditions: first they neutralize security (by “blinding” AV/EDR), and then they freely carry out their primary malicious actions under the cover of a system that can no longer defend itself. This turns a BYOVD-based attack into an elegant and highly effective way to achieve absolute dominance over a compromised system.

BYOVD Exploitation Example: Access Token Stealing

In addition to manipulating the fields of _SEP_TOKEN_PRIVILEGES, attackers can replace their own Access Token with the token of another, more privileged process. To do this, they look for processes running, for example, under the System account, read their _EPROCESS->Token, and overwrite the Token in their own _EPROCESS with that value.

By analogy with privilege manipulation, let’s see what token stealing looks like using a debugger.

With a few simple manipulations in the debugger, we were able to assign cmd.exe the token of the System process.

To implement this attack scheme, it is enough to have a vulnerable driver that provides primitives for arbitrary reads and writes to an arbitrary address. An attacker can use these primitives to obtain a pointer to another process’s _EPROCESS, for example an antivirus process or the System process (read primitive). Then they copy the Token pointer into their “own process” (write primitive).

Exploit Communication with a Vulnerable Driver

As mentioned earlier, “bring your own vulnerable driver” (BYOVD) attacks are based on using a legitimate, signed driver to execute malicious code in kernel mode. A key stage of this technique is establishing a communication channel between the malicious program running in user mode (the exploit) and the vulnerable driver running in kernel mode.

Interaction between the exploit and the vulnerable driver in Windows takes place via a standardized input/output mechanism (I/O), which includes the following steps:

  1. Driver initialization and creation of a communication device

The interaction begins when the vulnerable driver is loaded into kernel memory. During initialization, the DriverEntry function creates a communication device (device) and a symbolic link.

The driver calls the function IoCreateDevice to create the device object (device), which represents the communication device in kernel space.

After the device object is created, the driver calls the function IoCreateSymbolicLink to make this device accessible to user-mode code. This link typically has a name like \??\or \DosDevices\and serves as a visible access point for user-mode processes.

In addition, DriverEntry fills in the DRIVER_OBJECT structure, including initializing the dispatch routine table (Dispatch Routines). These routines are entry points (handler functions) for different types of I/O requests such as read, write, or—most importantly for BYOVD—IRP_MJ_DEVICE_CONTROL.

  1. Initiating a request from user mode

Once the driver is ready, a malicious exploit running in user mode obtains a handle to the communication device object (device), prepares the data structure that will be passed into the kernel, and sends that data.

The exploit calls a Windows API function, most often CreateFile, using the link created by the driver (for example, \\.\<device-name>). As a result, the exploit obtains a handle to the communication device object in the kernel.

Next, the exploit prepares the required data structures (for example, vulnerability exploitation arguments) and an input/output control code (IOCTL Code), then calls the Windows API function DeviceIoControl. This function is key: it packages the user-mode request, including the IOCTL and data buffers, and passes it to the I/O manager in the kernel.

  1. Request handling by the I/O manager

After DeviceIoControl is called, control transitions to the kernel, where it is received by the I/O manager (I/O Manager), which forms an IRP packet.

The I/O manager receives the request to process user data and forms the IRP (I/O Request Packet) structure. This packet is the standard way to represent all I/O requests in the kernel. It contains all information about the request, including: the operation type, pointers to user-mode buffers (input and output data) and the IOCTL code, provided by the user-mode process.

The I/O manager reads the operation type from the IRP (DeviceIoControl -> IRP_MJ_DEVICE_CONTROL) and uses it as an index to call the corresponding dispatch routine, whose address was previously initialized by the driver in the DRIVER_OBJECT structure.

  1. Driver-side IRP handling

After the corresponding handler is called, control passes to the main code of the vulnerable driver. The driver code begins processing the IRP request and first reads key parameters from the IRP structure (and its substructures):

MajorFunction — the dispatch routine type: open/close handle (IRP_MJ_CREATE/IRP_MJ_CLOSE), read/write operations (IRP_MJ_READ/IRP_MJ_WRITE), or handling a request with specific logic (IRP_MJ_DEVICE_CONTROL).

IoControlCode — the IOCTL code, which indicates which specific internal function the driver should execute.

SystemBuffer — a pointer to the buffer containing data prepared and passed by the exploit from user mode.

After reading the parameters, the handler proceeds to dispatch the internal function. Based on the IoControlCode, the driver code typically uses a switch/case to call a specific internal handler function. This is where the vulnerable primitive (for example, a kernel memory read or write vulnerability) is exploited using specially crafted data from SystemBuffer.

Thus, the IOCTL code acts as a “command” and SystemBuffer as the “payload”, allowing the exploit to use the legitimate I/O channel to execute malicious code with the highest privileges (kernel privileges).

Examples of Arbitrary Memory Read and Write Primitives

In this section, we will look at several kernel primitives that allow arbitrary reads from kernel memory as well as writes to kernel memory. All examples shown are taken from real drivers with confirmed CVEs. However, for ethical reasons we will not disclose the names of these drivers or the CVEs.

Let’s look at the kernel API MmMapIoSpace. The MmMapIoSpace function performs a critical task: it maps a contiguous block of physical addresses (typically belonging to hardware) into the kernel’s virtual address space. This allows driver developers to interact with registers or buffers of various peripheral devices as if it were normal RAM, using standard read/write operations (for example, memcpy).

By itself, MmMapIoSpace is not harmful and is a legitimate API for working with devices. The real danger lies in the fact that a call to MmMapIoSpace is, in most cases, followed by critical operations—reading from or writing to the mapped memory.

If a vulnerable driver allows user-mode code to call MmMapIoSpace arbitrarily (giving the attacker control over the physical address and mapping size), the attacker gains a powerful arbitrary read/write primitive (Arbitrary Read/Write) over physical memory.

Using this primitive, an attacker can:

  1. Find the physical addresses allocated to store the most important structures (_EPROCESS -> Token) of highly privileged processes.

  2. Map these physical addresses into virtual memory.

  3. Write (swap) the Access Token of their malicious process with the token of a highly privileged process.

Let’s see how this can be implemented in a vulnerable driver.

First, the driver checks the sizes of the input (InputBufferLength) and output (OutputBufferLength) buffers, then calculates the effective number of bytes to read from kernel memory (_bittest). This read size is strictly limited to 1, 2, 4, or 8 bytes. Next, depending on OutputBufferLength, the driver reads data from kernel memory and writes it into SystemBuffer, completes IRP processing, and returns control to the I/O manager, which in turn passes the data to the user-mode application.

The vulnerability in this implementation is that the driver code does not validate that the physical address falls within an allowable range, which leads to uncontrolled reads of kernel memory.

Let’s look at a primitive for writing to kernel memory.

This example is similar to the previous one, except that data provided by the user-mode application is written into kernel memory. Thus, MmMapIoSpace acts like a key that, once compromised, unlocks full access to physical memory and gives the attacker virtually unlimited capabilities to manipulate critical kernel structures, while also reducing the chance of being detected in the attacked infrastructure.

Protection and Mitigation

For software developers, we recommend adhering to the following principles:

  1. Input validation — all data received from user-mode applications must be thoroughly validated and its allowable ranges defined to prevent malicious use.

  2. Use safe APIs — use safer API variants that provide protection against common attack types.

  3. Privilege checks — the driver should be able to authenticate the calling process (for example, by its digital signature) and verify that it is allowed to perform the requested operations (for example, by checking for specific privileges).

  4. Regular vulnerability testing — the driver should be regularly tested for vulnerabilities by an internal secure development team to identify and eliminate potential issues.

Do not neglect the mitigation mechanisms developed by Microsoft. They are aimed at blocking exploitation of BYOVD techniques. These protective measures create a multi-layer barrier that makes it harder both to load and to use vulnerable drivers:

Key BYOVD mitigation mechanisms:

Kernel Mode Code Signing (KMCS) — signature enforcement for all drivers loaded into kernel mode. This mechanism ensures that only drivers from trusted developers can be loaded. Attackers are forced to look for vulnerabilities only in signed, legitimate drivers, which narrows the attack vector.

Hypervisor-enforced Code Integrity (HVCI) — isolation and enhanced integrity checking of kernel code performed by the hypervisor (Virtualization-Based Security, VBS). This measure increases exploitation difficulty by isolating kernel code from potentially compromised processes. Combined with VBS, it can protect against writes to critical kernel memory areas, making arbitrary read/write primitives (obtained via BYOVD) less effective.

Kernel Patch Protection (KPP, or PatchGuard) — protection against unauthorized modifications of critical Windows kernel structures and code in real time. PatchGuard blocks attempts by malicious code (executed via a vulnerable driver) to directly change important system tables, hooks, or kernel code required for privilege escalation or security bypass.

Code Integrity Policy (Windows Defender Application Control, WDAC) — a dynamic list containing hashes or certificates of known vulnerable or malicious drivers. It is a direct response to BYOVD. It allows administrators to proactively block loading of drivers whose vulnerabilities have become known and are being actively exploited, even if they have a valid digital signature.

Overall, protecting against BYOVD comes down to two key strategies:

  1. KMCS is the first-level filter that blocks unsigned or forged drivers.

  2. WDAC (Code Integrity Policy) is a “blacklist” for already identified bad actors that revokes the right of a legitimately signed but vulnerable driver to load. This forces attackers to constantly look for new drivers that have not yet been blocked. Note that using this mechanism requires initial setup by security administrators as well as ongoing maintenance of policies to keep them up to date.

Even if a vulnerable driver is successfully loaded and the exploit obtains an arbitrary read/write primitive (for example, via MmMapIoSpace), KPP and HVCI act as the “last line of defense”. They prevent using that primitive to make critical changes to kernel structures (for example, to manipulate access tokens) that are required to ultimately take control of the system.

Thus, OS developers help infrastructure owners improve their chances of protecting corporate information, and administrators only need to use these mechanisms correctly and in a timely manner.

Conclusion

BYOVD is a serious security threat that requires a comprehensive approach to defense. Understanding how this technique works, as well as Windows kernel protection mechanisms, is key to preventing attacks. Driver developers should follow secure programming practices to avoid introducing new vulnerabilities. Continuous monitoring and timely system updates are also important to protect against new threats.

Paranoid Security Analysis of Microsoft Patch Tuesday updates - May 2025 May 13
MS Patch Tuesday Analysis of Microsoft Patch Tuesday updates - May 2025
Paranoid Security Analysis of the Use-After-Free vulnerability in ole32.dll (CVE-2025-21298) May 7
Analysis of the Use-After-Free vulnerability in ole32.dll (CVE-2025-21298)
Paranoid Security Bypassing KASLR in Windows: An attack on the mechanism of pre-selection May 6
Bypassing KASLR in Windows: An attack on the mechanism of pre-selection