SwapBuffer分析

来源:互联网 发布:产品市场矩阵 编辑:程序博客网 时间:2024/06/14 00:58

将原先自己的博客迁移!

(调试: *.info文件安装,服务启动sc start **)

介绍

自己开辟一块缓存,将文件进行缓存交换,后续都是操作该缓存,例如加密解密等,等待操作完成后,再将缓存交换回去。(https://docs.microsoft.com/zh-cn/windows-hardware/drivers/ifs/file-system-minifilter-drivers)
        修改示例代码的INF文件,将相关的swapbuffer,变成你的工程的名字就可以编译通过了。Win7-X86
流程
根据流程开发介绍,先注册FltRegisterFilter(),然后再FltStartFiltering ()
         其中,注册有个结构:FLT_REGISTRATION structure , 首先分析这个结构的InstanceSetup()函数,结构可以查看 如下这个 PFLT_INSTANCE_SETUP_CALLBACK (https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff551096)
https://github.com/Microsoft/Windows-driver-samples   ----Windows驱动实例代码

(一)InstanceSetup() ----函数流程说明        
参考微软自己提供的代码swapbuffer中的InstanceSetup(),运行时候,InstanceSetup()函数将针对每一个Volume创建一个Instance,默认会附加到所有卷上,包含NT网络卷,若是检索到名称,则将使用该名称创建一个卷的上下文。
这个回调,会在一开始启动的时候,枚举所有的卷,并且会在运行过程中,当有卷挂载时候,会自动运行该回调,通知有卷进行挂载。当某个卷被挂载或作为一个外部绑定请求 (fltmc attach, FltAttachVolume或FilterAttach)的结果时,那也会在运行时期间发生。
1) 第一次进入,开始附加: /FileSystem/Mup文件系统, 设备名称为\Device\Mup, 
       FltAllocateContext()     ----分配该卷的上下文
       FltGetVolumeProperties(FltObjects->Volume, volProp); ----获取该卷的属性
       ctx->SectorSize = max(volProp->SectorSize, MIN_SECTOR_SIZE);   ----保存该卷的扇区大小,便于后面使用,如果没有扇区大小,我们选择最小扇区
       FltGetDiskDeviceObject(FltObjects->Volume, &devObj);    ----然后获取该存储设备对象,我们需要一个名称。
       (这里就是这样,如果我们不能获取到DOS名称,获取NT名称,volProp->RealDeviceName.Length >0 , 或者是 volProp->FileSystemDeviceName.Length>0,来获取到一个名称, ctx->Name.Buffer = ExAllocatePoolWithTag()来保存下来)
       最后调用, FltSetVolumeContext(FltObjects->Volume,  FLT_SET_CONTEXT_KEEP_IF_EXISTS,  ctx,  NULL); ---设置上下文。  FltGetVolumeContext()  ---可以调用这个,来获取该卷的上下文。 

注意: 运行过程中,还有创建了: (这个符合右键,计算机管理->存储->磁盘管理器,看到的系统的所有卷的信息)
a)  volProp->FileSystemDriverName = "\Filesystem\Ntfs "
     volProp->FileSystemDeviceName = "\Ntfs"
     volProp->RealDeviceName           = "\Device\HarddiskVolume2"
     注意: 这个我们用RtlVolumeDeviceToDosName(devObj, &ctx->Name);函数,直接取到了DOS的名称,也就是"C:",这个字符串,而不是像”\Filesystem\Mup“,这样的东东了。 最终,ctx->Name = "C:"

b).  volProp->FileSystemDriverName = "\Filesystem\Ntfs "
     volProp->FileSystemDeviceName = "\Ntfs"
     volProp->RealDeviceName           = "\Device\HarddiskVolume1"    ----这个我们用RtlVolumeDeviceToDosName(devObj, &ctx->Name)函数,也可以直接取到了DOS的名称, ctx->Name ="\\?\Volume{5e24f8f7-5d58-11e7-870e-806e6f6e6963}", ---这个有点奇怪,不知道是什么卷

c)  volProp->FileSystemDriverName = "\Filesystem\udfs "
     volProp->FileSystemDeviceName = "\UdfsCdRom"
     volProp->RealDeviceName           = "\Device\CdRom0"
     注意: 这个我们用RtlVolumeDeviceToDosName(devObj, &ctx->Name);函数,直接取到了DOS的名称,ctx->Name = "D:", 这个应该就是CD光盘的。
    
d).  volProp->FileSystemDriverName = "\Filesystem\Ntfs "
     volProp->FileSystemDeviceName = "\Ntfs"
     volProp->RealDeviceName           = "\Device\HarddiskVolume3"    ----其实这个是我后面在VM中新增的一个SCSI盘,然后就出现了这个, 这个我们用RtlVolumeDeviceToDosName(devObj, &ctx->Name)函数,也可以直接取到了DOS的名称, ctx->Name ="E:"。

(二)ExInitializeNPagedLookasideList()系列的函数说明
该系列函数,主要是提供个内存管理,创建一个lookasidelist,让系统自己进行管理,通过几个操作就可以使用。
ExInitializeNPagedLookasideList()   -----首先初始化一个链表头,分页和非分页的,有2个参数说明下,
 NPAGED_LOOKASIDE_LIST      Pre2PostContextList;     ---这个其实就是管理的链表句柄,后面操作都用这个作为句柄
 IN SIZE_T Size, 例如:sizeof(PRE_2_POST_CONTEXT)                ---这个其实就是设定一个成员的结构大小,后面分配出来的就是这个大小
ExAllocateFromNPagedLookasideList()  -----这个用于从该链表管理句柄中分配出一个内存, 分配的内存大小就是签名的Size大小的内存
ExFreeToNPagedLookasideList()     ------将之前从链表管理中分配出的内存换回去
ExDeleteNPagedLookasideList()   -----删除这个链表句柄。

(三)SwapPreReadBuffers() 函数说明
    这个例子演示如何在Read操作中,交换缓冲区,注意的是,这个例子中,所有的错误,都只是不进行交换缓冲区来进行处理
参数中的FLT_RELATED_OBJECTS结构中,包含了该过滤器不透明的句柄,实例,以及关联的卷和文件对象; 另外一个参数,CompletionContext,也是比较重要的,如果有Post操作回调,那么这个参数可以设置上下文传递给post回调进行处理。
    FltGetVolumeContext(FltObjects->Filter,FltObjects->Volume, &volCtx);   ---通过之前的Set卷上下文,所以这里就可以用了,我们可以在这里Get我们的卷上下文,将对卷的信息放在这里,例如,可以判断这个卷是不是我们需要处理的,代码如下:
比较卷名称:
     UNICODE_STRING MyVolumeName;
RtlInitUnicodeString(&MyVolumeName, L"E:");
if ( 0 != RtlCompareUnicodeString(&volCtx->Name, &MyVolumeName, TRUE) )
{
leave;
}
比较文件名称:
               UNICODE_STRING MyVolumeName;
RtlInitUnicodeString(&MyVolumeName, L"\\1111\\1111.txt");
if ( 0 != RtlCompareUnicodeString(&FltObjects->FileObject->FileName, &MyVolumeName, TRUE) )
{
leave;
}
bu FsfilterMinifilter!SwapPreReadBuffers  ----通过这样设置断点,就可以看到是否生效了。
很奇怪的,在E:盘操作了文件夹,以及新建文件夹,新建记事本等操作都没有进入到这个过滤条件中,看到的都是C:盘的操作,很果断的怀疑是系统缓存了,此时直接关机验证了下,发现也没有进入到E:盘下,连关机都不写入吗?
DiskGenius   http://www.diskgenius.cn/download.php ---用这个查看硬盘的数据,看看文件的内容是否存在吧。(虚拟机无法查看)
重启电脑后,再次启动该驱动进行过滤,发现可以看到该过滤条件生效了,现在不知道该工作情况,没搞懂是什么时候写入的???(可能还有一种情况,我关机的时候,驱动已经被停止了,所以就没有监控到写入的情况了)
此时,我们可以看到,卷已经是E:盘的卷了,并且还可以看到该卷下的实际的文件路径,是"\dirpath\123.txt"这样的路径,如果要完整的路径,要自己拼接卷名, “E:\dirpath\123.txt”  , 类似于这样
检查标记一:在该IRP请求中,我们先判断下该请求的标记,是否有存在IRP_NOCACHE的标记( IRP_NOCACHE ----如果这是一个非高速缓存的I / O,我们需要将readlen长度最大化为此设备的扇区大小。 我们必须这样做,因为文件系统这样做,我们需要确保我们的缓冲区与他们期望的一样大。)
附标记的解释:
IRP_NOCACHE    0x00000001 //表示I/O请求从存储的媒介而不是高速缓存中读取数据 
IRP_PAGING_IO  0x00000002//表示此时执行内存页的I/O操作 
IRP_MOUNT_COMPLETION  0x00000002//卷挂载操作完成 
IRP_SYNCHRONOUS_API  0x00000004//该操作是一个同步分页I/O操作。 
IRP_ASSOCIATED_IRP //该操作与主IRP关联。
IRP_BUFFERED_IO //该操作是一个缓冲的I/O操作。 
IRP_DEALLOCATE_BUFFER //在I/O管理器将在该IRP完成阶段释放缓冲区。
IRP_INPUT_OPERATION //该操作是一个输入操作。 
IRP_SYNCHRONOUS_PAGING_IO //表示内存页需要同步更新,此标志也是由内存管理器使用 
IRP_CREATE_OPERATION //该操作是一个创建操作。
IRP_READ_OPERATION //该操作是一个读操作。
IRP_WRITE_OPERATION //该操作是一个写操作。
IRP_CLOSE_OPERATION //该操作是一个关闭操作。 
IRP_DEFER_IO_COMPLETION //I/O操作完成被推迟。
IRP_OB_QUERY_NAME //该操作是异步的名称查询。
IRP_HOLD_DEVICE_QUEUE //保留
接下来,为正在交换的缓冲区分配非分页内存,大小就是上面的readlen长度,为设备的扇区大小。
检查标记二: 检查数据的标记,是否是FLTFL_CALLBACK_DATA_IRP_OPERATION, 我们只需要构建一个用于IRP操作的MDL,由于FASTIO接口没有将MDL传递给文件系统的参数,我们不需要为FASTIO操作执行此操作。
附MDL小知识: (http://laokaddk.blog.51cto.com/368606/404588)

如何使用MDL:
一个连续的虚拟内存地址范围可能是由多个分布(spread over)在不相邻的物理页所组成的。系统使用MDL(内存描述符表)结构体来表明虚拟内存缓冲区的物理页面布局。我们应该避免直接访问MDL。我们可以使用MS-Windows提供的宏,他们提供了对这个结构体基本

的访问。
·MmGetMdlVirtualAddress 获取缓冲区的虚拟内存地址
·MmGetMdlByteCount 获取缓冲区的大小(字节数)
·MmGetMdlByteOffset 获取缓冲区开端的物理页的大小(字节数)
·MmGetMdlPfnArray  获取记录物理页码的一个数组指针。

DO_BUFFERED_IO:一般用于比较简单且不追求效率情况下的解决方案,把应用层(Ring3层)中内存空间中的缓冲数据拷贝到内核空间。IRP的AssociatedIrp.SystemBuffer成员指向内核中的缓冲区。

DO_DIRECT_IO:把应用层的地址空间映射到内核空间,这需要在页表中增加一个映射。当然这不需要程序员手工去修改页表,通过构造MDL就能实现这个功能。MDL可以翻译为“内存描述符链”,按业界传统习惯一律称之为MDL。IRP中的MDLAddress域是一个MDL的指针,从这个MDL中可以读出一个内核空间的虚拟地址。
可以通过调用MmGetSystemAddressForMdlSafe来获得内核空间所映射的虚拟地址。
通过MmGetMdlByteCount、MmGetMdlVirtualAddress、MmGetMdlByteOffset可以得到缓冲区的长度、虚拟内存地址、偏移量。

既不使用DO_BUFFERED_IO也不使用DO_DIRECT_IO(0):直接使用应用程序提供的缓冲区地址,缓冲区地址可以在派遣函数中通过IRP的UserBuffer字段获得。这种方式通常很少用到,用起来时需要格外小心,因为ReadFile可以传入一个NULL指针或者非法地址给驱动程序,在驱动程序中可以结合ProbeForWrite和try块来操作。
(P.S. 绝对不能在中断服务程序和DPC函数中使用用户空间虚拟地址。特别地,由于Windows设备驱动的异步性,许多操作其实是作为DPC函数得到执行的;所以一般而言实际的设备驱动不是采用缓冲方法就是采用直接方法
。《Windows 内核情景分析–采用开源代码ReactOS》)

接下来就开始准备交换缓存了,上面已经创建了两个,一个是newBuf(非分页内存),另一个是该IRP的MDL
通过ExAllocateFromNPagedLookasideList(&Pre2PostContextList)这个函数,我们分配一个非分页的上下文p2pctx,用来保存上面创建好的newbuf,然后更新iopb的参数中的ReadBuffer和MdlAddress为我们新建的这两个地址
FltSetCallbackDataDirty(Data); ---来告诉系统,我们已经把数据给修改了.
                iopb->Parameters.Read.ReadBuffer = newBuf;
iopb->Parameters.Read.MdlAddress = newMdl;
FltSetCallbackDataDirty(Data);
这个地方其实就是将系统的IRP的读写缓存,修改为我们自己分配的缓存,然后告诉系统(可能系统会释放内部的原缓存?)
这里上下文保存的内容:
                p2pCtx->SwappedBuffer = newBuf;   -----新缓存操作的地方, 注意到和上面的iopb的ReadBuffer是同一个地址
(在post我们可以看到,iopb->Parameters.Read.ReadBuffer,这个在post
                                                                                   的时候已经读取到了对应的数据了,同样的,在这个上下文的
                                                                                   SwappedBuffer因为也指向同一个地址,所以也可以获取到数据了
  长度就是Data->IoStatus.Information的长度。
p2pCtx->VolCtx = volCtx;
*CompletionContext = p2pCtx;    ------这个非常的关键,这里赋值后,post就可以获取到上下文。
                retValue = FLT_PREOP_SUCCESS_WITH_CALLBACK;     ----告诉系统要调用post回调处理。

(四)SwapPostReadBuffers()函数流程分析
FLTFL_POST_OPERATION_DRAINING   ----Post回调函数需要查看这个标记,如果有设置,表示该系统不会用交换的缓冲区方式进行操作。

我们需要将读取的数据复制回用户缓冲区, 之前我们已经看到, iopb->Parameters.Read.MdlAddress这个是我们新建的一个虚拟地址

origBuf = MmGetSystemAddressForMdlSafe(iopb->Parameters.Read.MdlAddress, NormalPagePriority);--获取该虚拟地址

RtlCopyMemory(origBuf,  p2pCtx->SwappedBuffer,   Data->IoStatus.Information);   ----在该post函数中,就可以拷贝数据到用户的MdlAddress中了,这样就基本完成了一个数据缓存的交换。

结束语: 目前测试中发现,该PreRead并不能百分之一百的监控到 文件的打开操作,遗留待解决!

原创粉丝点击