阶段性demo 键盘信息过滤
写在前面的话
本来想按照理论+实践的方式继续向下写文章,但是写来写去发现这一阶段涉及的知识实在是太多太杂,如果什么都事无巨细的去讲解,那么这样反倒会让文章变得杂乱,所以我打算直接上一个demo代码,然后根据这个demo代码来补全我们现阶段应该知道的知识(不一定理解深入,先会用)
demo
代码先放在最上面,但是这个代码是有问题的
重要的话说三遍!但是这个代码是有问题的!
重要的话说三遍!但是这个代码是有问题的!
重要的话说三遍!但是这个代码是有问题的!
如果是正常的键盘记录,安装驱动和运行服务都是没有问题的,但是一旦停止服务,就会蓝屏(访问缺页),疑似是因为没有正确的处理Irp导致卸载了驱动之后还在访问缺页地址,但是限于本人目前水平,没有办法得出具体的原因,这也不会是教学的终点,所以就先把代码作为示例来讲解即可
#include<ntifs.h>
#define MY_COMPONENT_ID 123
#define MY_INFO_LEVEL DPFLTR_INFO_LEVEL
#define MY_ERROR_LEVEL DPFLTR_ERROR_LEVEL
//键盘输入包
typedef struct _KEYBOARD_INPUT_DATA {
USHORT UnitId;
USHORT MakeCode;
USHORT Flags;
USHORT Reserved;
ULONG ExtraInformation;
} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
//设备扩展结构
typedef struct _Dev_exten {
ULONG Size; //该结构的大小
PDEVICE_OBJECT FilterDevice; //过滤设备对象
PDEVICE_OBJECT TargetDevice; //目标设备对象
PDEVICE_OBJECT LowDevice; //低级设备对象
KSPIN_LOCK IoRequestSpinLock; //自旋锁
KEVENT IoProgressEvent; //事件
PIRP pIrp; //IRP
}DEV_EXTENSION, *PDEV_EXTENSION;
NTSTATUS DeAttach(PDEVICE_OBJECT pDevice) {
PDEV_EXTENSION devExt;
devExt = (PDEV_EXTENSION)pDevice->DeviceExtension;
if (devExt->TargetDevice) {
IoDetachDevice(devExt->TargetDevice);
devExt->TargetDevice = NULL;
}
if (devExt->FilterDevice) {
devExt->FilterDevice = NULL;
}
if (devExt->pIrp != NULL) {
#ifdef DEBUG
DbgBreakPoint();
#endif
if (IoCancelIrp(devExt->pIrp)) {
DbgPrintEx(MY_COMPONENT_ID, MY_INFO_LEVEL, "取消成功...\r\n");
}
else {
DbgPrintEx(MY_COMPONENT_ID, MY_ERROR_LEVEL, "取消失败...\r\n");
}
// 释放 IRP 的内存
IoFreeIrp(devExt->pIrp);
devExt->pIrp = NULL;
}
// 删除设备对象
IoDeleteDevice(pDevice);
return STATUS_SUCCESS;
}
//设备卸载函数
NTSTATUS DriverUnload(PDRIVER_OBJECT pDriver) {
PDEVICE_OBJECT pDevice;
PDEV_EXTENSION devExt;
UNREFERENCED_PARAMETER(pDriver);
DbgPrint("Driver Unloading..\r\n");
//DbgBreakPoint();
pDevice = pDriver->DeviceObject;
while (pDevice) {
DeAttach(pDevice);
pDevice = pDevice->NextDevice;
}
pDriver->DeviceObject = NULL;
//DbgBreakPoint();
return STATUS_SUCCESS;
}
extern POBJECT_TYPE *IoDriverObjectType;
NTSTATUS ObReferenceObjectByName(
PUNICODE_STRING ObjectName,
ULONG Attributes,
PACCESS_STATE PassedAccessState,
ACCESS_MASK DesiredAccess,
POBJECT_TYPE ObjectType,
KPROCESSOR_MODE AccessMode,
PVOID ParseContext,
PVOID *Object
);
//设备操作通用分发函数
NTSTATUS GeneralDispatch(PDEVICE_OBJECT pDevice, PIRP pIrp) {
NTSTATUS status;
PDEV_EXTENSION devExt = (PDEV_EXTENSION)pDevice->DeviceExtension;
PDEVICE_OBJECT lowDevice = devExt->LowDevice;
IoSkipCurrentIrpStackLocation(pIrp);
status = IoCallDriver(lowDevice, pIrp);
return status;
}
//即插即用IRP分发函数
NTSTATUS PnpDispatch(PDEVICE_OBJECT pDevice, PIRP pIrp) {
PDEV_EXTENSION devExt;
PIO_STACK_LOCATION stack;
NTSTATUS status = STATUS_SUCCESS;
devExt = (PDEV_EXTENSION)pDevice->DeviceExtension;
stack = IoGetCurrentIrpStackLocation(pIrp);
switch (stack->MinorFunction)
{
case IRP_MN_REMOVE_DEVICE:
//向下继续分发请求
IoSkipCurrentIrpStackLocation(pIrp);
IoCallDriver(devExt->LowDevice, pIrp);
//解除绑定
IoDetachDevice(devExt->LowDevice);
//删除我们自己生成的设备
IoDeleteDevice(pDevice);
status = STATUS_SUCCESS;
break;
default:
//其他类型的IRP,全部正常下发
IoSkipCurrentIrpStackLocation(pIrp);
status = IoCallDriver(devExt->LowDevice, pIrp);
}
return status;
}
//电源IRP分发函数
NTSTATUS PowerDispatch(PDEVICE_OBJECT pDevice, PIRP pIrp) {
PDEV_EXTENSION devExt;
devExt = (PDEV_EXTENSION)pDevice->DeviceExtension;
PoStartNextPowerIrp(pIrp);
IoSkipCurrentIrpStackLocation(pIrp);
return PoCallDriver(devExt->TargetDevice, pIrp);
}
NTSTATUS DevExtInit(PDEV_EXTENSION devExt, PDEVICE_OBJECT filterDevice, PDEVICE_OBJECT targetDevice, PDEVICE_OBJECT lowDevice) {
memset(devExt, 0, sizeof(DEV_EXTENSION));
devExt->FilterDevice = filterDevice;
devExt->TargetDevice = targetDevice;
devExt->LowDevice = lowDevice;
devExt->Size = sizeof(DEV_EXTENSION);
KeInitializeSpinLock(&devExt->IoRequestSpinLock);
KeInitializeEvent(&devExt->IoProgressEvent, NotificationEvent, FALSE);
return STATUS_SUCCESS;
}
NTSTATUS AttachDevice(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegPatch) {
UNICODE_STRING KeyBoardName = RTL_CONSTANT_STRING(L"\\Driver\\KbdClass");
NTSTATUS status = 0;
PDEVICE_OBJECT filterDevice;//过滤设备
PDEV_EXTENSION devExt;//过滤设备的扩展
PDEVICE_OBJECT targetDevice;// 目标设备
PDEVICE_OBJECT lowDevice;// 低级设备
PDRIVER_OBJECT khdDriver;//用于接受所查询对象的指针
//获取键盘的驱动对象,保存在khdDriver里面
status = ObReferenceObjectByName(&KeyBoardName, OBJ_CASE_INSENSITIVE, NULL, 0, *IoDriverObjectType, KernelMode, NULL, &khdDriver);
if (!NT_SUCCESS(status)){
DbgPrint("Open KeyBoard Driver Failed\r\n");
return status;
}
else {
ObDereferenceObject(khdDriver);
//获取第一个设备
targetDevice = khdDriver->DeviceObject;
//遍历驱动中的设备
while (targetDevice) {
//创建一个过滤设备
status = IoCreateDevice(pDriver, sizeof(DEV_EXTENSION), NULL, targetDevice->DeviceType, targetDevice->Characteristics, FALSE, &filterDevice);
if (!NT_SUCCESS(status)) {
DbgPrint("Create FilterDevice Error\r\n");
filterDevice = targetDevice = NULL;
return status;
}
//绑定下一个设备
lowDevice = IoAttachDeviceToDeviceStack(filterDevice, targetDevice);
if (!lowDevice) {
DbgPrint("Attach targetDevice Error\r\n");
IoDeleteDevice(filterDevice);
filterDevice = NULL;
return STATUS_UNSUCCESSFUL;
}
//初始化设备的扩展
devExt = (PDEV_EXTENSION)filterDevice->DeviceExtension;
DevExtInit(devExt, filterDevice, targetDevice, lowDevice);
filterDevice->StackSize = lowDevice->StackSize + 1;//栈大小要自己加一,因为没有框架帮我们,所以要手工来
filterDevice->Flags |= lowDevice->Flags&(DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);
//遍历下一个设备
targetDevice = targetDevice->NextDevice;
}
}
}
//读取回调函数
NTSTATUS ReadComp(PDEVICE_OBJECT pDriver, PIRP pIrp, PVOID Context) {
NTSTATUS status;
PIO_STACK_LOCATION stack;
ULONG KeyNumber;
PKEYBOARD_INPUT_DATA MyData;
KAPC_STATE kApcState = { 0 };
PEPROCESS Process = NULL;
stack = IoGetCurrentIrpStackLocation(pIrp);
if (NT_SUCCESS(pIrp->IoStatus.Status)) {
//获取键盘数据
do {
MyData = pIrp->AssociatedIrp.SystemBuffer;
KeyNumber = (ULONG)(pIrp->IoStatus.Information / sizeof(PKEYBOARD_INPUT_DATA));
for (ULONG i = 0; i < KeyNumber; i++) {
DbgPrint("NumKey:%d,code:%x,%s\n", KeyNumber, MyData->MakeCode, MyData->Flags?"Up":"Down");
}
MyData++;
} while (0);
if (pIrp->PendingReturned) {
IoMarkIrpPending(pIrp);
}
return pIrp->IoStatus.Status;
}
}
//IRP读分发函数
NTSTATUS ReadDispatch(PDEVICE_OBJECT pDevice, PIRP pIrp) {
NTSTATUS status = STATUS_SUCCESS;
PDEV_EXTENSION devExt;
PDEVICE_OBJECT lowDevice;
PIO_STACK_LOCATION stack;
//如果当前栈为1,那么说明我们是最底层的一个栈,所以对这种请求我们直接过滤
if (pIrp->CurrentLocation == 1) {
DbgPrint("Irp send Error\r\n");
status = STATUS_INVALID_DEVICE_REQUEST;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
//得到设备扩展,目的是为了得到下一个设备的指针
devExt = pDevice->DeviceExtension;
if (!devExt) {
DbgPrint("Get Extension Error\r\n");
return STATUS_UNSUCCESSFUL;
}
lowDevice = devExt->LowDevice;
//复制Irp栈
IoCopyCurrentIrpStackLocationToNext(pIrp);
//设置IRP完成回调函数
IoSetCompletionRoutine(pIrp, ReadComp, pDevice, TRUE, TRUE, TRUE);
status = IoCallDriver(lowDevice, pIrp);
return status;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegPath) {
ULONG Num;
NTSTATUS status = STATUS_SUCCESS;
pDriver->DriverUnload = DriverUnload;
for (Num = 0; Num < IRP_MJ_MAXIMUM_FUNCTION; Num++) {//首先将所有的IRP派遣函数赋一个默认分发的值
pDriver->MajorFunction[Num] = GeneralDispatch;
}
pDriver->MajorFunction[IRP_MJ_READ] = ReadDispatch; // 注册IRP读时函数
pDriver->MajorFunction[IRP_MJ_POWER] = PowerDispatch;// 注册电源IRP分发函数
pDriver->MajorFunction[IRP_MJ_PNP] = PnpDispatch; // 注册即插即用IRP分发函数
AttachDevice(pDriver, RegPath);
//绑定设备
return STATUS_SUCCESS;
}
上面就是完整代码了,之后我会从驱动入口开始介绍,中途补充理论知识
代码分析
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegPath) {
ULONG Num;
NTSTATUS status = STATUS_SUCCESS;
pDriver->DriverUnload = DriverUnload;
for (Num = 0; Num < IRP_MJ_MAXIMUM_FUNCTION; Num++) {//首先将所有的IRP派遣函数赋一个默认分发的值
pDriver->MajorFunction[Num] = GeneralDispatch;
}
pDriver->MajorFunction[IRP_MJ_READ] = ReadDispatch; // 注册IRP读时函数
pDriver->MajorFunction[IRP_MJ_POWER] = PowerDispatch;// 注册电源IRP分发函数
pDriver->MajorFunction[IRP_MJ_PNP] = PnpDispatch; // 注册即插即用IRP分发函数
AttachDevice(pDriver, RegPath);
//绑定设备
return STATUS_SUCCESS;
}
上面的代码中,我们首先看见了这么一段代码,他把我们之前所介绍的回调函数全部先填入了一个默认值,为什么要这么做呢?
for (Num = 0; Num < IRP_MJ_MAXIMUM_FUNCTION; Num++) {//首先将所有的IRP派遣函数赋一个默认分发的值
pDriver->MajorFunction[Num] = GeneralDispatch;
}
GeneralDispatch
让我们把目光集中到这里面所赋予的默认函数上,下面用到了一个我们自己定义的DEV_EXTENSION结构,为了方便理解我也先贴出来
//设备操作通用分发函数
NTSTATUS GeneralDispatch(PDEVICE_OBJECT pDevice, PIRP pIrp) {
NTSTATUS status;
PDEV_EXTENSION devExt = (PDEV_EXTENSION)pDevice->DeviceExtension;
PDEVICE_OBJECT lowDevice = devExt->LowDevice;
IoSkipCurrentIrpStackLocation(pIrp);
status = IoCallDriver(lowDevice, pIrp);
return status;
}
//设备扩展结构
typedef struct _Dev_exten {
ULONG Size; //该结构的大小
PDEVICE_OBJECT FilterDevice; //过滤设备对象
PDEVICE_OBJECT TargetDevice; //目标设备对象
PDEVICE_OBJECT LowDevice; //低级设备对象
KSPIN_LOCK IoRequestSpinLock; //自旋锁
KEVENT IoProgressEvent; //事件
PIRP pIrp; //IRP
}DEV_EXTENSION, *PDEV_EXTENSION;
先从这个默认结构说起,为了方便我们寻找到当前的设备,当前设备更加低一级的设备,我们自创建的设备这些对象,我们可以创建一个结构把这些信息存储起来,方便我们后面的取用,画个图理解一下
在理解上面的图之前,首先我们先简单的理解驱动和设备的关系,对于当前阶段的我们来说,只需要知道设备是由驱动创建的就行了。
我们的电脑在底层会适配各种外部设备,但是这些外部设备可能千奇百怪,所遵循的协议等也不一定相同,所以为了能够正确的处理各种外设的I /O请求,win自己是会创建设备来“翻译”各种外设输入的
对于这些设备,他们像是串在一起一样,信息从顶层的设备向下传递(外设->win虚拟设备->win物理设备),所以如果我们能够附加在win的这个“翻译”设备上面,比它更早的收到外设传递来的信息,是不是就可以拦截外设信息了呢?
由此我们便可以讲清楚为什么要记录 _Dev_exten这个结构里面的信息了
回到我们的代码,可以看见我们从这个扩展结构里面取出了当前设备的下级指针,然后我们碰见了一个新函数IoSkipCurrentIrpStackLocation
//设备操作通用分发函数
NTSTATUS GeneralDispatch(PDEVICE_OBJECT pDevice, PIRP pIrp) {
NTSTATUS status;
PDEV_EXTENSION devExt = (PDEV_EXTENSION)pDevice->DeviceExtension;
PDEVICE_OBJECT lowDevice = devExt->LowDevice;
IoSkipCurrentIrpStackLocation(pIrp);
status = IoCallDriver(lowDevice, pIrp);
return status;
}
我们刚刚提到,“信息”是由“设备串”顶端向下一层一层传递的,这是抽象的说法
在具体的代码中,“信息”就是Irp包,“设备串”就是我们的I/O设备栈
Irp和I/O设备栈
I/O request packets,简称IRP。即输入输出请求包。它是WINDOWS内核中的一种非常重要的数据结构。上层应用程序与底层驱动程序通信时,应用程序会发出I/O请求。操作系统将相应的I/O请求转换为相应的IRP。不同的IRP会根据类型被分派的不同的派遣历程中进行处理。
typedef
NTSTATUS
DRIVER_DISPATCH (
_In_ struct _DEVICE_OBJECT *DeviceObject,
_Inout_ struct _IRP *Irp
);
上面是公式化的表达,简单的说,这个Irp包是一个结构体,会带着我们的信息在设备栈中传递
回到我们碰见的陌生函数IoSkipCurrentIrpStackLocation,从意思上我们就可以知道,这是让我们的Irp包从当前设备栈,向下级设备传递。
IoSkipCurrentIrpStackLocation
它是有源码的
VOID
IoSkipCurrentIrpStackLocation (
_Inout_ PIRP Irp
)
{
NT_ASSERT(Irp->CurrentLocation <= Irp->StackCount);
Irp->CurrentLocation++;//本质上就是让Irp包中结构加1
Irp->Tail.Overlay.CurrentStackLocation++;
}
IoCallDriver
继续看会我们的默认分发函数,又看见一个IoCallDriver函数,它的作用是将 IRP 发送到与指定设备对象关联的驱动程序。
NTSTATUS IofCallDriver(
PDEVICE_OBJECT DeviceObject,
__drv_aliasesMem PIRP Irp
);
所以到现在,我们理清了这个默认函数的逻辑,这就是直接让自己的设备收到Irp包(信息),然后什么也不做,直接转发给下级设备
不难理解,这是因为对于我们当前的需求(读取键盘输入信息)来说,大部分回调我们是不需要改的,就原封不动的将这些调用传给我们的下级设备就行了
回到我们的DriverEntry,我们看见我们改了三个回调的位置,接下来一个一个看
pDriver->MajorFunction[IRP_MJ_READ] = ReadDispatch; // 注册IRP读时函数
pDriver->MajorFunction[IRP_MJ_POWER] = PowerDispatch;// 注册电源IRP分发函数
pDriver->MajorFunction[IRP_MJ_PNP] = PnpDispatch; // 注册即插即用IRP分发函数
ReadDispatch
首先是我们的最重要的读分发函数,可以看见,除了常规的初始化之外,我们写了一个错误处理,这个错误处理的意思就是如果当前栈为1(也就是说明我们的接收到的Irp已经发到了底层,但是作为附加设备的我们肯定在栈顶),那么就忽略掉这个I/O请求,用到了IoCompleteRequest这个函数,它就是用来完成Irp包的,没什么好说
//IRP读分发函数
NTSTATUS ReadDispatch(PDEVICE_OBJECT pDevice, PIRP pIrp) {
NTSTATUS status = STATUS_SUCCESS;
PDEV_EXTENSION devExt;
PDEVICE_OBJECT lowDevice;
PIO_STACK_LOCATION stack;
//如果当前栈为1,那么说明我们是最底层的一个栈,所以对这种请求我们直接过滤
if (pIrp->CurrentLocation == 1) {
DbgPrint("Irp send Error\r\n");
status = STATUS_INVALID_DEVICE_REQUEST;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
//得到设备扩展,目的是为了得到下一个设备的指针
devExt = pDevice->DeviceExtension;
if (!devExt) {
DbgPrint("Get Extension Error\r\n");
return STATUS_UNSUCCESSFUL;
}
lowDevice = devExt->LowDevice;
//复制Irp栈
IoCopyCurrentIrpStackLocationToNext(pIrp);
//设置IRP完成回调函数
IoSetCompletionRoutine(pIrp, ReadComp, pDevice, TRUE, TRUE, TRUE);
status = IoCallDriver(lowDevice, pIrp);
return status;
}
继续向下看代码,照常取出了设备扩展,取到了下级设备的指针,然后用IoCopyCurrentIrpStackLocationToNext函数将当前I/O栈里面的信息放到下一个栈中,这是因为我们的键盘Irp包是带参数的,所以要整个复制过去,这就是和之前IoSkipCurrentIrpStackLocation的区别。
然后设置回调函数ReadComp,指定好pIrp和下级设备,然后就是三个True
-
TRUE, TRUE, TRUE
: 这三个布尔参数分别表示:
TRUE
: 如果I/O操作成功,调用回调函数。TRUE
: 如果I/O操作失败,调用回调函数。TRUE
: 如果I/O操作被取消,调用回调函数。
做好这些处理之后,我们向下Call设备,当完成I/O请求后,我们的回调函数会被触发
ReadComp
来继续看看我们的回调函数是怎么写的,首先用IoGetCurrentIrpStackLocation函数拿到了当前得到设备栈,然后从我们的Irp的参数区里面的缓冲区拿到了书接,把这个数据打印出来,然后处理挂起的I/O请求,这样就能正确的读取键盘输入了
//读取回调函数
NTSTATUS ReadComp(PDEVICE_OBJECT pDriver, PIRP pIrp, PVOID Context) {
NTSTATUS status;
PIO_STACK_LOCATION stack;
ULONG KeyNumber;
PKEYBOARD_INPUT_DATA MyData;
KAPC_STATE kApcState = { 0 };
PEPROCESS Process = NULL;
stack = IoGetCurrentIrpStackLocation(pIrp);
if (NT_SUCCESS(pIrp->IoStatus.Status)) {
//获取键盘数据
do {
MyData = pIrp->AssociatedIrp.SystemBuffer;
KeyNumber = (ULONG)(pIrp->IoStatus.Information / sizeof(PKEYBOARD_INPUT_DATA));
for (ULONG i = 0; i < KeyNumber; i++) {
DbgPrint("NumKey:%d,code:%x,%s\n", KeyNumber, MyData->MakeCode, MyData->Flags?"Up":"Down");
}
MyData++;
} while (0);
if (pIrp->PendingReturned) {
IoMarkIrpPending(pIrp);
}
return pIrp->IoStatus.Status;
}
}
PowerDispatch
再看当初DriverEntry里面的另外两个回调函数,这里我们有了之前默认分发的知识后,就可以看出来,结构是差不多的,只不过对于电源I/O请求,Win有自己独特的API,里面的处理更加细致,这里先暂时不细说(我也不会
//电源IRP分发函数
NTSTATUS PowerDispatch(PDEVICE_OBJECT pDevice, PIRP pIrp) {
PDEV_EXTENSION devExt;
devExt = (PDEV_EXTENSION)pDevice->DeviceExtension;
PoStartNextPowerIrp(pIrp);
IoSkipCurrentIrpStackLocation(pIrp);
return PoCallDriver(devExt->TargetDevice, pIrp);
}
PnpDispatch
最后一个回调,这是为了方便我们的设备(键盘)在拔掉的时候,同时删除掉我们之前创建的附加的设备
//即插即用IRP分发函数
NTSTATUS PnpDispatch(PDEVICE_OBJECT pDevice, PIRP pIrp) {
PDEV_EXTENSION devExt;
PIO_STACK_LOCATION stack;
NTSTATUS status = STATUS_SUCCESS;
devExt = (PDEV_EXTENSION)pDevice->DeviceExtension;
stack = IoGetCurrentIrpStackLocation(pIrp);
switch (stack->MinorFunction)
{
case IRP_MN_REMOVE_DEVICE:
//向下继续分发请求
IoSkipCurrentIrpStackLocation(pIrp);
IoCallDriver(devExt->LowDevice, pIrp);
//解除绑定
IoDetachDevice(devExt->LowDevice);
//删除我们自己生成的设备
IoDeleteDevice(pDevice);
status = STATUS_SUCCESS;
break;
default:
//其他类型的IRP,全部正常下发
IoSkipCurrentIrpStackLocation(pIrp);
status = IoCallDriver(devExt->LowDevice, pIrp);
}
return status;
}
AttachDevice
最后一个在DriverEntry里面的函数,他就是首先用ObReferenceObjectByName(未导出函数,要额外声明),来根据我们的键盘驱动的名字来查询这个驱动对象,然后调用ObDereferenceObject来给我们的对象引用减1,为了后面对象的自动销毁,我们之前说过,在内核层面是不会像R3一样自动释放内存的
NTSTATUS AttachDevice(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegPatch) {
UNICODE_STRING KeyBoardName = RTL_CONSTANT_STRING(L"\\Driver\\KbdClass");
NTSTATUS status = 0;
PDEVICE_OBJECT filterDevice;//过滤设备
PDEV_EXTENSION devExt;//过滤设备的扩展
PDEVICE_OBJECT targetDevice;// 目标设备
PDEVICE_OBJECT lowDevice;// 低级设备
PDRIVER_OBJECT khdDriver;//用于接受所查询对象的指针
//获取键盘的驱动对象,保存在khdDriver里面
status = ObReferenceObjectByName(&KeyBoardName, OBJ_CASE_INSENSITIVE, NULL, 0, *IoDriverObjectType, KernelMode, NULL, &khdDriver);
if (!NT_SUCCESS(status)){
DbgPrint("Open KeyBoard Driver Failed\r\n");
return status;
}
else {
ObDereferenceObject(khdDriver);
//获取第一个设备
targetDevice = khdDriver->DeviceObject;
//遍历驱动中的设备
while (targetDevice) {
//创建一个过滤设备
status = IoCreateDevice(pDriver, sizeof(DEV_EXTENSION), NULL, targetDevice->DeviceType, targetDevice->Characteristics, FALSE, &filterDevice);
if (!NT_SUCCESS(status)) {
DbgPrint("Create FilterDevice Error\r\n");
filterDevice = targetDevice = NULL;
return status;
}
//绑定下一个设备
lowDevice = IoAttachDeviceToDeviceStack(filterDevice, targetDevice);
if (!lowDevice) {
DbgPrint("Attach targetDevice Error\r\n");
IoDeleteDevice(filterDevice);
filterDevice = NULL;
return STATUS_UNSUCCESSFUL;
}
//初始化设备的扩展
devExt = (PDEV_EXTENSION)filterDevice->DeviceExtension;
DevExtInit(devExt, filterDevice, targetDevice, lowDevice);
filterDevice->StackSize = lowDevice->StackSize + 1;//栈大小要自己加一,因为没有框架帮我们,所以要手工来
filterDevice->Flags |= lowDevice->Flags&(DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);
//遍历下一个设备
targetDevice = targetDevice->NextDevice;
}
}
}
然后我们使用IoCreateDevice函数来创建这个附加设备,需要注意的是,这里我们不能像之前在驱动通信里面介绍的一样,随便给我们的驱动来赋予类型了,为了确保I/O请求的正确传递,我们必须要保证这些属性和被附加的设备一致
接着我们使用IoAttachDeviceToDeviceStack来绑定设备,然后初始化设备(这个初始化函数我就不单独讲了)
NTSTATUS DevExtInit(PDEV_EXTENSION devExt, PDEVICE_OBJECT filterDevice, PDEVICE_OBJECT targetDevice, PDEVICE_OBJECT lowDevice) {
memset(devExt, 0, sizeof(DEV_EXTENSION));
devExt->FilterDevice = filterDevice;
devExt->TargetDevice = targetDevice;
devExt->LowDevice = lowDevice;
devExt->Size = sizeof(DEV_EXTENSION);
KeInitializeSpinLock(&devExt->IoRequestSpinLock);
KeInitializeEvent(&devExt->IoProgressEvent, NotificationEvent, FALSE);
return STATUS_SUCCESS;
}
再接下来有个细节,就是我们的设备的Flags要加上DO_BUFFERED_IO和DO_DIRECT_IO 以及
DO_POWER_PAGABLE
设备对象标志:
DO_BUFFERED_IO
: 指示设备对象支持缓冲 I/O。在这种模式下,系统会为每次 I/O 操作分配一个缓冲区,驱动程序可以使用这个缓冲区进行数据传输。DO_DIRECT_IO
: 指示设备对象支持直接 I/O。在这种模式下,系统会将用户模式缓冲区直接映射到内核模式,驱动程序可以直接访问用户模式缓冲区。DO_POWER_PAGABLE
: 指示设备对象的驱动程序可以在电源管理操作中被分页。这个标志通常用于驱动程序的优化,特别是在电源管理场景中。
这里我专门说一下前两个参数
typedef struct _IRP {
PMDL MdlAddress;
ULONG Flags;
union {
struct _IRP* MasterIrp;
PVOID SystemBuffer;//这里就是系统所创建的缓冲区
} AssociatedIrp;
IO_STATUS_BLOCK IoStatus;
KPROCESSOR_MODE RequestorMode;
BOOLEAN PendingReturned;
BOOLEAN Cancel;
KIRQL CancelIrql;
PDRIVER_CANCEL CancelRoutine;
PVOID UserBuffer;//这里就是DO_DIRECT_IO说的驱动直接访问的用户模式的缓冲区
union {
struct {
union {
KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
struct {
PVOID DriverContext[4];
};
};
PETHREAD Thread;
LIST_ENTRY ListEntry;
} Overlay;
} Tail;
} IRP, *PIRP;
最后结果
出问题的地方
经过调试,在停止服务后,任意按下一个按键就蓝屏,所以问题就在这个驱动卸载函数里面。更加精确的定位应该在这个DeAttach函数中,但是反正也不影响我们使用,就懒得搞了(菜
这也是为后来的自己或者其他人指个路
//设备卸载函数
NTSTATUS DriverUnload(PDRIVER_OBJECT pDriver) {
PDEVICE_OBJECT pDevice;
PDEV_EXTENSION devExt;
UNREFERENCED_PARAMETER(pDriver);
DbgPrint("Driver Unloading..\r\n");
//DbgBreakPoint();
pDevice = pDriver->DeviceObject;
while (pDevice) {
DeAttach(pDevice);
pDevice = pDevice->NextDevice;
}
pDriver->DeviceObject = NULL;
//DbgBreakPoint();
return STATUS_SUCCESS;
}
NTSTATUS DeAttach(PDEVICE_OBJECT pDevice) {
PDEV_EXTENSION devExt;
devExt = (PDEV_EXTENSION)pDevice->DeviceExtension;
if (devExt->TargetDevice) {
IoDetachDevice(devExt->TargetDevice);
devExt->TargetDevice = NULL;
}
if (devExt->FilterDevice) {
devExt->FilterDevice = NULL;
}
if (devExt->pIrp != NULL) {
#ifdef DEBUG
DbgBreakPoint();
#endif
if (IoCancelIrp(devExt->pIrp)) {
DbgPrintEx(MY_COMPONENT_ID, MY_INFO_LEVEL, "取消成功...\r\n");
}
else {
DbgPrintEx(MY_COMPONENT_ID, MY_ERROR_LEVEL, "取消失败...\r\n");
}
// 释放 IRP 的内存
IoFreeIrp(devExt->pIrp);
devExt->pIrp = NULL;
}
// 删除设备对象
IoDeleteDevice(pDevice);
return STATUS_SUCCESS;
}