64位内开发第二十二讲,分层过滤驱动编程详解

64位内开发第二十二讲,分层过滤驱动编程详解

来自: iBinary - 博客园 禁止爬虫.如果遇到此文章不是 出自 博客园 或者 腾讯云+社区. 请举报目标网站. 或者跳转至 本人博客园进行查看. 因为文章随时更改.可能今天只是写了一部分.或者有错误. 而明天就进行更改重发了. 但是爬虫爬取的文章还是之前错误的文章.会为读者造成文章有错误的假象.

一丶分层驱动

1.1 分层驱动的概念

之前学过第二十一讲 驱动调用驱动. 了解了其流程,那么理解分层驱动也不算困难.

分层驱动可以理解为就是一个HOOK类型的驱动. 假设有 A C两个驱动. 正常情况下 A驱动可以发送IRP给C驱动.然后C驱动完成数据之后交由 A驱动进行处理. 而分层驱动则是自己写一个B驱动.将B驱动挂载到C驱动上面. 这样形成的调用链则是 A B C 而B驱动可以选择是否下发IRP给C亦或者只是收集一些数据.

1.2 分层驱动的实际应用场景

分层驱动是程序员编写的最多的一种类型的驱动. 可应用于如下

1.模块化驱动程序. A B C 三个驱动可以独立

2.限制读写请求. C驱动没有限制读写大小.则添加的B过滤驱动可以限制读写请求

3.监视设备操作情况. 比如可以 监视用户的按键.(做一个密码盗取的驱动..) 比如可以监控网络.比如 TDI防火墙.(虽已过时)

二丶分层驱动的概念

2.1 概念简介

既然说过 我们可以写一个B驱动附加到C驱动之上.那么自然我们就要明白要如何进行附加.

原理是啥. 所以第一步就是要对设备对象进行了解. 整个主题的核心就是围绕着设备对象如何进行挂载以及处理的. 而挂载指的就是将高一层的设备对象挂载到第一层的设备对象上,从而形成一个设备栈.

所以在这里栈的概念也很重要. 相当于我生成了一个DriverB 然后挂载到C上面. 此时我的DriverB也有一层栈. 而DriverC 也有一层堆栈. 如何进行挂载之前的通信传输.其实就是如何应用每层的堆栈.

2.2 了解设备对象

typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT {
CSHORT Type; //类型
USHORT Size; //大小
LONG ReferenceCount; //引用计数
struct _DRIVER_OBJECT *DriverObject; //核心--> 记录着驱动对象
struct _DEVICE_OBJECT *NextDevice; //核心--> 记录着下一个设备对象
struct _DEVICE_OBJECT *AttachedDevice;//核心--> 记录着附加的设备对象
struct _IRP *CurrentIrp; //当前的Irp指针
PIO_TIMER Timer; //计时器指针
ULONG Flags; //标记参数 关乎设备的读取方式
ULONG Characteristics; //设备对象的特性
__volatile PVPB Vpb;
PVOID DeviceExtension; //设备扩展
DEVICE_TYPE DeviceType; //设备类型
CCHAR StackSize; //核心--> Io堆栈大小
union {
LIST_ENTRY ListEntry; //Irp的链表
WAIT_CONTEXT_BLOCK Wcb;
} Queue;
ULONG AlignmentRequirement; //对齐的属性
KDEVICE_QUEUE DeviceQueue; //设备对象
KDPC Dpc; //DPC延迟过程调用
ULONG ActiveThreadCount; //当前线程的额数量
PSECURITY_DESCRIPTOR SecurityDescriptor;//安全描述符表
KEVENT DeviceLock; //设备锁
USHORT SectorSize;
USHORT Spare1;
struct _DEVOBJ_EXTENSION *DeviceObjectExtension; //请注意这是设备对象扩展不是设备扩展
PVOID Reserved;
} DEVICE_OBJECT;

观看其结构体 其实重要的成员有很多.但是前面都已经学过了. 比如 设备的读写标志 设备扩展

等等.

而学习过滤驱动那么我们就要重视里面的三个子域的成员.

struct _DRIVER_OBJECT *DriverObject; //核心--> 记录着驱动对象
struct _DEVICE_OBJECT *NextDevice; //核心--> 记录着下一个设备对象
struct _DEVICE_OBJECT *AttachedDevice;//核心--> 记录着附加的设备对象

DriverObject ---> 记录着的就是这个设备驱动对象

NextDevice ---> 记录着就是下一个设备对象. 比如我们Attach上了.那么记录的就是Attach的设备对象.

AttachedDevice --> 记录着的附加的设备对象. 这个有点难以理解. 其实就是 原有A C两个驱动.你有一个B驱动.然后B驱动挂载到C驱动上.调用链就是 A B C 而这个成员就是记录了""自己" 是被那个设备对象所挂载的. 比如调用链如果为 A ->B ->C. 那么在C驱动里面这个成员记录着的就是B. 如果在B驱动里面这个成员记录的就是A的设备对象. 那么就剩下了A了.A因为没有人挂载它(HOOK它)所以这个域记录的就是NULL.

简单理解: NextDevice 就是记录了我挂载了谁.如果我是B 我挂载了C.那么我就在C上面. 那么此成员记录的就是C的设备对象.

AttachedDevice 就是记录了 我被谁挂载了. 如果我是C我被B挂载了. B在我之上.那么我的域记录的就是B.

2.3 设备堆栈的挂载

进行设备挂载有如下几个API.

NTSTATUS IoAttachDevice(
[in] PDEVICE_OBJECT SourceDevice,
[in] PUNICODE_STRING TargetDevice,
[out] PDEVICE_OBJECT *AttachedDevice
);
PDEVICE_OBJECT IoAttachDeviceToDeviceStack(
[in] PDEVICE_OBJECT SourceDevice,
[in] PDEVICE_OBJECT TargetDevice
);
NTSTATUS IoAttachDeviceToDeviceStackSafe(
[in] PDEVICE_OBJECT SourceDevice,
[in] PDEVICE_OBJECT TargetDevice,
[out] PDEVICE_OBJECT *AttachedToDeviceObject
);
void IoDetachDevice(
[in, out] PDEVICE_OBJECT TargetDevice
);

其实就分为两类 一类是挂载 另一类则是取消挂载. 而挂载是提供给我们了三个API进行使用的.在高版本上面一般都会使用 IoAttachDeviceToDeviceStackSafe 在低版本上面其它两个也很常用. 主要功能就是提供一个 源设备对象 和一个目标设备对象名字 然后来返回一个附加的设备 三个函数中功能都类似.唯一不同就是有的通过返回值返回Attach的设备.有的通过设备名来进行绑定.而有的是通过提供一个目标设备对象来进行绑定的.

拿第二个函数举例:

IoAttachDeviceToDeviceStack

参数1 SourceDevice: 源设备对象 可以理解为就是自己生成的 B设备对象

参数2 TargetDevice: 这个是你想要挂载的设备对象 可以理解为你想要将B挂载到那个设备对象上. 比如这是C. 那么SourceDevice 就会挂载到 C上. 以后处理Irp就可以使用 SourceDevice来进行过滤了.

返回值: 返回 TargetDevice下面的第一层的设备 如果挂载的是C.那么返回的就是C的设备对象.

2.4 寻找目标设备对象进行挂载

寻找目标对象,我们在 驱动调用驱动一节中已经讲过. 可以使用API

NTSTATUS
IoGetDeviceObjectPointer(
_In_ PUNICODE_STRING ObjectName,
_In_ ACCESS_MASK DesiredAccess, //FILE_ALL_ACCESS
_Out_ PFILE_OBJECT *FileObject,
_Out_ PDEVICE_OBJECT *DeviceObject
);

此API 就是通过设备名称 来找到其对应的 文件对象以及它的设备对象. 请注意不使用的话文件对象要进行引用计数-1

在驱动调用驱动一节中也说过. 如果找不到设备名字 那么可以通过打开符号链接查询符号链接. 查询的时候自然就会返回设备名字给我们.

2.5 堆栈IRP的转发处理.

之前说过,如果我们成功挂载到一个目标设备上之后.那么我们会有一层堆栈. 如果我们不想处理那么就要交给下一层(之前被挂载的)设备进行处理.

那么有几种情况

1.本层不想处理交给下一层进行处理.

使用API IoSkipCurrentIrpStackLocation(pIrp); 进行向下转发. 然后调用API IoCallDriver(下一层的设备对象) 进行处理即可.

2.本层想直接进行处理

如果本层想直接进行处理. 那么跟之前写驱动的操作一样. 直接使用 API. IoCompleteRequest函数即可. 然后不需要调用 IoCallDriver 也不需要跳过本层堆栈.

3.IoSkipCurrentIrpStackLocation的原理

在进行IRP操作的时候,每一个设备堆栈都对应着一个IO堆栈元素. 也就是 IO_STACK_LOCATION结构 IRP的内部有个指针会指向当前正在使用的 IO堆栈.

当每次调用 IoCallDriver额时候,内部的内核函数都会讲IRP的当前指针往下移动,也就是指向了下一个的IO_STACK_LOCATION 指针.

但是这样就会产生一个问题.如果当前的设备堆栈 不对IRP处理.那么自然内核函数也不能让其IO堆栈指针下移. 但是调用了 IoCallDriver之后就已经移动了. 那么操作系统就提供了 IoSkipCurrentIrpStackLocation 函数.它的作用就是将已将下移的堆栈指针,移动回来.也就是向上移动.恢复到原来的位置.

因此使用 IoCallDriver IoSkipCurrentIrpStackLocation 就对设备堆栈的移动实现了平衡.也就是没有改变. 此时如果在调用IoCallDriver时候其实应用的设备堆栈和上一层的设备堆栈是同一个.

4.IoCopyCurrentIrpStackLocationToNext 拷贝转发

另一种情况过滤驱动的 IRP派遣函数中对这个IRP进行了操作.那么我们就必须拷贝一层堆栈交给下层处理. 否则你操作的都是无效的. 比如你对IRP堆栈进行了操作. 此时你并不是拷贝并且往下转发.而是使用的IoSkipCurrentIrpStackLocation 那么自然下一层驱动使用的设备堆栈就和你本层驱动使用的设备堆栈是一样的. 如果你想让下层设备堆栈使用你修改过的那么就必须使用IoCopyCurrentIrpStackLocationToNext 拷贝一份并且转发.

举个例子:

WriteFile发送IRP_MJ_WRITEDriverB. 正常情况下写入的数据为 Love You 而此时过滤设备会先拦截到这块数据.在此数据里面加入了一个 I. 那么就是 I Love You 如果你使用拷贝下发,那么 DriverB则会打印 I Love you 如果你使用 Skip转发.那么你加入的I则是无意义的.因为 DriverB使用的堆栈并不是你修改过的. 那么还是会打印 Love you.

下面是它的实现

VOID
IoCopyCurrentIrpStackLocationToNext(
_Inout_ PIRP Irp
)
{
PIO_STACK_LOCATION irpSp;
PIO_STACK_LOCATION nextIrpSp;
irpSp = IoGetCurrentIrpStackLocation(Irp); //获取本层IRP
nextIrpSp = IoGetNextIrpStackLocation(Irp);//获取下一层IRP
//进行内存拷贝
RtlCopyMemory( nextIrpSp, irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine));
nextIrpSp->Control = 0;
}

三丶代码实战

3.1 提供环境 Driver B代码

首先我们要提供一个 DriverB驱动. 此驱动是被挂载的驱动. 也就是Target驱动. 也可以说成是我们将要进行HOOK的驱动.

此驱动就是和正常驱动无意义.提供并实现了 IRP_MJ_READ IRP_MJ_WRITE

其派遣函数如下:

NTSTATUS FilterWrite(IN PDEVICE_OBJECT driver, IN PIRP pIrp)
{
KdPrint(("[DriverA] Enter FilterWrite\r\n"));
PVOID buffer = pIrp->AssociatedIrp.SystemBuffer;
if (buffer)
{
KdPrint(("[DriverA]--->Value = %ls \r\n", (wchar_t*)buffer));
}
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS FilterRead(IN PDEVICE_OBJECT driver,
IN PIRP pIrp)
{
KdBreakPoint();
KdPrint(("[DriverA] Enter FilterRead\r\n"));
NTSTATUS ntStatus = STATUS_SUCCESS;
PVOID buffer = pIrp->AssociatedIrp.SystemBuffer;
if (buffer != nullptr)
{
RtlStringCbCopyNW(
(wchar_t*)buffer,
0x100,
L"DriverA Read",
wcslen(L"DriverA Read") * 2);
pIrp->IoStatus.Information = wcslen(L"DriverA Read") * 2;
pIrp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
else
{
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
KdPrint(("[DriverA] FilterRead End\r\n"));
return ntStatus;
}

Write就是将数据进行输出. Read就是给传出一个 DriverA Read的字符串

3.2 开始实战->寻找目标设备

寻找目标设备驱动很简单.也就是API的使用. 函数如下:

PDEVICE_OBJECT GetAttachDeviceByName(
IN wchar_t* attach_device_name,
OUT PFILE_OBJECT* fileobj)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
UNICODE_STRING ucd_attach_device_name = { 0 };
PDEVICE_OBJECT target_next_device = nullptr;
if (attach_device_name == nullptr)
return nullptr;
status = RtlUnicodeStringInit(&ucd_attach_device_name, attach_device_name);
if (NT_ERROR(status))
return nullptr;
status = IoGetDeviceObjectPointer(&ucd_attach_device_name,
FILE_ALL_ACCESS,
fileobj,
&target_next_device);
if (NT_ERROR(status))
{
KdPrint(("[Filter]--->IoGetDeviceObjectPointer Error\r\n"));
return nullptr;
}
return target_next_device;
}

使用

target_device = GetAttachDeviceByName(L"\\Device\\DriverB", &target_fileobj);

这样就找到了我们想要 Hook(挂载)的目标设备驱动了.

3.3 开始实战->创建过滤设备

我们要进行过滤,那么首先就要创建一个自己的设备对象,并且附加到目标设备对象上面. 创建设备很简单. 学习驱动的一开始就已经学过了.

PDEVICE_OBJECT CreateFilterDevice(PDRIVER_OBJECT driver)
{
UNICODE_STRING ucd_filter_device_name = { 0 };
NTSTATUS status = STATUS_UNSUCCESSFUL;
PDEVICE_OBJECT filter_device = nullptr;
if (driver == nullptr)
return nullptr;
//1.初始化设备创建之前的必要信息
status = RtlUnicodeStringInit(&ucd_filter_device_name, L"\\Device\\Filter");
if (NT_ERROR(status))
return nullptr;
//2.创建设备
status = IoCreateDevice(
driver,
sizeof(DEVICE_SAVE_INFOMATION),
&ucd_filter_device_name,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&filter_device);
if (NT_ERROR(status))
return nullptr;
//3.设置设备通信方式
filter_device->Flags |= DO_BUFFERED_IO;
return filter_device;
}

注意,这里使用了一个设备扩展.用于记录我们绑定过后的设备对象是哪一个. 在IRP派遣函数中我们不想处理的数据可以通过记录在设备扩展中的目标设备对象,转发IRP进行执行.

在驱动卸载的时候也要进行解除附加.

其结构如下:

typedef struct _DEVICE_SAVE_INFOMATION
{
PDEVICE_OBJECT src_device; //本层的过滤设备对象
PFILE_OBJECT target_next_fileobj;//目标的文件指针对象
PDEVICE_OBJECT target_next_device; //目标的设备对象
PDEVICE_OBJECT attach_to_device; //挂载的设备对象 <==> 目标的设备对象 他俩等价
}DEVICE_SAVE_INFOMATION,*PDEVICE_SAVE_INFOMATION;

3.4 开始实战->挂载设备

挂载就是之前所说的API

PDEVICE_OBJECT FilterAttach(PDEVICE_OBJECT src_device,PDEVICE_OBJECT target_device)
{
PDEVICE_OBJECT attach_to_device = nullptr;
NTSTATUS status = STATUS_UNSUCCESSFUL;
status = IoAttachDeviceToDeviceStackSafe(src_device, target_device, &attach_to_device);
if (NT_ERROR(status))
return nullptr;
return attach_to_device;
}

挂载之后我们就要进行记录

//5.记录一下信息
PDEVICE_SAVE_INFOMATION device_save_info_ptr =
(PDEVICE_SAVE_INFOMATION)src_device->DeviceExtension;
device_save_info_ptr->src_device = src_device; //记录我们的设备
device_save_info_ptr->target_next_device = target_device; //记录我们要挂载的目标设备
device_save_info_ptr->target_next_fileobj = target_fileobj; //记录文件对象
device_save_info_ptr->attach_to_device = attach_to_device; //记录下一层设备

3.5 开始实战->处理卸载

众所周知,驱动不卸载没问题.最难的就是卸载了. 关于过滤驱动的卸载我们就要注意如下

1.在删除设备之前先Detach掉附加的设备

2.注意再打开目标设备对象的时候返回的文件对象,要对其进行引用计数-1

3.注意资源泄露问题. 遍历设备对象链表逐个删除.

其实如果我们在设备扩展中记录过信息.那么删除的时候注意上面说的几点即可.

VOID FilterUnload(IN PDRIVER_OBJECT pDriverObject)
{
//跟以往卸载不通.过滤驱动卸载的时候 需要解除挂载.然后删除该设备对象
//循环卸载
//IoEnumerateDeviceObjectList()
KdPrint(("[Filter]-->DriverUnload \r\n"));
PDEVICE_OBJECT next_device = nullptr;
if (pDriverObject->DeviceObject == nullptr)
{
KdPrint(("[Filter]--> Previous Driver Unload \r\n"));
return;
}
next_device = pDriverObject->DeviceObject;
while (next_device != nullptr)
{
PDEVICE_SAVE_INFOMATION device_save_info =
(PDEVICE_SAVE_INFOMATION)next_device->DeviceExtension;
if (device_save_info == nullptr)
{
IoDeleteDevice(next_device);
break;
}
//得到记录的下一个设备.
if (device_save_info->attach_to_device != nullptr)
{
//解除附加
IoDetachDevice(device_save_info->attach_to_device);
device_save_info->attach_to_device = nullptr;
}
if (device_save_info->target_next_fileobj != nullptr)
{
//解除引用
ObDereferenceObject(device_save_info->target_next_fileobj);
device_save_info->target_next_fileobj = nullptr;
device_save_info->target_next_device = nullptr;
}
//删除设备
IoDeleteDevice(next_device);
device_save_info->src_device = nullptr;
next_device = next_device->NextDevice;
}
KdPrint(("[Filter]--> Perfect Driver Unload \r\n"));
}

3.6.过滤驱动完成代码

#include "CMain.h"
PDEVICE_OBJECT GetAttachDeviceByName(
IN wchar_t* attach_device_name,
OUT PFILE_OBJECT* fileobj)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
UNICODE_STRING ucd_attach_device_name = { 0 };
PDEVICE_OBJECT target_next_device = nullptr;
if (attach_device_name == nullptr)
return nullptr;
status = RtlUnicodeStringInit(&ucd_attach_device_name, attach_device_name);
if (NT_ERROR(status))
return nullptr;
status = IoGetDeviceObjectPointer(&ucd_attach_device_name,
FILE_ALL_ACCESS,
fileobj,
&target_next_device);
if (NT_ERROR(status))
{
KdPrint(("[Filter]--->IoGetDeviceObjectPointer Error\r\n"));
return nullptr;
}
return target_next_device;
}
VOID FilterUnload(IN PDRIVER_OBJECT pDriverObject)
{
//跟以往卸载不通.过滤驱动卸载的时候 需要解除挂载.然后删除该设备对象
//循环卸载
//IoEnumerateDeviceObjectList()
KdPrint(("[Filter]-->DriverUnload \r\n"));
PDEVICE_OBJECT next_device = nullptr;
if (pDriverObject->DeviceObject == nullptr)
{
KdPrint(("[Filter]--> Previous Driver Unload \r\n"));
return;
}
next_device = pDriverObject->DeviceObject;
while (next_device != nullptr)
{
PDEVICE_SAVE_INFOMATION device_save_info =
(PDEVICE_SAVE_INFOMATION)next_device->DeviceExtension;
if (device_save_info == nullptr)
{
IoDeleteDevice(next_device);
break;
}
//得到记录的下一个设备.
if (device_save_info->attach_to_device != nullptr)
{
//解除附加
IoDetachDevice(device_save_info->attach_to_device);
device_save_info->attach_to_device = nullptr;
}
if (device_save_info->target_next_fileobj != nullptr)
{
//解除引用
ObDereferenceObject(device_save_info->target_next_fileobj);
device_save_info->target_next_fileobj = nullptr;
device_save_info->target_next_device = nullptr;
}
//删除设备
IoDeleteDevice(next_device);
device_save_info->src_device = nullptr;
next_device = next_device->NextDevice;
}
KdPrint(("[Filter]--> Perfect Driver Unload \r\n"));
}
PDEVICE_OBJECT CreateFilterDevice(PDRIVER_OBJECT driver)
{
UNICODE_STRING ucd_filter_device_name = { 0 };
NTSTATUS status = STATUS_UNSUCCESSFUL;
PDEVICE_OBJECT filter_device = nullptr;
if (driver == nullptr)
return nullptr;
//1.初始化设备创建之前的必要信息
status = RtlUnicodeStringInit(&ucd_filter_device_name, L"\\Device\\Filter");
if (NT_ERROR(status))
return nullptr;
//2.创建设备
status = IoCreateDevice(
driver,
sizeof(DEVICE_SAVE_INFOMATION),
&ucd_filter_device_name,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&filter_device);
if (NT_ERROR(status))
return nullptr;
//3.设置设备通信方式
filter_device->Flags |= DO_BUFFERED_IO;
return filter_device;
}
PDEVICE_OBJECT FilterAttach(PDEVICE_OBJECT src_device,PDEVICE_OBJECT target_device)
{
PDEVICE_OBJECT attach_to_device = nullptr;
NTSTATUS status = STATUS_UNSUCCESSFUL;
status = IoAttachDeviceToDeviceStackSafe(src_device, target_device, &attach_to_device);
if (NT_ERROR(status))
return nullptr;
return attach_to_device;
}
NTSTATUS FilterComplete(IN PDEVICE_OBJECT driver,
IN PIRP pIrp)
{
KdPrint(("[Filter] Enter FilterComplete\r\n"));
NTSTATUS ntStatus = STATUS_SUCCESS;
//将自己完成IRP,改成由底层驱动负责
PDEVICE_SAVE_INFOMATION pdx = (PDEVICE_SAVE_INFOMATION)driver->DeviceExtension;
//调用底层驱动
IoSkipCurrentIrpStackLocation(pIrp);
ntStatus = IoCallDriver(pdx->attach_to_device, pIrp);
KdPrint(("[Filter] FilterComplete End\r\n"));
return ntStatus;
}
NTSTATUS FilterRead(IN PDEVICE_OBJECT driver,
IN PIRP pIrp)
{
KdBreakPoint();
KdPrint(("[Filter] Enter FilterRead\r\n"));
NTSTATUS ntStatus = STATUS_SUCCESS;
KdPrint(("[Filter] Enter Hook\r\n"));
PVOID buffer = nullptr;
ULONG bufsize = 0;
buffer = pIrp->AssociatedIrp.SystemBuffer;
if (buffer)
{
bufsize = wcslen(L"Hook\rn") * 2;
RtlStringCbCopyNW((wchar_t*)buffer, 0x100, L"Hook", bufsize);
}
pIrp->IoStatus.Information = bufsize;
pIrp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("[Filter] FilterRead End\r\n"));
return ntStatus;
}
NTSTATUS FilterWrite(IN PDEVICE_OBJECT driver,
IN PIRP pIrp)
{
KdBreakPoint();
KdPrint(("[Filter] Enter FilterWrite\r\n"));
NTSTATUS ntStatus = STATUS_SUCCESS;
KdPrint(("[Filter] Enter Hook\r\n"));
PVOID buffer = nullptr;
ULONG bufsize = 0;
buffer = pIrp->AssociatedIrp.SystemBuffer;
wchar_t old_buf[0x100] = { 0 };
wcscpy(old_buf, (wchar_t*)buffer);
if (buffer)
{
memset(buffer, 0, 0x100);
RtlStringCbCopyNW((wchar_t*)buffer, 0x100, L"I ", wcslen(L"I ") * 2);
RtlStringCbCatW((WCHAR*)buffer, 0X100, old_buf);
}
//将自己完成IRP,改成由底层驱动负责
PDEVICE_SAVE_INFOMATION pdx = (PDEVICE_SAVE_INFOMATION)driver->DeviceExtension;
//调用底层驱动
//IoSkipCurrentIrpStackLocation(pIrp);
IoCopyCurrentIrpStackLocationToNext(pIrp);
ntStatus = IoCallDriver(pdx->attach_to_device, pIrp);
KdPrint(("[Filter] FilterWrite End\r\n"));
return ntStatus;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING regpath)
{
//1.设置驱动的卸载以及派遣函数
driver->DriverUnload = FilterUnload;
for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
driver->MajorFunction[i] = FilterComplete;
}
driver->MajorFunction[IRP_MJ_READ] = FilterRead;
driver->MajorFunction[IRP_MJ_WRITE] = FilterWrite;
//2.寻找目标设备.也就是我们想要Attah的设备
PDEVICE_OBJECT target_device = nullptr;
PFILE_OBJECT target_fileobj = nullptr;
PDEVICE_OBJECT src_device = nullptr;
PDEVICE_OBJECT attach_to_device = nullptr;
KdBreakPoint();
target_device = GetAttachDeviceByName(L"\\Device\\DriverB", &target_fileobj);
if (target_device == nullptr)
{
KdPrint(("[Filter]--> GetAttachDeviceByName Fail \r\n"));
return STATUS_UNSUCCESSFUL;
}
KdPrint(("[Filter]--> Mount Target Device = [%p] \r\n", target_device));
//3.创建设备,以及设置设备扩展
src_device = CreateFilterDevice(driver);
if (src_device == nullptr)
{
KdPrint(("[Filter]--> CreateFilterDevice Fail \r\n"));
if (target_fileobj != nullptr)
{
ObReferenceObject(target_fileobj);
target_fileobj = nullptr;
}
return STATUS_UNSUCCESSFUL;
}
KdPrint(("[Filter]--> Filter Device = [%p] \r\n", src_device));
//4.Attach到目标设备
attach_to_device = FilterAttach(src_device, target_device);
if (attach_to_device == nullptr)
{
KdPrint(("[Filter]--> FilterAttach Fail \r\n"));
if (target_fileobj != nullptr)
{
ObReferenceObject(target_fileobj);
target_fileobj = nullptr;
}
return STATUS_UNSUCCESSFUL;
}
KdPrint(("[Filter]--> Attach Device = [%p] \r\n", attach_to_device));
//5.记录一下信息
PDEVICE_SAVE_INFOMATION device_save_info_ptr =
(PDEVICE_SAVE_INFOMATION)src_device->DeviceExtension;
device_save_info_ptr->src_device = src_device; //记录我们的设备
device_save_info_ptr->target_next_device = target_device; //记录我们要挂载的目标设备
device_save_info_ptr->target_next_fileobj = target_fileobj; //记录文件对象
device_save_info_ptr->attach_to_device = attach_to_device; //记录下一层设备
return STATUS_SUCCESS;
}

3.7 应用层代码

#include <windows.h>
#include <stdio.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hDevice =
CreateFileW(L"\\\\.\\DriverB",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Failed to obtain file handle to device "
"with Win32 error code: %d\n",
GetLastError());
system("pause");
return 1;
}
wchar_t wzBuffer[0x100]{ 0 };
DWORD dRet;
ReadFile(
hDevice,
wzBuffer,
sizeof(wzBuffer)/sizeof(wzBuffer[0]),
&dRet, NULL);
wcout << "ReadBuffer = " << wzBuffer << endl;
system("pause");
memset(wzBuffer, 0, sizeof(wzBuffer) / sizeof(wzBuffer[0]));
wcscpy_s(wzBuffer, 0x100, L"Love You");
WriteFile(hDevice, wzBuffer, sizeof(wzBuffer) / sizeof(wzBuffer[0]), &dRet, NULL);
CloseHandle(hDevice);
system("pause");
return 0;
}

应用层代码很简单,就是 读一下 写一下.

内核层用了三种方式转发IRP .直接完成 Skip copy方式. 便于学习.

其实现效果如下:

读取的时候,我们进行了过滤,所以不会输出 Driver Read 这个字符串.而是输出了 Hook 这个字符串.

四丶完成例程

4.1 概念

在将 IRP转发给其它驱动之前的时候.可以设置一个回调函数(参考驱动调用驱动一节 IRP同步异步处理).一旦底层驱动执行完毕.那么完成例程设置的回调函数则会被调用. 通过设置完成例程,那么就很方便程序员写代码.

IoCallDriver将Irp交给目标驱动的时候,有两种情况,第一种同步处理.那么代表的就是IoCallDriver返回的时候就代表Irp执行完成了.另一种是异步处理,IoCallDriver会立刻返回.但是此时并不是真正的完成了IRP. 第二种情况就是在调用IoCallDriver之前,设置一个完成例程.底层驱动完成IRP的时候则会调用这个完成例程来通知程序员. 其实完成例程就是在IRP对象中的CompletionRoutine这个域记录着.

如果使用完成例程 那么就不能使用内核宏(IoSkipCurrentIrpStackLocation) 而必须使用 Copy的方式将本层IRP堆栈拷贝到下一层的IRP堆栈.

4.2 完成例程的API

void IoSetCompletionRoutine(
[in] PIRP Irp,
[in, optional] PIO_COMPLETION_ROUTINE CompletionRoutine,
[in, optional] __drv_aliasesMem PVOID Context,
[in] BOOLEAN InvokeOnSuccess,
[in] BOOLEAN InvokeOnError,
[in] BOOLEAN InvokeOnCancel
);

参数1: 要设置的IRP 要给那个IRP设置完成例程(也就是给IRP的子域CompletionRoutine设置)

参数2: 完成例程的回调函数. 如果为NULL 则代表取消这个IRP设置.也就是取消完成例程的设置.简单理解为(Irp->completionRoutine = 参数2)

参数3: 完成例程的上下文. 也就是自定义的参数.当完成例程调用的时候可以使用传递的上下文.

参数4: 指定是否IRP被成功完成后进入完成例程

参数5: 指定是否IRP被错误完成后进入的完成例程

参数6: 是否是IRP被取消后...

此函数的内核实现:

VOID
IoSetCompletionRoutine(
_In_ PIRP Irp,
_In_opt_ PIO_COMPLETION_ROUTINE CompletionRoutine,
_In_opt_ __drv_aliasesMem PVOID Context,
_In_ BOOLEAN InvokeOnSuccess,
_In_ BOOLEAN InvokeOnError,
_In_ BOOLEAN InvokeOnCancel
)
{
PIO_STACK_LOCATION irpSp;
NT_ASSERT( (InvokeOnSuccess || InvokeOnError || InvokeOnCancel) ? (CompletionRoutine != NULL) : TRUE );
irpSp = IoGetNextIrpStackLocation(Irp); //获取下一层IRP堆栈
irpSp->CompletionRoutine = CompletionRoutine;//设置完成例程
irpSp->Context = Context;//设置上下文
irpSp->Control = 0;
if (InvokeOnSuccess) {
irpSp->Control = SL_INVOKE_ON_SUCCESS;
}
if (InvokeOnError) {
irpSp->Control |= SL_INVOKE_ON_ERROR;
}
if (InvokeOnCancel) {
irpSp->Control |= SL_INVOKE_ON_CANCEL;
}
}

IRP处于某个设备栈的时候,IRPIoCompleteRequest完成的时候,则会一层一层的出栈.出栈的时候如果遇到了该栈的完成例程.那么就会调用. 因此完成例程可以作为通知IRP完成的标志. 并且可以很清楚的知道IRP的完成情况.

4.3 完成例程的巧用

完成例程可以知道IRP的完成情况.

当 IoCallDriver被调用之后,当前的驱动(派遣函数)内就会失去对IRP的控制.如果此时本层修改IRP.那么就会BSOD(蓝屏). 而完成回调有两种返回可能.一种是 STATUS_SUCCESS. 返回此标记代表驱动不会在得到IRP的控制. 另一种是返回 STATUS_MORE_PROCESSING__REQUIRED

如果是这个标记.那么本层设备堆栈就会重新获取对IRP的控制 并且设备栈不会向上弹出. 也就可我们可以选择在此向底层设备发送此IRP

4.4 pending位的传播

当低级别的 驱动完成IRP之后,会将堆栈向上回卷 此时底层驱动中的 IO堆栈中的 Control域 如果是 SL_PENDING_RETURNED 那么就必须传送给上一层. 如果本层没有 设置完成例程.那么这种传播是自动的 也就是不需要程序员参与. 如果设置了完成例程,那么就要程序员自己实现.

在没有设置完成例程的时候,编写的代码类似于下面这种情况.

//向下复制堆栈
IoCopyCurrentIrpStackLocationToNext(...)
//向下转发IRP
status = IoCallDriver(...);
return status;

观看上面的代码,我们并没有调用 IoMarkIrpPending 来挂起IRP. 是因为我们没有设置完成例程. 这样底层驱动会自动将堆栈的control域复制到本层堆栈中.

而如果设置了 完成例程 那么则需要我们自己去在完成例程里面去挂起IRP.

NTSTATUS CopletionRoutine(...)
{
if (Irp->PendingReturned)
{
//如果底层驱动有pending位.那么当前堆栈也要设置pending位.使用API设置
IoMarkIrpPending(irp);
}
return STATUS_CONTINUE_COMPLETION; //等价于STATUS_SUCCESS
}

前面说过.当调用 IoCallDriver的时候 本层就不再对IRP有掌控权了.也就是不能修改了.

那么pending的传播.如果我们设置了完成例程.而又写了如下代码.那么就会出问题.

//向下复制堆栈
IoCopyCurrentIrpStackLocationToNext(...)
//向下转发IRP
status = IoCallDriver(...);
if (status == STATUS_PENDING)
{
//错误, 如果设置了完成例程,则必须在完成例程里面设置pending位. 本层已经丧失了IRP的操作
//如果没有设置完成例程,则pending位,操作系统已经帮我们设置了.我们不需要设置.
IoMarkIrpPending(irp);
}
return status;

4.5 完成例程返回 STATUS_MORE_PROCESSING__REQUIRED

如果返回 STATUS_MORE_PROCESSING__REQUIRED 那么调用 IoCallDriver之后还可以继续使用Irp. 也可以直接使用 IoCompleteRequest来完成这个Irp 因为此标记代表回卷停止.我们还可以继续操作IRP.

如果返回 STATUS_SUCCESS 那么则不能操作了.因为已经回卷过了. 此时这个标记只是作为一个通知我们的手段而已.

posted @   iBinary  阅读(673)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
历史上的今天:
2018-08-19 学习逆向知识之用于游戏外挂的实现.第一讲,通过游戏外挂.学习逆向技术指超级马里奥.
点击右上角即可分享
微信分享提示