Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Driver's Dispatch Routines Hooking

5.00/5 (4 votes)
18 Jan 2018CPOL5 min read 11.9K   272  
This article shows you how to hook driver's dispatch routines.

This article appears in the Third Party Products and Tools section. Articles in this section are for the members only and must not be used to promote or advertise products in any way, shape or form. Please report any spam or advertising.

Introduction

The driver described in this article allows you to log dispatch routines calls (and their relative sequence) for given device object(s). The steps are:

  1. Reference device object
  2. Set hooks for selected dispatch routines
  3. Release hooks and retrieve log info
  4. Dereference device object

Also, the driver allows to view device stack, device parent, and device children.

I used this driver to view device subtree and Pnp dispatch routines called (and their relative sequence) when we plug/unplug/safe remove/enable/disable USB flash drive.

Background

Since we are going to consider USB flash drive, we will outline device nodes used to represent it. See https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/device-tree for more details about device tree and device nodes.

Image 1Image 2

Here, two USB flash drives are connected to USB hub, each one is represented by mass storage device node and its child - disk device node. USB hub is connected to USB host controller, which is connected to PCI bus, which is connected to ACPI, which is connected to Root (we don't show it here for simplicity). You can open device manager and select View - Devices by connection to see more details.

Each device node has associated device stack. In this diagram, FDO is on top and PDO is at the bottom, other device objects are not shown (primary Filter DOs) for simplicity.

Image 3Image 4

  • disk.sys functions as a function driver for disk device.
  • usbstor.sys functions as a bus driver for disk device, and functions as a function driver for mass storage device.
  • iusb3hub.sys functions as a bus driver for mass storage device, and functions as a function driver for hub device.
  • iusb3xhc.sys functions as a bus driver for hub device, and functions as a function driver for host controller device.

Note: Pnp requests are sent to the top device object in the device stack and passed down to the lower device object, so each driver can contribute.

Pseudocode I wrote observing Pnp requests:

C++
Plugged:    // device is plugged event { USB hub PDO }
{
    PnpEnumerate(PDO);
}

Unplugged:    // device is unplugged event { USB hub PDO }
{
    PnpEnumerate(PDO);
}

bool SafeRemove(PDO)                // safe remove request (from tray) { USB mass storage PDO }
{
    // handle removal and ejection relations...
    b = PnpQueryRemoveDevice(PDO);
    if (b) PnpRemoveDevice(PDO);
    else PnpCancelRemoveDevice(PDO);
    return b;
}

bool Enable(PDO)        // enable request (from device manager)
{
    PnpGatherInfo(PDO);
    PnpSendRequest(PDO, { IRP_MN_DEVICE_ENUMERATED });
    PnpConstructStack(PDO);
    PnpSendRequest(PDO, { 0x18 });
    ResInfo[0] = PnpSendRequest(PDO, { IRP_MN_QUERY_RESOURCE_REQUIREMENTS });
    ResInfo[1] = PnpSendRequest(PDO, { IRP_MN_FILTER_RESOURCE_REQUIREMENTS });
    SetInfo(PDO, ResInfo);
    b = PnpSendRequest(PDO, { IRP_MN_START_DEVICE });
    if (b)
    {
        CapInfo = PnpSendRequest(PDO, { IRP_MN_QUERY_CAPABILITIES });
        SetInfo(PDO, CapInfo);
        StateInfo = PnpSendRequest(PDO, { IRP_MN_QUERY_PNP_DEVICE_STATE });
        SetInfo(PDO, StateInfo);
        SetState(PDO, STATE_STARTED);
        PnpEnumerate(PDO);
    }
    else
    {
        PnpSendRequest(PDO, { IRP_MN_REMOVE_DEVICE });
        SetState(PDO, STATE_FAILED);
    }
    return b;
}

bool Disable(PDO)        // disable request (from device manager)
{
    // handle removal relations...
    b = PnpQueryRemoveDevice(PDO);
    if (b)
    {
        PnpRemoveDevice(PDO);
        PnpGatherInfo(PDO);
        PnpSendRequest(PDO, { IRP_MN_QUERY_REMOVE_DEVICE });
        PnpSendRequest(PDO, { IRP_MN_REMOVE_DEVICE });
        PnpSendRequest(PDO, { IRP_MN_DEVICE_ENUMERATED });
    }
    else
    {
        PnpCancelRemoveDevice(PDO);
    }
    return b;
}

void PnpRemoveDevice(PDO)
{
    Children[] = GetChildren(PDO);
    for each Child in Children
    {
        PnpRemoveDevice(Child);
    }
    PnpSendRequest(PDO, { IRP_MN_REMOVE_DEVICE });
    SetState(PDO, STATE_REMOVED);
}

void PnpCancelRemoveDevice(PDO)
{
    PnpSendRequest(PDO, { IRP_MN_CANCEL_REMOVE_DEVICE });
    SetState(PDO, STATE_STARTED);
    Children[] = GetChildren(PDO);
    for each Child in Children
    {
        PnpCancelRemoveDevice(Child);
    }
}

void PnpSurpriseRemoval(PDO)
{
    Children[] = GetChildren(PDO);
    for each Child in Children
    {
        PnpSurpriseRemoval(Child);
    }
    PnpSendRequest(PDO, { IRP_MN_SURPRISE_REMOVAL  });
    SetState(PDO, STATE_SURPRISE_REMOVED);
}

bool PnpQueryRemoveDevice(PDO)
{
    Children[] = GetChildren(PDO);
    for each Child in Children
    {
        if (!PnpQueryRemoveDevice(Child)) return false;
    }
    b = PnpSendRequest(PDO, { IRP_MN_QUERY_REMOVE_DEVICE });
    if (b) SetState(PDO, STATE_REMOVE_PENDING);
    return b;
}

void PnpGatherInfo(PDO)
{
    IdInfo[0] = PnpSendRequest(PDO, { IRP_MN_QUERY_ID, BusQueryDeviceID });
    IdInfo[1] = PnpSendRequest(PDO, { IRP_MN_QUERY_ID, BusQueryInstanceID });
    IdInfo[2] = PnpSendRequest(PDO, { IRP_MN_QUERY_ID, BusQueryHardwareIDs });
    IdInfo[3] = PnpSendRequest(PDO, { IRP_MN_QUERY_ID, BusQueryCompatibleIDs });
    IdInfo[4] = PnpSendRequest(PDO, { IRP_MN_QUERY_ID, BusQueryContainerID });
    SetInfo(PDO, IdInfo);
    CapInfo = PnpSendRequest(PDO, { IRP_MN_QUERY_CAPABILITIES });
    SetInfo(PDO, CapInfo);
    TextInfo[0] = PnpSendRequest(PDO, { IRP_MN_QUERY_DEVICE_TEXT, DeviceTextDescription });
    TextInfo[1] = PnpSendRequest(PDO, { IRP_MN_QUERY_DEVICE_TEXT, DeviceTextLocationInformation });
    SetInfo(PDO, TextInfo);
    BusInfo = PnpSendRequest(PDO, { IRP_MN_QUERY_BUS_INFORMATION });
    SetInfo(PDO, BusInfo);
    ResInfo[0] = PnpSendRequest(PDO, { IRP_MN_QUERY_RESOURCE_REQUIREMENTS });
    ResInfo[1] = PnpSendRequest(PDO, { IRP_MN_QUERY_RESOURCES });
    SetInfo(PDO, ResInfo);
}

void PnpEnumerate(PDO)
{
    if (CanBeParent(PDO))
    {
        ChildrenNew[] = PnpSendRequest(PDO, { IRP_MN_QUERY_DEVICE_RELATIONS, BusRelations  });
        ChildrenOld[] = GetChildren(PDO);
        for each Child in ChildrenNew
        {
            if (!Find(Child, ChildrenOld))
            {
                AddChild(PDO, Child);
                b = Enable(Child);
                // handle result...
            }
        }
        for each Child in ChildrenOld
        {
            if (!Find(Child, ChildrenNew))
            {
                if (GetState(Child) != STATE_REMOVED)
                {
                    // handle removal and ejection relations...
                    PnpSurpriseRemoval(Child);
                }
                else
                {
                    // handle ejection relations...
                }
                PnpRemoveDevice(Child);
                RemoveChild(PDO, Child);
            }
        }
    }
    else
    {
        PnpSendRequest(PDO, { IRP_MN_QUERY_DEVICE_RELATIONS, 0xffffffff });
    }
}

Let's introduce device stack state notion, possible values are:

  1. device stack (and device node) does not exist
  2. device stack contains PDO only
  3. device stack contains PDO and FDO

Vector { X, Y } will denote device stacks states of { disk, mass storage }. Vector shown after user action title denotes initial device stacks states before any requests are processed. Vector shown after request title denotes device stacks states after request is processed (if there is no vector, then device stacks states are not changed).

Observed sequence of Pnp requests for user actions:

USB flash drive is plugged in { 1, 1 }:

  1. { IRP_MN_QUERY_DEVICE_RELATIONS, BusRelations } -> hub -> { 1, 2 }
  2. Configure -> mass storage -> { 1, 3 }
  3. { IRP_MN_QUERY_DEVICE_RELATIONS, BusRelations } -> mass storage -> { 2, 3 }
  4. Configure -> disk -> { 3, 3 }

USB flash drive is plugged out (without safe removal) { 3, 3 }:

  1. { IRP_MN_QUERY_DEVICE_RELATIONS, BusRelations } -> hub
  2. { IRP_MN_SURPRISE_REMOVAL } -> disk
  3. { IRP_MN_SURPRISE_REMOVAL } -> mass storage
  4. { IRP_MN_REMOVE_DEVICE } -> disk -> { 1, 3 }
  5. { IRP_MN_REMOVE_DEVICE } -> mass storage -> { 1, 1 }

USB flash drive safe remove (from tray) { 3, 3 }:

  1. { IRP_MN_QUERY_REMOVE_DEVICE } -> disk
  2. { IRP_MN_QUERY_REMOVE_DEVICE } -> mass storage
  3. { IRP_MN_REMOVE_DEVICE } -> disk -> { 2, 3 }
  4. { IRP_MN_REMOVE_DEVICE } -> mass storage -> { 1, 2 }

USB flash drive is plugged out (after safe remove) { 1, 2 }:

  1. { IRP_MN_QUERY_DEVICE_RELATIONS, BusRelations } -> hub
  2. { IRP_MN_REMOVE_DEVICE } -> mass storage -> { 1, 1 }

Disk disable (from device manager) { 3, 3 }:

  1. { IRP_MN_QUERY_REMOVE_DEVICE } -> disk
  2. { IRP_MN_REMOVE_DEVICE } -> disk -> { 2, 3 }
  3. { IRP_MN_QUERY_REMOVE_DEVICE } -> disk
  4. { IRP_MN_REMOVE_DEVICE } -> disk

Disk enable (from device manager) { 2, 3 }:

  1. Configure -> disk -> { 3, 3 }

Mass storage disable (from device manager) { 3, 3 }:

  1. { IRP_MN_QUERY_REMOVE_DEVICE } -> disk
  2. { IRP_MN_QUERY_REMOVE_DEVICE } -> mass storage
  3. { IRP_MN_REMOVE_DEVICE } -> disk -> { 2, 3 }
  4. { IRP_MN_REMOVE_DEVICE } -> mass storage -> { 1, 2 }
  5. { IRP_MN_QUERY_REMOVE_DEVICE } -> mass storage
  6. { IRP_MN_REMOVE_DEVICE } -> mass storage

Mass storage enable (from device manager) { 1, 2 }:

  1. Configure -> mass storage -> { 1, 3 }
  2. { IRP_MN_QUERY_DEVICE_RELATIONS, BusRelations } -> mass storage -> { 2, 3 }
  3. Configure -> disk -> { 3, 3 }

Using the Code

Driver maintains two linked lists guarded by spinlocks:

  1. List that stores items (DEVICE_INFORMATION) representing referenced device objects
  2. List that stores items (DRIVER_INFORMATION) representing patched driver objects (with dispatch routines pointers replaced)
C++
struct DEVICE_INFORMATION
{
    LIST_ENTRY ListEntry;
    ULONG Id;
    LIST_ENTRY LogList;
    DEVICE_OBJECT *pDeviceObject;
    BOOLEAN HookActive;
    BOOLEAN HookDispatch[DISPATCH_TABLE_LENGTH];
};

struct DRIVER_INFORMATION
{
    LIST_ENTRY ListEntry;
    DRIVER_OBJECT *pDriverObject;
    ULONG HookCount;
    DRIVER_DISPATCH* OriginalDispatch[DISPATCH_TABLE_LENGTH];
};

// Each device object in device stack is represented by device entry:

struct DEVICE_ENTRY
{
    LIST_ENTRY ListEntry;
    ULONG ReferenceCount;
    ULONG DeviceType;
    ULONG Characteristics;
    BOOLEAN Dispatch[DISPATCH_TABLE_LENGTH];
    OBJECT_NAME_INFORMATION *pNameRecord[NAME_RECORD_TABLE_LENGTH];
};

// Each logged call is represented by log entry:

struct LOG_RECORD_INFORMATION
{
    UNICODE_STRING Record;
};

struct LOG_ENTRY
{
    LIST_ENTRY ListEntry;
    ULONG Id;
    LOG_RECORD_INFORMATION *pLogRecord[LOG_RECORD_TABLE_LENGTH];
};

We can use LiveKD to see the layout of internal structures not defined in supplied header files.

dt nt!_DEVICE_NODE

Image 5Image 6

C++
struct DEVICE_NODE
{
    DEVICE_NODE *Sibling;
    DEVICE_NODE *Child;
    DEVICE_NODE *Parent;
    DEVICE_NODE *LastChild;
    DEVICE_OBJECT *PhysicalDeviceObject;
};

dt nt!_OBJECT_HEADER

Image 7Image 8

C++
struct OBJECT_HEADER
{
    LONG PointerCount;
    union
    {
        LONG HandleCount;
        PVOID NextToFree;
    };
    PVOID Lock;
    UCHAR TypeIndex;
    UCHAR TraceFlags;
    UCHAR InfoMask;
    UCHAR Flags;
    union
    {
        PVOID ObjectCreateInfo;
        PVOID QuotaBlockCharged;
    };
    PVOID SecurityDescriptor;
    QUAD Body;
};

Now I will show supported ioctls and their handlers:

  1. C++
    #define IOCTL_DEVICE_REF CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
    
    struct DEVICE_REF_INPUT
    {
        ULONG Length;
        LONG Shift;
        ULONG PathOffset;
    };
    
    struct DEVICE_REF_OUTPUT
    {
        ULONG Length;
        ULONG Id;
    };
    
    NTSTATUS IoctlDeviceRef(DEVICE_REF_INPUT *pInputBuffer, DEVICE_REF_OUTPUT *pOutputBuffer)
    {
        NTSTATUS Status;
        WCHAR *pDevicePath;
        DEVICE_OBJECT *pDeviceObject;
        DEVICE_INFORMATION *pDeviceInfo;
    
        pDevicePath = (WCHAR*)GET_FIELD_POINTER(pInputBuffer, PathOffset);
        Status = GetDeviceObject(pDevicePath, pInputBuffer->Shift, &pDeviceObject);
    
        if (NT_SUCCESS(Status))
        {
            Status = GetDeviceByPointer(pDeviceObject, &pDeviceInfo);
    
            if (!NT_SUCCESS(Status))
            {
                Status = AddDevice(pDeviceObject, &pDeviceInfo);
    
                if (NT_SUCCESS(Status)) pOutputBuffer->Id = pDeviceInfo->Id;
            }
            else Status = STATUS_ALREADY_REGISTERED;
    
            ObDereferenceObject(pDeviceObject);
        }
    
        return Status;
    }
  2. C++
    #define IOCTL_DEVICE_UNREF CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
    
    struct DEVICE_UNREF_INPUT
    {
        ULONG Length;
        ULONG Id;
    };
    
    NTSTATUS IoctlDeviceUnref(DEVICE_UNREF_INPUT *pInputBuffer)
    {
        NTSTATUS Status;
        DEVICE_INFORMATION *pDeviceInfo;
    
        Status = GetDevice(pInputBuffer->Id, &pDeviceInfo);
    
        if (NT_SUCCESS(Status)) RemoveDevice(pDeviceInfo);
    
        return Status;
    }
  3. C++
    #define IOCTL_DEVICE_LIST        
    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
    
    enum DEVICE_LIST_TYPE
    {
        DeviceListSingle,
        DeviceListStack,
        DeviceListDriver,
        DeviceListParent,
        DeviceListChildren
    };
    
    struct DEVICE_LIST_INPUT
    {
        ULONG Length;
        ULONG Id;
        ULONG DeviceListType;
    };
    
    struct DEVICE_LIST_OUTPUT
    {
        ULONG Length;
        LIST_PACKED List;
    };
    
    NTSTATUS IoctlDeviceList(DEVICE_LIST_INPUT *pInputBuffer, DEVICE_LIST_OUTPUT *pOutputBuffer)
    {
        NTSTATUS Status;
        ULONG Count, Length;
        DEVICE_INFORMATION *pDeviceInfo;
        DEVICE_OBJECT **DeviceObjectList;
        DEVICE_OBJECT *pDeviceObject;
        DEVICE_ENTRY *pDeviceEntry;
        DEVICE_NODE *pDeviceNode;
        LIST_ENTRY DeviceList;
        LIST_INFO ListInfo;
        HANDLE Handle;
        IO_STATUS_BLOCK IoStatusBlock;
        OBJECT_ATTRIBUTES ObjectAttributes;
        OBJECT_NAME_INFORMATION *pNameRecord;
    
        Status = GetDevice(pInputBuffer->Id, &pDeviceInfo);
    
        if (NT_SUCCESS(Status))
        {
            InitializeListHead(&DeviceList);
    
            switch (pInputBuffer->DeviceListType)
            {
            case DeviceListDriver:
                pDeviceObject = pDeviceInfo->pDeviceObject;
    
                Status = CreateDeviceObjectList
                         (pDeviceObject->DriverObject, &DeviceObjectList, &Count);
    
                if (NT_SUCCESS(Status))
                {
                    for (ULONG i = 0; i < Count; ++i)
                    {
                        Status = CreateDeviceEntry(DeviceObjectList[i], &pDeviceEntry);
    
                        if (NT_SUCCESS(Status)) InsertTailList(&DeviceList, &pDeviceEntry->ListEntry);
                        else break;
                    }
    
                    ReleaseDeviceObjectList(DeviceObjectList, Count);
                }
    
                break;
            case DeviceListSingle:
                pDeviceObject = pDeviceInfo->pDeviceObject;
    
                Status = CreateDeviceEntry(pDeviceObject, &pDeviceEntry);
    
                if (NT_SUCCESS(Status)) InsertTailList(&DeviceList, &pDeviceEntry->ListEntry);
    
                break;
            case DeviceListStack:
                pDeviceObject = pDeviceInfo->pDeviceObject;
    
                pDeviceNode = GetDeviceNode(pDeviceObject);
    
                if (pDeviceNode)        // PDO
                {
                    Status = CreateNameRecord(pDeviceObject, &pNameRecord);
    
                    if (NT_SUCCESS(Status))
                    {
                        InitializeObjectAttributes(&ObjectAttributes, 
                                &pNameRecord->Name, OBJ_CASE_INSENSITIVE, NULL, NULL);
    
                        Status = ZwOpenFile(&Handle, FILE_GENERIC_READ, 
                                 &ObjectAttributes, &IoStatusBlock, FILE_SHARE_READ | 
                                 FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0);
    
                        if (NT_SUCCESS(Status))   // IRP_MN_REMOVE_DEVICE will not be 
                                                  // sent to the device stack untill handle is opened
                        {
                            pDeviceObject = GetTopDeviceObject(pDeviceObject);
    
                            while (pDeviceObject)
                            {
                                Status = CreateDeviceEntry(pDeviceObject, &pDeviceEntry);
    
                                if (NT_SUCCESS(Status)) InsertTailList
                                          (&DeviceList, &pDeviceEntry->ListEntry);
                                else break;
    
                                pDeviceObject = GetLowerDeviceObject(pDeviceObject);
                            }
    
                            ZwClose(Handle);
                        }
                        else
                        {
                            Status = CreateDeviceEntry(pDeviceObject, &pDeviceEntry);
    
                            if (NT_SUCCESS(Status)) InsertTailList
                                     (&DeviceList, &pDeviceEntry->ListEntry);
                        }
    
                        ReleaseNameRecord(pNameRecord);
                    }
                }
                else Status = STATUS_NOT_SUPPORTED;
    
                break;
            case DeviceListParent:
                pDeviceObject = pDeviceInfo->pDeviceObject;
    
                pDeviceNode = GetDeviceNode(pDeviceObject);
    
                if (pDeviceNode)        // PDO
                {
                    // acquire tree lock...
    
                    pDeviceNode = pDeviceNode->Parent;
    
                    if (pDeviceNode)
                    {
                        pDeviceObject = pDeviceNode->PhysicalDeviceObject;
    
                        Status = CreateDeviceEntry(pDeviceObject, &pDeviceEntry);
    
                        if (NT_SUCCESS(Status)) 
                              InsertTailList(&DeviceList, &pDeviceEntry->ListEntry);
                    }
    
                    // release tree lock...
                }
                else Status = STATUS_NOT_SUPPORTED;
    
                break;
            case DeviceListChildren:
                pDeviceObject = pDeviceInfo->pDeviceObject;
    
                pDeviceNode = GetDeviceNode(pDeviceObject);
    
                if (pDeviceNode)        // PDO
                {
                    // acquire tree lock...
    
                    pDeviceNode = pDeviceNode->Child;
    
                    while (pDeviceNode)
                    {
                        pDeviceObject = pDeviceNode->PhysicalDeviceObject;
    
                        Status = CreateDeviceEntry(pDeviceObject, &pDeviceEntry);
    
                        if (NT_SUCCESS(Status)) InsertTailList(&DeviceList, &pDeviceEntry->ListEntry);
                        else break;
    
                        pDeviceNode = pDeviceNode->Sibling;
                    }
    
                    // release tree lock...
                }
                else Status = STATUS_NOT_SUPPORTED;
    
                break;
            }
    
            if (NT_SUCCESS(Status))
            {
                GetDeviceListInfo(&DeviceList, &ListInfo);
    
                Length = sizeof(DEVICE_LIST_OUTPUT) + ListInfo.HeaderLength + ListInfo.ListLength;
    
                if (Length <= pOutputBuffer->Length)
                {
                    StoreDeviceList(&DeviceList, &ListInfo, &pOutputBuffer->List);
                }
                else Status = STATUS_BUFFER_TOO_SMALL;
    
                pOutputBuffer->Length = Length;
            }
    
            ReleaseDeviceList(&DeviceList);
        }
    
        return Status;
    }
  4. C++
    #define IOCTL_DEVICE_HOOK        
    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
    
    struct DEVICE_HOOK_INPUT
    {
        ULONG Length;
        ULONG Id;
        BOOLEAN Dispatch[DISPATCH_TABLE_LENGTH];
    };
    
    NTSTATUS IoctlDeviceHook(DEVICE_HOOK_INPUT *pInputBuffer)
    {
        NTSTATUS Status;
        DRIVER_OBJECT *pDriverObject;
        DEVICE_INFORMATION *pDeviceInfo;
        DRIVER_INFORMATION *pDriverInfo;
    
        Status = GetDevice(pInputBuffer->Id, &pDeviceInfo);
    
        if (NT_SUCCESS(Status))
        {
            if (!pDeviceInfo->HookActive)
            {
                pDriverObject = pDeviceInfo->pDeviceObject->DriverObject;
    
                Status = GetDriver(pDriverObject, &pDriverInfo);
    
                if (!NT_SUCCESS(Status)) Status = AddDriver(pDriverObject, &pDriverInfo);
    
                if (NT_SUCCESS(Status))
                {
                    ReleaseLogList(&pDeviceInfo->LogList);
    
                    EnableDeviceHook(pDeviceInfo, &pInputBuffer->Dispatch);
    
                    ++pDriverInfo->HookCount;
                }
            }
            else Status = STATUS_UNSUCCESSFUL;
        }
    
        return Status;
    }
  5. C++
    #define IOCTL_DEVICE_UNHOOK        
    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
    
    struct DEVICE_UNHOOK_INPUT
    {
        ULONG Length;
        ULONG Id;
    };
    
    struct DEVICE_UNHOOK_OUTPUT
    {
        ULONG Length;
        LIST_PACKED List;
    };
    
    NTSTATUS IoctlDeviceUnhook(DEVICE_UNHOOK_INPUT *pInputBuffer, DEVICE_UNHOOK_OUTPUT *pOutputBuffer)
    {
        NTSTATUS Status;
        DRIVER_OBJECT *pDriverObject;
        DEVICE_INFORMATION *pDeviceInfo;
        DRIVER_INFORMATION *pDriverInfo;
        LIST_INFO ListInfo;
        ULONG Length;
    
        Status = GetDevice(pInputBuffer->Id, &pDeviceInfo);
    
        if (NT_SUCCESS(Status))
        {
            if (pDeviceInfo->HookActive)
            {
                DisableDeviceHook(pDeviceInfo);
    
                pDriverObject = pDeviceInfo->pDeviceObject->DriverObject;
    
                Status = GetDriver(pDriverObject, &pDriverInfo);
    
                if (NT_SUCCESS(Status))
                {
                    --pDriverInfo->HookCount;
    
                    if (!pDriverInfo->HookCount) RemoveDriver(pDriverInfo);
                }
            }
    
            GetLogListInfo(&pDeviceInfo->LogList, &ListInfo);
    
            Length = sizeof(DEVICE_UNHOOK_OUTPUT)+ListInfo.HeaderLength + ListInfo.ListLength;
    
            if (Length <= pOutputBuffer->Length)
            {
                StoreLogList(&pDeviceInfo->LogList, &ListInfo, &pOutputBuffer->List);
    
                ReleaseLogList(&pDeviceInfo->LogList);
            }
            else Status = STATUS_BUFFER_TOO_SMALL;
    
            pOutputBuffer->Length = Length;
        }
    
        return Status;
    }

Dispatch routine (replaces original dispatch routines):

C++
NTSTATUS DispatchHook(DEVICE_OBJECT *pDeviceObject, IRP *pIrp)
{
    IO_STACK_LOCATION *Stack;
    KLOCK_QUEUE_HANDLE LockHandle;
    DEVICE_INFORMATION *pDeviceInfo;
    DRIVER_INFORMATION *pDriverInfo;
    DEVICE_EXTENSION *pDeviceExtension;
    DRIVER_DISPATCH *pDriverDispatch;
    LOG_ENTRY *pLogEntry;
    NTSTATUS Status;

    pDeviceExtension = (DEVICE_EXTENSION*)g_pDeviceObject->DeviceExtension;

    IoAcquireRemoveLock(&pDeviceExtension->RemoveLock, NULL);

    Stack = IoGetCurrentIrpStackLocation(pIrp);

    KeAcquireInStackQueuedSpinLockAtDpcLevel(&g_DeviceLock, &LockHandle);

    pDeviceInfo = (DEVICE_INFORMATION*)g_DeviceList.Flink;

    while (pDeviceInfo != (DEVICE_INFORMATION*)&g_DeviceList)
    {
        if (pDeviceObject == pDeviceInfo->pDeviceObject)
        {
            if ((pDeviceInfo->HookActive) && (pDeviceInfo->HookDispatch[Stack->MajorFunction]))
            {
                Status = CreateLogEntry(pDeviceObject, Stack, &pLogEntry);

                if (NT_SUCCESS(Status)) InsertTailList(&pDeviceInfo->LogList, &pLogEntry->ListEntry);
            }

            break;
        }

        pDeviceInfo = (DEVICE_INFORMATION*)pDeviceInfo->ListEntry.Flink;
    }

    KeReleaseInStackQueuedSpinLockFromDpcLevel(&LockHandle);

    pDriverDispatch = NULL;

    KeAcquireInStackQueuedSpinLockAtDpcLevel(&g_DriverLock, &LockHandle);

    pDriverInfo = (DRIVER_INFORMATION*)g_DriverList.Flink;

    while (pDriverInfo != (DRIVER_INFORMATION*)&g_DriverList)
    {
        if (pDeviceObject->DriverObject == pDriverInfo->pDriverObject)
        {
            pDriverDispatch = pDriverInfo->OriginalDispatch[Stack->MajorFunction];
            break;
        }

        pDriverInfo = (DRIVER_INFORMATION*)pDriverInfo->ListEntry.Flink;
    }

    KeReleaseInStackQueuedSpinLockFromDpcLevel(&LockHandle);

    if (!pDriverDispatch) pDriverDispatch = 
              pDeviceObject->DriverObject->MajorFunction[Stack->MajorFunction];

    Status = pDriverDispatch(pDeviceObject, pIrp);

    IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, NULL);

    return Status;
}

Create device entry routine:

C++
NTSTATUS CreateDeviceEntry(DEVICE_OBJECT *pDeviceObject, DEVICE_ENTRY **ppDeviceEntry)
{
    NTSTATUS Status;
    DEVICE_ENTRY *pDeviceEntry;
    DRIVER_OBJECT *pDriverObject;
    OBJECT_HEADER *pObjectHeader;

    pDeviceEntry = (DEVICE_ENTRY*)ExAllocatePool(PagedPool, sizeof(DEVICE_ENTRY));

    if (pDeviceEntry)
    {
        pObjectHeader = OBJECT_TO_OBJECT_HEADER(pDeviceObject);
        pDeviceEntry->ReferenceCount = pObjectHeader->PointerCount;
        pDeviceEntry->DeviceType = pDeviceObject->DeviceType;
        pDeviceEntry->Characteristics = pDeviceObject->Characteristics;

        pDriverObject = pDeviceObject->DriverObject;

        for (ULONG i = 0; i < DISPATCH_TABLE_LENGTH; ++i)
        {
            if (pDriverObject->MajorFunction[i]) pDeviceEntry->Dispatch[i] = TRUE;
            else pDeviceEntry->Dispatch[i] = FALSE;
        }

        Status = CreateNameRecord(pDeviceObject, &pDeviceEntry->pNameRecord[NAME_RECORD_DEVICE]);

        if (NT_SUCCESS(Status))
        {
            Status = CreateNameRecord(pDeviceObject->DriverObject, 
                              &pDeviceEntry->pNameRecord[NAME_RECORD_DRIVER]);

            if (NT_SUCCESS(Status))
            {
                *ppDeviceEntry = pDeviceEntry;
            }
            else
            {
                ReleaseNameRecord(pDeviceEntry->pNameRecord[NAME_RECORD_DEVICE]);
                ExFreePool(pDeviceEntry);
            }
        }
        else ExFreePool(pDeviceEntry);
    }
    else Status = STATUS_INSUFFICIENT_RESOURCES;

    return Status;
}

Create log entry routine:

C++
NTSTATUS CreateLogEntry(DEVICE_OBJECT *pDeviceObject, IO_STACK_LOCATION *Stack, LOG_ENTRY **ppLogEntry)
{
    ULONG Value;
    NTSTATUS Status;
    LOG_ENTRY *pLogEntry;
    DEVICE_NODE *pDeviceNode;
    DEVICE_INFORMATION *pDeviceInfoChild;

    pLogEntry = (LOG_ENTRY*)ExAllocatePool(PagedPool, sizeof(LOG_ENTRY));

    if (pLogEntry)
    {
        pLogEntry->Id = g_LogId;
        ++g_LogId;

        for (ULONG i = 0; i < LOG_RECORD_TABLE_LENGTH; ++i) pLogEntry->pLogRecord[i] = NULL;

        switch (Stack->MajorFunction)
        {
        case IRP_MJ_PNP:
            if (Stack->MinorFunction < ARRAYSIZE(g_PNPMinorFunction))
            {
                Status = CreateLogRecord(&pLogEntry->pLogRecord[LOG_RECORD_0], 
                         0, 0, 0, L"%ls - %ls", g_MajorFunction[Stack->MajorFunction], 
                         g_PNPMinorFunction[Stack->MinorFunction]);
            }
            else
            {
                Status = CreateLogRecord(&pLogEntry->pLogRecord[LOG_RECORD_0], 0, 0, 0, 
                         L"%ls - %xh", g_MajorFunction[Stack->MajorFunction], Stack->MinorFunction);
            }

            if (NT_SUCCESS(Status))
            {
                switch (Stack->MinorFunction)
                {
                case IRP_MN_QUERY_DEVICE_RELATIONS:
                    Value = Stack->Parameters.QueryDeviceRelations.Type;

                    if (Value < ARRAYSIZE(g_PNPRelations))
                    {
                        Status = CreateLogRecord(&pLogEntry->pLogRecord[LOG_RECORD_1], 
                                 0, 0, 0, L"%s", g_PNPRelations[Value]);
                    }
                    else
                    {
                        Status = CreateLogRecord(&pLogEntry->pLogRecord[LOG_RECORD_1], 
                                 0, 0, 0, L"%xh", Value);
                    }

                    break;
                }
            }

            break;
        default:
            Status = STATUS_NOT_SUPPORTED;
            break;
        }

        if (NT_SUCCESS(Status))
        {
            *ppLogEntry = pLogEntry;
        }
        else
        {
            for (ULONG i = 0; i < LOG_RECORD_TABLE_LENGTH; ++i)
            {
                if (pLogEntry->pLogRecord[i]) ReleaseLogRecord(pLogEntry->pLogRecord[i]);
            }

            ExFreePool(pLogEntry);
        }
    }
    else Status = STATUS_INSUFFICIENT_RESOURCES;

    return Status;
}

We can run our driver with the following code (run it as administrator):

C++
#include <Windows.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    SC_HANDLE hSCManager;
    SC_HANDLE hService;
    SERVICE_STATUS ServiceStatus;

    hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CREATE_SERVICE);

    if (!hSCManager) DbgRaiseAssertionFailure();

    hService = CreateServiceW(hSCManager, L"Device Info Service Name",
        L"Device Info Display Name",
        SERVICE_START | DELETE | SERVICE_STOP,
        SERVICE_KERNEL_DRIVER,
        SERVICE_DEMAND_START,
        SERVICE_ERROR_IGNORE,
        L"D:\\Data\\New\\DeviceOwner\\x64\\Win7Debug\\DeviceOwner.sys",
        NULL, NULL, NULL, NULL, NULL);

    if (!hService)
    {
        hService = OpenServiceW(hSCManager, L"Device Info Service Name",
            SERVICE_START | DELETE | SERVICE_STOP);

        if (!hService) DbgRaiseAssertionFailure();
    }

    if (!StartServiceW(hService, 0, NULL)) DbgRaiseAssertionFailure();

    printf("Press Enter to close service\n");
    getchar();

    ControlService(hService, SERVICE_CONTROL_STOP, &ServiceStatus);

    DeleteService(hService);
    CloseServiceHandle(hService);

    CloseServiceHandle(hSCManager);
    return 0;
}

Now let's examine USB flash drive subtree and set hooks to see what happens when we safe remove and unplug it. User mode code looks like this (error checking is omitted):

C++
...

int main(int argc, char* argv[])
{    
    HANDLE Handle;
    ULONG *IdTable, Value, N;
    DEVICE_ENTRY_PACKED *pDeviceEntry;
    BOOLEAN Dispatch[DISPATCH_TABLE_LENGTH];

    Handle = CreateFileW(L"\\\\.\\DeviceInfo", 0, FILE_SHARE_READ | FILE_SHARE_WRITE | 
                         FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL);

    if (Handle != INVALID_HANDLE_VALUE)
    {
        N = 4;
        IdTable = (ULONG*)AllocateMemory(sizeof(ULONG) * N);

        IdTable[0] = USBDriveRef(Handle, 0);

        if (IdTable[0] != INVALID_ID)
        {
            SubtreeRef(Handle, IdTable, N);

            for (ULONG i = 0; i < DISPATCH_TABLE_LENGTH; ++i) Dispatch[i] = FALSE;
            Dispatch[IRP_MJ_PNP] = TRUE;

            while (TRUE)
            {
                SubtreeViewStack(Handle, IdTable, N);

                wprintf(L"press Enter to apply hook\n");
                getchar();
                SubtreeHook(Handle, IdTable, N, &Dispatch);

                wprintf(L"press Enter to release hook\n");
                getchar();
                SubtreeUnhook(Handle, IdTable, N, &Dispatch);

                wprintf(L"0: terminate; 1: continue;\n");
                wscanf(L"%u", &Value);
                getchar();
                if (!Value) break;
            }

            SubtreeUnref(Handle, IdTable, N);
        }

        FreeMemory(IdTable);

        CloseHandle(Handle);
    }

    return 0;
}

Run the driver, plug in USB flash drive, run user mode app:

Image 9Image 10

Apply hook, safe remove usb flash drive, release hook:

Image 11Image 12

Continue, apply hook, unplug usb flash drive, release hook:

Image 13Image 14

Terminate user mode app and close the service.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)