吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 445|回复: 3
收起左侧

[学习记录] Minifilter-SwapBuffer源码解读

[复制链接]
我还是个孩子丶 发表于 2024-10-15 17:59
本帖最后由 我还是个孩子丶 于 2024-10-15 18:25 编辑

    0.前言
最近在学习Windows内核编程,想要实现一个透明加解密的驱动。故对微软提供的SwapBuffer示例程序进行了分析,在此作为笔记。为了方便观看,大部分的解释我都放到了源代码的注释当中,并且我删除了一些功能无关的代码。
    1.功能
SwapBuffers过滤器在读/写或目录控制操作之前引入了一个新的缓冲区,以后所有的操作都在自己定义的缓冲区上进行;直到所有操作完成后,才会将自己缓冲区的内容复制回系统提供的缓冲区。
    2.结构体声明
实例中定义两种结构体,一是和卷设备相关信息保存的结构体、二是用于前操作向后操作传递参数的结构体:
[C] 纯文本查看 复制代码
//卷设备上下文
typedef struct _VOLUME_CONTEXT {
     //要显示的名称
    UNICODE_STRING Name;
      //该卷的扇区大小。
    ULONG SectorSize;
} VOLUME_CONTEXT, *PVOLUME_CONTEXT;
//前操作向后操作传递的上下文
typedef struct _PRE_2_POST_CONTEXT {
     // 卷的上下文
     PVOLUME_CONTEXT VolCtx;
     // 新的缓冲区指针 方便传递给后操作函数
     PVOID SwappedBuffer;
} PRE_2_POST_CONTEXT, *PPRE_2_POST_CONTEXT;

3初始化

初始化主要有两动作:1.初始化一个旁氏列表用于内存分配(相当于内存池) 2.将过滤器注册并启动
[C] 纯文本查看 复制代码
NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT DriverObject,
    _In_ PUNICODE_STRING RegistryPath
)
{
     NTSTATUS status;
     //使用 NonPagedPoolNx 进行非分页池分配(在支持的情况下)。
     ExInitializeDriverRuntime(DrvRtPoolNxOptIn);
  
      //获取调试跟踪标志
     //从注册表获取参数  这里可以忽略 他只设置了一个无关紧要的打印输出的参数
       ReadDriverParameters(RegistryPath);
    
     //初始化旁视列表,用于分配上下文结构,以便在预操作回调和后操作回调之间传递信息。
     //ExInitializeNPagedLookasideList 例程初始化指定大小的非分页条目的旁视列表
     //Pre2PostContextList是一个NPAGED_LOOKASIDE_LIST类型的全局变量 即一个旁氏列表结构体(分配内存用的)
     ExInitializeNPagedLookasideList(&Pre2PostContextList,
         NULL,
         NULL,
         0,
          sizeof(PRE_2_POST_CONTEXT),
         PRE_2_POST_TAG,
         0);
   
     //注册过滤器
     status = FltRegisterFilter(DriverObject,
        &FilterRegistration,
        &gFilterHandle);
     if (!NT_SUCCESS(status)) {
        goto SwapDriverEntryExit;
     }
  
    //开启过滤器
    status = FltStartFiltering(gFilterHandle);
     if (!NT_SUCCESS(status)) {

        FltUnregisterFilter(gFilterHandle);
        goto SwapDriverEntryExit;
     }
SwapDriverEntryExit:
    if (!NT_SUCCESS(status)) {
        //启动失败 删除旁视列表
        ExDeleteNPagedLookasideList(&Pre2PostContextList);
    }
    return status;
}

4 读操作的处理

读操作是指将磁盘中的数据读到内存中,作为一个过滤驱动,在收到读请求的时候我们并不知道读取的真实数据是什么;因此需要再读请求中做一些读之前(前操作回调)的处理然后将请求放行,待到底层驱动将数据获取完成后再进行处理(后操作回调);并且,因为后操作是在DPC中断等级下执行的对于那些不适合在DPC等级下处理的任务,需要设置一个额外的处理函数(后操作安全回调)待到中断等级下降时在进行处理。
[C] 纯文本查看 复制代码
FLT_PREOP_CALLBACK_STATUS
SwapPreReadBuffers(
        _Inout_ PFLT_CALLBACK_DATA Data,
        _In_ PCFLT_RELATED_OBJECTS FltObjects,
        _Flt_CompletionContext_Outptr_ PVOID *CompletionContext
)
{
        PFLT_IO_PARAMETER_BLOCK iopb = Data->Iopb;//请求相关的操作
        //返回值 NO_CALLBACK意味着不需要后操作处理(即初始化时默认处理失败)
        FLT_PREOP_CALLBACK_STATUS retValue = FLT_PREOP_SUCCESS_NO_CALLBACK;
        PVOID newBuf = NULL;                //新的缓冲器(即自己申请的)
        PMDL newMdl = NULL;                        //缓冲区对应的内存映射地址
        PVOLUME_CONTEXT volCtx = NULL;        //上文定义的卷上下文指针
        PPRE_2_POST_CONTEXT p2pCtx;        //上文定义的前后操做上下文结构
        NTSTATUS status;                        //返回状态
        ULONG readLen = iopb->Parameters.Read.Length;                //读取的长度

        try {

                //        读取长度为0时不需要任何操作 也不需要后操作
                if (readLen == 0) { leave; }

                //获取当前卷设备的相关信息 
                status = FltGetVolumeContext(FltObjects->Filter,
                        FltObjects->Volume,
                        &volCtx);
                if (!NT_SUCCESS(status)) { leave; }

                //        对于非缓存操作,因为要在磁盘上直接读取,因此需要将读取长度和磁盘扇区大小对齐
                if (FlagOn(IRP_NOCACHE, iopb->IrpFlags)) {
                        readLen = (ULONG)ROUND_TO_SIZE(readLen, volCtx->SectorSize);
                }

                //获取一段对齐的缓冲区,其实只有在非缓冲读时才需要这么申请
                //这里为了方便,所有读操作都这么申请
                newBuf = FltAllocatePoolAlignedWithTag(FltObjects->Instance,
                        NonPagedPool,
                        (SIZE_T)readLen,
                        BUFFER_SWAP_TAG);
                if (newBuf == NULL) { leave; }

                //只有普通的IRP才会需要MDL这个参数 FASTIO不需要这个参数
                if (FlagOn(Data->Flags, FLTFL_CALLBACK_DATA_IRP_OPERATION)) {
                        //if (!FlagOn(Data->Flags, FLTFL_CALLBACK_DATA_FAST_IO_OPERATION)) { //这样会不会好一些?

                                //        为新分配的内存(虚拟地址)分配一个内存描述符(MDL)。
                        newMdl = IoAllocateMdl(newBuf,
                                readLen,
                                FALSE,
                                FALSE,
                                NULL);

                        if (newMdl == NULL) { leave; }

                        //  setup the MDL for the non-paged pool we just allocated
                        //设置我们刚刚分配的非分页池的MDL。
                        //更新MDL的物理映射
                        MmBuildMdlForNonPagedPool(newMdl);
                }


                //        从旁氏列表获取内存 用于保存前操作向后操作传递的上下文
                p2pCtx = ExAllocateFromNPagedLookasideList(&Pre2PostContextList);

                if (p2pCtx == NULL) { leave; }


                //        更新缓冲区指针和MDL地址,标记我们已经更改了某些内容。
                iopb->Parameters.Read.ReadBuffer = newBuf;
                iopb->Parameters.Read.MdlAddress = newMdl;
                //        通知操作系统数据被修改 必须通知
                FltSetCallbackDataDirty(Data);

                //
                //  Pass state to our post-operation callback.
                //        将状态传递给我们的后操作回调。
                //        Mdl不用传 因为MDL就是映射到缓存上的
                p2pCtx->SwappedBuffer = newBuf;
                p2pCtx->VolCtx = volCtx;
                *CompletionContext = p2pCtx;

                retValue = FLT_PREOP_SUCCESS_WITH_CALLBACK;

        }
        finally{


                //        不需要后操作说明处理失败 进行清理工作
                if (retValue != FLT_PREOP_SUCCESS_WITH_CALLBACK) {
                        if (newBuf != NULL) {
                                FltFreePoolAlignedWithTag(FltObjects->Instance,
                                                                                   newBuf,
                                                                                   BUFFER_SWAP_TAG);
                        }
                        if (newMdl != NULL) {

                                IoFreeMdl(newMdl);
                        }
                        if (volCtx != NULL) {

                                FltReleaseContext(volCtx);
                        }
                }
        }
        return retValue;
}


FLT_POSTOP_CALLBACK_STATUS
SwapPostReadBuffers(
        _Inout_ PFLT_CALLBACK_DATA Data,
        _In_ PCFLT_RELATED_OBJECTS FltObjects,
        _In_ PVOID CompletionContext,
        _In_ FLT_POST_OPERATION_FLAGS Flags
)
{
        PVOID origBuf;                //原始缓冲区
        PFLT_IO_PARAMETER_BLOCK iopb = Data->Iopb;
        FLT_POSTOP_CALLBACK_STATUS retValue = FLT_POSTOP_FINISHED_PROCESSING;
        PPRE_2_POST_CONTEXT p2pCtx = CompletionContext;//前操作传过来的上下文
        BOOLEAN cleanupAllocatedBuffer = TRUE;                //清理工作标志

        // 在读写请求中不应该存在分离过滤器的操作
        FLT_ASSERT(!FlagOn(Flags, FLTFL_POST_OPERATION_DRAINING));
        try {

                //如果操作失败或计数为零,则没有数据可供处理

                if (!NT_SUCCESS(Data->IoStatus.Status) ||
                        (Data->IoStatus.Information == 0)) {
                        leave;
                }

                //  注意 过滤管理器再调用后操作时会恢复参数
                //        所以这里指向的还是原始的缓冲区
                if (iopb->Parameters.Read.MdlAddress != NULL) {
                        //        这应该是一个简单的MDL,而不应该是链式MDL
                        FLT_ASSERT(((PMDL)iopb->Parameters.Read.MdlAddress)->Next == NULL);

                        //        获取地址 以便我们可以将数据复制回去
                        origBuf = MmGetSystemAddressForMdlSafe(iopb->Parameters.Read.MdlAddress,
                                NormalPagePriority | MdlMappingNoExecute);

                        if (origBuf == NULL) {
                                //获取失败返回
                                Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
                                Data->IoStatus.Information = 0;
                                leave;
                        }

                }
                else if (FlagOn(Data->Flags, FLTFL_CALLBACK_DATA_SYSTEM_BUFFER) ||
                        FlagOn(Data->Flags, FLTFL_CALLBACK_DATA_FAST_IO_OPERATION)) {

                        //0环内存是有效的和FASTIO证明我们再当前进程上下问中
                        //所以这两个情况 ReadBuffer是有效的
                        origBuf = iopb->Parameters.Read.ReadBuffer;
                }
                else {

                        //其他情况无法再DPC中断等级处理 设置安全后操作函数
                        if (FltDoCompletionProcessingWhenSafe(Data,
                                FltObjects,
                                CompletionContext,
                                Flags,
                                SwapPostReadBuffersWhenSafe,
                                &retValue)) {

                                //        安全后操作还需要使用 故不能清理
                                cleanupAllocatedBuffer = FALSE;
                        }
                        else {
                                //        没法调用安全后操作 返回失败
                                Data->IoStatus.Status = STATUS_UNSUCCESSFUL;
                                Data->IoStatus.Information = 0;
                        }
                        leave;
                }



                //        复制操作
                try {

                        RtlCopyMemory(origBuf,
                                p2pCtx->SwappedBuffer,
                                Data->IoStatus.Information);

                } except(EXCEPTION_EXECUTE_HANDLER) {
                        //        复制失败处理
                        Data->IoStatus.Status = GetExceptionCode();
                        Data->IoStatus.Information = 0;
                }

        }
        finally{
                //清理工作 cleanupAllocatedBuffer==TRUE时
                if (cleanupAllocatedBuffer) {

                        FltFreePoolAlignedWithTag(FltObjects->Instance,
                                                                           p2pCtx->SwappedBuffer,
                                                                           BUFFER_SWAP_TAG);

                        FltReleaseContext(p2pCtx->VolCtx);

                        ExFreeToNPagedLookasideList(&Pre2PostContextList,
                                                                                 p2pCtx);
                }
        }
        return retValue;
}

//是一个用户缓冲区
//需要再低中断等级互斥访问缓冲区并复制数据
FLT_POSTOP_CALLBACK_STATUS
SwapPostReadBuffersWhenSafe(
        _Inout_ PFLT_CALLBACK_DATA Data,
        _In_ PCFLT_RELATED_OBJECTS FltObjects,
        _In_ PVOID CompletionContext,
        _In_ FLT_POST_OPERATION_FLAGS Flags
)

{
        PFLT_IO_PARAMETER_BLOCK iopb = Data->Iopb;
        PPRE_2_POST_CONTEXT p2pCtx = CompletionContext;
        PVOID origBuf;
        NTSTATUS status;

        UNREFERENCED_PARAMETER(FltObjects);
        UNREFERENCED_PARAMETER(Flags);
        FLT_ASSERT(Data->IoStatus.Information != 0);


        // 锁定用户缓冲区
        status = FltLockUserBuffer(Data);

        if (!NT_SUCCESS(status)) {
                Data->IoStatus.Status = status;
                Data->IoStatus.Information = 0;

        }
        else {

                origBuf = MmGetSystemAddressForMdlSafe(iopb->Parameters.Read.MdlAddress,
                        NormalPagePriority | MdlMappingNoExecute);

                if (origBuf == NULL) {
                        Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
                        Data->IoStatus.Information = 0;

                }
                else {
                        RtlCopyMemory(origBuf,
                                p2pCtx->SwappedBuffer,
                                Data->IoStatus.Information);
                }
        }

        //清理工作
        FltFreePoolAlignedWithTag(FltObjects->Instance,
                p2pCtx->SwappedBuffer,
                BUFFER_SWAP_TAG);
        FltReleaseContext(p2pCtx->VolCtx);
        ExFreeToNPagedLookasideList(&Pre2PostContextList,
                p2pCtx);

        return FLT_POSTOP_FINISHED_PROCESSING;
}



流程总结:对于读的处理 就是在前操作中申请了一块缓冲区用来替换原始的缓冲区,这样在将请求放行以后,底层获取的数据就会传入我们申请的缓冲区当中。然后,再后操作中就可以对我们申请的缓冲区做处理,在将处理完的数据复制到原始的缓冲区中显示给上层调用者。
其他处理对于另外两个操作,处理方式基本相同且比读操作要简单,故不在此重复。
微软官方代码示例链接:https://github.com/microsoft/Windows-driver-samples/tree/main/filesys/miniFilter/swapBuffers

疑问:为什么不能直接在原始缓存上进行处理?我之前试过在后操作中对原始缓存直接进行修改,但不知道为什么确实没有成功。但示例中的RtlCopyMemory同样需要对原始缓冲操作,为什么他的就能成功。。。
后话:然而,光知道SwapBuffer是无法实现文件透明加密的。。因为Windows的缓存机制,还需要对文件缓存实施动态的加密和解密(所有程序在打开文件时公用一个全局的文件缓存,存放与FCB中)。目前了解到两种办法:1.对缓存进行清空并重新获取。2.双缓存机制。
缓存清空了解到两种办法:1.自己调用CcFlushCache、MmFlushIMAGESection和CcPurgeCacheSection进行清空
    2.触发文件写进行清空(文件写会稳定的触发文件缓存的清空)(这种方法吾爱大佬已有给出例子的)
对于缓存处理的方法,后续将会继续学习并实现。
小白第一次写帖子,欢迎各位大佬指正。

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

侃遍天下无二人 发表于 2024-10-15 18:35
看你的描述原始缓冲是不是从磁盘映射的,新申请的才是真正在内存的区域,如果是这样的话,写入不成功很大概率就是因为出于性能考虑,操作系统本身就有写缓冲区,得调用类似flush的操作进行刷新才行
 楼主| 我还是个孩子丶 发表于 2024-10-15 18:42
侃遍天下无二人 发表于 2024-10-15 18:35
看你的描述原始缓冲是不是从磁盘映射的,新申请的才是真正在内存的区域,如果是这样的话,写入不成功很大概 ...

好的大佬,后面我再分析一下请求类别
Panel 发表于 2024-10-15 21:12
直接操作原始缓冲区内容会导致清理缓存失败,在xp是这样的
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-16 04:35

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表