Windows驱动开发入门-键盘过滤驱动

碎碎念

PDO是设备栈最下面的那个物理设备对象。

csrss.exe的进程有个线程叫做win32!RawInputThread,它通过一个GUID叫GUID_CLASS_KEYBOARD获得键盘设备栈PDO符号链接名。应用程序不能通过设备名字打开设备,一般通过符号链接名来打开。win32k!RawInputThread执行到win32k!OpenDevice,一个参数能找到键盘设备栈的PDO符号链接名。win32!OpenDevice有个OBJECT_ATTRIBUTES结构局部变量,它自己初始化这个局部变量,用传入参数中的符号链接名赋值OBJECT_ATTRIBUTES+0x8处的PUNICODE_STRING ObjectName。然后调用ZwCreateFile打开设备得到句柄。

ZwCreateFile通过系统服务,调用内核NtCreateFile,执行到nt!IopParseDevice调用nt!IoGetAttachedDevice,通过PDO获得键盘设备栈最顶端的设备对象,该对象+30 char StackSize作为参数调用IoAllocateIrp创建IRP。调用nt!ObCreateObject创建文件对象,初始化这个文件对象。+04 struct _DEVICE_OBJECT *DeviceObject赋值为键盘设备栈PDO。调用nt!IopfCallDriver将IRP发往驱动。一系列返回到nt!ObOpenObjectByName,调用nt!ObpCreateHandle在csrss.exe句柄表中创建一个新的句柄,对应的对象就是刚才创建并初始化的那个文件对象。

win32!RawInputThread在获得句柄之后,以这个为参数调用nt!ZwReadFile向键盘驱动要求读入数据,并创建一个IRP_MJ_READ的IRP发送给键盘驱动,告诉键盘驱动要求读入数据。键盘驱动一般让这个IRP Pending,该IRP不会被满足被一直放在那等待来自键盘的数据,win32k!RawInputThread也会等待这个读操作完成。

当键盘有键被按下时,触发键盘那个中断,引起中断服务例程执行(由键盘驱动提供),键盘驱动从端口读取扫描码,处理后把键盘得到的数据交给IRP并结束。

IRP结束导致win32k!RawInputThread线程将会对得到的数据做出处理并分发给合适的进程。处理后win32k!RawInputThread线程立刻调用下一个nt!ZwReadFile,向键盘驱动要求读入数据,开始另一个等待键盘键被按下。

PS/2键盘设备栈长这样:顶层设备对象是驱动KbdClass生成的设备对象,中间层设别对象是驱动i8042prt生成的设备对象,底层设备对象是驱动ACPI生成的设备对象。

中断号0x93端口0x60,每次扫描码1字节,2字节的必须分两次读。设按下扫描码为X,抬起为X+0x80。

找到所有键盘设备

尝试绑定KbdClass所有设备对象。每个DRIVER_OBJECT下有个域叫做DeviceObject,每个这个域中又有个域叫做NextDevice指向同一个驱动中的下一个设备,实际上就是个设备链。另一个方法调用函数IoEnumerateDeviceObjectList枚举。ObReferenceObjectByName用于通过名字获得一个对象指针。

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
extern POBJECT_TYPE IoDriverObjectType;
#define KBD_DRIVER_NAME L"\\Driver\\Kbdclass" // Kbdclass驱动的名字
NTSTATUS ObReferenceObjectByName(PUNICODE_STRING ObjectName,ULONG Attributes,PACCESS_STATE AccessState,ACCESS_MASK DesiredAccess,POBJECT_TYPE ObjectType,KPROCESSOR_MODE AccessMode,PVOID ParseContext,PVOID* Object);
NTSTATUS c2pDevExtInit(IN PC2P_DEV_EXT devExt, IN PDEVICE_OBJECT pFilterDeviceObject, IN PDEVICE_OBJECT pTargetDeviceObject, IN PDEVICE_OBJECT pLowerDeviceObject) {
memset(devExt, 0, sizeof(C2P_DEV_EXT));
devExt->NodeSize = sizeof(C2P_DEV_EXT);
devExt->pFilterDeviceObject = pFilterDeviceObject;
KeInitializeSpinLock(&(devExt->IoRequestsSpinLock));
KeInitializeEvent(&(devExt->IoInProgressEvent), NotificationEvent, FALSE);
devExt->TargetDeviceObject = pTargetDeviceObject;
devExt->LowerDeviceObject = pLowerDeviceObject;
return(STATUS_SUCCESS);
};
// 这个函数经过改造。能打开驱动对象Kbdclass,然后绑定它下面的所有的设备:
NTSTATUS c2pAttachDevices(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
NTSTATUS status = 0;
UNICODE_STRING uniNtNameString;
PC2P_DEV_EXT devExt;
PDEVICE_OBJECT pFilterDeviceObject = NULL;
PDEVICE_OBJECT pTargetDeviceObject = NULL;
PDEVICE_OBJECT pLowerDeviceObject = NULL;
PDRIVER_OBJECT KbdDriverObject = NULL;
KdPrint(("MyAttach\n"));
// 初始化一个字符串,就是Kdbclass驱动的名字。
RtlInitUnicodeString(&uniNtNameString, KBD_DRIVER_NAME);
// 请参照前面打开设备对象的例子。只是这里打开的是驱动对象。
status = ObReferenceObjectByName(&uniNtNameString, OBJ_CASE_INSENSITIVE, NULL, 0, IoDriverObjectType, KernelMode, NULL, &KbdDriverObject);
// 如果失败了就直接返回
if (!NT_SUCCESS(status)) {
KdPrint(("MyAttach: Couldn't get the MyTest Device Object\n"));
return(status);
}
else
// 这个打开需要解应用。早点解除了免得之后忘记。
ObDereferenceObject(DriverObject);
// 这是设备链中的第一个设备
pTargetDeviceObject = KbdDriverObject->DeviceObject;
// 现在开始遍历这个设备链
while (pTargetDeviceObject) {
// 生成一个过滤设备,这是前面读者学习过的。这里的IN宏和OUT宏都是空宏,只有标志性意义,表明这个参数是一个输入或者输出参数。
status = IoCreateDevice(IN DriverObject, IN sizeof(C2P_DEV_EXT), IN NULL, IN pTargetDeviceObject->DeviceType, IN pTargetDeviceObject->Characteristics, IN FALSE, OUT & pFilterDeviceObject);
// 如果失败了就直接退出。
if (!NT_SUCCESS(status)) {
KdPrint(("MyAttach: Couldn't create the MyFilter Filter Device Object\n"));
return (status);
};
// 绑定。pLowerDeviceObject是绑定之后得到的下一个设备。也就是前面常常说的所谓真实设备。
pLowerDeviceObject = IoAttachDeviceToDeviceStack(pFilterDeviceObject, pTargetDeviceObject);
// 如果绑定失败了,放弃之前的操作,退出。
if (!pLowerDeviceObject) {
KdPrint(("MyAttach: Couldn't attach to MyTest Device Object\n"));
IoDeleteDevice(pFilterDeviceObject);
pFilterDeviceObject = NULL;
return(status);
};
// 设备扩展!下面要详细讲述设备扩展的应用。
devExt = (PC2P_DEV_EXT)(pFilterDeviceObject->DeviceExtension);
c2pDevExtInit(devExt, pFilterDeviceObject, pTargetDeviceObject, pLowerDeviceObject);
// 下面的操作和前面过滤串口的操作基本一致。这里不再解释了。
pFilterDeviceObject->DeviceType = pLowerDeviceObject->DeviceType;
pFilterDeviceObject->Characteristics = pLowerDeviceObject->Characteristics;
pFilterDeviceObject->StackSize = pLowerDeviceObject->StackSize + 1;
pFilterDeviceObject->Flags |= pLowerDeviceObject->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);
//next device
pTargetDeviceObject = pTargetDeviceObject->NextDevice;
};
return status;
};

应用设备扩展

生成一个过滤设备时可以给这个设备指定任意长度“设备扩展”,扩展中内容可任意填写作为自定义数据结构,这里就是C2P_DEV_EXT。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct _C2P_DEV_EXT{
// 这个结构的大小
ULONG NodeSize;
// 过滤设备对象
PDEVICE_OBJECT pFilterDeviceObject;
// 同时调用时的保护锁
KSPIN_LOCK IoRequestsSpinLock;
// 进程间同步处理
KEVENT IoInProgressEvent;
// 绑定的设备对象
PDEVICE_OBJECT TargetDeviceObject;
// 绑定前底层设备对象
PDEVICE_OBJECT LowerDeviceObject;
} C2P_DEV_EXT, * PC2P_DEV_EXT;

生成带有设备扩展信息的设备对象关键在调用IoCreateDevice时第二个参数填扩展长度。

1
status = IoCreateDevice(IN DriverObject, IN sizeof(C2P_DEV_EXT), IN NULL, IN pTargetDeviceObject->DeviceType, IN pTargetDeviceObject->Characteristics, IN FALSE, OUT & pFilterDeviceObject);

然后就是上面所说的:

1
2
3
// 设备扩展!下面要详细讲述设备扩展的应用。
devExt = (PC2P_DEV_EXT)(pFilterDeviceObject->DeviceExtension);
c2pDevExtInit(devExt, pFilterDeviceObject, pTargetDeviceObject, pLowerDeviceObject);

怎样填这个域:

1
2
3
4
5
6
7
8
9
10
NTSTATUS c2pDevExtInit(IN PC2P_DEV_EXT devExt, IN PDEVICE_OBJECT pFilterDeviceObject, IN PDEVICE_OBJECT pTargetDeviceObject, IN PDEVICE_OBJECT pLowerDeviceObject) {
memset(devExt, 0, sizeof(C2P_DEV_EXT));
devExt->NodeSize = sizeof(C2P_DEV_EXT);
devExt->pFilterDeviceObject = pFilterDeviceObject;
KeInitializeSpinLock(&(devExt->IoRequestsSpinLock));
KeInitializeEvent(&(devExt->IoInProgressEvent), NotificationEvent, FALSE);
devExt->TargetDeviceObject = pTargetDeviceObject;
devExt->LowerDeviceObject = pLowerDeviceObject;
return(STATUS_SUCCESS);
};

驱动入口

直接去找KbdClass下所有设备进行绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
ULONG i;
NTSTATUS status;
KdPrint(("c2p.SYS: entering DriverEntry\n"));
// 填写所有的分发函数的指针
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
DriverObject->MajorFunction[i] = c2pDispatchGeneral;
// 单独的填写一个Read分发函数。因为要的过滤就是读取来的按键信息其他的都不重要。这个分发函数单独写。
DriverObject->MajorFunction[IRP_MJ_READ] = c2pDispatchRead;
// 单独的填写一个IRP_MJ_POWER函数。这是因为这类请求中间要调用一个PoCallDriver和一个PoStartNextPowerIrp,比较特殊。
DriverObject->MajorFunction[IRP_MJ_POWER] = c2pPower;
// 我们想知道什么时候一个我们绑定过的设备被卸载了(比如从机器上被拔掉了?)所以专门写一个PNP(即插即用)分发函数
DriverObject->MajorFunction[IRP_MJ_PNP] = c2pPnP;
// 卸载函数。
DriverObject->DriverUnload = c2pUnload;
gDriverObject = DriverObject;
// 绑定所有键盘设备
status = c2pAttachDevices(DriverObject, RegistryPath);
return status;
};

动态卸载

如果没有按键时请求未必会完成,卸载过滤驱动后等下次一按键直接蓝。防止未决请求没完成的方法是使用gC2pKeyCount全局变量,有请求到来时加1,完成时减1

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
VOID c2pDetach(IN PDEVICE_OBJECT pDeviceObject) {
PC2P_DEV_EXT devExt;
BOOLEAN NoRequestsOutstanding = FALSE;
devExt = (PC2P_DEV_EXT)pDeviceObject->DeviceExtension;
__try {
__try {
IoDetachDevice(devExt->TargetDeviceObject);
devExt->TargetDeviceObject = NULL;
IoDeleteDevice(pDeviceObject);
devExt->pFilterDeviceObject = NULL;
DbgPrint(("Detach Finished\n"));
}
__except (EXCEPTION_EXECUTE_HANDLER) {};
}
__finally {}
return;
};
#define DELAY_ONE_MICROSECOND (-10)
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
#define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)
VOID c2pUnload(IN PDRIVER_OBJECT DriverObject) {
PDEVICE_OBJECT DeviceObject;
PDEVICE_OBJECT OldDeviceObject;
PC2P_DEV_EXT devExt;
LARGE_INTEGER lDelay;
PRKTHREAD CurrentThread;
//delay some time
lDelay = RtlConvertLongToLargeInteger(100 * DELAY_ONE_MILLISECOND);
CurrentThread = KeGetCurrentThread();
// 把当前线程设置为低实时模式,以便让它的运行尽量少影响其他程序。
KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY);
UNREFERENCED_PARAMETER(DriverObject);
KdPrint(("DriverEntry unLoading...\n"));
// 遍历所有设备并一律解除绑定
DeviceObject = DriverObject->DeviceObject;
while (DeviceObject) {
// 解除绑定并删除所有的设备
c2pDetach(DeviceObject);
DeviceObject = DeviceObject->NextDevice;
};
ASSERT(NULL == DriverObject->DeviceObject);
while (gC2pKeyCount)
KeDelayExecutionThread(KernelMode, FALSE, &lDelay);
KdPrint(("DriverEntry unLoad OK!\n"));
return;
};

请求处理

通常的处理

跟串口一样,跳过虚拟设备处理直接发送到真实设备。

1
2
3
4
5
6
NTSTATUS c2pDispatchGeneral(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
// 其他的分发函数,直接skip然后用IoCallDriver把IRP发送到真实设备的设备对象。
KdPrint(("Other Diapatch!"));
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(((PC2P_DEV_EXT)DeviceObject->DeviceExtension)->LowerDeviceObject, Irp);
};

电源相关的处理

有些不一样,主要是先用PoStartNextPowerIrp,并用IoCallDriver代替。

1
2
3
4
5
6
7
NTSTATUS c2pPower(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
PC2P_DEV_EXT devExt;
devExt = (PC2P_DEV_EXT)DeviceObject->DeviceExtension;
PoStartNextPowerIrp(Irp);
IoSkipCurrentIrpStackLocation(Irp);
return PoCallDriver(devExt->LowerDeviceObject, Irp);
};

PNP处理

设备热插拔,当PNP请求过来时Windows处理掉所有未决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
NTSTATUS c2pPnP(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
PC2P_DEV_EXT devExt;
PIO_STACK_LOCATION irpStack;
NTSTATUS status = STATUS_SUCCESS;
KIRQL oldIrql;
KEVENT event;
// 获得真实设备。
devExt = (PC2P_DEV_EXT)(DeviceObject->DeviceExtension);
irpStack = IoGetCurrentIrpStackLocation(Irp);
switch (irpStack->MinorFunction) {
case IRP_MN_REMOVE_DEVICE:
KdPrint(("IRP_MN_REMOVE_DEVICE\n"));
// 首先把请求发下去
IoSkipCurrentIrpStackLocation(Irp);
IoCallDriver(devExt->LowerDeviceObject, Irp);
// 然后解除绑定。
IoDetachDevice(devExt->LowerDeviceObject);
// 删除我们自己生成的虚拟设备。
IoDeleteDevice(DeviceObject);
status = STATUS_SUCCESS;
break;
default:
// 对于其他类型的IRP,全部都直接下发即可。
IoSkipCurrentIrpStackLocation(Irp);
status = IoCallDriver(devExt->LowerDeviceObject, Irp);
};
return status;
};

读处理

这个请求可不敢恭维,咱键盘过滤驱动要的就是这个请求。大概步骤为:先用IoCopyCurrentIrpStackLocationToNext把当前IRP栈空间拷贝到下一个栈空间;再给这个IRP一个完成函数,当IRP完成时系统调用这个函数;最后用IoCallDriver把请求发送到下一个设备。

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
NTSTATUS c2pDispatchRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
NTSTATUS status = STATUS_SUCCESS;
PC2P_DEV_EXT devExt;
PIO_STACK_LOCATION currentIrpStack;
KEVENT waitEvent;
KeInitializeEvent(&waitEvent, NotificationEvent, FALSE);
if (Irp->CurrentLocation == 1) {
ULONG ReturnedInformation = 0;
KdPrint(("Dispatch encountered bogus current location\n"));
status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = ReturnedInformation;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return(status);
};
// 全局变量键计数器加1
gC2pKeyCount++;
// 得到设备扩展。目的是之后为了获得下一个设备的指针。
devExt = (PC2P_DEV_EXT)DeviceObject->DeviceExtension;
// 设置回调函数并把IRP传递下去。 之后读的处理也就结束了。剩下的任务是要等待读请求完成。
currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp, c2pReadComplete, DeviceObject, TRUE, TRUE, TRUE);
return IoCallDriver(devExt->LowerDeviceObject, Irp);
};

读请求完成后应获得输出缓冲区,按键信息就在这里。缓冲区有多个KEYBOARD_INPUT_DATA结构。

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
// 这是一个IRP完成回调函数的原型
NTSTATUS c2pReadComplete(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context) {
PIO_STACK_LOCATION IrpSp;
ULONG buf_len = 0;
PUCHAR buf = NULL;
size_t i, numKeys;
PKEYBOARD_INPUT_DATA KeyData;
IrpSp = IoGetCurrentIrpStackLocation(Irp);
// 如果这个请求是成功的。很显然,如果请求失败了,这么获取进一步的信息是没意义的。
if (NT_SUCCESS(Irp->IoStatus.Status)) {
// 获得读请求完成后输出的缓冲区
buf = Irp->AssociatedIrp.SystemBuffer;
KeyData = (PKEYBOARD_INPUT_DATA)buf;
// 获得这个缓冲区的长度。一般的说返回值有多长都保存在Information中。
buf_len = Irp->IoStatus.Information;
numKeys = buf_len / sizeof(KEYBOARD_INPUT_DATA);
//… 这里可以做进一步的处理。我这里很简单的打印出所有的扫描码。
//for(i=0;i<buf_len;++i)
for (i = 0; i < numKeys; ++i) {
//DbgPrint("ctrl2cap: %2x\r\n", buf[i]);
DbgPrint("\n");
DbgPrint("numKeys : %d", numKeys);
DbgPrint("ScanCode: %x ", KeyData->MakeCode);
DbgPrint("%s\n", KeyData->Flags ? "Up" : "Down");
print_keystroke((UCHAR)KeyData->MakeCode);
if (KeyData->MakeCode == CAPS_LOCK)
KeyData->MakeCode = LCONTROL; //恶作剧:将Caps Lock起到与Ctrl一样的作用
};
};
gC2pKeyCount--;
if (Irp->PendingReturned)
IoMarkIrpPending(Irp);
return Irp->IoStatus.Status;
};

自己定义控制键状态保存到kb_status变量中用3位保留。

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
// flags for keyboard status
#define S_SHIFT 1
#define S_CAPS 2
#define S_NUM 4
static int kb_status = S_NUM;
VOID __stdcall print_keystroke(UCHAR sch) {
UCHAR ch = 0;
int off = 0;
if ((sch & 0x80) == 0) { //按下(make)
if ((sch < 0x47) || ((sch >= 0x47 && sch < 0x54) && (kb_status & S_NUM))) // Num Lock
ch = asciiTbl[off + sch];
switch (sch) {
case 0x3A: //Caps Lock
kb_status ^= S_CAPS;
break;
case 0x2A: //左右俩Shift扫描码不一样
case 0x36:
kb_status |= S_SHIFT;
break;
case 0x45: //Num Lock
kb_status ^= S_NUM;
};
}
else //break
if (sch == 0xAA || sch == 0xB6)
kb_status &= ~S_SHIFT;
if (ch >= 0x20 && ch < 0x7F)
DbgPrint("%C \n", ch);
return;
};

完整代码

Driver.h:

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
#pragma once
// Kbdclass驱动的名字
#define KBD_DRIVER_NAME L"\\Driver\\Kbdclass"
#define KEY_UP 1
#define KEY_DOWN 0
#define LCONTROL ((USHORT)0x1D)
#define CAPS_LOCK ((USHORT)0x3A)
//
// Delay values for KeDelayExecutionThread()
// (Values are negative to represent relative time)
//
#define DELAY_ONE_MICROSECOND (-10)
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
#define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)
unsigned char asciiTbl[] = {
0x00, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x08, 0x09, //normal
0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x5B, 0x5D, 0x0D, 0x00, 0x61, 0x73,
0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3B, 0x27, 0x60, 0x00, 0x5C, 0x7A, 0x78, 0x63, 0x76,
0x62, 0x6E, 0x6D, 0x2C, 0x2E, 0x2F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,
0x32, 0x33, 0x30, 0x2E,
0x00, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x08, 0x09, //caps
0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x5B, 0x5D, 0x0D, 0x00, 0x41, 0x53,
0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3B, 0x27, 0x60, 0x00, 0x5C, 0x5A, 0x58, 0x43, 0x56,
0x42, 0x4E, 0x4D, 0x2C, 0x2E, 0x2F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,
0x32, 0x33, 0x30, 0x2E,
0x00, 0x1B, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x08, 0x09, //shift
0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x7B, 0x7D, 0x0D, 0x00, 0x41, 0x53,
0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3A, 0x22, 0x7E, 0x00, 0x7C, 0x5A, 0x58, 0x43, 0x56,
0x42, 0x4E, 0x4D, 0x3C, 0x3E, 0x3F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,
0x32, 0x33, 0x30, 0x2E,
0x00, 0x1B, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x08, 0x09, //caps + shift
0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x7B, 0x7D, 0x0D, 0x00, 0x61, 0x73,
0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3A, 0x22, 0x7E, 0x00, 0x7C, 0x7A, 0x78, 0x63, 0x76,
0x62, 0x6E, 0x6D, 0x3C, 0x3E, 0x3F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,
0x32, 0x33, 0x30, 0x2E
};

Driver.cpp:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#include <wdm.h>
#include <ntddkbd.h>
#include "Driver.h"
typedef struct _C2P_DEV_EXT{
// 这个结构的大小
ULONG NodeSize;
// 过滤设备对象
PDEVICE_OBJECT pFilterDeviceObject;
// 同时调用时的保护锁
KSPIN_LOCK IoRequestsSpinLock;
// 进程间同步处理
KEVENT IoInProgressEvent;
// 绑定的设备对象
PDEVICE_OBJECT TargetDeviceObject;
// 绑定前底层设备对象
PDEVICE_OBJECT LowerDeviceObject;
} C2P_DEV_EXT, * PC2P_DEV_EXT;
// flags for keyboard status
#define S_SHIFT 1
#define S_CAPS 2
#define S_NUM 4
static int kb_status = S_NUM;
VOID __stdcall print_keystroke(UCHAR sch) {
UCHAR ch = 0;
int off = 0;
if ((sch & 0x80) == 0) { //make
if ((sch < 0x47) || ((sch >= 0x47 && sch < 0x54) && (kb_status & S_NUM))) // Num Lock
ch = asciiTbl[off + sch];
switch (sch) {
case 0x3A:
kb_status ^= S_CAPS;
break;
case 0x2A:
case 0x36:
kb_status |= S_SHIFT;
break;
case 0x45:
kb_status ^= S_NUM;
};
}
else //break
if (sch == 0xAA || sch == 0xB6)
kb_status &= ~S_SHIFT;
if (ch >= 0x20 && ch < 0x7F)
DbgPrint("%C \n", ch);
return;
};
NTSTATUS c2pDevExtInit(IN PC2P_DEV_EXT devExt, IN PDEVICE_OBJECT pFilterDeviceObject, IN PDEVICE_OBJECT pTargetDeviceObject, IN PDEVICE_OBJECT pLowerDeviceObject) {
memset(devExt, 0, sizeof(C2P_DEV_EXT));
devExt->NodeSize = sizeof(C2P_DEV_EXT);
devExt->pFilterDeviceObject = pFilterDeviceObject;
KeInitializeSpinLock(&(devExt->IoRequestsSpinLock));
KeInitializeEvent(&(devExt->IoInProgressEvent), NotificationEvent, FALSE);
devExt->TargetDeviceObject = pTargetDeviceObject;
devExt->LowerDeviceObject = pLowerDeviceObject;
return(STATUS_SUCCESS);
};
// 这个函数是事实存在的,只是文档中没有公开。声明一下就可以直接使用了。
NTSTATUS ObReferenceObjectByName(PUNICODE_STRING ObjectName,ULONG Attributes,PACCESS_STATE AccessState,ACCESS_MASK DesiredAccess,POBJECT_TYPE ObjectType,KPROCESSOR_MODE AccessMode,PVOID ParseContext,PVOID* Object);
extern POBJECT_TYPE IoDriverObjectType;
ULONG gC2pKeyCount = 0;
PDRIVER_OBJECT gDriverObject = NULL;
// 这个函数经过改造。能打开驱动对象Kbdclass,然后绑定它下面的所有的设备:
NTSTATUS c2pAttachDevices(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
NTSTATUS status = 0;
UNICODE_STRING uniNtNameString;
PC2P_DEV_EXT devExt;
PDEVICE_OBJECT pFilterDeviceObject = NULL;
PDEVICE_OBJECT pTargetDeviceObject = NULL;
PDEVICE_OBJECT pLowerDeviceObject = NULL;
PDRIVER_OBJECT KbdDriverObject = NULL;
KdPrint(("MyAttach\n"));
// 初始化一个字符串,就是Kdbclass驱动的名字。
RtlInitUnicodeString(&uniNtNameString, KBD_DRIVER_NAME);
// 请参照前面打开设备对象的例子。只是这里打开的是驱动对象。
status = ObReferenceObjectByName(&uniNtNameString, OBJ_CASE_INSENSITIVE, NULL, 0, IoDriverObjectType, KernelMode, NULL, &KbdDriverObject);
// 如果失败了就直接返回
if (!NT_SUCCESS(status)) {
KdPrint(("MyAttach: Couldn't get the MyTest Device Object\n"));
return(status);
}
else
// 这个打开需要解应用。早点解除了免得之后忘记。
ObDereferenceObject(DriverObject);
// 这是设备链中的第一个设备
pTargetDeviceObject = KbdDriverObject->DeviceObject;
// 现在开始遍历这个设备链
while (pTargetDeviceObject) {
// 生成一个过滤设备,这是前面读者学习过的。这里的IN宏和OUT宏都是空宏,只有标志性意义,表明这个参数是一个输入或者输出参数。
status = IoCreateDevice(IN DriverObject, IN sizeof(C2P_DEV_EXT), IN NULL, IN pTargetDeviceObject->DeviceType, IN pTargetDeviceObject->Characteristics, IN FALSE, OUT & pFilterDeviceObject);
// 如果失败了就直接退出。
if (!NT_SUCCESS(status)) {
KdPrint(("MyAttach: Couldn't create the MyFilter Filter Device Object\n"));
return (status);
};
// 绑定。pLowerDeviceObject是绑定之后得到的下一个设备。也就是前面常常说的所谓真实设备。
pLowerDeviceObject = IoAttachDeviceToDeviceStack(pFilterDeviceObject, pTargetDeviceObject);
// 如果绑定失败了,放弃之前的操作,退出。
if (!pLowerDeviceObject) {
KdPrint(("MyAttach: Couldn't attach to MyTest Device Object\n"));
IoDeleteDevice(pFilterDeviceObject);
pFilterDeviceObject = NULL;
return(status);
};
// 设备扩展!下面要详细讲述设备扩展的应用。
devExt = (PC2P_DEV_EXT)(pFilterDeviceObject->DeviceExtension);
c2pDevExtInit(devExt, pFilterDeviceObject, pTargetDeviceObject, pLowerDeviceObject);
// 下面的操作和前面过滤串口的操作基本一致。这里不再解释了。
pFilterDeviceObject->DeviceType = pLowerDeviceObject->DeviceType;
pFilterDeviceObject->Characteristics = pLowerDeviceObject->Characteristics;
pFilterDeviceObject->StackSize = pLowerDeviceObject->StackSize + 1;
pFilterDeviceObject->Flags |= pLowerDeviceObject->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);
//next device
pTargetDeviceObject = pTargetDeviceObject->NextDevice;
};
return status;
};
VOID c2pDetach(IN PDEVICE_OBJECT pDeviceObject) {
PC2P_DEV_EXT devExt;
BOOLEAN NoRequestsOutstanding = FALSE;
devExt = (PC2P_DEV_EXT)pDeviceObject->DeviceExtension;
__try {
__try {
IoDetachDevice(devExt->TargetDeviceObject);
devExt->TargetDeviceObject = NULL;
IoDeleteDevice(pDeviceObject);
devExt->pFilterDeviceObject = NULL;
DbgPrint(("Detach Finished\n"));
}
__except (EXCEPTION_EXECUTE_HANDLER) {};
}
__finally {}
return;
};
#define DELAY_ONE_MICROSECOND (-10)
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
#define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)
VOID c2pUnload(IN PDRIVER_OBJECT DriverObject) {
PDEVICE_OBJECT DeviceObject;
PDEVICE_OBJECT OldDeviceObject;
PC2P_DEV_EXT devExt;
LARGE_INTEGER lDelay;
PRKTHREAD CurrentThread;
//delay some time
lDelay = RtlConvertLongToLargeInteger(100 * DELAY_ONE_MILLISECOND);
CurrentThread = KeGetCurrentThread();
// 把当前线程设置为低实时模式,以便让它的运行尽量少影响其他程序。
KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY);
UNREFERENCED_PARAMETER(DriverObject);
KdPrint(("DriverEntry unLoading...\n"));
// 遍历所有设备并一律解除绑定
DeviceObject = DriverObject->DeviceObject;
while (DeviceObject) {
// 解除绑定并删除所有的设备
c2pDetach(DeviceObject);
DeviceObject = DeviceObject->NextDevice;
};
ASSERT(NULL == DriverObject->DeviceObject);
while (gC2pKeyCount)
KeDelayExecutionThread(KernelMode, FALSE, &lDelay);
KdPrint(("DriverEntry unLoad OK!\n"));
return;
};
NTSTATUS c2pDispatchGeneral(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
// 其他的分发函数,直接skip然后用IoCallDriver把IRP发送到真实设备的设备对象。
KdPrint(("Other Diapatch!"));
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(((PC2P_DEV_EXT)DeviceObject->DeviceExtension)->LowerDeviceObject, Irp);
};
NTSTATUS c2pPower(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
PC2P_DEV_EXT devExt;
devExt = (PC2P_DEV_EXT)DeviceObject->DeviceExtension;
PoStartNextPowerIrp(Irp);
IoSkipCurrentIrpStackLocation(Irp);
return PoCallDriver(devExt->LowerDeviceObject, Irp);
};
NTSTATUS c2pPnP(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
PC2P_DEV_EXT devExt;
PIO_STACK_LOCATION irpStack;
NTSTATUS status = STATUS_SUCCESS;
KIRQL oldIrql;
KEVENT event;
// 获得真实设备。
devExt = (PC2P_DEV_EXT)(DeviceObject->DeviceExtension);
irpStack = IoGetCurrentIrpStackLocation(Irp);
switch (irpStack->MinorFunction) {
case IRP_MN_REMOVE_DEVICE:
KdPrint(("IRP_MN_REMOVE_DEVICE\n"));
// 首先把请求发下去
IoSkipCurrentIrpStackLocation(Irp);
IoCallDriver(devExt->LowerDeviceObject, Irp);
// 然后解除绑定。
IoDetachDevice(devExt->LowerDeviceObject);
// 删除我们自己生成的虚拟设备。
IoDeleteDevice(DeviceObject);
status = STATUS_SUCCESS;
break;
default:
// 对于其他类型的IRP,全部都直接下发即可。
IoSkipCurrentIrpStackLocation(Irp);
status = IoCallDriver(devExt->LowerDeviceObject, Irp);
};
return status;
};
// 这是一个IRP完成回调函数的原型
NTSTATUS c2pReadComplete(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context) {
PIO_STACK_LOCATION IrpSp;
ULONG buf_len = 0;
PUCHAR buf = NULL;
size_t i, numKeys;
PKEYBOARD_INPUT_DATA KeyData;
IrpSp = IoGetCurrentIrpStackLocation(Irp);
// 如果这个请求是成功的。很显然,如果请求失败了,这么获取进一步的信息是没意义的。
if (NT_SUCCESS(Irp->IoStatus.Status)) {
// 获得读请求完成后输出的缓冲区
buf = Irp->AssociatedIrp.SystemBuffer;
KeyData = (PKEYBOARD_INPUT_DATA)buf;
// 获得这个缓冲区的长度。一般的说返回值有多长都保存在Information中。
buf_len = Irp->IoStatus.Information;
numKeys = buf_len / sizeof(KEYBOARD_INPUT_DATA);
//… 这里可以做进一步的处理。我这里很简单的打印出所有的扫描码。
//for(i=0;i<buf_len;++i)
for (i = 0; i < numKeys; ++i) {
//DbgPrint("ctrl2cap: %2x\r\n", buf[i]);
DbgPrint("\n");
DbgPrint("numKeys : %d", numKeys);
DbgPrint("ScanCode: %x ", KeyData->MakeCode);
DbgPrint("%s\n", KeyData->Flags ? "Up" : "Down");
print_keystroke((UCHAR)KeyData->MakeCode);
if (KeyData->MakeCode == CAPS_LOCK)
KeyData->MakeCode = LCONTROL;
};
};
gC2pKeyCount--;
if (Irp->PendingReturned)
IoMarkIrpPending(Irp);
return Irp->IoStatus.Status;
};
NTSTATUS c2pDispatchRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
NTSTATUS status = STATUS_SUCCESS;
PC2P_DEV_EXT devExt;
PIO_STACK_LOCATION currentIrpStack;
KEVENT waitEvent;
KeInitializeEvent(&waitEvent, NotificationEvent, FALSE);
if (Irp->CurrentLocation == 1) {
ULONG ReturnedInformation = 0;
KdPrint(("Dispatch encountered bogus current location\n"));
status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = ReturnedInformation;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return(status);
};
// 全局变量键计数器加1
gC2pKeyCount++;
// 得到设备扩展。目的是之后为了获得下一个设备的指针。
devExt = (PC2P_DEV_EXT)DeviceObject->DeviceExtension;
// 设置回调函数并把IRP传递下去。 之后读的处理也就结束了。剩下的任务是要等待读请求完成。
currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp, c2pReadComplete, DeviceObject, TRUE, TRUE, TRUE);
return IoCallDriver(devExt->LowerDeviceObject, Irp);
};
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
ULONG i;
NTSTATUS status;
KdPrint(("c2p.SYS: entering DriverEntry\n"));
// 填写所有的分发函数的指针
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
DriverObject->MajorFunction[i] = c2pDispatchGeneral;
// 单独的填写一个Read分发函数。因为要的过滤就是读取来的按键信息其他的都不重要。这个分发函数单独写。
DriverObject->MajorFunction[IRP_MJ_READ] = c2pDispatchRead;
// 单独的填写一个IRP_MJ_POWER函数。这是因为这类请求中间要调用一个PoCallDriver和一个PoStartNextPowerIrp,比较特殊。
DriverObject->MajorFunction[IRP_MJ_POWER] = c2pPower;
// 我们想知道什么时候一个我们绑定过的设备被卸载了(比如从机器上被拔掉了?)所以专门写一个PNP(即插即用)分发函数
DriverObject->MajorFunction[IRP_MJ_PNP] = c2pPnP;
// 卸载函数。
DriverObject->DriverUnload = c2pUnload;
gDriverObject = DriverObject;
// 绑定所有键盘设备
status = c2pAttachDevices(DriverObject, RegistryPath);
return status;
};