键盘过滤器驱动程序卸载BSOD

时间:2017-03-17 02:49:23

标签: windows keyboard kernel driver bsod

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

此驱动程序正常。

但是,执行卸载后,按键盘按钮会导致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 Call Stack。

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功能似乎没有正确发布。

如何解决这个问题?

2 个答案:

答案 0 :(得分:2)

这听起来像是在解释你的问题:

  

注意只有在完成例程完成之前不能卸载的驱动程序才能使用IoSetCompletionRoutine。否则,驱动程序必须使用IoSetCompletionRoutineEx,这会阻止驱动程序在完成例程执行之前卸载。

(来自MSDN documentation for IoSetCompletionRoutine。)

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

答案 1 :(得分:2)

如果您将指针传递给自己的驱动程序主体(在您的情况下为ReadCompletion) - 在使用此指针(ReadCompletion调用并返回您的情况)之前,不得卸载驱动程序

通知 Harry Johnston 需要使用IoSetCompletionRoutineEx - 但是这方面的文档很糟糕,并没有解释所有细节。绝对必要的研究windows src文件(例如WRK-v1.2)和二进制windows代码。如果您查找IoSetCompletionRoutineEx的实现 - 您可以查看此例程没有做以防止驱动程序卸载。它只是分配小内存块,在此保存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时有效。您可以在DeviceObject内删除/取消引用CompletionRoutine,执行导致您的驱动程序卸载的任务 - 并且不会崩溃,因为您的CompletionRoutine通过添加对您的设备的引用来保护。但是如果你的设备已经被销毁并且驱动程序被卸载,将会调用IopUnloadSafeCompletion - 任何方式都会崩溃。

部分解决方案将在您的调度例程中调用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);
}

ObfReferenceObject(DeviceObject);中致电KbdDispatch,阻止卸载您的驱动程序,直到ObfDereferenceObject(DeviceObject);OnComplete内调用。

如果我们自己致电ObfReferenceObject / ObfDereferenceObject,您可以在这种情况下IoSetCompletionRoutineEx询问什么?因为如果DriverUnload已经被调用 - 您的所有代码都只保留在DeviceObject上的单个引用 - 那么当您从ObfDereferenceObject(DeviceObject);调用OnComplete时 - 您的设备将被删除并且驱动程序在内部卸载ObfDereferenceObject最后,此例程返回到已卸载的代码。所以IoSetCompletionRoutineEx的感觉就是保护你的完成程序。

但需要明白,这无论如何都不是100%正确的解决方案。从IoDetachDevice/IoDeleteDevice致电DriverUnload附加设备不正确。 (必须从IRP_MN_REMOVE_DEVICEFAST_IO_DETACH_DEVICE回调调用)

假设下一个场景 - 有人为NtReadFile设备所附加的设备A致电BNtReadFile通过B获取指向IoGetRelatedDeviceObject设备的指针。在这个例程中调用IoGetAttachedDevice。读这个:

  

IoGetAttachedDevice 不会增加引用计数   设备对象。 (因此没有与 ObDereferenceObject 的匹配调用    IoGetAttachedDevice 的来电者必须确保没有设备   对象被添加到堆栈或从堆栈中删除    IoGetAttachedDevice 正在执行。无法执行此操作的呼叫者必须使用    IoGetAttachedDeviceReference 代替。

假设NtReadFile使用指向B设备的指针,另一个称为DriverUnload的线程删除B设备并卸载驱动程序。句柄/文件对象存在于设备A上 - 这样可以保留它并防止卸载。但是您附加的B设备没有任何内容。因此,如果NtReadFile或使用您的设备的任何其他I / O子系统例程与调用分离/删除设备的DriverUnload同时执行 - 系统可能已在NtReadFile代码内崩溃。你无能为力。调用IoDetachDevice后,只有一种方式可以在调用IoDeleteDevice之前等待一些(多少?!)时间。幸运的是,这种情况的可能性很低。

因此请尝试理解 - 系统可能已在NtReadFile崩溃。即使您的Dispatch调用了 - 您的DeviceObject可以在调度例程中被删除/无效或驱动程序已卸载。只有在你致电ObfReferenceObject(DeviceObject)之后,一切都变好了。以及所有这个问题,因为您尝试在DriverUnload中分离连接的设备(不是为此设计的窗口)。

还可以注意到代码中的许多其他错误。说完成例程不能返回Irp->IoStatus.Status它必须返回或StopCompletion(即STATUS_MORE_PROCESSING_REQUIRED)或任何其他值 - 通常ContinueCompletion(即STATUS_CONTINUE_COMPLETION或0)也如果您不是驱动程序,则无需硬编码"\\Device\\KeyboardClass0",而是将IoRegisterPlugPlayNotificationGUID_CLASS_KEYBOARD一起使用。同样对于xp需要IRP_MJ_POWERPassing 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;
}