键盘过滤
PDO
:Physical Device Object,字面意义是物理设备,暂时可以这么理解:PDO为设备栈中最下面的那个设备对象。
键盘工作原理:P121页;简述:当键盘有按键按下/松开时,将会触发中断处理函数,中断服务例程由键盘驱动提供,键盘的驱动会从端口读取扫描码,经过处理后,将获取到的数据提交给IRP,将会有对应线程对该IRP进行读取获取键盘信息。
一般来说一个PS/2的键盘的设备栈(没有安装其它键盘过滤驱动的话)为:顶层设备对象为KbdClass
驱动生成的设备对象,中间层的设备对象是驱动8042prt
生成的设备对象,底层设备对象是驱动ACPI
生成的设备对象,
无须关心上面的细节,只需要知道一个正规的键盘过滤驱动一般是去绑定KbdClass
驱动生成的设备对象。
键盘过滤的框架
-
找到并绑定所有的键盘设备。
KbdClass
驱动名为\Driver\Kbdclass
,通过ObReferenceObjectByName
,可以打开该驱动。- 通过设备链遍历
KbdClass
驱动的设备对象,利用IoCreateDevice
创建过滤设备对象,使用IoAttachDeviceToDeviceStack
绑定每一个设备即可绑定到所有的键盘设备,将该API返回的参数以设备扩展的方式保存,方便直接下发IRP给下层设备对象。
-
设置相应的IRP回调,需要关心的有
IRP_MJ_READ
(因为要过滤的就是读取按键信息)、IRP_MJ_POWER
(因为这类请求需要使用PoCallDriver和一个PoStartNextPowerIrp来向下层设备发送IRP)、IRP_MJ_PNP
(当设备被插拔时的分发函数)、以及相应的卸载函数。-
IRP_MJ_READ
:当截到read的irp请求时,应该有一个全局变量计数器对其进行+1操作,目的是为了在卸载驱动的时候,等待该全局变量计数器归0再去卸载,不然会造成irp完成例程还没结束时就卸载的情况,此时就会蓝屏。
因为读取的IRP还没下发,所以并不知道读取了什么内容,只能通过设置完成例程来获取IRP读取的内容。通过如下代码拷贝栈空间,设置IRP完成例程来读取键盘的内容。
IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine( Irp, c2pReadComplete, DeviceObject, TRUE, TRUE, TRUE );
在完成例程中可以通过如下代码来获取所有读取的内容,也可以修改读取的内容来讲LOCK键改成CTRL键,当完成例程结束时还需要将全局变量-1.
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; }
-