Understanding the DRIVER_OBJECT Structure in Windows Kernel Development

The DRIVER_OBJECT structure is fundamental to Windows kernel driver development, representing a loaded driver's core attributes. This structure is automatically passed to the driver's DriverEntry entry point and contains critical information such as the driver's size, name, flags, and associated device objects.

Below is the Microsoft-defined structure with key fields annotated:

typedef struct _DRIVER_OBJECT {
    CSHORT Type;                                // Object type identifier
    CSHORT Size;                                // Structure size in bytes
    PDEVICE_OBJECT DeviceObject;                // Pointer to device object list
    ULONG Flags;                                // Driver behavior flags
    PVOID DriverStart;                          // Driver image base address
    ULONG DriverSize;                           // Driver image size
    PVOID DriverSection;                        // Memory section object pointer
    PDRIVER_EXTENSION DriverExtension;          // Extended driver data
    UNICODE_STRING DriverName;                  // Driver name string
    PUNICODE_STRING HardwareDatabase;
    PFAST_IO_DISPATCH FastIoDispatch;
    PDRIVER_INITIALIZE DriverInit;
    PDRIVER_STARTIO DriverStartIo;
    PDRIVER_UNLOAD DriverUnload;                // Unload routine pointer
    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;

Key structure members include:

  • Type: Always set to DRIVER_OBJECT_TYPE
  • Size: Total structure size in bytes
  • DeviceObject: Head of created device object chain
  • DriverStart: Entry point address for driver initialization
  • DriverSize: Size of driver image in memory
  • DriverName: Unicode string containing driver name
  • Flags: Bitmask defining driver I/O characteristics (e.g., DO_BUFFERED_IO)

Driver self-inspection can be implemented by parsing the _DRIVER_OBJECT structure in the entry routine:

#include <ntifs.h>

VOID DriverCleanup(PDRIVER_OBJECT DriverObj)
{
    DbgPrint("Driver unloaded successfully\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObj, PUNICODE_STRING RegPath)
{
    DbgPrint("Driver initialization started\n");
    
    DriverObj->DriverUnload = DriverCleanup;
    
    DbgPrint("Driver name: %wZ\n", DriverObj->DriverName);
    DbgPrint("Image base: %p | Size: %x | End: %p\n", 
             DriverObj->DriverStart, 
             DriverObj->DriverSize,
             (ULONG_PTR)DriverObj->DriverStart + DriverObj->DriverSize);
    
    DbgPrint("Unload routine: %p\n", DriverObj->DriverUnload);
    DbgPrint("IRP_MJ_READ handler: %p\n", DriverObj->MajorFunction[IRP_MJ_READ]);
    DbgPrint("IRP_MJ_WRITE handler: %p\n", DriverObj->MajorFunction[IRP_MJ_WRITE]);
    DbgPrint("IRP_MJ_CREATE handler: %p\n", DriverObj->MajorFunction[IRP_MJ_CREATE]);
    DbgPrint("IRP_MJ_CLOSE handler: %p\n", DriverObj->MajorFunction[IRP_MJ_CLOSE]);
    DbgPrint("IRP_MJ_DEVICE_CONTROL handler: %p\n", DriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL]);
    
    for (int idx = 0; idx < IRP_MJ_MAXIMUM_FUNCTION; idx++)
    {
        DbgPrint("IRP major function %d -> Handler: %p\n", idx, DriverObj->MajorFunction[idx]);
    }
    
    return STATUS_SUCCESS;
}

Executing this driver displays comprehensive driver metadata, including IRP major function dispatch addresses.

The DriverSection field enables system-wide driver enumeration by pointing to a _LDR_DATA_TABLE_ENTRY structure:

typedef struct _LDR_DATA_TABLE_ENTRY {
    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderLinks;
    LIST_ENTRY InInitializationOrderLinks;
    PVOID DllBase;
    PVOID EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    ULONG Flags;
    USHORT LoadCount;
    USHORT TlsIndex;
    union {
        LIST_ENTRY HashLinks;
        struct {
            PVOID SectionPointer;
            ULONG CheckSum;
        };
    };
    union {
        struct {
            ULONG TimeDateStamp;
        };
        struct {
            PVOID LoadedImports;
        };
    };
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

System driver traversal implemantation:

#include <ntifs.h>

typedef struct _LDR_DATA_TABLE_ENTRY {
    // Structure definition from above
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

VOID DriverCleanup(PDRIVER_OBJECT DriverObj)
{
    DbgPrint("Driver unloaded successfully\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObj, PUNICODE_STRING RegPath)
{
    DbgPrint("Starting driver enumeration\n");
    
    DriverObj->DriverUnload = DriverCleanup;
    
    PLDR_DATA_TABLE_ENTRY ldrEntry = (PLDR_DATA_TABLE_ENTRY)DriverObj->DriverSection;
    PLIST_ENTRY listHead = ldrEntry->InLoadOrderLinks.Flink;
    PLIST_ENTRY currentEntry = listHead->Flink;
    
    while (currentEntry != listHead)
    {
        PLDR_DATA_TABLE_ENTRY moduleEntry = CONTAINING_RECORD(currentEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
        
        if (moduleEntry->BaseDllName.Buffer != NULL)
        {
            DbgPrint("Module: %wZ | Base: %p | Entry: %p | Timestamp: %d\n",
                     moduleEntry->BaseDllName,
                     moduleEntry->DllBase,
                     moduleEntry->EntryPoint,
                     moduleEntry->TimeDateStamp);
        }
        currentEntry = currentEntry->Flink;
    }
    
    return STATUS_SUCCESS;
}

This code iterates through all loaded drivers, displaying their names, base addresses, entry points, and timestamps.

Combining this with string comparison functions enables targeted driver lookup:

ULONG_PTR FindDriverBase(PDRIVER_OBJECT DriverObj, UNICODE_STRING TargetName)
{
    PLDR_DATA_TABLE_ENTRY ldrEntry = (PLDR_DATA_TABLE_ENTRY)DriverObj->DriverSection;
    PLIST_ENTRY listHead = ldrEntry->InLoadOrderLinks.Flink;
    PLIST_ENTRY currentEntry = listHead->Flink;
    
    while (currentEntry != listHead)
    {
        PLDR_DATA_TABLE_ENTRY moduleEntry = CONTAINING_RECORD(currentEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
        
        if (moduleEntry->BaseDllName.Buffer != NULL)
        {
            if (RtlCompareUnicodeString(&moduleEntry->BaseDllName, &TargetName, TRUE) == 0)
            {
                return (ULONG_PTR)moduleEntry->DllBase;
            }
        }
        currentEntry = currentEntry->Flink;
    }
    return 0;
}

Usage example:

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObj, PUNICODE_STRING RegPath)
{
    UNICODE_STRING targetDriver;
    
    RtlUnicodeStringInit(&targetDriver, L"WinDDK.sys");
    ULONG_PTR winDDKBase = FindDriverBase(DriverObj, targetDriver);
    DbgPrint("WinDDK.sys base address: %p\n", winDDKBase);
    
    RtlUnicodeStringInit(&targetDriver, L"ACPI.sys");
    ULONG_PTR acpiBase = FindDriverBase(DriverObj, targetDriver);
    DbgPrint("ACPI.sys base address: %p\n", acpiBase);
    
    DriverObj->DriverUnload = DriverCleanup;
    return STATUS_SUCCESS;
}

This appproach returns base addresses for specified drivers, facilitating driver interaction and analysis.

Tags: windows-kernel driver-development driver-object kernel-structures windows-drivers

Posted on Thu, 10 Sep 2026 16:34:34 +0000 by amit beniwal