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:
- Reference device object
- Set hooks for selected dispatch routines
- Release hooks and retrieve log info
- 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.
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.
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:
Plugged: {
PnpEnumerate(PDO);
}
Unplugged: {
PnpEnumerate(PDO);
}
bool SafeRemove(PDO) {
b = PnpQueryRemoveDevice(PDO);
if (b) PnpRemoveDevice(PDO);
else PnpCancelRemoveDevice(PDO);
return b;
}
bool Enable(PDO) {
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) {
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);
}
}
for each Child in ChildrenOld
{
if (!Find(Child, ChildrenNew))
{
if (GetState(Child) != STATE_REMOVED)
{
PnpSurpriseRemoval(Child);
}
else
{
}
PnpRemoveDevice(Child);
RemoveChild(PDO, Child);
}
}
}
else
{
PnpSendRequest(PDO, { IRP_MN_QUERY_DEVICE_RELATIONS, 0xffffffff });
}
}
Let's introduce device stack state notion, possible values are:
- device stack (and device node) does not exist
- device stack contains PDO only
- 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 }:
{ IRP_MN_QUERY_DEVICE_RELATIONS, BusRelations }
-> hub -> { 1, 2 }
- Configure -> mass storage ->
{ 1, 3 }
{ IRP_MN_QUERY_DEVICE_RELATIONS, BusRelations }
-> mass storage -> { 2, 3 }
- Configure -> disk ->
{ 3, 3 }
USB flash drive is plugged out (without safe removal) { 3, 3 }
:
{ IRP_MN_QUERY_DEVICE_RELATIONS, BusRelations }
-> hub { IRP_MN_SURPRISE_REMOVAL }
-> disk { IRP_MN_SURPRISE_REMOVAL }
-> mass storage { IRP_MN_REMOVE_DEVICE }
-> disk -> { 1, 3 }
{ IRP_MN_REMOVE_DEVICE }
-> mass storage -> { 1, 1 }
USB flash drive safe remove (from tray) { 3, 3 }
:
{ IRP_MN_QUERY_REMOVE_DEVICE }
-> disk { IRP_MN_QUERY_REMOVE_DEVICE }
-> mass storage { IRP_MN_REMOVE_DEVICE }
-> disk -> { 2, 3 }
{ IRP_MN_REMOVE_DEVICE }
-> mass storage -> { 1, 2 }
USB flash drive is plugged out (after safe remove) { 1, 2 }
:
{ IRP_MN_QUERY_DEVICE_RELATIONS, BusRelations }
-> hub { IRP_MN_REMOVE_DEVICE }
-> mass storage -> { 1, 1 }
Disk disable (from device manager) { 3, 3 }
:
{ IRP_MN_QUERY_REMOVE_DEVICE }
-> disk { IRP_MN_REMOVE_DEVICE }
-> disk -> { 2, 3 }
{ IRP_MN_QUERY_REMOVE_DEVICE }
-> disk { IRP_MN_REMOVE_DEVICE }
-> disk
Disk enable (from device manager) { 2, 3 }
:
- Configure -> disk ->
{ 3, 3 }
Mass storage disable (from device manager) { 3, 3 }
:
{ IRP_MN_QUERY_REMOVE_DEVICE }
-> disk { IRP_MN_QUERY_REMOVE_DEVICE }
-> mass storage { IRP_MN_REMOVE_DEVICE }
-> disk -> { 2, 3 }
{ IRP_MN_REMOVE_DEVICE }
-> mass storage -> { 1, 2 }
{ IRP_MN_QUERY_REMOVE_DEVICE }
-> mass storage { IRP_MN_REMOVE_DEVICE }
-> mass storage
Mass storage enable (from device manager) { 1, 2 }
:
- Configure -> mass storage ->
{ 1, 3 }
{ IRP_MN_QUERY_DEVICE_RELATIONS, BusRelations }
-> mass storage -> { 2, 3 }
- Configure -> disk ->
{ 3, 3 }
Using the Code
Driver maintains two linked lists guarded by spinlocks:
- List that stores items (
DEVICE_INFORMATION
) representing referenced device objects - List that stores items (
DRIVER_INFORMATION
) representing patched driver objects (with dispatch routines pointers replaced)
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];
};
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];
};
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
struct DEVICE_NODE
{
DEVICE_NODE *Sibling;
DEVICE_NODE *Child;
DEVICE_NODE *Parent;
DEVICE_NODE *LastChild;
DEVICE_OBJECT *PhysicalDeviceObject;
};
dt nt!_OBJECT_HEADER
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:
-
#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;
}
-
#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;
}
-
#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) {
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)) {
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) {
pDeviceNode = pDeviceNode->Parent;
if (pDeviceNode)
{
pDeviceObject = pDeviceNode->PhysicalDeviceObject;
Status = CreateDeviceEntry(pDeviceObject, &pDeviceEntry);
if (NT_SUCCESS(Status))
InsertTailList(&DeviceList, &pDeviceEntry->ListEntry);
}
}
else Status = STATUS_NOT_SUPPORTED;
break;
case DeviceListChildren:
pDeviceObject = pDeviceInfo->pDeviceObject;
pDeviceNode = GetDeviceNode(pDeviceObject);
if (pDeviceNode) {
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;
}
}
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;
}
-
#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;
}
-
#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):
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:
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:
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):
#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):
...
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:
Apply hook, safe remove usb flash drive, release hook:
Continue, apply hook, unplug usb flash drive, release hook:
Terminate user mode app and close the service.