抑郁症健康,内容丰富有趣,生活中的好帮手!
抑郁症健康 > windows7 《寒江独钓》传统键盘过滤驱动学习

windows7 《寒江独钓》传统键盘过滤驱动学习

时间:2022-11-24 18:27:32

相关推荐

本人驱动方面小白一枚,如果有什么不足之处与不正确之处还请各位大佬多多指正。

另外,本篇博客参考了以下博客,非常感谢原作者的分享!我在学习的时候真的受到了很大的帮助!

IRP相关:

/whatday/article/details/7106721

/thread-59944-1-1.html

/zacklin/article/details/7612499

设备对象结构:

/xuankuwa/p/3657968.html

卸载后蓝屏问题解决:

/s/blog_538396fd0100zgef.html

1:原理:

通过ObReferenceByName打开KbdClass驱动对象,然后遍历KbdClass的设备栈,对上面的设备逐一绑定。随后发送到KbdClass的IRP都会先经过我们自己的设备对象,就可以在读派遣函数中设置完成例程,当IRP完成后在完成历程中得到按键信息。

2:实现:

A:宏定义与数据结构:

extern POBJECT_TYPEIoDriverObjectType;

ULONG count = 0;

#define KBD_DRIVER_NAME L”\Driver\Kbdclass”

#define DELAY_ONE_MICROSECOND (-10)

#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND1000)

#define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND1000)

typedef struct

{

ULONG NodeSize;

PDEVICE_OBJECT filterdevice;

PDEVICE_OBJECT lowerdevice;

PDEVICE_OBJECT targetdevice;

KSPIN_LOCK IoRequestsSpinLock;

KEVENT IoInProcessEvent;

}C2P_DEV_EXT,PC2P_DEV_EXT;12345678910111213141516

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;

}1234567891011121314

C:绑定函数:

本部分功能是寻找KbdClass和绑定设备,首先需要用到的是通过设备名打开驱动对象的函数ObReference().该函数在官方文档里面虽然没有给出,但是的确已经被导出。所以只需要在函数里面声明一下即可使用。

NTSTATUS ObReferenceObjectByName

(

PUNICODE_STRING ObjectName,

ULONG Attributes,

PACCESS_STATE AccessState,

ACCESS_MASK DesiredAccess,

POBJECT_TYPE ObjectType,

KPROCESSOR_MODE AccessMode,

PVOID ParseContext,

PVOIDObject

);123456789101112

然后通过获取到的驱动对象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;

}123456789101112131415161718192223242526272829303132333435363738394041424344454647484950515253545556575859606162

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

}1234567

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;

}

}12345678910111213141516171819

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

}123456789101112131415161718192223242526272829303132

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;

}1234567891011121314151617181922232425262728

关于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);

}123456

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;

}123456789101112131415161718192021222324252627282930313233343536373839404142

F:关于《寒江独钓》书中代码的两个问题。

在windows7下执行书中的源代码生成的驱动的时候,会出现通过无法打开设备对象的问题。要解决这个问题,只需要修改下面两行代码即可。

原代码:

extern POBJECT_TYPE IoDriverObjectType;

status = ObReferenceObjectByName (

&uniNtNameString,

OBJ_CASE_INSENSITIVE,

NULL,

0,

IoDriverObjectType,

KernelMode,

NULL,

&KbdDriverObject

);1234567891011

修改之后:

extern POBJECT_TYPEIoDriverObjectType;

status = ObReferenceObjectByName (

&uniNtNameString,

OBJ_CASE_INSENSITIVE,

NULL,

0,

*IoDriverObjectType,

KernelMode,

NULL,

&KbdDriverObject

);1234567891011

这个问题在windowsXP下并没有,我也搞不清楚原因,还请各位大佬指教!在这里先感谢大家了!

另一个问题是windows7下面在卸载原代码编译生成的驱动之后,再次击键的一瞬间会出现蓝屏的问题。这是因为ObDeferenceObject()里面的参数应该是ObReferenceByName得到的驱动对象,而不应该是本驱动的驱动对象。或许是windowsXP检查不严谨导致?

原代码:

ObDereferenceObject(DriverObject);1

修改:

ObDereferenceObject(kbd); //kbd是通过ObReferenceByName获取到的KbdClass的驱动对象1

如果觉得《windows7 《寒江独钓》传统键盘过滤驱动学习》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。