0%

内核驱动实现键盘按键过滤(2)

内核驱动中实现键盘过滤

在WINDOWS中所有接入的键盘设备,都存储在 kbdclass.sys 这个驱动中。
我们可以通过监控 键盘设备接入 的动作,来遍历 kbdclass.sys 驱动中所有的设备。
然后创建 过滤设备 并附加到 键盘设备 上,就可以拦截到该设备 键盘按键 的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <ntifs.h>
#include <ntddkbd.h>
// 键盘设备驱动名称
#define KBD_DRIVER_NAME L"\\Driver\\kbdclass"
// 驱动对象的类型
extern POBJECT_TYPE* IoDriverObjectType;
// 已附加的设备链表节点定义
typedef struct _ATTACH_ENTRY {
LIST_ENTRY Entry;
PDEVICE_OBJECT FilterDevice;
PDEVICE_OBJECT PhysicalDevice;
} ATTACH_ENTRY, *PATTACH_ENTRY;
// 设备扩展信息,保存附加的设备
typedef struct _DEVICE_EXTENSION {
PDEVICE_OBJECT AttachedToDevice;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
// 通过名称获取内核对象,已导出但未文档化的函数
NTKERNELAPI NTSTATUS ObReferenceObjectByName(
IN PUNICODE_STRING ObjectName,
IN ULONG Attributes,
IN PACCESS_STATE AccessState,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_TYPE ObjectType,
IN KPROCESSOR_MODE AccessMode,
IN OUT PVOID ParseContext,
OUT PVOID* Object);

另外我们需要处理 已经附加的设备 的列表,已附加的设备要跳过,设备移除也要删除对应节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PATTACH_ENTRY FindPhysicalEntry(PDEVICE_OBJECT DeviceObject)
{
KIRQL OldIrql = 0;
PLIST_ENTRY Entry = NULL;
PATTACH_ENTRY AttachEntry = NULL;
// 检验参数是否为空指针
if (DeviceObject == NULL) return NULL;
// 遍历链表
KeAcquireSpinLock(&AttachLock, &OldIrql);
for (Entry = AttachList.Flink; Entry != &AttachList; Entry = Entry->Flink)
{
AttachEntry = CONTAINING_RECORD(Entry, ATTACH_ENTRY, Entry);
if (AttachEntry->PhysicalDevice == DeviceObject)
{
KeReleaseSpinLock(&AttachLock, OldIrql);
return AttachEntry;
}
}
KeReleaseSpinLock(&AttachLock, OldIrql);
return NULL;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PATTACH_ENTRY RemoveFilterEntry(PDEVICE_OBJECT DeviceObject)
{
KIRQL OldIrql = 0;
PLIST_ENTRY Entry = NULL;
PATTACH_ENTRY AttachEntry = NULL;
// 检验参数是否为空指针
if (DeviceObject == NULL) return NULL;
// 遍历链表
KeAcquireSpinLock(&AttachLock, &OldIrql);
for (Entry = AttachList.Flink; Entry != &AttachList; Entry = Entry->Flink)
{
AttachEntry = CONTAINING_RECORD(Entry, ATTACH_ENTRY, Entry);
if (AttachEntry->FilterDevice == DeviceObject)
{
// 移除节点
RemoveEntryList(&AttachEntry->Entry);
KeReleaseSpinLock(&AttachLock, OldIrql);
return AttachEntry;
}
}
KeReleaseSpinLock(&AttachLock, OldIrql);
return NULL;
}

过滤驱动的处理

我们需要处理的驱动 派遣函数IRP_MJ_READ IRP_MJ_POWER IRP_MJ_PNP 共三项

1
2
3
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
DriverObject->MajorFunction[IRP_MJ_POWER] = DispatchPower;
DriverObject->MajorFunction[IRP_MJ_PNP] = DispatchPnp;

IRP_MJ_READ 中,我们处理获取到的 键盘按键 信息,注意是在 完成函数 中处理

1
2
3
4
5
6
7
8
NTSTATUS DispatchRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PDEVICE_EXTENSION DevExt = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
// 设置完成函数ReadCompletion
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp, ReadCompletion, NULL, TRUE, TRUE, TRUE);
return IoCallDriver(DevExt->AttachedToDevice, Irp);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
NTSTATUS ReadCompletion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context)
{
ULONG_PTR Index = 0;
ULONG_PTR NumKeys = 0;
PIO_STACK_LOCATION IrpSp = NULL;
PKEYBOARD_INPUT_DATA KeyData = NULL;
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Context);
// 如果读取键盘的操作成功
IrpSp = IoGetCurrentIrpStackLocation(Irp);
if (Irp->IoStatus.Status == STATUS_SUCCESS)
{
// 系统在SystemBuffer中保存按键信息
KeyData = Irp->AssociatedIrp.SystemBuffer;
if (KeyData != NULL)
{
// 获取按键的数量
NumKeys = Irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA);
for (Index = 0; Index < NumKeys; Index++)
{
// 检查按键码
if (bKeyboardLock) // 如果开启了过滤
{
// (KeyData[Index].MakeCode == 29) // CTRL
if ((KeyData[Index].MakeCode == 1) || // ESC
(KeyData[Index].MakeCode == 56) || // ALT
(KeyData[Index].MakeCode == 91)) // WIN
{
// 把按下键修改为弹起键
if (KeyData[Index].Flags == 0) KeyData[Index].Flags = 1;
if (KeyData[Index].Flags == 2) KeyData[Index].Flags = 3;
}
}
}
}
}
// 如果IRP是PENDING状态,继续标记PENDING
if (Irp->PendingReturned) IoMarkIrpPending(Irp);
return Irp->IoStatus.Status;
}

IRP_MJ_POWER 中处理电源管理,因为我们是过滤驱动,只需要把IRP直接往下转发就行

1
2
3
4
5
6
7
8
9
10
11
12
13
NTSTATUS DispatchPower(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PDEVICE_EXTENSION DevExt = (PDEVICE_EXTENSION)(DeviceObject->DeviceExtension);
// 转发到下层设备,注意VISTA以上系统有变化
#if (NTDDI_VERSION < NTDDI_VISTA)
PoStartNextPowerIrp(Irp);
IoSkipCurrentIrpStackLocation(Irp);
return PoCallDriver(DevExt->AttachedToDevice, Irp);
#else
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(DevExt->AttachedToDevice, Irp);
#endif
}

IRP_MJ_PNP 中,我们要处理 键盘设备移除 时,解绑和删除 过滤设备 的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NTSTATUS DispatchPnp(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PIO_STACK_LOCATION IrpSp = NULL;
PDEVICE_EXTENSION DevExt = (PDEVICE_EXTENSION)(DeviceObject->DeviceExtension);
// 处理子功能
IrpSp = IoGetCurrentIrpStackLocation(Irp);
switch (IrpSp->MinorFunction)
{
case IRP_MN_REMOVE_DEVICE: // 设备移除
// 从过滤设备列表中移除
AttachEntry = RemoveFilterEntry(DeviceObject);
if (AttachEntry != NULL)
ExFreePool(AttachEntry);
// 解绑和删除过滤设备
IoDetachDevice(DevExt->AttachedToDevice);
IoDeleteDevice(DeviceObject);
break;
}
// 跳过当前设备,继续下发
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(DevExt->AttachedToDevice, Irp);
}

键盘设备接入的监控

如何知道有键盘设备接入了呢,可以通过 IoRegisterPlugPlayNotification 函数,来注册一个PNP设备通知

1
2
3
4
5
6
7
8
9
NTSTATUS IoRegisterPlugPlayNotification(
_In_ IO_NOTIFICATION_EVENT_CATEGORY EventCategory,
_In_ ULONG EventCategoryFlags,
_In_opt_ PVOID EventCategoryData,
_In_ PDRIVER_OBJECT DriverObject,
_In_ PDRIVER_NOTIFICATION_CALLBACK_ROUTINE CallbackRoutine,
_In_opt_ PVOID Context,
_Out_ PVOID *NotificationEntry
);

通过指定参数 EventCategory 的类型,可以监控PNP设备的某些特定行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
EventCategoryDeviceInterfaceChange
// PnP events in this category include the arrival (enabling) of a new instance of a
// device interface class (GUID_DEVICE_INTERFACE_ARRIVAL), or the removal (disabling)
// of an existing device interface instance (GUID_DEVICE_INTERFACE_REMOVAL).

EventCategoryHardwareProfileChange
// PnP events in this category include query-change (GUID_HWPROFILE_QUERY_CHANGE),
// change-complete (GUID_HWPROFILE_CHANGE_COMPLETE), and change-cancel
// (GUID_HWPROFILE_CHANGE_CANCELLED) of a hardware profile.

EventCategoryTargetDeviceChange
// PnP events in this category include events related to removing a device: the
// device's drivers received a query-remove IRP (GUID_TARGET_DEVICE_QUERY_REMOVE),
// the drivers completed a remove IRP (GUID_TARGET_DEVICE_REMOVE_COMPLETE), or the
// drivers received a cancel-remove IRP (GUID_TARGET_DEVICE_REMOVE_CANCELLED). This
// category is also used for custom notification events.

我们这里关注的是 EventCategoryDeviceInterfaceChange 类型,可以监控到 设备的接入 动作,
相关的GUID信息都在 wdmguid.h 中定义,这里因为只用其中几个信息,就单独拿出来重新定义

1
2
3
4
5
6
// GUID_DEVICE_INTERFACE_ARRIVAL
GUID GUID_INTERFACE_ARRIVAL = {
0xcb3a4004, 0x46f0, 0x11d0, 0xb0, 0x8f, 0x00, 0x60, 0x97, 0x13, 0x05, 0x3f };
// GUID_DEVINTERFACE_KEYBOARD
GUID GUID_INTERFACE_KEYBOARD = {
0x884b96c3, 0x56ef, 0x11d1, 0xbc, 0x8c, 0x00, 0xa0, 0xc9, 0x14, 0x05, 0xdd };

我们要在 DriverEntry 中保存 DriverObject 到全局变量中,因为在 附加设备 时要用

1
2
3
4
5
6
7
8
9
10
11
// 保存当前驱动对象
CurrentDriver = DriverObject;
// 注册PNP设备通知
Status = IoRegisterPlugPlayNotification(
EventCategoryDeviceInterfaceChange,
PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
&GUID_INTERFACE_KEYBOARD,
DriverObject,
PnpNotifyCallback,
NULL,
&NotificationEntry);

PNP通知的回调函数代码,主要是检测是不是有 键盘设备接入 的动作

1
2
3
4
5
6
7
8
9
10
11
12
13
NTSTATUS PnpNotifyCallback(IN PVOID Structure, IN PVOID Context)
{
PGUID pEvent = NULL;
UNREFERENCED_PARAMETER(Context);
if (Structure == NULL) return STATUS_SUCCESS;
pEvent = &(((PDEVICE_INTERFACE_CHANGE_NOTIFICATION)Structure)->Event);
// 比较内存空间,返回相等的字节数
if (RtlEqualMemory(pEvent, &GUID_INTERFACE_ARRIVAL, sizeof(GUID)))
{
EnumKeyboardDevice(); // 遍历所有键盘设备
}
return STATUS_SUCCESS;
}

如下为遍历 kbdclass.sys 驱动中所有键盘设备,并进行附加的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
NTSTATUS EnumKeyboardDevice()
{
PATTACH_ENTRY AttachEntry = NULL;
NTSTATUS Status = STATUS_SUCCESS;
PDRIVER_OBJECT KBDDriverObject = NULL;
PDEVICE_OBJECT KBDDeviceObject = NULL;
UNICODE_STRING KBDDriverName = RTL_CONSTANT_STRING(KBD_DRIVER_NAME);
// 获取键盘驱动对象以及设备对象
Status = ObReferenceObjectByName(&KBDDriverName, OBJ_CASE_INSENSITIVE,
NULL, 0, *IoDriverObjectType, KernelMode, NULL, &KBDDriverObject);
if (!NT_SUCCESS(Status))
{
return Status;
}
KBDDeviceObject = KBDDriverObject->DeviceObject;
// 遍历键盘设备对象
while (KBDDeviceObject != NULL)
{
AttachEntry = FindPhysicalEntry(KBDDeviceObject);
if (AttachEntry == NULL)
Status = AttachKeyboardDevice(KBDDeviceObject);
KBDDeviceObject = KBDDeviceObject->NextDevice;
}
ObDereferenceObject(KBDDriverObject);
return STATUS_SUCCESS;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
NTSTATUS AttachKeyboardDevice(IN PDEVICE_OBJECT PhysicalDevice)
{
KIRQL OldIrql = 0;
PATTACH_ENTRY AttachEntry = NULL;
NTSTATUS Status = STATUS_SUCCESS;
PDEVICE_OBJECT FilterDevice = NULL;
PDEVICE_EXTENSION DevExt = NULL;
// 检查参数
if (PhysicalDevice == NULL) return STATUS_UNSUCCESSFUL;
// 创建过滤设备对象
Status = IoCreateDevice(CurrentDriver, sizeof(DEVICE_EXTENSION), NULL,
PhysicalDevice->DeviceType, PhysicalDevice->Characteristics, FALSE, &FilterDevice);
if (!NT_SUCCESS(Status))
{
return Status;
}
// 申请链表节点空间
AttachEntry = (PATTACH_ENTRY)EXALLOCATE(sizeof(ATTACH_ENTRY));
if (AttachEntry == NULL)
{
IoDeleteDevice(FilterDevice);
return STATUS_UNSUCCESSFUL;
}
RtlZeroMemory(AttachEntry, sizeof(ATTACH_ENTRY));
AttachEntry->FilterDevice = FilterDevice;
AttachEntry->PhysicalDevice = PhysicalDevice;
// 设置过滤设备标志
FilterDevice->Flags |= PhysicalDevice->Flags;
// 得到设备扩展结构,以便下面保存过滤设备信息
DevExt = (PDEVICE_EXTENSION)FilterDevice->DeviceExtension;
// 将过滤设备对象附加在目标设备对象之上
DevExt->AttachedToDevice = IoAttachDeviceToDeviceStack(
FilterDevice, PhysicalDevice);
if (DeviceExtension->AttachedToDevice == NULL)
{
ExFreePool(AttachEntry);
IoDeleteDevice(FilterDevice);
return STATUS_UNSUCCESSFUL;
}
// 清除过滤设备初始化标记
FilterDevice->Flags &= ~DO_DEVICE_INITIALIZING;
// 添加到链表中
KeAcquireSpinLock(&AttachLock, &OldIrql);
InsertTailList(&AttachList, &AttachEntry->Entry);
KeReleaseSpinLock(&AttachLock, OldIrql);
return STATUS_SUCCESS;
}

这里需要注意的是,如果是在 DriverObject->DriverExtension->AddDevice 对应的函数中,进行
过滤设备的新建和附加,驱动自己会帮我们去除 过滤设备初始化 的标记,而这里需要我们自己手动去除。