从源码角度看IRP的构建
一、前言
1、写作目的
最近研究了文件加解密的驱动代码,获益良多。其中在构建Post过滤函数上遇到了重新从当前设备发送请求查看数据中是否存在加密的代码操作,其中有几步代码看的我百思不得其解,毕竟是刚接触驱动编程的新手,于是乎查阅了《Windows驱动开发技术详解》这本书籍,但是在很多层面上来讲还是研究的不够透彻。于是乎我又查询了源码,去网上找了几篇大牛写的文章,总结了一下构建IRP请求数据常见的问题。如有错误,还望斧正!
2、参考资料
《Windows驱动开发技术详解》
ReactOs源码
郑瀚Andrew.Hann博客园文章(附连接)https://www.cnblogs.com/LittleHann/p/3450436.html
二、文件加解密中的IRP应用
1、概览
文件加解密无非就是自己构建进程和文件的黑白名单,当Ring3层发送请求申请操作时,首先经过我们的前过滤(此过程对Read和Write等的加解密操作暂时忽略,直接withcallback返回就可以),然后通过Ring3调用的函数,系统NT系列的函数创建主功能码打包IRP发送给设备对象。此时IRP向下寻找FSD磁盘驱动,在返回的过程中因为没有IRP的返回值,所以在设备栈的栈顶IRP停住了。我们在自己的MiniFilter上的Post函数进行后过滤处理。此时我们重新下发IRP,判断是否加密,进行加解密操作!这里牵扯到了自己构建并下发IRP的过程,这非常的重要!
要进行IRP的手动派发,首先要定位到文件设备对象也就是最底层的设备对象,因为那里存在着与卷的交互,我们用到了IoGetDeviceAttachmentBaseRef循环得到了最底层DeviceObject!
DeviceObject = IoGetDeviceAttachmentBaseRef(FileObject->DeviceObject); //最底层的设备对象
IoGetDeviceAttachmentBaseRef函数是为了寻找栈底DeviceObject设备对象。首先来看参数,传参为本层的设备对象。通过Device指向的DeviceExtension(设备扩展是设备对象的唯一私有属性内存空间)的AttachedTo成员可以访问到底部的下一个成员!如此便找到了最底层的设备对象!底层设备对象是非常重要的,它牵扯到了与卷之间的交互,至于如何进行信息传递,后面我们会讲到!
附上ReactOS中对IoGetDeviceAttachmentBaseRef的源码!
PDEVICE_OBJECT
NTAPI
IoGetDeviceAttachmentBaseRef(IN PDEVICE_OBJECT DeviceObject)
{
/* Reference the lowest attached device */
DeviceObject = IopGetLowestDevice(DeviceObject);
ObReferenceObject(DeviceObject);
return DeviceObject;
}
PDEVICE_OBJECT
NTAPI
IopGetLowestDevice(IN PDEVICE_OBJECT DeviceObject)
{
PDEVICE_OBJECT LowestDevice;
PEXTENDED_DEVOBJ_EXTENSION DeviceExtension;
/* Get the current device and its extension */
LowestDevice = DeviceObject;
DeviceExtension = IoGetDevObjExtension(LowestDevice); //Extension就是扩展内存,里面的attachedto指向底层的设备对象!
/* Keep looping as long as we're attached */
while (DeviceExtension->AttachedTo)
{
/* Get the lowest device and its extension */
LowestDevice = DeviceExtension->AttachedTo;
DeviceExtension = IoGetDevObjExtension(LowestDevice);
}
/* Return the lowest device */
return LowestDevice;
}
2、什么是IRP
IRP是一个数据结构,其中包含了用来描述一个IO请求的完整信息。
IO管理器创建一个IRP来代表一个IO操作,并且将该IRP传递给正确的驱动程序,当此IO操作完成时再处理该请求包。相对的,驱动程序(上层的虚拟设备驱动或者底层的真实设备驱动)接收一个IRP,执行该IRP指定的操作,然后将IRP传回给IO管理器,告诉它,该操作已经完成,或者应该传给另一个驱动以进行进一步处理。
谈到IRP,IRP是个总的概念,本质上IRP由IRP Header和IRP Sub-Request组成。[这里参考了大牛Andrew.Hann的一篇文章,让我搞清了IRP的真正结构,文章在末尾有连接!
从数据结构的角度上来说,其实数据结构 IRP 只是"I/O 请求包"IRP的头部,在 IRP 数据结构的后面还有一个IO_STACK_LOCATION 数据结构的数组,数组的大小则取决于 IRP 数据结构中的StackCount(这个结构对设备对象进行了计数),其数值来自设备堆栈中顶层设备对象的 StackSize字段。IRP为目标设备对象设备堆栈中的每一层即每个驱动都准备好了一个IO_STACK_LOCATION数据结构(数组中的每个堆栈单元