windows7 《寒江独钓》传统键盘过滤驱动学习
2017-08-01 16:08
381 查看
本人驱动方面小白一枚,如果有什么不足之处与不正确之处还请各位大佬多多指正。
另外,本篇博客参考了以下博客,非常感谢原作者的分享!我在学习的时候真的受到了很大的帮助!
IRP相关:
http://blog.csdn.net/whatday/article/details/7106721
http://bbs.fishc.com/thread-59944-1-1.html
http://blog.csdn.net/zacklin/article/details/7612499
设备对象结构:
http://www.cnblogs.com/xuankuwa/p/3657968.html
卸载后蓝屏问题解决:
http://blog.sina.com.cn/s/blog_538396fd0100zgef.html
1:原理:
通过ObReferenceByName打开KbdClass驱动对象,然后遍历KbdClass的设备栈,对上面的设备逐一绑定。随后发送到KbdClass的IRP都会先经过我们自己的设备对象,就可以在读派遣函数中设置完成例程,当IRP完成后在完成历程中得到按键信息。
2:实现:
A:宏定义与数据结构:
B:DriverEntry:
程序入口部分,设定卸载函数,分发函数等等。
C:绑定函数:
本部分功能是寻找KbdClass和绑定设备,首先需要用到的是通过设备名打开驱动对象的函数ObReference().该函数在官方文档里面虽然没有给出,但是的确已经被导出。所以只需要在函数里面声明一下即可使用。
然后通过获取到的驱动对象kbddriver指向的设备链进行遍历。对遍历得到的每一个设备对象进行绑定。
为了便于后面IRP请求以及卸载的操作,我们为设备扩展(DeviceExtensions)定义了数据结构,有兴趣的为了保证内核线程的安全性,可以把时间和锁加上去。
IoAttachDeviceToDeviceStack是将调用方的设备对象附加到设备对象链中的最高层,并返回之前在设备对象链中最高的设备对象。靠着这个函数,我们的设备对象从低层的kbdclass指向的设备链对象绑定到了设备链的最顶层。也就是说,我们创建的filterdevice载设备链里紧贴着的并不一定是Kbdclass设备链的对象。故此我们使用C2P_DEV_EXT结构中的plowerdevice存储filterdevice下面的那个设备对象。这个设备对象我们一般称为真实设备。
但是在接触绑定的时候,传递的参数依然是最开始绑定的那个设备对象。所以在C2P_DEV_EXT结构中,我们使用ptargetdevice存储最开始的kbdclass的设备链中的设备对象。
D:分发函数:
a:电源事件分发函数:
调用PoStartNextPowerIrp()后把请求下发即可。
b:pnp(即插即拔)事件分发函数:
将请求下发,解除绑定,删除绑定的设备。
在触发pnp请求的时候,毫无疑问所有的击键与中断都已经由系统完成。所以不需要考虑会有中断滞留的情况。
c:读请求分发函数:
在我们没有击键的时候,系统的函数会创建一个IRP_MJ_READ的请求到内核,然后该请求会滞留在那里。一直到我们击键触发中断。在这种情况下,我们卸载驱动的时候,如果直接删除驱动,那么这个IRP_MJ_READ在我们下一次击键触发中断的时候会导致系统的蓝屏。所以为了解决这个问题,我们必须在一次中断得到满足的时候卸载驱动,count用来标记当前的IRP_MJ_READ有没有得到满足。
如此一来,键盘的读请求就应该分成两个状态:击键和击键完成上一个IRP_MJ_READ请求被满足。故此我们设定两个函数Dispatchread和readcomplete。将readcomplete设为回调函数。
关于irp.Iostatus的详细信息,参看参考博客。
d:普通分发函数:
简单的将请求传到下一层即可。
E:卸载函数:
解除绑定删除设备这些常见的姑且不提,有一点需要注意。在读分发函数的解释里面已经说过,卸载必须在彻底完成一个请求的时候进行,否则就会导致系统蓝屏。所以我们通过判断标志位count是否为0来确定请求是否被满足。这样一来,该驱动卸载时间就变成了下一次击键的时候。
F:关于《寒江独钓》书中代码的两个问题。
在windows7下执行书中的源代码生成的驱动的时候,会出现通过无法打开设备对象的问题。要解决这个问题,只需要修改下面两行代码即可。
原代码:
修改之后:
这个问题在windowsXP下并没有,我也搞不清楚原因,还请各位大佬指教!在这里先感谢大家了!
另一个问题是windows7下面在卸载原代码编译生成的驱动之后,再次击键的一瞬间会出现蓝屏的问题。这是因为ObDeferenceObject()里面的参数应该是ObReferenceByName得到的驱动对象,而不应该是本驱动的驱动对象。或许是windowsXP检查不严谨导致?
原代码:
修改:
另外,本篇博客参考了以下博客,非常感谢原作者的分享!我在学习的时候真的受到了很大的帮助!
IRP相关:
http://blog.csdn.net/whatday/article/details/7106721
http://bbs.fishc.com/thread-59944-1-1.html
http://blog.csdn.net/zacklin/article/details/7612499
设备对象结构:
http://www.cnblogs.com/xuankuwa/p/3657968.html
卸载后蓝屏问题解决:
http://blog.sina.com.cn/s/blog_538396fd0100zgef.html
1:原理:
通过ObReferenceByName打开KbdClass驱动对象,然后遍历KbdClass的设备栈,对上面的设备逐一绑定。随后发送到KbdClass的IRP都会先经过我们自己的设备对象,就可以在读派遣函数中设置完成例程,当IRP完成后在完成历程中得到按键信息。
2:实现:
A:宏定义与数据结构:
extern POBJECT_TYPE *IoDriverObjectType; ULONG count = 0; #define KBD_DRIVER_NAME L"\\Driver\\Kbdclass" #define DELAY_ONE_MICROSECOND (-10) #define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000) #define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000) typedef struct { ULONG NodeSize; PDEVICE_OBJECT filterdevice; PDEVICE_OBJECT lowerdevice; PDEVICE_OBJECT targetdevice; KSPIN_LOCK IoRequestsSpinLock; KEVENT IoInProcessEvent; }C2P_DEV_EXT,*PC2P_DEV_EXT;
B:DriverEntry:
程序入口部分,设定卸载函数,分发函数等等。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT driver,IN PUNICODE_STRING path) { ULONG i; NTSTATUS status; KdPrint(("Entering the filter driver\r\n")); for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) driver->MajorFunction[i] = Gdispatch; driver->MajorFunction[IRP_MJ_READ] = Dispatchread; //读分发函数 driver->MajorFunction[IRP_MJ_POWER] = Power; //电源事件分发函数 driver->MajorFunction[IRP_MJ_PNP] = Pnp; //PNP事件分发函数 driver->DriverUnload = Unload; //卸载函数 status = AttachDevices(driver,path); return status; }
C:绑定函数:
本部分功能是寻找KbdClass和绑定设备,首先需要用到的是通过设备名打开驱动对象的函数ObReference().该函数在官方文档里面虽然没有给出,但是的确已经被导出。所以只需要在函数里面声明一下即可使用。
NTSTATUS ObReferenceObjectByName ( PUNICODE_STRING ObjectName, ULONG Attributes, PACCESS_STATE AccessState, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, PVOID ParseContext, PVOID *Object );
然后通过获取到的驱动对象kbddriver指向的设备链进行遍历。对遍历得到的每一个设备对象进行绑定。
为了便于后面IRP请求以及卸载的操作,我们为设备扩展(DeviceExtensions)定义了数据结构,有兴趣的为了保证内核线程的安全性,可以把时间和锁加上去。
NTSTATUS AttachDevices(PDRIVER_OBJECT driver,PUNICODE_STRING path) { NTSTATUS status=0; UNICODE_STRING string; PC2P_DEV_EXT ext; PDEVICE_OBJECT filter = NULL; PDEVICE_OBJECT lower = NULL; PDEVICE_OBJECT target = NULL; PDRIVER_OBJECT kbd = NULL; //获取Kbdclass驱动对象 RtlInitUnicodeString(&string, KBD_DRIVER_NAME); status = ObReferenceObjectByName(&string,OBJ_CASE_INSENSITIVE,NULL,0,*IoDriverObjectType,KernelMode,NULL,&kbd); if (!NT_SUCCESS(status)) { KdPrint(("attach failed\r\n")); return( status ); } else { ObDereferenceObject(kbd); } //遍历设备链 target = kbd->DeviceObject; while (target != NULL) { //创建过滤设备 status = IoCreateDevice(driver, sizeof(C2P_DEV_EXT), NULL, target->DeviceType, target->Characteristics,TRUE, &filter); if (!NT_SUCCESS(status)) { KdPrint(("could not create device\r\n")); return (status); } lower=IoAttachDeviceToDeviceStack(filter, target); if (!lower) { KdPrint(("could not attach device\r\n")); IoDeleteDevice(filter); filter = NULL; return (status); } //初始化设备扩展项,便于后来操作 ext = (PC2P_DEV_EXT)(filter->DeviceExtension); memset(ext,0,sizeof(C2P_DEV_EXT)); ext->lowerdevice = lower; ext->filterdevice = filter; ext->targetdevice = target; //KeInitializeSpinLock(&(ext->IoRequestsSpinLock)); //KeInitializeEvent(&(ext->IoInProcessEvent), NotificationEvent, FALSE); //设置过滤设备的一些参数,具体作用参看引用博客 filter->Characteristics = lower->Characteristics; filter->DeviceType = lower->DeviceType; filter->StackSize = lower->StackSize; filter->Flags |= lower->Flags & (DO_DIRECT_IO | DO_BUFFERED_IO | DO_POWER_PAGABLE); target = target->NextDevice; } return status; }
IoAttachDeviceToDeviceStack是将调用方的设备对象附加到设备对象链中的最高层,并返回之前在设备对象链中最高的设备对象。靠着这个函数,我们的设备对象从低层的kbdclass指向的设备链对象绑定到了设备链的最顶层。也就是说,我们创建的filterdevice载设备链里紧贴着的并不一定是Kbdclass设备链的对象。故此我们使用C2P_DEV_EXT结构中的plowerdevice存储filterdevice下面的那个设备对象。这个设备对象我们一般称为真实设备。
但是在接触绑定的时候,传递的参数依然是最开始绑定的那个设备对象。所以在C2P_DEV_EXT结构中,我们使用ptargetdevice存储最开始的kbdclass的设备链中的设备对象。
D:分发函数:
a:电源事件分发函数:
调用PoStartNextPowerIrp()后把请求下发即可。
NTSTATUS Power( IN PDEVICE_OBJECT device,IN PIRP irp) { KdPrint(("power dispatch\r\n")); PoStartNextPowerIrp(irp); IoSkipCurrentIrpStackLocation(irp); return IoCallDriver(((C2P_DEV_EXT *)(device->DeviceExtension))->lowerdevice, irp); }
b:pnp(即插即拔)事件分发函数:
将请求下发,解除绑定,删除绑定的设备。
在触发pnp请求的时候,毫无疑问所有的击键与中断都已经由系统完成。所以不需要考虑会有中断滞留的情况。
NTSTATUS Pnp(IN PDEVICE_OBJECT device,IN PIRP irp) { NTSTATUS status; KdPrint(("PNP dispatch\r\n")); PC2P_DEV_EXT ext = (PC2P_DEV_EXT)(device->DeviceExtension); PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp); switch(irpsp->MinorFunction) { case IRP_MN_CANCEL_REMOVE_DEVICE: IoSkipCurrentIrpStackLocation(irp); IoCallDriver(ext->lowerdevice,irp); IoDetachDevice(ext->lowerdevice); IoDeleteDevice(device); status = STATUS_SUCCESS; break; default: IoSkipCurrentIrpStackLocation(irp); IoCallDriver(ext->lowerdevice, irp); break; } }
c:读请求分发函数:
在我们没有击键的时候,系统的函数会创建一个IRP_MJ_READ的请求到内核,然后该请求会滞留在那里。一直到我们击键触发中断。在这种情况下,我们卸载驱动的时候,如果直接删除驱动,那么这个IRP_MJ_READ在我们下一次击键触发中断的时候会导致系统的蓝屏。所以为了解决这个问题,我们必须在一次中断得到满足的时候卸载驱动,count用来标记当前的IRP_MJ_READ有没有得到满足。
如此一来,键盘的读请求就应该分成两个状态:击键和击键完成上一个IRP_MJ_READ请求被满足。故此我们设定两个函数Dispatchread和readcomplete。将readcomplete设为回调函数。
NTSTATUS Dispatchread(IN PDEVICE_OBJECT device,IN PIRP irp) { PC2P_DEV_EXT ext; NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION currentstack; //KEVENT event; //KeInitializeEvent(&event, NotificationEvent, FALSE); //当请求已经到达了栈底的时候,设置请求已经完成,开始回调 if (irp->CurrentLocation == 1) { ULONG Returnedinformation=0; KdPrint(("Dispatch encountered bogus current location\r\n")); status = STATUS_INVALID_DEVICE_REQUEST; irp->IoStatus.Status = status; irp->IoStatus.Information = 0; IoCompleteRequest(irp,IO_NO_INCREMENT); return (status); } count++; ext = (PC2P_DEV_EXT)(device->DeviceExtension); //将请求传递到下一层 currentstack = IoGetCurrentIrpStackLocation(irp); IoCopyCurrentIrpStackLocationToNext(irp); //设置回调函数 IoSetCompletionRoutine(irp, readcomplete, device, TRUE, TRUE, TRUE); return IoCallDriver(ext->lowerdevice, irp); }
NTSTATUS readcomplete(IN PDEVICE_OBJECT device,IN PIRP irp,IN PVOID context) { PIO_STACK_LOCATION irpsp; PUCHAR buf = NULL; ULONG buf_len; size_t i; UNREFERENCED_PARAMETER(device); irpsp = IoGetCurrentIrpStackLocation(irp); //打印击键信息 if (NT_SUCCESS(irp->IoStatus.Status)) { buf = irp->AssociatedIrp.SystemBuffer; buf_len = irp->IoStatus.Information; for (i = 0; i < buf_len; i++) DbgPrint("ctralcap:%2x\r\n",buf[i]); } //计数器减一 count--; //声明irp没有完成,用以检查irp返回值 if (irp->PendingReturned) { IoMarkIrpPending(irp); } return irp->IoStatus.Status; }
关于irp.Iostatus的详细信息,参看参考博客。
d:普通分发函数:
简单的将请求传到下一层即可。
NTSTATUS Gdispatch(IN PDEVICE_OBJECT device,IN PIRP irp) { KdPrint(("other dispatch\r\n")); IoSkipCurrentIrpStackLocation(irp); return IoCallDriver(((C2P_DEV_EXT *)(device->DeviceExtension))->lowerdevice, irp); }
E:卸载函数:
解除绑定删除设备这些常见的姑且不提,有一点需要注意。在读分发函数的解释里面已经说过,卸载必须在彻底完成一个请求的时候进行,否则就会导致系统蓝屏。所以我们通过判断标志位count是否为0来确定请求是否被满足。这样一来,该驱动卸载时间就变成了下一次击键的时候。
VOID Unload(IN PDRIVER_OBJECT driver) { PDEVICE_OBJECT device, old; LARGE_INTEGER delay; PC2P_DEV_EXT ext; delay = RtlConvertLongToLargeInteger(100 * DELAY_ONE_MILLISECOND); PRKTHREAD current; current = KeGetCurrentThread(); KeSetPriorityThread(current,LOW_REALTIME_PRIORITY); UNREFERENCED_PARAMETER(driver); //通知编译器driver已被使用过 KdPrint(("unloading\r\n")); device = driver->DeviceObject; //遍历设备链,解除绑定和删除设备。 while (device != NULL) { ext = (PC2P_DEV_EXT)device->DeviceExtension; __try { __try{ IoDetachDevice(ext->targetdevice); ext->targetdevice = NULL; IoDeleteDevice(device); ext->filterdevice = NULL; } _except(EXCEPTION_EXECUTE_HANDLER){} } __finally{} device = device->NextDevice; } KdPrint(("%ld\r\n",count)); //等待IRP_MJ_READ完成 while (count!=0) { KeDelayExecutionThread(KernelMode, FALSE, &delay); } KdPrint(("unload completely\r\n")); return; }
F:关于《寒江独钓》书中代码的两个问题。
在windows7下执行书中的源代码生成的驱动的时候,会出现通过无法打开设备对象的问题。要解决这个问题,只需要修改下面两行代码即可。
原代码:
extern POBJECT_TYPE IoDriverObjectType; status = ObReferenceObjectByName ( &uniNtNameString, OBJ_CASE_INSENSITIVE, NULL, 0, IoDriverObjectType, KernelMode, NULL, &KbdDriverObject );
修改之后:
extern POBJECT_TYPE *IoDriverObjectType; status = ObReferenceObjectByName ( &uniNtNameString, OBJ_CASE_INSENSITIVE, NULL, 0, *IoDriverObjectType, KernelMode, NULL, &KbdDriverObject );
这个问题在windowsXP下并没有,我也搞不清楚原因,还请各位大佬指教!在这里先感谢大家了!
另一个问题是windows7下面在卸载原代码编译生成的驱动之后,再次击键的一瞬间会出现蓝屏的问题。这是因为ObDeferenceObject()里面的参数应该是ObReferenceByName得到的驱动对象,而不应该是本驱动的驱动对象。或许是windowsXP检查不严谨导致?
原代码:
ObDereferenceObject(DriverObject);
修改:
ObDereferenceObject(kbd); //kbd是通过ObReferenceByName获取到的KbdClass的驱动对象
相关文章推荐
- [IRP HOOK] 键盘过滤驱动学习
- 驱动开发 键盘过滤驱动程序-- 传统的键盘过滤
- 键盘过滤驱动(学习)
- 键盘过滤驱动学习
- 驱动笔记15 - 键盘过滤驱动学习笔记
- 键盘过滤驱动
- 初尝windows内核编程-键盘过滤驱动
- 【转帖】文件过滤系统驱动开发Filemon学习笔记
- 文件过滤系统驱动开发Filemon学习笔记
- 以前读不懂,现在读了发现有太多的东西可以参考了:支持PS2与USB的键盘过滤驱动(可卸载)
- (转)支持 PS/2 与 USB 的键盘过滤驱动(可卸载)
- 支持 PS/2 与 USB 的键盘过滤驱动(可卸载)
- <寒江独钓>Windows内核安全编程__传统键盘过滤程序
- 支持 PS/2 与 USB 的键盘过滤驱动(可卸载)
- Windows内核安全编程:传统键盘过滤程序
- [转帖]支持PS2和USB的键盘过滤驱动(xfocus)
- 开发键盘过滤驱动实现模拟按键过程中遇到的问题
- 键盘的过滤(绑定驱动kdbclass的所有设备对象)
- 支持 PS/2 与 USB 的键盘过滤驱动(可卸载)
- 驱动开发之 键盘过滤驱动--传统型键盘过滤