Last active
September 12, 2023 11:02
-
-
Save Barakat/45e56b77fb708f9e3a61ee4df794194d to your computer and use it in GitHub Desktop.
Kbdclass kernel filter driver to log scan-codes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <wdm.h> | |
| #include <ntddkbd.h> | |
| // | |
| // Per-device object extension | |
| // | |
| typedef struct _DEVICE_EXTENSTION | |
| { | |
| // | |
| // Driver must not be deleted as long as there is a pending IRP | |
| // | |
| LONG PendingIrp; | |
| // | |
| // Reference to the attached device so we pass the IRP down to it | |
| // | |
| PDEVICE_OBJECT AttachedDevice; | |
| } DEVICE_EXTENSTION, *PDEVICE_EXTENSTION; | |
| // | |
| // Linked list of scan code information | |
| // | |
| typedef struct _SCAN_CODE_INFORMATION | |
| { | |
| LIST_ENTRY ListEntry; | |
| USHORT UnitId; | |
| USHORT MakeCode; | |
| USHORT Flags; | |
| ULONG ExtraInformation; | |
| } SCAN_CODE_INFORMATION, *PSCAN_CODE_INFORMATION; | |
| // | |
| // Definition of undocumented routine ObReferenceObjectByName | |
| // | |
| typedef NTSTATUS (NTAPI *FPN_OB_REFERENCE_OBJECT_BY_NAME)(PUNICODE_STRING ObjectName, | |
| ULONG Attributes, | |
| PACCESS_STATE AccessState, | |
| ACCESS_MASK DesiredAccess, | |
| POBJECT_TYPE ObjectType, | |
| KPROCESSOR_MODE AccessMode, | |
| PVOID ParseContext, | |
| PVOID* Object); | |
| // | |
| // Global variables | |
| // | |
| // | |
| // Head of the list for sending scan code down to the worker thread. Use atomic methods | |
| // for accessing the list | |
| // | |
| static SCAN_CODE_INFORMATION ScanCodeInformationHead; | |
| // | |
| // Lock to protect access to the list | |
| // | |
| static KSPIN_LOCK ScanCodeInformationHeadSpinLock; | |
| // | |
| // Event to notify the consumer about pending data | |
| // | |
| static KEVENT ScanCodeInformationHeadPendingDataEvent; | |
| // | |
| // Event to notify the consumer to exit | |
| // | |
| static KEVENT EndConsumerEvent; | |
| // | |
| // Reference to the consumer thread | |
| // | |
| static PETHREAD ConsumerThreadObject; | |
| // | |
| // Use this allocator to allocate and free entries in the list | |
| // | |
| static LOOKASIDE_LIST_EX ScanCodeInformationAllocator; | |
| // | |
| // Consumer thread to downgrade processing down to passive level | |
| // | |
| static | |
| VOID | |
| _Function_class_(KSTART_ROUTINE) | |
| ConsumerThreadRoutine(PVOID StartContext) | |
| { | |
| UNREFERENCED_PARAMETER(StartContext); | |
| for (;;) | |
| { | |
| PVOID WaitableObjects[2] = | |
| { | |
| &ScanCodeInformationHeadPendingDataEvent, | |
| &EndConsumerEvent | |
| }; | |
| // | |
| // Wait for any event | |
| // | |
| const NTSTATUS Status = KeWaitForMultipleObjects(2, | |
| WaitableObjects, | |
| WaitAny, | |
| Executive, | |
| KernelMode, | |
| FALSE, | |
| NULL, | |
| NULL); | |
| // | |
| // Consume any pending work | |
| // | |
| PLIST_ENTRY ListEntry; | |
| while ((ListEntry = ExInterlockedRemoveHeadList(&ScanCodeInformationHead.ListEntry, | |
| &ScanCodeInformationHeadSpinLock)) != NULL) | |
| { | |
| const PSCAN_CODE_INFORMATION ScanCodeInformation = CONTAINING_RECORD(ListEntry, SCAN_CODE_INFORMATION, ListEntry); | |
| DbgPrint("[!] Consumer thread (UnitId = %hu, MakeCode = %hu, Flags = 0x%02hx, ExtraInformation = 0x%08lx)\n", | |
| ScanCodeInformation->UnitId, | |
| ScanCodeInformation->MakeCode, | |
| ScanCodeInformation->Flags, | |
| ScanCodeInformation->ExtraInformation); | |
| ExFreeToLookasideListEx(&ScanCodeInformationAllocator, ScanCodeInformation); | |
| } | |
| // | |
| // Exit if we received an exit event | |
| // | |
| if (Status == STATUS_WAIT_1) | |
| { | |
| break; | |
| } | |
| } | |
| } | |
| // | |
| // Driver unload routine | |
| // | |
| static | |
| VOID | |
| _Function_class_(DRIVER_UNLOAD) | |
| DriverUnload(PDRIVER_OBJECT DriverObject) | |
| { | |
| UNREFERENCED_PARAMETER(DriverObject); | |
| ULONG SuccessfulDetachments = 0; | |
| // Wait for 0.25 seconds | |
| LARGE_INTEGER WaitInterval; | |
| WaitInterval.QuadPart = -250000; | |
| // | |
| // Iterate over any create device, detach the filter and delete the device object | |
| // | |
| PDEVICE_OBJECT KeyboardClassFilterDevice = DriverObject->DeviceObject; | |
| while (KeyboardClassFilterDevice != NULL) | |
| { | |
| const PDEVICE_EXTENSTION DeviceExtenstion = KeyboardClassFilterDevice->DeviceExtension; | |
| const PDEVICE_OBJECT KeyboardClassFilterDeviceNext = KeyboardClassFilterDevice->NextDevice; | |
| // | |
| // Detach the filter from the stack | |
| // | |
| IoDetachDevice(DeviceExtenstion->AttachedDevice); | |
| // | |
| // By now, we no longer revive any new IRPs, but there might be some pending IRP that has not yet been completed. | |
| // Wait for it before deleting the device object | |
| // | |
| do | |
| { | |
| KeDelayExecutionThread(KernelMode, FALSE, &WaitInterval); | |
| } | |
| while (InterlockedCompareExchange(&DeviceExtenstion->PendingIrp, 0, 0) > 0); | |
| // | |
| // The reference counter must drop to zero | |
| // | |
| ASSERT(DeviceExtenstion->PendingIrp >= 0); | |
| // | |
| // Delete the device object | |
| // | |
| IoDeleteDevice(KeyboardClassFilterDevice); | |
| DbgPrint("[*] Keyboard filter device #%lu has been detached\n", SuccessfulDetachments++); | |
| // | |
| // Go to next device | |
| // | |
| KeyboardClassFilterDevice = KeyboardClassFilterDeviceNext; | |
| } | |
| // | |
| // Signal the consumer thread to exit | |
| // | |
| KeSetEvent(&EndConsumerEvent, 0, FALSE); | |
| // | |
| // Wait for the consumer thread exit | |
| // | |
| const NTSTATUS Status = KeWaitForSingleObject(ConsumerThreadObject, Executive, KernelMode, FALSE, NULL); | |
| ASSERT(Status == STATUS_WAIT_0); | |
| // | |
| // Dereference the thread object | |
| // | |
| ObDereferenceObject(ConsumerThreadObject); | |
| // | |
| // Remove the look-aside list list | |
| // | |
| ExDeleteLookasideListEx(&ScanCodeInformationAllocator); | |
| DbgPrint("[*] Driver has been unloaded\n"); | |
| } | |
| // | |
| // Pass down the current IRP to the next device in the stack | |
| // | |
| static | |
| NTSTATUS | |
| _Function_class_(DRIVER_DISPATCH) | |
| DispatchPassThrough(PDEVICE_OBJECT DeviceObject, PIRP Irp) | |
| { | |
| IoSkipCurrentIrpStackLocation(Irp); | |
| return IoCallDriver(((PDEVICE_EXTENSTION)DeviceObject->DeviceExtension)->AttachedDevice, Irp); | |
| } | |
| // | |
| // This routine will be invoked when the IRP request is completed | |
| // | |
| // Reference: https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/implementing-an-iocompletion-routine | |
| // | |
| static | |
| NTSTATUS | |
| _Function_class_(IO_COMPLETION_ROUTINE) | |
| CompletionRoutine(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context) | |
| { | |
| UNREFERENCED_PARAMETER(Context); | |
| const PDEVICE_EXTENSTION DeviceExtenstion = DeviceObject->DeviceExtension; | |
| // | |
| // If PendingReturned is true, we must call IoMarkIrpPending on the current IRP | |
| // | |
| if (Irp->PendingReturned) | |
| { | |
| IoMarkIrpPending(Irp); | |
| } | |
| // | |
| // Process successful IRPs by pushing work to the worker thread | |
| // | |
| if (Irp->IoStatus.Status == STATUS_SUCCESS) | |
| { | |
| const ULONG ScanCodesCount = Irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA); | |
| const PKEYBOARD_INPUT_DATA KeyboardInputData = Irp->AssociatedIrp.SystemBuffer; | |
| for (ULONG I = 0; I < ScanCodesCount; ++I) | |
| { | |
| const PKEYBOARD_INPUT_DATA CurrentKeyboardInputData = &KeyboardInputData[I]; | |
| PSCAN_CODE_INFORMATION ScanCodeInformation = ExAllocateFromLookasideListEx(&ScanCodeInformationAllocator); | |
| ScanCodeInformation->UnitId = CurrentKeyboardInputData->UnitId; | |
| ScanCodeInformation->MakeCode = CurrentKeyboardInputData->MakeCode; | |
| ScanCodeInformation->Flags = CurrentKeyboardInputData->Flags; | |
| ScanCodeInformation->ExtraInformation = CurrentKeyboardInputData->ExtraInformation; | |
| ExInterlockedInsertTailList(&ScanCodeInformationHead.ListEntry, | |
| &ScanCodeInformation->ListEntry, | |
| &ScanCodeInformationHeadSpinLock); | |
| KeSetEvent(&ScanCodeInformationHeadPendingDataEvent, 0, FALSE); | |
| } | |
| } | |
| // | |
| // Decrement the pending IRPs reference counter to allow waiting DriverUnload to continue | |
| // | |
| InterlockedDecrement(&DeviceExtenstion->PendingIrp); | |
| // | |
| // We are "hooking" into the stack and not doing actual processing so we can not determine | |
| // if this request needs more processing | |
| // | |
| return STATUS_SUCCESS; | |
| } | |
| // | |
| // Intercept keyboard strokes | |
| // | |
| static | |
| NTSTATUS | |
| _Function_class_(DRIVER_DISPATCH) | |
| DispatchRead(PDEVICE_OBJECT DeviceObject, PIRP Irp) | |
| { | |
| const PDEVICE_EXTENSTION DeviceExtenstion = DeviceObject->DeviceExtension; | |
| // | |
| // Increment the pending IRPs counter | |
| // | |
| InterlockedIncrement(&DeviceExtenstion->PendingIrp); | |
| IoCopyCurrentIrpStackLocationToNext(Irp); | |
| IoSetCompletionRoutine(Irp, CompletionRoutine, NULL, TRUE, TRUE, TRUE); | |
| return IoCallDriver(DeviceExtenstion->AttachedDevice, Irp); | |
| } | |
| // | |
| // Driver entry routine | |
| // | |
| NTSTATUS | |
| NTAPI | |
| DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) | |
| { | |
| UNREFERENCED_PARAMETER(RegistryPath); | |
| ULONG SuccessfulAttachments = 0; | |
| // | |
| // Setup unload function | |
| // | |
| DriverObject->DriverUnload = DriverUnload; | |
| // | |
| // Setup dispatch function that just passes the IRP down the stack | |
| // | |
| for (ULONG I = 0; I < IRP_MJ_MAXIMUM_FUNCTION; ++I) | |
| { | |
| DriverObject->MajorFunction[I] = DispatchPassThrough; | |
| } | |
| // | |
| // We are only interested in read IRPs | |
| // | |
| DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead; | |
| // | |
| // Initialize the linked list head | |
| // | |
| InitializeListHead(&ScanCodeInformationHead.ListEntry); | |
| // | |
| // Lock to protect the ScanCodeInformationHead from concurrent access | |
| // | |
| KeInitializeSpinLock(&ScanCodeInformationHeadSpinLock); | |
| // | |
| // Event to tell the worker thread about pending data | |
| // | |
| KeInitializeEvent(&ScanCodeInformationHeadPendingDataEvent, SynchronizationEvent, FALSE); | |
| // | |
| // Event to tell the worker thread to exit | |
| // | |
| KeInitializeEvent(&EndConsumerEvent, NotificationEvent, FALSE); | |
| // | |
| // Create a look-aside list for allocating list entries | |
| // | |
| NTSTATUS Status = ExInitializeLookasideListEx(&ScanCodeInformationAllocator, | |
| NULL, | |
| NULL, | |
| NonPagedPool, | |
| EX_LOOKASIDE_LIST_EX_FLAGS_RAISE_ON_FAIL, | |
| sizeof(SCAN_CODE_INFORMATION), | |
| 'kPt8', | |
| 0); | |
| if (!NT_SUCCESS(Status)) | |
| { | |
| return STATUS_UNSUCCESSFUL; | |
| } | |
| // | |
| // Create worker thread | |
| // | |
| HANDLE ConsumerThread; | |
| Status = PsCreateSystemThread(&ConsumerThread, | |
| THREAD_ALL_ACCESS, | |
| NULL, | |
| NULL, | |
| NULL, | |
| ConsumerThreadRoutine, | |
| NULL); | |
| if (!NT_SUCCESS(Status)) | |
| { | |
| ExDeleteLookasideListEx(&ScanCodeInformationAllocator); | |
| return Status; | |
| } | |
| // | |
| // Get a reference to the thread object so we can wait for the thread to exit | |
| // | |
| Status = ObReferenceObjectByHandle(ConsumerThread, | |
| THREAD_ALL_ACCESS, | |
| *PsThreadType, | |
| KernelMode, | |
| (PVOID)&ConsumerThreadObject, | |
| NULL); | |
| // | |
| // We (should) have a direct reference to the object itself, we don't need the handle | |
| // | |
| ZwClose(ConsumerThread); | |
| // This should not fail | |
| if (!NT_SUCCESS(Status)) | |
| { | |
| ASSERT(FALSE); | |
| ExDeleteLookasideListEx(&ScanCodeInformationAllocator); | |
| return Status; | |
| } | |
| // | |
| // Resolve IoDeviceObjectType and IoDeviceObjectType types | |
| // | |
| UNICODE_STRING Unicode; | |
| RtlInitUnicodeString(&Unicode, L"IoDriverObjectType"); | |
| const POBJECT_TYPE* IoDriverObjectType = MmGetSystemRoutineAddress(&Unicode); | |
| RtlInitUnicodeString(&Unicode, L"IoDeviceObjectType"); | |
| const POBJECT_TYPE* IoDeviceObjectType = MmGetSystemRoutineAddress(&Unicode); | |
| if (IoDriverObjectType != NULL && IoDeviceObjectType != NULL) | |
| { | |
| // | |
| // Resolve ObReferenceObjectByName | |
| // | |
| RtlInitUnicodeString(&Unicode, L"ObReferenceObjectByName"); | |
| #pragma warning(suppress: 4152) | |
| const FPN_OB_REFERENCE_OBJECT_BY_NAME ObReferenceObjectByName = MmGetSystemRoutineAddress(&Unicode); | |
| if (ObReferenceObjectByName != NULL) | |
| { | |
| // | |
| // Get a pointer to the KbdClass class driver | |
| // | |
| RtlInitUnicodeString(&Unicode, L"\\Driver\\KbdClass"); | |
| PDRIVER_OBJECT KbdClassDriver; | |
| Status = ObReferenceObjectByName(&Unicode, | |
| OBJ_CASE_INSENSITIVE, | |
| 0, | |
| 0, | |
| *IoDriverObjectType, | |
| KernelMode, | |
| NULL, | |
| (PVOID)&KbdClassDriver); | |
| if (NT_SUCCESS(Status)) | |
| { | |
| DbgPrint("[*] KbdClass driver been referenced\n"); | |
| // | |
| // Get a reference to all keyboard devices created by KbdClass | |
| // | |
| PDEVICE_OBJECT KeyboardClassDevice = KbdClassDriver->DeviceObject; | |
| while (KeyboardClassDevice != NULL) | |
| { | |
| // | |
| // Reference the keyboard device | |
| // | |
| Status = ObReferenceObjectByPointer(KeyboardClassDevice, | |
| 0, | |
| *IoDeviceObjectType, | |
| KernelMode); | |
| if (NT_SUCCESS(Status)) | |
| { | |
| DbgPrint("[*] Referenced a keyboard class #%lu created by KbdClass\n", | |
| SuccessfulAttachments); | |
| // | |
| // Attach our drivers to the keyboard device object | |
| // | |
| PDEVICE_OBJECT KeyboardClassFilterDevice; | |
| Status = IoCreateDevice(DriverObject, | |
| sizeof(DEVICE_EXTENSTION), | |
| NULL, | |
| FILE_DEVICE_KEYBOARD, | |
| 0, | |
| TRUE, | |
| &KeyboardClassFilterDevice); | |
| if (NT_SUCCESS(Status)) | |
| { | |
| // | |
| // Attach our object to the top of the chain | |
| // | |
| const PDEVICE_EXTENSTION DeviceExtenstion = KeyboardClassFilterDevice->DeviceExtension; | |
| DeviceExtenstion->PendingIrp = 0; | |
| DeviceExtenstion->AttachedDevice = IoAttachDeviceToDeviceStack(KeyboardClassFilterDevice, | |
| KeyboardClassDevice); | |
| // | |
| // We have failed to attach this device, delete it | |
| // | |
| if (DeviceExtenstion->AttachedDevice == NULL) | |
| { | |
| IoDeleteDevice(KeyboardClassFilterDevice); | |
| } | |
| else | |
| { | |
| DbgPrint("[*] Filter has been attached to the stack of a keyboard device #%lu\n", | |
| SuccessfulAttachments); | |
| // | |
| // The keyboard class driver is using buffered IO. We need to clear the flag DO_DEVICE_INITIALIZING | |
| // meaning that we have done initializing and we are ready to receive IRPs | |
| // | |
| KeyboardClassFilterDevice->Flags |= DO_BUFFERED_IO; | |
| KeyboardClassFilterDevice->Flags &= ~DO_DEVICE_INITIALIZING; | |
| ++SuccessfulAttachments; | |
| } | |
| } | |
| // | |
| // We no longer need this reference | |
| // | |
| ObDereferenceObject(KeyboardClassDevice); | |
| } | |
| KeyboardClassDevice = KeyboardClassDevice->NextDevice; | |
| } | |
| // | |
| // KbdClassDriver reference is no longer needed | |
| // | |
| ObDereferenceObject(KbdClassDriver); | |
| } | |
| } | |
| } | |
| DbgPrint("[*] Driver has been loaded\n"); | |
| // | |
| // Did we create at least one filter? | |
| // | |
| return SuccessfulAttachments != 0 ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment