键盘过滤驱动

      在笔者接触驱动到现在以来一以后大半个月的时间,从中让我深深的体会到了万事开头难,以及学习持之以恒的重要性。笔者也是个驱动新人,开始接触驱动的时候看着张帆的《Windows驱动开发技术详解》讲的挺细,对新手来说是个不错的学习资料,但是更重要的还是自己要多动手练习,笔者在学习到同步操作的相关知识的时候,实在是看天书。最后还是放弃了学习本书。再找了本楚狂人的资料学习,感觉本书对新手来说还是比较吃力的,其中笔者就是这样,很多知识点不是很明白,只能凭借自己的感觉去做,不过造成的后果就是无情的蓝屏^_^。最终要的是笔者坚持下来了。


  今天来分享下学习过程中,编写键盘过滤的心得。关于工作原理因为笔者也是一知半解,就不在阐述。

 


  我们的目的就是将自己的驱动设备挂接/driver/kbdclass驱动下的所有设备,如图所示:

 

 

键盘过滤驱动_第1张图片

 

 

   然后通过处理来达到过滤我们想要的按键信息。挂接后的驱动中的第一个设备就是我们的过滤设备,当有按键触发,按键信息首先会被我们自己写的设备所拦截,但是这时候拦截到的是没有处理的按键信息,那改怎么处理呢?我们去问键盘驱动,当我们拦截到按键IRP的时候先不做处理,给IRP设置完成回调函数并传递给键盘驱动的设备。这样一来,当按键IRP被键盘驱动处理完毕之后就会执行我们的回调函数,这时我们在处理按键信息。当卸载我们的过滤设备的时候会有个麻烦就是会有个IRP已经设备了回调例程,并且在等待按键触发。如果这个IRP在没有处理之前就卸载掉我们的过滤驱动,就会引发按键蓝盘。为什么会蓝屏呢?因为这个IRP是已经被设置了回调函数,当IRP被处理完成之后去找我们设置的回调函数,因为我们在IRP没有处理之前已经卸载了,所以这时IRP已经找不到回调函数了,所以导致蓝屏。大部分都的解决方案是在处理IRP的时候放置个计数器,当计数器不为0的时候说明还有IRP未完成,这是卸载的时候就用while来一直等待这个IRP完成,如果我们要是不按键盘的话,它会无休止的等待下去,并且也影响系统性能。
  笔者通过相关资料的查阅,另个解决方案就是做个代理IRP,然后保存原来的IRP,因为我们可以取消自己的IRP。在卸载的时候先卸载我们的代理IRP,然后在发送原来保存的IRP,这样就很好的解决了无限的等待的BUG...但是笔者也没有找到相关代码,只好自己动手试。经过一下午的测试,笔者发现我们只需要做一个代理IRP即可,并不需要保存原来的IRP,卸载的时候直接取消我们的IRP,并不需要重新发送个IRP。下面我们来通过具体代码学习一下键盘过滤驱动。

 

首先:

 

 //驱动入口 extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath) { NTSTATUS status; DbgPrint("驱动加载开始.../n"); pDriverObject->DriverUnload=FilterUnload; //设置读取派遣函数 pDriverObject->MajorFunction[IRP_MJ_READ]=FilterDispatchRoutin; BindDevice(pDriverObject); DbgPrint("驱动加载结束.../n"); return STATUS_SUCCESS; }    

 

  在主函数中,调用BindDevice来实现过滤驱动的创建与绑定,代码如下:

 //设备类型 extern "C" POBJECT_TYPE IoDriverObjectType; NTSTATUS BindDevice(PDRIVER_OBJECT pDriverObject) { NTSTATUS status; UNICODE_STRING uniNtNameString; //要打开的驱动对象 PDRIVER_OBJECT KbdDriverObject = NULL; //驱动对象的设备 PDEVICE_OBJECT kbdDeviceOjbect; //初始化一个字符串,就是kbdclass驱动的名子 RtlInitUnicodeString(&uniNtNameString,KBD_DRIVER_NAME); //根据名字打开驱动对象 status=ObReferenceObjectByName( &uniNtNameString, OBJ_CASE_INSENSITIVE, NULL, 0, IoDriverObjectType, KernelMode, NULL, (PVOID*)&KbdDriverObject); //如果失败了就直接返回 if(!NT_SUCCESS(status)) { DbgPrint("打开设备失败.../n"); return status; } //调用ObReferenceObjectByName会导致对驱动对象的引用计数增加 //必须响应的调用解引用ObDereferenceObject ObDereferenceObject(pDriverObject); DbgPrint("打开成功,解除引用.../n"); //键盘驱动的第一个设备 kbdDeviceOjbect=KbdDriverObject->DeviceObject; while(kbdDeviceOjbect!=NULL) { //创建并绑定过滤设备 CreateDevice(pDriverObject,kbdDeviceOjbect); //下一个设备 kbdDeviceOjbect=kbdDeviceOjbect->NextDevice; } return status; }  

在这里说一下ObReferenceObjectByName函数,该方法没有被导出,知我我们在头文件中声明一下即可使用,声明如下:

//根据名字获取设备对象,此函数没有公开,声明一下就可以直接使用了 extern "C" NTSTATUS ObReferenceObjectByName( PUNICODE_STRING objectName, ULONG Attributes, PACCESS_STATE AccessState, ACCESS_MASK DesiredAccess, POBJECT_TYPE objectType, KPROCESSOR_MODE accessMode, PVOID ParseContext, PVOID *Object);  

 

BindDevice方法中,调用了一个CreateDevice方法,该方法负责创建过滤设备,并且附加在目标设备上,具体代码如下:

//创建设备 NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDriverObject,IN PDEVICE_OBJECT oldDevObj) { NTSTATUS status; PDEVICE_OBJECT pDevObj; //谁被扩展 PDEVICE_EXTENSION pDevExt; status=IoCreateDevice(pDriverObject, sizeof(PDEVICE_EXTENSION), NULL, oldDevObj->DeviceType,//设备类型需要和被附加的设备类型相等 0, FALSE,//如果指定设备是独占的,大部分驱动程序设置这个值为FALSE,如果不是独占的话设置为TRUE. &pDevObj); if(!NT_SUCCESS(status)) { DbgPrint("创建设备失败..../n"); return NULL; } pDevExt=(PDEVICE_EXTENSION)pDevObj->DeviceExtension; //存储设备对象 pDevExt->pDevice=pDevObj; //绑定前原设备 pDevExt->poldDevice=oldDevObj; //标志位 pDevObj->Flags |=oldDevObj->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE); //该标识指示I/O管理器对所有发送到控制设备对象的Open请求进行安全检测 pDevObj->Characteristics=oldDevObj->Characteristics; //绑定设备 PDEVICE_OBJECT topDev = IoAttachDeviceToDeviceStack(pDevObj,oldDevObj); if(topDev==NULL) { //如果绑定失败,销毁设备 IoDeleteDevice(pDevObj); status=STATUS_UNSUCCESSFUL; return status; } //将绑定的设备和原始设备放入设备扩展中 pDevExt->poldDevice=oldDevObj; pDevExt->pbindDevice=topDev; pDevObj->Flags=pDevObj->Flags & ~DO_DEVICE_INITIALIZING; KdPrint(("绑定成功../n")); return STATUS_SUCCESS; }  

 

 

 通过以上代码可以实现过滤设备的绑定,绑定了之后还是主要处理派遣函数,功能如下:

//派遣函数 NTSTATUS FilterDispatchRoutin(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp) { PIO_STACK_LOCATION currentIrpStack; PDEVICE_EXTENSION pDevExt; //得到设备扩展 pDevExt=(PDEVICE_EXTENSION)pDevObj->DeviceExtension; //得到当前irp包 currentIrpStack=IoGetCurrentIrpStackLocation(pIrp); //将当前irp复制到下层设备irp堆栈 IoCopyCurrentIrpStackLocationToNext(pIrp); //保存原来的irp //pDevExt->tagIrp=pIrp; //代理irp pDevExt->proxyIrp=pIrp; //设置当irp完成时的回调例程 IoSetCompletionRoutine(pDevExt->proxyIrp,CallBackKbdFilter,pDevObj,TRUE,TRUE,TRUE); DbgPrint("irp回调例程设置完毕.../n"); return IoCallDriver(pDevExt->poldDevice,pDevExt->proxyIrp); }  

 

  注意的是在处理派遣函数的时候我们将IRP换成我们自己的IRP,这样就能达到取消IRP的目的,我们给IRP设置了回调函数,当IRP处理完成的时候就去执行回调函数,回调函数如下:

// 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); } } NTSTATUS CallBackKbdFilter( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { PIO_STACK_LOCATION currentIrp; PKEYBOARD_INPUT_DATA keyData; currentIrp=IoGetCurrentIrpStackLocation(Irp); if(NT_SUCCESS(Irp->IoStatus.Status)) { keyData=(PKEYBOARD_INPUT_DATA)Irp->AssociatedIrp.SystemBuffer; //DbgPrint("扫描码:%x",keyData->MakeCode); DbgPrint("键盘 :%s",keyData->Flags?"弹起":"按下"); print_keystroke((UCHAR)keyData->MakeCode); } if( Irp->PendingReturned ) { IoMarkIrpPending( Irp ); } return Irp->IoStatus.Status; }  

  函数就不说明了,主要就是对makecode的处理,不过在回调函数中引用了对照表,如下:

 

 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 };

  就是卸载函数,在卸载的时候我们要删除设备和附加的设备,然后取消最后一个IRP,代码如下:

//卸载例程 void FilterUnload(IN PDRIVER_OBJECT pDriverObject) { //得到设备 PDEVICE_OBJECT pDevObj=pDriverObject->DeviceObject; while(pDevObj!=NULL) { //设备扩展 PDEVICE_EXTENSION pDevExt=(PDEVICE_EXTENSION)pDevObj->DeviceExtension; PDEVICE_OBJECT pTagObj=pDevExt->pbindDevice; //解除绑定 if(pDevExt->pbindDevice!=NULL) { IoDetachDevice(pDevExt->pbindDevice); } //删除设备 if(pDevExt->pDevice!=NULL) { IoDeleteDevice(pDevExt->pDevice); } if(pDevExt->proxyIrp!=NULL) { if(CancelIrp(pDevExt->proxyIrp)) { DbgPrint("取消成功。。。/n"); } else { DbgPrint("取消失败。。。/n"); } } //下一个设备 pDevObj=pDevObj->NextDevice; } }  

 

  载函数中调用了个取消IRP的方法,代码如下:

BOOLEAN CancelIrp(PIRP pIrp) { if(pIrp==NULL) { DbgPrint("取消irp错误.../n"); return FALSE; } if(pIrp->Cancel || pIrp->CancelRoutine==NULL) { DbgPrint("取消irp错误.../n"); return FALSE; } if(FALSE==IoCancelIrp(pIrp)) { DbgPrint("IoCancelIrp to irp错误.../n"); return FALSE; } //取消后重设此例为空 IoSetCancelRoutine(pIrp,NULL); return TRUE; }  

 

整个键盘过滤驱动就完成了,以后还得多多学习,多多总结。

转载请注明来自:http://blog.csdn.net/ms2146

你可能感兴趣的:(object,String,null,extension,attributes)