键盘过滤驱动卸载蓝屏

Keyboard filter driver unload BSOD

我开发了一个键盘过滤器驱动程序,可以将键盘按钮“1”(Q 按钮上方)更改为“2”。

这个驱动程序工作正常。

但是执行Unload后,按下键盘按键会导致BSOD

如果不按键盘按键加载和卸载驱动,会正常卸载。

当我用 Windbg 检查时,我的驱动程序的 ReadCompletion() 函数即使在卸载后也会被调用。

虽然我调用了 IoDetachDevice () 和 IoDeleteDevice (),但我不知道为什么会这样。

另外,加载驱动后,如果一开始按下键盘按键'1',它不会变成'2'。

然后改的很好

不知道和什么有关

我希望你能找到解决这个问题的方法。

请回答我的问题。

下面是源代码。

#include <wdm.h>

typedef struct
{
    PDEVICE_OBJECT NextLayerDeviceObject;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

const WCHAR next_device_name[] = L"\Device\KeyboardClass0";

const char dbg_name[] = "[Test]";

NTSTATUS IrpSkip(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);

    DbgPrint("%s IrpSkip() Start\n", dbg_name);
    DbgPrint("%s IrpSkip() - MajorFunction %d\n", dbg_name, Stack->MajorFunction);

    IoSkipCurrentIrpStackLocation(Irp);
    ret = IoCallDriver(((PDEVICE_EXTENSION)(DeviceObject->DeviceExtension))->NextLayerDeviceObject, Irp);
    DbgPrint("IoCallDriver return %x\n", ret);
    DbgPrint("%s IrpSkip() End\n", dbg_name);

    return ret;
}

NTSTATUS ReadCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)
{
    NTSTATUS ret = STATUS_SUCCESS;
    PIO_STACK_LOCATION Stack;
    unsigned char key[32];

    DbgPrint("%s ReadCompletion() Start\n", dbg_name);

    if (Irp->IoStatus.Status == STATUS_SUCCESS)
    {
        DbgPrint("%s ReadCompletion() - Success\n", dbg_name);
        RtlCopyMemory(key, Irp->AssociatedIrp.SystemBuffer, 32);
        DbgPrint("%s Data : %d %d %d %d %d %d %d %d\n", dbg_name, key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7]);
        if (key[2] == 2)
        {
            key[2] = 3;
            RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, key, 32);
            DbgPrint("%s Key '1' changed '2'\n", dbg_name);
        }
    }
    //else if (Irp->IoStatus.Status == STATUS_PENDING)
    else
    {
        DbgPrint("%s ReadCompletion() - Fail... %x\n", Irp->IoStatus.Status);
    }

    if (Irp->PendingReturned)
    {
        IoMarkIrpPending(Irp);
    }

    DbgPrint("%s ReadCompletion() End\n", dbg_name);

    return Irp->IoStatus.Status;
}

NTSTATUS Read(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);

    DbgPrint("%s Read() Start\n", dbg_name);

    PDEVICE_EXTENSION device_extension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    //IoCopyCurrentIrpStackLocationToNext(Irp);
    PIO_STACK_LOCATION current_irp = IoGetCurrentIrpStackLocation(Irp);
    PIO_STACK_LOCATION next_irp = IoGetNextIrpStackLocation(Irp);
    *next_irp = *current_irp;

    IoSetCompletionRoutine(Irp, ReadCompletion, DeviceObject, TRUE, TRUE, TRUE);

    ret=IoCallDriver(((PDEVICE_EXTENSION)device_extension)->NextLayerDeviceObject, Irp);

    DbgPrint("%s Read() End\n", dbg_name);

    return ret;
}

NTSTATUS Unload(IN PDRIVER_OBJECT DriverObject)
{
    NTSTATUS ret = STATUS_SUCCESS;

    IoDetachDevice(((PDEVICE_EXTENSION)(DriverObject->DeviceObject->DeviceExtension))->NextLayerDeviceObject);
    IoDeleteDevice(DriverObject->DeviceObject);

    DbgPrint("%s Unload()...\n", dbg_name);

    return ret;
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
    NTSTATUS ret=STATUS_SUCCESS;
    UNICODE_STRING _next_device_name;

    DbgSetDebugFilterState(DPFLTR_DEFAULT_ID, DPFLTR_INFO_LEVEL, TRUE);

    DbgPrint("%s DriverEntry() Start\n", dbg_name);

    RtlInitUnicodeString(&_next_device_name, next_device_name);

    for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION ; i++)
    {
        DriverObject->MajorFunction[i] = IrpSkip;
    }
    DriverObject->DriverUnload = Unload;
    DriverObject->MajorFunction[IRP_MJ_READ] = Read;

    PDEVICE_OBJECT DeviceObject = 0;
    PDEVICE_EXTENSION DeviceExtension;

    ret = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), 0, FILE_DEVICE_KEYBOARD, 0, TRUE, &DeviceObject);
    if (ret == STATUS_SUCCESS)
    {
        DbgPrint("%s DriverEntry() - IoCreateDevice() Success\n", dbg_name);
    }
    else
    {
        DbgPrint("%s DriverEntry() - IoCreateDevice() Fail\n", dbg_name);
        return ret;
    }
    DeviceExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
    DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
    DeviceObject->Flags |= (DO_BUFFERED_IO | DO_POWER_PAGABLE);

    ret = IoAttachDevice(DeviceObject, &_next_device_name, &DeviceExtension->NextLayerDeviceObject);
    if (ret == STATUS_SUCCESS)
    {
        DbgPrint("%s DriverEntry() - IoAttachDevice() Success\n", dbg_name);
    }
    else
    {
        DbgPrint("%s DriverEntry() - IoAttachDevice() Fail\n", dbg_name);
        IoDeleteDevice(DriverObject->DeviceObject);
        return ret;
    }

    DbgPrint("%s DriverEntry() End\n", dbg_name);

    return ret;
}

下面是 Windbg 调用堆栈。

0: kd> k
 # ChildEBP RetAddr  
00 82f33604 82eea083 nt!RtlpBreakWithStatusInstruction
01 82f33654 82eeab81 nt!KiBugCheckDebugBreak+0x1c
02 82f33a1c 82e4c5cb nt!KeBugCheck2+0x68b
03 82f33a1c 975e36e0 nt!KiTrap0E+0x2cf
WARNING: Frame IP not in any known module. Following frames may be wrong.
04 82f33aac 82e83933 <Unloaded_Test.sys>+0x16e0
05 82f33af0 8efed7a2 nt!IopfCompleteRequest+0x128
06 82f33b14 8eea7b74 kbdclass!KeyboardClassServiceCallback+0x2fa
07 82f33b78 82e831b5 i8042prt!I8042KeyboardIsrDpc+0x18c
08 82f33bd4 82e83018 nt!KiExecuteAllDpcs+0xf9
09 82f33c20 82e82e38 nt!KiRetireDpcList+0xd5
0a 82f33c24 00000000 nt!KiIdleLoop+0x38

CallBack函数好像没有正常释放

如何解决这个问题?

这听起来好像解释了你的问题:

Note Only a driver that can guarantee it will not be unloaded before its completion routine finishes can use IoSetCompletionRoutine. Otherwise, the driver must use IoSetCompletionRoutineEx, which prevents the driver from unloading until its completion routine executes.

(来自MSDN documentation for IoSetCompletionRoutine.)


PS:功能生效的一键延迟是可以预料的,因为您的驱动程序没有挂接到加载时已经在进行的读取操作。我不确定是否有任何合理的方法可以做到这一点。

如果您将指针传递给自己的 driver 主体(ReadCompletion 在您的情况下)- driver 在使用此指针之前不得卸载(ReadCompletion 调用并return编辑了你的案例)

如通知Harry Johnston需要使用IoSetCompletionRoutineEx - but documentation for this is bad and not explain all details. absolute mandatory study windows src files (WRK-v1.2 for example) and binary windows code. if you look for implementation of IoSetCompletionRoutineEx - 你可以看到这个程序没有做来阻止你driver 用于卸载。它只是分配小内存块,在此处保存您的 DeviceObjectContextCompletionRoutine 并将 IopUnloadSafeCompletion 设置为完成并将指向已分配内存块的指针设置为上下文。

IopUnloadSafeCompletion在做什么?

NTSTATUS
IopUnloadSafeCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
{
    PIO_UNLOAD_SAFE_COMPLETION_CONTEXT Usc = Context;
    NTSTATUS Status;

    ObReferenceObject (Usc->DeviceObject);

    Status = Usc->CompletionRoutine (DeviceObject, Irp, Usc->Context);

    ObDereferenceObject (Usc->DeviceObject);
    ExFreePool (Usc);
    return Status;
}

但这假设 Usc->DeviceObject 在调用 IopUnloadSafeCompletion 时是有效的。你可以 delete/de-reference DeviceObjectCompletionRoutine 里面,做一些导致你的 driver 卸载的任务 - 并且不会崩溃,因为你的 CompletionRoutine 通过添加引用来保护你的设备。但是如果 IopUnloadSafeCompletion 将在您的设备已经销毁并且 driver 卸载时被调用 - 任何方式都会崩溃。

partial 解决方案将在您的调度例程中调用 ObfReferenceObject(DeviceObject) 并在完成例程中调用 ObfDereferenceObject(DeviceObject) 。这在实践中解决了问题。所以代码必须是下一个

NTSTATUS OnComplete(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID /*Context*/)
{
    ObfDereferenceObject(DeviceObject);// !!!

    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);

    if (Irp->PendingReturned)
    {
        IrpSp->Control |= SL_PENDING_RETURNED;
    }

    if (IrpSp->MajorFunction == IRP_MJ_READ &&
        Irp->IoStatus.Status == STATUS_SUCCESS && 
        (Irp->Flags & IRP_BUFFERED_IO))
    {
        if (ULONG n = (ULONG)Irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA))
        {
            PKEYBOARD_INPUT_DATA pkid = (PKEYBOARD_INPUT_DATA)Irp->AssociatedIrp.SystemBuffer;

            do 
            {
                DbgPrint("Port%x> %x %x\n", pkid->UnitId, pkid->MakeCode, pkid->Flags);
            } while (pkid++, --n);
        }
    }

    return ContinueCompletion;
}

NTSTATUS KbdDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    IoCopyCurrentIrpStackLocationToNext(Irp);

    if (0 > IoSetCompletionRoutineEx(DeviceObject, Irp, OnComplete, NULL, TRUE, TRUE, TRUE))
    {
        IoSkipCurrentIrpStackLocation(Irp);
    }
    else
    {
        ObfReferenceObject(DeviceObject);// !!!
    }

    return IofCallDriver(
        reinterpret_cast<DEVICE_EXTENSION*>(DeviceObject->DeviceExtension)->_NextDeviceObject, Irp);
}

KbdDispatch 中调用 ObfReferenceObject(DeviceObject); 防止卸载您的 driver 直到 ObfDereferenceObject(DeviceObject);OnComplete 中调用。

你可以问在这种情况下 IoSetCompletionRoutineEx at all, if we yourself call ObfReferenceObject / ObfDereferenceObject ? because if DriverUnload already called - all your code hold only on single reference on DeviceObject - so when you call ObfDereferenceObject(DeviceObject); from OnComplete - your device will be deleted and driver unloaded inside ObfDereferenceObject and finally this routine returned to your unloaded code. so sense of IoSetCompletionRoutineEx 是保护你的完成例程。

但需要明白这不是 100% 正确的解决方案。从 DriverUnload 调用 IoDetachDevice/IoDeleteDevice 连接的设备不正确。 (这必须从 IRP_MN_REMOVE_DEVICEFAST_IO_DETACH_DEVICE 回调 调用)

假设下一个场景 - 有人为您的 B 设备所连接的设备 A 呼叫 NtReadFileNtReadFile 通过 IoGetRelatedDeviceObject 获取指向您的 B 设备的指针。在内部这个例程调用 IoGetAttachedDevice。读这个:

IoGetAttachedDevice does not increment the reference count on the device object. (Thus no matching call to ObDereferenceObject is required.) Callers of IoGetAttachedDevice must ensure that no device objects are added to or removed from the stack while IoGetAttachedDevice is executing. Callers that cannot do this must use IoGetAttachedDeviceReference instead.

假设当 NtReadFile 使用指向您的 B 设备的指针时,另一个线程调用您的 DriverUnload 删除 B 设备并卸载 driver。 handle/file object 存在于设备上 A - 这将保留它并防止卸载。但是你附加的 B 设备 什么都没有 。结果,如果 NtReadFile 或使用您的设备的任何其他 I/O 子系统例程与您调用 detach/delete 设备的 DriverUnload 同时执行 - 系统可能已经在 NtReadFile 代码。而你对此无能为力。调用后只有一种方式 IoDetachDevice 调用 IoDeleteDevice 之前等待一些(多少?!)时间。幸运的是这种情况的可能性通常很低。

所以试着理解 - 系统可能已经在 NtReadFile 中崩溃了。即使您的 Dispatch 被调用 - 您的 DeviceObject 可能已经 deleted/not 有效或 driver 在调度程序期间卸载。只有在你调用 ObfReferenceObject(DeviceObject) 之后,一切都正常了。所有这些问题都是因为您尝试在 DriverUnload 中分离附加设备(windows 不是为此设计的)。

还可以注意到您的代码中的许多其他错误。说完成例程不能 return Irp->IoStatus.Status 它必须 return 或 StopCompletion (即 STATUS_MORE_PROCESSING_REQUIRED )或任何其他值 - 通常 ContinueCompletion (即 STATUS_CONTINUE_COMPLETION 或 0) 也不需要硬编码 "\Device\KeyboardClass0",但如果您不使用 wdm driver,则将 IoRegisterPlugPlayNotificationGUID_CLASS_KEYBOARD 一起使用。对于 xp 也需要 IRP_MJ_POWER ( Passing Power IRPs ) 的特殊处理程序,但如果 xp 支持不是实际的,这可能已经不是实际的。

代码示例如下所示:

struct DEVICE_EXTENSION
{
    PDEVICE_OBJECT _NextDeviceObject;
};

NTSTATUS KbdPower(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    PoStartNextPowerIrp(Irp);

    IoSkipCurrentIrpStackLocation(Irp);

    return PoCallDriver(
        reinterpret_cast<DEVICE_EXTENSION*>(DeviceObject->DeviceExtension)->_NextDeviceObject, Irp);
}

NTSTATUS OnComplete(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID /*Context*/)
{
    ObfDereferenceObject(DeviceObject);

    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);

    if (Irp->PendingReturned)
    {
        IrpSp->Control |= SL_PENDING_RETURNED;
    }

    if (IrpSp->MajorFunction == IRP_MJ_READ &&
        Irp->IoStatus.Status == STATUS_SUCCESS && 
        (Irp->Flags & IRP_BUFFERED_IO))
    {
        if (ULONG n = (ULONG)Irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA))
        {
            PKEYBOARD_INPUT_DATA pkid = (PKEYBOARD_INPUT_DATA)Irp->AssociatedIrp.SystemBuffer;

            do 
            {
                DbgPrint("Port%x> %x %x\n", pkid->UnitId, pkid->MakeCode, pkid->Flags);
            } while (pkid++, --n);
        }
    }

    return ContinueCompletion;
}

NTSTATUS KbdDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    IoCopyCurrentIrpStackLocationToNext(Irp);

    if (0 > IoSetCompletionRoutineEx(DeviceObject, Irp, OnComplete, NULL, TRUE, TRUE, TRUE))
    {
        IoSkipCurrentIrpStackLocation(Irp);
    }
    else
    {
        ObfReferenceObject(DeviceObject);
    }

    return IofCallDriver(
        reinterpret_cast<DEVICE_EXTENSION*>(DeviceObject->DeviceExtension)->_NextDeviceObject, Irp);
}

NTSTATUS KbdNotifyCallback(PDEVICE_INTERFACE_CHANGE_NOTIFICATION Notification, PDRIVER_OBJECT DriverObject)
{
    if (::RtlCompareMemory(&Notification->Event, &GUID_DEVICE_INTERFACE_ARRIVAL, sizeof(GUID)) == sizeof(GUID))
    {
        DbgPrint("++%wZ\n", Notification->SymbolicLinkName);

        HANDLE hFile;
        OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, Notification->SymbolicLinkName, OBJ_CASE_INSENSITIVE };
        IO_STATUS_BLOCK iosb;

        if (0 <= IoCreateFile(&hFile, SYNCHRONIZE, &oa, &iosb, 0, 0, FILE_SHARE_VALID_FLAGS, FILE_OPEN, 0, 0, 0, CreateFileTypeNone, 0, IO_ATTACH_DEVICE))
        {
            PFILE_OBJECT FileObject;

            NTSTATUS status = ObReferenceObjectByHandle(hFile, 0, 0, 0, (void**)&FileObject, 0);

            NtClose(hFile);

            if (0 <= status)
            {
                PDEVICE_OBJECT DeviceObject, TargetDevice = IoGetAttachedDeviceReference(FileObject->DeviceObject);

                ObfDereferenceObject(FileObject);

                if (0 <= IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), 0, 
                    TargetDevice->DeviceType, 
                    TargetDevice->Characteristics & (FILE_REMOVABLE_MEDIA|FILE_DEVICE_SECURE_OPEN), 
                    FALSE, &DeviceObject))
                {
                    DeviceObject->Flags |= TargetDevice->Flags & 
                        (DO_BUFFERED_IO|DO_DIRECT_IO|DO_SUPPORTS_TRANSACTIONS|DO_POWER_PAGABLE|DO_POWER_INRUSH);

                    DEVICE_EXTENSION* pExt = (DEVICE_EXTENSION*)DeviceObject->DeviceExtension;

                    if (0 > IoAttachDeviceToDeviceStackSafe(DeviceObject, TargetDevice, &pExt->_NextDeviceObject))
                    {
                        IoDeleteDevice(DeviceObject);
                    }
                    else
                    {
                        DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

                        DbgPrint("++DeviceObject<%p> %x\n", DeviceObject, DeviceObject->Flags);
                    }
                }

                ObfDereferenceObject(TargetDevice);
            }
        }
    }

    return STATUS_SUCCESS;
}

PVOID NotificationEntry;

void KbdUnload(PDRIVER_OBJECT DriverObject)
{
    DbgPrint("KbdUnload(%p)\n", DriverObject);

    if (NotificationEntry) IoUnregisterPlugPlayNotification(NotificationEntry);

    PDEVICE_OBJECT NextDevice = DriverObject->DeviceObject, DeviceObject;

    while (DeviceObject = NextDevice)
    {
        NextDevice = DeviceObject->NextDevice;
        DbgPrint("--DeviceObject<%p>\n", DeviceObject);
        IoDetachDevice(reinterpret_cast<DEVICE_EXTENSION*>(DeviceObject->DeviceExtension)->_NextDeviceObject);
        IoDeleteDevice(DeviceObject);
    }
}

NTSTATUS KbdInit(PDRIVER_OBJECT DriverObject, PUNICODE_STRING /*RegistryPath*/)
{       
    DbgPrint("KbdInit(%p)\n", DriverObject);

    DriverObject->DriverUnload = KbdUnload;

#ifdef _WIN64
    __stosq
#else
    __stosd
#endif
        ((PULONG_PTR)DriverObject->MajorFunction, (ULONG_PTR)KbdDispatch, RTL_NUMBER_OF(DriverObject->MajorFunction));

    ULONG MajorVersion;
    PsGetVersion(&MajorVersion, 0, 0, 0);
    if (MajorVersion < 6) DriverObject->MajorFunction[IRP_MJ_POWER] = KbdPower; 

    IoRegisterPlugPlayNotification(
        EventCategoryDeviceInterfaceChange,
        PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
        (void*)&GUID_CLASS_KEYBOARD, DriverObject,
        (PDRIVER_NOTIFICATION_CALLBACK_ROUTINE)KbdNotifyCallback,
        DriverObject, &NotificationEntry);

    return STATUS_SUCCESS;
}