吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

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

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

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

    0.前言
最近在学习Windows内核编程,想要实现一个透明加解密的驱动。故对微软提供的SwapBuffer示例程序进行了分析,在此作为笔记。为了方便观看,大部分的解释我都放到了源代码的注释当中,并且我删除了一些功能无关的代码。
    1.功能
SwapBuffers过滤器在读/写或目录控制操作之前引入了一个新的缓冲区,以后所有的操作都在自己定义的缓冲区上进行;直到所有操作完成后,才会将自己缓冲区的内容复制回系统提供的缓冲区。
    2.结构体声明
实例中定义两种结构体,一是和卷设备相关信息保存的结构体、二是用于前操作向后操作传递参数的结构体:
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
//卷设备上下文
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] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
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, 2025-3-5 14:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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