首页
社区
课程
招聘
[原创]文件注册表过滤驱动
发表于: 2023-2-7 08:32 9173

[原创]文件注册表过滤驱动

2023-2-7 08:32
9173

本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如有错漏,欢迎留言交流指正

文件,注册表过滤


分层驱动框架

  • NT驱动框架:单层驱动,只能接受自己进程的IRP
  • Sifileter驱动框架:多层驱动,接受所有进程的IRP
    • 过滤:分层驱动中再加一层而不影响它的上下层,以过滤它们之间的数据,对数据或行为进行安全控制。过滤是通过设备绑定实现的(有多少个文件卷设备就生成多少个文件过滤驱动设备对象一一绑定,这样,发给各个卷设备对象的IRP都会被监控到)。
  • 磁盘过滤驱动:用于文件还原

    绑定与过滤

  • 设备栈绑定的形式:驱动自己生成一个设备(过滤设备),调用系统提供的绑定API,绑定到自标设备上,并返回一个在未绑定之前目标设备所在设备栈的最顶层设备。这样发往下层的IRP或者发往土层的数据都会被过滤设备截获。
  • 绑定的API:
    • loAttachDevice()
    • loAttachDeviceToDeviceStackSafe(2000 SP4以及XP以上)
    • loAttachDeviceToDeviceStack()

      IoAttachDeviceToDeviceStack(绑定)

  • windbg查看设备栈
    • !devobj查看设备对象信息
    • !drvobj查看驱动对象信息
    • !devstack查看设备栈
    • !devnode 0 1系统设备树
1
2
3
4
5
6
7
/// AttachedDevice需要记录在DEVICE_EXTENSION中,以便调用IoCallDriver()继续下发IRP
/// 返回一个在未绑定之前目标设备所在设备栈的最顶层设备,
/// 目的是为了IRP继续往下发,所以需要备份下一层的设备对象,即未绑定之前目标设备所在设备栈的最顶层设备
PDEVICE_OBJECT IoAttachDeviceToDeviceStack(
    IN PDEVICE_OBJECT SourceDevice,
    IN PDEVICE_OBJECT TargetDevice
);

  • 设备对象中有2个设备指针
    • 1.驱动对象生成的所有设备对象通过NextDevice指针组织在1个链表里
    • 2.AttachedDevice: 设备栈,绑定在设备对象上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
typedef struct _DEVICE _OBJECT
{
    SHORT Type;
    WORD Size;
    LONG ReferenceCount;
    PORITER OBIECT DriverObject; ///< 生成该设备对象的驱动对象
    DEVICE OBTECT NextDevice; ///< 驱动对象生成的所有设备对象通过NextDevice指针组织在1个链表里
    PDEVICE OBTECT AttachedDevice; ///< 设备栈,绑定在设备对象上
    PIRP CurrentIrp;
    PIO TIMER Timer;
    LONG Flags;
    LONG Characteristics;
    PUPB Vpb;
    VOID DeviceExtension;
    LONG DeviceType;
    CHAR StackSize;
    BYTE Queue [40];
    LONG AlignmentRequirement:
    DEVICE QUEUE DeviceQueue;
    KDPC Dpc;
    LONG ActiveThreadCount;
    VOID SecurityDescriptor;
    EVENT DeviceLock;
    WORD SectorSize;
    WORD Sparel;
    PDEVOBT _EXTENSION DeviceObjectExtension;
    VOID Reserved;
}DEVICE OBJECT, *PDEVICE_OBJECT:

对设备对象作不同处理(过滤)

  • 控制设备对象
    • DriverEntry中创建的设备对象
    • 用来接收自己客户端的IRP
  • 过滤设备对象
    • 绑定时候创建的设备对象
    • 用来接收其它R3程序的IRP
  • 分发函数将接收各进程IRP
    • 设备对象分控制设备对象和过滤设备对象,但用来接受Irp分发函数只有一个
    • 比如FilterCreate(PDEVICE_OBJECT DeviceObject,PIRP plrp),既接收自己进程的IRP(发给控制设备对象),也用来接受其他进程IRP(发给过滤设备对象)
  • 如何区分IRP是自己进程下发的(发给控制设备对象),还是别的进程下发的(发给过滤设备对象)?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// 判断设备对象是否是过滤设备对象:
/// 1.过滤设备对象不为空
/// 2.过滤设备对象是由驱动对象创建
/// 3.如果是过滤设备对象,DeviceExtension保存着下一层的设备对象
#define IS_MY_DEVICE OBJECT(_devObj)\
    (((_devObj) != NULL) &&\
    ((_devObj)->DriverObject == gSfilterDriverObject)&&\
    ((devObj)->DeviceExtension != NULL))
 
/// 判断设备对象是否是控制设备对象:
/// 1.gSfilterControlDeviceObject指针保存的是在DriverEntry创建的控制设备对象的地址
/// 2.控制设备对象是由驱动对象创建
/// 3.如果是控制设备对象,DeviceExtension为空
#define IS_MY_CONTROL_DEVICE_OBJECT(_devObj)\
    (((_devObj) == gSfilterControlDeviceObject) ?\
    (ASSERT(((_devObj)->DriverObject == gSfilterDriverObject) && \
    (_devObj)->DeviceExtension == NULL)),TRUE):FALSE)
  • 过滤分发函数对各种IRP的处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// xxx表示任意的过滤分发函数
NTSTATUS FilterXXX(PDEVICE_OBJECT DeviceObject,PIRP plrp)
{
    NTSTATUS Status = STATUS_suCCESs;
    ULONG ullnfomation = O;
    IO_STACK_LOCATION* lpIrpStack = IoGetcurrentIrpStackLocation(pIrp);
    if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject))
    {
        /// 如果需要与R3交互,这里必须返回成功
        pIrp->loStatus.Status = Status,
        plrp->loStatus.Information = ullnfomation;
        IoCompleteRequest(lplrp,IO_NO_INCREMENTY;
    }
    else if (IS_MY_DEVIGE_OBJECT(DeyiceObject))
    {
        /// 这里才是我们要过滤的操作,直接放行
        IoSkipCurrentIrpStackLocation(plrp); //忽略当前的IRP
        Status = IoCallDriver(((PSfilter DEVICE_EXTENSION)->DeviceExtension)DeviceObject->NLExtlHeader.AttachedToDeviceObject,plrp); //发给下一层的设备对象,下发后关心Irp成功或者失败
    }
    else
    {   
        /// 非法参数
        plrp->loStatus.Status = Status = STATUS_INVALID_PARAMETER;
         plrp->loStatus.Information = 0
        IoCompleteRequest(plrp,IO_NO_INCREMENT);
    }
        return Status;
}

文件系统过滤框架

  • Filemon
    • 只能监控固定卷,无法感知移动设备(比如U盘,移动硬盘)
  • Sfilter
  • Minifilter
  • Filespy

    Sfilter

    Sfilter代码通读分析

  • 框架代码不要钻牛角尖,把握整体流程即可,不求把每行代码每个变量都扣的清楚。(上万行代码)
    • 除了过滤分发函数,其他代码理解即可,不需要做改动。
    • 所以在Minifilter中就把这部分代码给封装隐藏了,只需要注册回调函数即可
  • DriverEntry()
    • 创建控制设备
    • 创建控制设备符号链接
    • 注册过滤分发函数,SfPassThrough()通用的分发函数
    • 注册一组Fastio
    • 绑定与过滤
      • 绑定1:IoRegisterFsRegistrationChange(); 注册回调函数,生成过滤对象,绑定到文件系统设备对象进行监控
        • SfFsNotification()
        • SfAttachToFileSystemDevice()
          • SfAttachDeviceToDeviceStack()
      • 绑定2:SfFsControl:lRP_WAFLE_SYSTEM_CONTROL处理发给文件系统设备对象mount IRP请求,从mount IRP中拿到卷设备对象并生成一个过滤设备对象与之绑定
        • SfFsControlMountVolumeComplete();
          • SfAttachToMountedDevice()
          • SfFsControlCompletion()
      • 过滤:SfCreate()全盘监控
        • NLAllocateNameControl()
        • NLGetFullPathName()
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
)
 
/*++
 
Routine Description:
 
    This is the initialization routine for the SFILTER file system filter
    driver.  This routine creates the device object that represents this
    driver in the system and registers it for watching all file systems that
    register or unregister themselves as active file systems.
 
Arguments:
 
    DriverObject - Pointer to driver object created by the system.
 
Return Value:
 
    The function value is the final status from the initialization operation.
 
--*/
 
{
    PFAST_IO_DISPATCH fastIoDispatch;
    UNICODE_STRING nameString;
    UNICODE_STRING dosName;
    NTSTATUS status;
    ULONG i;
    /// xp及以上系统
#if WINVER >= 0x0501
    //
    //  Try to load the dynamic functions that may be available for our use.
    //
 
    SfLoadDynamicFunctions();
 
    //
    //  Now get the current OS version that we will use to determine what logic
    //  paths to take when this driver is built to run on various OS version.
    //
 
    SfGetCurrentVersion();
#endif
 
    //
    //  Get Registry values
    //
 
    SfReadDriverParameters(RegistryPath);
 
    //
    //  Save our Driver Object, set our UNLOAD routine
    //
    /// 用来判断是不是过滤设备对象
    gSFilterDriverObject = DriverObject;
 
#if DBG && WINVER >= 0x0501
 
    //
    //  MULTIVERSION NOTE:
    //
    //  We can only support unload for testing environments if we can enumerate
    //  the outstanding device objects that our driver has.
    //
 
    //
    //  Unload is useful for development purposes. It is not recommended for
    //  production versions
    //
 
    if (NULL != gSfDynamicFunctions.EnumerateDeviceObjectList) {
        gSFilterDriverObject->DriverUnload = DriverUnload;
    }
#endif
 
    //
    //  Setup other global variables
    //
 
    ExInitializeFastMutex(&gSfilterAttachLock);
 
    //
    //  Initialize the lookaside list for name buffering.  This is used in
    //  several places to avoid having a large name buffer on the stack.  It is
    //  also needed by the name lookup routines (NLxxx).
    //
    /// 避免内存碎片,SFilter监控大量的文件的操作,需要把这些操作保存起来,涉及到固定大小内存的分配,用LookasideList数据结构比较合适
    ExInitializePagedLookasideList(&gSfNameBufferLookasideList,
        NULL,
        NULL,
        0,
        SFILTER_LOOKASIDE_SIZE,
        SFLT_POOL_TAG_NAME_BUFFER,
        0);
 
    //
    //  Create the Control Device Object (CDO).  This object represents this
    //  driver.  Note that it does not have a device extension.
    //
 
    RtlInitUnicodeString(&nameString, L"\\FileSystem\\Filters\\SFilterDrv");
 
    status = IoCreateDevice(DriverObject,
        0,                      //has no device extension
        &nameString,
        FILE_DEVICE_DISK_FILE_SYSTEM, ///< 不再是FILE_DEVICE_UNKNOWN了
        FILE_DEVICE_SECURE_OPEN,
        FALSE,
        &gSFilterControlDeviceObject); ///< 保存在全局指针,用于后续区分控制设备对象
 
    if (status == STATUS_OBJECT_PATH_NOT_FOUND) {
        //
        //  This must be a version of the OS that doesn't have the Filters
        //  path in its namespace.  This was added in Windows XP.
        //
        //  We will try just putting our control device object in the
        //  \FileSystem portion of the object name space.
        //
 
        /// 如果创建设备对象失败,则换一种创建方式
        RtlInitUnicodeString(&nameString, L"\\FileSystem\\SFilterDrv");
 
        status = IoCreateDevice(DriverObject,
            0,                  //has no device extension
            &nameString,
            FILE_DEVICE_DISK_FILE_SYSTEM,
            FILE_DEVICE_SECURE_OPEN,
            FALSE,
            &gSFilterControlDeviceObject);
 
        if (!NT_SUCCESS(status)) {
            KdPrint(("SFilter!DriverEntry: Error creating control device object \"%wZ\", status=%08x\n",
                &nameString,
                status));
            return status;
        }
    }
    else if (!NT_SUCCESS(status)) {
        KdPrint(("SFilter!DriverEntry: Error creating control device object \"%wZ\", status=%08x\n",
            &nameString, status));
        return status;
    }
 
    RtlInitUnicodeString(&dosName, L"\\DosDevices\\SFilterDrv");
    status = IoCreateSymbolicLink(&dosName, &nameString);
    if (NT_SUCCESS(status) == FALSE)
    {
        IoDeleteDevice(gSFilterControlDeviceObject);
 
        ExDeletePagedLookasideList(&gSfNameBufferLookasideList);
        return STATUS_UNSUCCESSFUL;
    }
 
    /// 控制设备对象通信方式设置成buffer_io
    /// 过滤设备对象则是要和被绑定的设备对象指定的通信方式一致
    gSFilterControlDeviceObject->Flags |= DO_BUFFERED_IO;
 
    //
    //  Initialize the driver object with this device driver's entry points.
    //
 
    for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {
        DriverObject->MajorFunction[i] = SfPassThrough; ///< 设置成通用的Irp处理函数,对Irp直接放行
    }
 
    //
    //  We will use SfCreate for all the create operations
    //
 
    /// 生成过滤设备对象绑定到目标设备对象之后,凡是发送给目标设备对象的IRP都会被分发函数拿到
    DriverObject->MajorFunction[IRP_MJ_CREATE] = SfCreate; ///< 如果是自己进程下发的IRP(发给控制设备对象),在这里都会替换掉初始化的通用分发函数SfPassThrough,不用担心SfPassThrough处理控制设备对象的IRP的情况
    //DriverObject->MajorFunction[IRP_MJ_CREATE_NAMED_PIPE] = SfCreate;
    //DriverObject->MajorFunction[IRP_MJ_CREATE_MAILSLOT] = SfCreate;
 
    DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = SfFsControl; ///< Mount是RP_MJ_FILE_SYSTEM_CONTROL的一个子功能号,通过这个函数监控Mout IRP,生成过滤设备对象与移动(U盘)卷设备绑定
    DriverObject->MajorFunction[IRP_MJ_CLEANUP] = SfCleanupClose;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = SfCleanupClose;
 
    //
    //  Allocate fast I/O data structure and fill it in.
    //
    //  NOTE:  The following FastIo Routines are not supported:
    //      AcquireFileForNtCreateSection
    //      ReleaseFileForNtCreateSection
    //      AcquireForModWrite
    //      ReleaseForModWrite
    //      AcquireForCcFlush
    //      ReleaseForCcFlush
    //
    //  For historical reasons these FastIO's have never been sent to filters
    //  by the NT I/O system.  Instead, they are sent directly to the base
    //  file system.  On Windows XP and later OS releases, you can use the new
    //  system routine "FsRtlRegisterFileSystemFilterCallbacks" if you need to
    //  intercept these callbacks (see below).
    //
 
    fastIoDispatch = ExAllocatePoolWithTag(NonPagedPool,
        sizeof(FAST_IO_DISPATCH),
        SFLT_POOL_TAG_FASTIO);
    if (!fastIoDispatch) {
        IoDeleteDevice(gSFilterControlDeviceObject);
        return STATUS_INSUFFICIENT_RESOURCES;
    }
 
    RtlZeroMemory(fastIoDispatch, sizeof(FAST_IO_DISPATCH));
 
    /// 真正的IRP是从文件系统的磁盘上进行IO操作的,
    /// 而fastIo不走IRP,直接在缓存中读写数据,目的是为了提高效率,比如读的时候提前把一些数据读出,写的时候提前把一些数据写入,
    /// 最后一下子把缓存中的数据一次性写入磁盘,减少IO次数,提高系统性能
    /// 在这里没有对fastIo做特殊处理,而是直接拒绝
    fastIoDispatch->SizeOfFastIoDispatch = sizeof(FAST_IO_DISPATCH);
    fastIoDispatch->FastIoCheckIfPossible = SfFastIoCheckIfPossible;
    fastIoDispatch->FastIoRead = SfFastIoRead; ///< 直接返回FALSE,相当于禁用掉了,性能会有一定的影响,性能损失在10%以下(《寒江独钓》中统计的数据),对于客户端来说,还是可以接受的
    fastIoDispatch->FastIoWrite = SfFastIoWrite;
    fastIoDispatch->FastIoQueryBasicInfo = SfFastIoQueryBasicInfo;
    fastIoDispatch->FastIoQueryStandardInfo = SfFastIoQueryStandardInfo;
    fastIoDispatch->FastIoLock = SfFastIoLock;
    fastIoDispatch->FastIoUnlockSingle = SfFastIoUnlockSingle;
    fastIoDispatch->FastIoUnlockAll = SfFastIoUnlockAll;
    fastIoDispatch->FastIoUnlockAllByKey = SfFastIoUnlockAllByKey;
    fastIoDispatch->FastIoDeviceControl = SfFastIoDeviceControl;
    fastIoDispatch->FastIoDetachDevice = SfFastIoDetachDevice;
    fastIoDispatch->FastIoQueryNetworkOpenInfo = SfFastIoQueryNetworkOpenInfo;
    fastIoDispatch->MdlRead = SfFastIoMdlRead;
    fastIoDispatch->MdlReadComplete = SfFastIoMdlReadComplete;
    fastIoDispatch->PrepareMdlWrite = SfFastIoPrepareMdlWrite;
    fastIoDispatch->MdlWriteComplete = SfFastIoMdlWriteComplete;
    fastIoDispatch->FastIoReadCompressed = SfFastIoReadCompressed;
    fastIoDispatch->FastIoWriteCompressed = SfFastIoWriteCompressed;
    fastIoDispatch->MdlReadCompleteCompressed = SfFastIoMdlReadCompleteCompressed;
    fastIoDispatch->MdlWriteCompleteCompressed = SfFastIoMdlWriteCompleteCompressed;
    fastIoDispatch->FastIoQueryOpen = SfFastIoQueryOpen;
 
    /// 将设置好的一组fastIo回调函数注册到DriverObject->FastIoDispatch
    DriverObject->FastIoDispatch = fastIoDispatch;
 
    //
    //  VERSION NOTE:
    //
    //  There are 6 FastIO routines for which file system filters are bypassed as
    //  the requests are passed directly to the base file system.  These 6 routines
    //  are AcquireFileForNtCreateSection, ReleaseFileForNtCreateSection,
    //  AcquireForModWrite, ReleaseForModWrite, AcquireForCcFlush, and
    //  ReleaseForCcFlush.
    //
    //  In Windows XP and later, the FsFilter callbacks were introduced to allow
    //  filters to safely hook these operations.  See the IFS Kit documentation for
    //  more details on how these new interfaces work.
    //
    //  MULTIVERSION NOTE:
    //
    //  If built for Windows XP or later, this driver is built to run on
    //  multiple versions.  When this is the case, we will test
    //  for the presence of FsFilter callbacks registration API.  If we have it,
    //  then we will register for those callbacks, otherwise, we will not.
    //
 
#if WINVER >= 0x0501
 
    {
        FS_FILTER_CALLBACKS fsFilterCallbacks;
 
        if (NULL != gSfDynamicFunctions.RegisterFileSystemFilterCallbacks) {
            //
            //  Setup the callbacks for the operations we receive through
            //  the FsFilter interface.
            //
            //  NOTE:  You only need to register for those routines you really
            //         need to handle.  SFilter is registering for all routines
            //         simply to give an example of how it is done.
            //
 
            fsFilterCallbacks.SizeOfFsFilterCallbacks = sizeof(FS_FILTER_CALLBACKS);
            fsFilterCallbacks.PreAcquireForSectionSynchronization = SfPreFsFilterPassThrough;
            fsFilterCallbacks.PostAcquireForSectionSynchronization = SfPostFsFilterPassThrough;
            fsFilterCallbacks.PreReleaseForSectionSynchronization = SfPreFsFilterPassThrough;
            fsFilterCallbacks.PostReleaseForSectionSynchronization = SfPostFsFilterPassThrough;
            fsFilterCallbacks.PreAcquireForCcFlush = SfPreFsFilterPassThrough;
            fsFilterCallbacks.PostAcquireForCcFlush = SfPostFsFilterPassThrough;
            fsFilterCallbacks.PreReleaseForCcFlush = SfPreFsFilterPassThrough;
            fsFilterCallbacks.PostReleaseForCcFlush = SfPostFsFilterPassThrough;
            fsFilterCallbacks.PreAcquireForModifiedPageWriter = SfPreFsFilterPassThrough;
            fsFilterCallbacks.PostAcquireForModifiedPageWriter = SfPostFsFilterPassThrough;
            fsFilterCallbacks.PreReleaseForModifiedPageWriter = SfPreFsFilterPassThrough;
            fsFilterCallbacks.PostReleaseForModifiedPageWriter = SfPostFsFilterPassThrough;
 
            status = (gSfDynamicFunctions.RegisterFileSystemFilterCallbacks)(DriverObject,
                &fsFilterCallbacks);
 
            if (!NT_SUCCESS(status)) {
                DriverObject->FastIoDispatch = NULL;
                ExFreePoolWithTag(fastIoDispatch, SFLT_POOL_TAG_FASTIO);
                IoDeleteDevice(gSFilterControlDeviceObject);
                return status;
            }
        }
    }
#endif
 
    //
    //  The registered callback routine "SfFsNotification" will be called
    //  whenever a new file systems is loaded or when any file system is
    //  unloaded.
    //
    //  VERSION NOTE:
    //
    //  On Windows XP and later this will also enumerate all existing file
    //  systems (except the RAW file systems).  On Windows 2000 this does not
    //  enumerate the file systems that were loaded before this filter was
    //  loaded.
    //
    /// 注册SfFsNotification回调函数,生成过滤设备对象,绑定在文件系统设备对象上,监控移动设备,同时也会遍历固定的卷设备对象,同样生成过滤设备对象进行绑定
    /// 绑定文件系统设备,是卷设备绑定的前提,怎么理解呢?
    /// 比如,U盘插入电脑之后,系统的动作:
    /// 首先生成一个文件系统设备对象(FAT32或ntfs),然后系统向文件系统设备对象发送一个IRP请求(Mount),在Mount IRP(IRP_MJ_SYSTEM_CONTROL)请求里面才会为U盘生成一个卷设备对象,
    /// 所以必须先生成一个过滤设备对象绑定在文件系统设备对象上,才能够监控Mount IRP(IRP_MJ_SYSTEM_CONTROL)从而拿到为U盘生成的动态卷设备对象,后续SfFsControl才能生成一个过滤设备对象绑定在U盘的设备对象上
    status = IoRegisterFsRegistrationChange(DriverObject, SfFsNotification);
    if (!NT_SUCCESS(status)) {
        KdPrint(("SFilter!DriverEntry: Error registering FS change notification, status=%08x\n",
            status));
 
        DriverObject->FastIoDispatch = NULL;
        ExFreePoolWithTag(fastIoDispatch, SFLT_POOL_TAG_FASTIO);
        IoDeleteDevice(gSFilterControlDeviceObject);
        return status;
    }
 
    //
    //  Attempt to attach to the appropriate RAW file system device objects
    //  since they are not enumerated by IoRegisterFsRegistrationChange.
    //
 
    {
        PDEVICE_OBJECT rawDeviceObject;
        PFILE_OBJECT fileObject;
 
        //
        //  Attach to RawDisk device
        //
 
        RtlInitUnicodeString(&nameString, L"\\Device\\RawDisk");
 
        status = IoGetDeviceObjectPointer(
            &nameString,
            FILE_READ_ATTRIBUTES,
            &fileObject,
            &rawDeviceObject);
 
        if (NT_SUCCESS(status)) {
            SfFsNotification(rawDeviceObject, TRUE);
            ObDereferenceObject(fileObject);
        }
 
        //
        //  Attach to the RawCdRom device
        //
 
        RtlInitUnicodeString(&nameString, L"\\Device\\RawCdRom");
 
        status = IoGetDeviceObjectPointer(
            &nameString,
            FILE_READ_ATTRIBUTES,
            &fileObject,
            &rawDeviceObject);
 
        if (NT_SUCCESS(status)) {
            SfFsNotification(rawDeviceObject, TRUE);
            ObDereferenceObject(fileObject);
        }
    }
 
    //
    //  Clear the initializing flag on the control device object since we
    //  have now successfully initialized everything.
    //
 
    ClearFlag(gSFilterControlDeviceObject->Flags, DO_DEVICE_INITIALIZING);
    DbgPrint("Sfilter installed\n");
 
    return STATUS_SUCCESS;
}
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
NTSTATUS
SfPassThrough(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
)
 
/*++
 
Routine Description:
 
    This routine is the main dispatch routine for the general purpose file
    system driver.  It simply passes requests onto the next driver in the
    stack, which is presumably a disk file system.
 
Arguments:
 
    DeviceObject - Pointer to the device object for this driver.
 
    IRP - Pointer to the request packet representing the I/O request.
 
Return Value:
 
    The function value is the status of the operation.
 
Note:
 
    A note to file system filter implementers:
        This routine actually "passes" through the request by taking this
        driver out of the IRP stack.  If the driver would like to pass the
        I/O request through, but then also see the result, then rather than
        taking itself out of the loop it could keep itself in by copying the
        caller's parameters to the next stack location and then set its own
        completion routine.
 
        Hence, instead of calling:
 
            IoSkipCurrentIrpStackLocation( Irp );
 
        You could instead call:
 
            IoCopyCurrentIrpStackLocationToNext( Irp );
            IoSetCompletionRoutine( Irp, NULL, NULL, FALSE, FALSE, FALSE );
 
        This example actually NULLs out the caller's I/O completion routine, but
        this driver could set its own completion routine so that it would be
        notified when the request was completed (see SfCreate for an example of
        this).
 
--*/
 
{
    PIO_STACK_LOCATION pIrp = IoGetCurrentIrpStackLocation(Irp);
 
    //
    //  Sfilter doesn't allow handles to its control device object to be
    //  created, therefore, no other operation should be able to come through.
    //
 
    ASSERT(!IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)); ///< 不能是控制设备对象
 
    ASSERT(IS_MY_DEVICE_OBJECT(DeviceObject)); ///< 必须是过滤设备对象
 
    //
    //  File systems should NEVER receive a power IRP
    //
 
    ASSERT(pIrp->MajorFunction != IRP_MJ_POWER);
 
    //
    //  Get this driver out of the driver stack and get to the next driver as
    //  quickly as possible.
    //
 
    IoSkipCurrentIrpStackLocation(Irp); ///< 忽略当前的IRP
 
    //
    //  Call the appropriate file system driver with the request.
    //
 
    return IoCallDriver(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
        Irp);
}
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
VOID
SfFsNotification(
    IN PDEVICE_OBJECT DeviceObject, ///<文件系统设备对象
    IN BOOLEAN FsActive  ///< 如果为ture表示mount,如何为false表示unmount
)
 
/*++
 
Routine Description:
 
    This routine is invoked whenever a file system has either registered or
    unregistered itself as an active file system.
 
    For the former case, this routine creates a device object and attaches it
    to the specified file system's device object.  This allows this driver
    to filter all requests to that file system.  Specifically we are looking
    for MOUNT requests so we can attach to newly mounted volumes.
 
    For the latter case, this file system's device object is located,
    detached, and deleted.  This removes this file system as a filter for
    the specified file system.
 
Arguments:
 
    DeviceObject - Pointer to the file system's device object.
 
    FsActive - Boolean indicating whether the file system has registered
        (TRUE) or unregistered (FALSE) itself as an active file system.
 
Return Value:
 
    None.
 
--*/
 
{
    PNAME_CONTROL devName;
 
    PAGED_CODE();
 
    //
    //  Display the names of all the file system we are notified of
    //
 
    devName = NLGetAndAllocateObjectName(DeviceObject,
        &gSfNameBufferLookasideList);
 
    if (devName == NULL) {
        SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
            ("SFilter!SfFsNotification:                    Not attaching to %p, insufficient resources.\n",
                DeviceObject));
        return;
    }
 
    SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
        ("SFilter!SfFsNotification:                    %s   %p \"%wZ\" (%s)\n",
            (FsActive) ? "Activating file system  " : "Deactivating file system",
            DeviceObject,
            &devName->Name,
            GET_DEVICE_TYPE_NAME(DeviceObject->DeviceType)));
 
    //
    //  Handle attaching/detaching from the given file system.
    //
 
    if (FsActive) {
        SfAttachToFileSystemDevice(DeviceObject, devName);
    }
    else {
        SfDetachFromFileSystemDevice(DeviceObject);
    }
 
    //
    //  We're done with name (SfAttachToFileSystemDevice copies the name to
    //  the device extension) so free it.
    //
 
    NLFreeNameControl(devName, &gSfNameBufferLookasideList);
}
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
NTSTATUS
SfAttachToFileSystemDevice(
    IN PDEVICE_OBJECT DeviceObject,
    IN PNAME_CONTROL DeviceName
)
 
/*++
 
Routine Description:
 
    This will attach to the given file system device object.  We attach to
    these devices so we will know when new volumes are mounted.
 
Arguments:
 
    DeviceObject - The File System CDO to attach to
 
    Name - An already initialized name control used to retrieve names.
           This is passed in to reduce the number of strings buffers on
           the stack.
 
Return Value:
 
    Status of the operation
 
--*/
 
{
    PDEVICE_OBJECT newDeviceObject;
    PSFILTER_DEVICE_EXTENSION devExt;
    UNICODE_STRING fsrecName;
    NTSTATUS status;
    PNAME_CONTROL fsName;
 
    PAGED_CODE();
 
    //
    //  See if this is a file system type we care about.  If not, return.
    //
 
    if (!IS_DESIRED_DEVICE_TYPE(DeviceObject->DeviceType)) {
        return STATUS_SUCCESS;
    }
 
    //
    //  See if we should attach to the standard file system recognizer device
    //  or not
    //
 
    if (!FlagOn(SfDebug, SFDEBUG_ATTACH_TO_FSRECOGNIZER)) {
        //
        //  See if this is one of the standard Microsoft file system recognizer
        //  devices (see if this device is in the FS_REC driver).  If so skip
        //  it.  We no longer attach to file system recognizer devices, we
        //  simply wait for the real file system driver to load.
        //
 
        RtlInitUnicodeString(&fsrecName, L"\\FileSystem\\Fs_Rec");
 
        fsName = NLGetAndAllocateObjectName(DeviceObject->DriverObject,
            &gSfNameBufferLookasideList);
 
        if (fsName == NULL) {
            SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
                ("SFilter!SfAttachToFileSystemDevice:          Error retrieving name, may attach to FS recognizer, status=%08x\n",
                    STATUS_INSUFFICIENT_RESOURCES));
        }
        else if (RtlCompareUnicodeString(&fsName->Name,
            &fsrecName, TRUE) == 0) {
            //
            //  If it is a recognizer, don't attach
            //
 
            NLFreeNameControl(fsName, &gSfNameBufferLookasideList);
            return STATUS_SUCCESS;
        }
 
        NLFreeNameControl(fsName, &gSfNameBufferLookasideList);
    }
 
    //
    //  We want to attach to this file system.  Create a new device object we
    //  can attach with.
    //
    /// 生成一个过滤设备对象
    status = IoCreateDevice(gSFilterDriverObject,
        sizeof(SFILTER_DEVICE_EXTENSION),
        NULL,
        DeviceObject->DeviceType,
        0,
        FALSE,
        &newDeviceObject);
 
    if (!NT_SUCCESS(status)) {
        return status;
    }
 
    //
    //  Propagate flags from Device Object we are trying to attach to.
    //  Note that we do this before the actual attachment to make sure
    //  the flags are properly set once we are attached (since an IRP
    //  can come in immediately after attachment but before the flags would
    //  be set).
    //
    /// 过滤设备对象的通信方式与目标设备对象保持一致
    if (FlagOn(DeviceObject->Flags, DO_BUFFERED_IO)) {
        SetFlag(newDeviceObject->Flags, DO_BUFFERED_IO);
    }
 
    if (FlagOn(DeviceObject->Flags, DO_DIRECT_IO)) {
        SetFlag(newDeviceObject->Flags, DO_DIRECT_IO);
    }
 
    if (FlagOn(DeviceObject->Characteristics, FILE_DEVICE_SECURE_OPEN)) {
        SetFlag(newDeviceObject->Characteristics, FILE_DEVICE_SECURE_OPEN);
    }
 
    //
    //  Initialize the device extension.
    //
 
    devExt = newDeviceObject->DeviceExtension;
 
    devExt->Flags = 0;
    NLInitDeviceExtensionHeader(&devExt->NLExtHeader,
        newDeviceObject,
        NULL);
 
    //
    //  Set the name.  We allocate from non-paged pool so this memory is always
    //  available for debugging (never gets paged out).
    //
 
    status = NLAllocateAndCopyUnicodeString(&devExt->NLExtHeader.DeviceName,
        &DeviceName->Name,
        SFLT_POOL_TAG_DEVNAME);
 
    if (!NT_SUCCESS(status)) {
        goto ErrorCleanupDevice;
    }
 
    //
    //  Do the attachment.
    //
    /// 把过滤设备对象绑定到文件系统设备对象上,拿到mount操作,才能拿到新生成卷设备对象
    status = SfAttachDeviceToDeviceStack(newDeviceObject,
        DeviceObject,
        &devExt->NLExtHeader.AttachedToDeviceObject);
 
    if (!NT_SUCCESS(status)) {
        goto ErrorCleanupDevice;
    }
 
    //
    //  Mark we are done initializing
    //
    /// 在DriverEntry创建的设备对象,初始化标志不用管,系统会帮忙清理掉
    /// 在在DriverEntry之外创建的设备对象,初始化标志必须由程序员自己清理掉
    ClearFlag(newDeviceObject->Flags, DO_DEVICE_INITIALIZING);
 
    //
    //  Display who we have attached to
    //
 
    SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
        ("SFilter!SfAttachToFileSystemDevice:          Attaching %p to file system %p \"%wZ\" (%s)\n",
            newDeviceObject,
            DeviceObject,
            &devExt->NLExtHeader.DeviceName,
            GET_DEVICE_TYPE_NAME(newDeviceObject->DeviceType)));
 
    //
    //  VERSION NOTE:
    //
    //  In Windows XP, the IO Manager provided APIs to safely enumerate all the
    //  device objects for a given driver.  This allows filters to attach to
    //  all mounted volumes for a given file system at some time after the
    //  volume has been mounted.  There is no support for this functionality
    //  in Windows 2000.
    //
    //  MULTIVERSION NOTE:
    //
    //  If built for Windows XP or later, this driver is built to run on
    //  multiple versions.  When this is the case, we will test
    //  for the presence of the new IO Manager routines that allow for volume
    //  enumeration.  If they are not present, we will not enumerate the volumes
    //  when we attach to a new file system.
    //
 
#if WINVER >= 0x0501
 
    if (IS_WINDOWSXP_OR_LATER()) {
        ASSERT(NULL != gSfDynamicFunctions.EnumerateDeviceObjectList &&
            NULL != gSfDynamicFunctions.GetDiskDeviceObject &&
            NULL != gSfDynamicFunctions.GetDeviceAttachmentBaseRef &&
            NULL != gSfDynamicFunctions.GetLowerDeviceObject);
 
        //
        //  Enumerate all the mounted devices that currently
        //  exist for this file system and attach to them.
        //
 
        status = SfEnumerateFileSystemVolumes(DeviceObject);
 
        if (!NT_SUCCESS(status)) {
            IoDetachDevice(devExt->NLExtHeader.AttachedToDeviceObject);
            goto ErrorCleanupDevice;
        }
    }
 
#endif
 
    return STATUS_SUCCESS;
 
    /////////////////////////////////////////////////////////////////////
    //                  Cleanup error handling
    /////////////////////////////////////////////////////////////////////
 
ErrorCleanupDevice:
    SfCleanupMountedDevice(newDeviceObject);
 
    IoDeleteDevice(newDeviceObject);
 
    return status;
}
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
NTSTATUS
SfAttachDeviceToDeviceStack(
    IN PDEVICE_OBJECT SourceDevice,
    IN PDEVICE_OBJECT TargetDevice,
    IN OUT PDEVICE_OBJECT* AttachedToDeviceObject
)
 
/*++
 
Routine Description:
 
    This routine attaches the SourceDevice to the TargetDevice's stack and
    returns the device object SourceDevice was directly attached to in
    AttachedToDeviceObject.  Note that the SourceDevice does not necessarily
    get attached directly to TargetDevice.  The SourceDevice will get attached
    to the top of the stack of which TargetDevice is a member.
 
    VERSION NOTE:
 
    In Windows XP, a new API was introduced to close a rare timing window that
    can cause IOs to start being sent to a device before its
    AttachedToDeviceObject is set in its device extension.  This is possible
    if a filter is attaching to a device stack while the system is actively
    processing IOs.  The new API closes this timing window by setting the
    device extension field that holds the AttachedToDeviceObject while holding
    the IO Manager's lock that protects the device stack.
 
    A sufficient work around for earlier versions of the OS is to set the
    AttachedToDeviceObject to the device object that the SourceDevice is most
    likely to attach to.  While it is possible that another filter will attach
    in between the SourceDevice and TargetDevice, this will prevent the
    system from bug checking if the SourceDevice receives IOs before the
    AttachedToDeviceObject is correctly set.
 
    For a driver built in the Windows 2000 build environment, we will always
    use the work-around code to attach.  For a driver that is built in the
    Windows XP or later build environments (therefore you are building a
    multiversion driver), we will determine which method of attachment to use
    based on which APIs are available.
 
Arguments:
 
    SourceDevice - The device object to be attached to the stack.
 
    TargetDevice - The device that we currently think is the top of the stack
        to which SourceDevice should be attached.
 
    AttachedToDeviceObject - This is set to the device object to which
        SourceDevice is attached if the attach is successful.
 
Return Value:
 
    Return STATUS_SUCCESS if the device is successfully attached.  If
    TargetDevice represents a stack to which devices can no longer be attached,
    STATUS_NO_SUCH_DEVICE is returned.
 
--*/
 
{
    PAGED_CODE();
    /// 如果是XP及以上系统
#if WINVER >= 0x0501
 
    if (IS_WINDOWSXP_OR_LATER()) {
        ASSERT(NULL != gSfDynamicFunctions.AttachDeviceToDeviceStackSafe);
        return (gSfDynamicFunctions.AttachDeviceToDeviceStackSafe)(SourceDevice,
            TargetDevice,
            AttachedToDeviceObject); ///< 把原设备绑定到目标设备上
    }
    else {
        ASSERT(NULL == gSfDynamicFunctions.AttachDeviceToDeviceStackSafe);
#endif
 
        * AttachedToDeviceObject = TargetDevice;
        *AttachedToDeviceObject = IoAttachDeviceToDeviceStack(SourceDevice,
            TargetDevice); ///< 如果不是则使用IoAttachDeviceToDeviceStack来绑定
 
        if (*AttachedToDeviceObject == NULL) {
            return STATUS_NO_SUCH_DEVICE;
        }
 
        return STATUS_SUCCESS;
 
#if WINVER >= 0x0501
    }
#endif
}
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
NTSTATUS
SfFsControl(
    IN PDEVICE_OBJECT DeviceObject,  ///< 文件系统设备对象
    IN PIRP Irp ///< Mount IRP
)
 
/*++
 
Routine Description:
 
    This routine is invoked whenever an I/O Request Packet (IRP) w/a major
    function code of IRP_MJ_FILE_SYSTEM_CONTROL is encountered.  For most
    IRPs of this type, the packet is simply passed through.  However, for
    some requests, special processing is required.
 
Arguments:
 
    DeviceObject - Pointer to the device object for this driver.
 
    Irp - Pointer to the request packet representing the I/O request.
 
Return Value:
 
    The function value is the status of the operation.
 
--*/
 
{
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
 
    PAGED_CODE();
 
    //
    //  Sfilter doesn't allow handles to its control device object to be
    //  created, therefore, no other operation should be able to come through.
    //
 
    ASSERT(!IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject));
 
    ASSERT(IS_MY_DEVICE_OBJECT(DeviceObject));
 
    //
    //  Process the minor function code.
    //
 
    switch (irpSp->MinorFunction) {
    case IRP_MN_MOUNT_VOLUME:
 
        return SfFsControlMountVolume(DeviceObject, Irp); ///< 拿到卷设备对象进行绑定
 
    case IRP_MN_LOAD_FILE_SYSTEM:
 
        return SfFsControlLoadFileSystem(DeviceObject, Irp);
 
    case IRP_MN_USER_FS_REQUEST:
    {
        switch (irpSp->Parameters.FileSystemControl.FsControlCode) {
        case FSCTL_DISMOUNT_VOLUME:
        {
            PSFILTER_DEVICE_EXTENSION devExt = DeviceObject->DeviceExtension;
 
            SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
                ("SFilter!SfFsControl:                         Dismounting volume %p \"%wZ\"\n",
                    devExt->NLExtHeader.AttachedToDeviceObject,
                    &devExt->NLExtHeader.DeviceName));
            break;
        }
        }
        break;
    }
    }
 
    //
    //  Pass all other file system control requests through.
    //
 
    IoSkipCurrentIrpStackLocation(Irp);
    return IoCallDriver(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
        Irp);
}
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
NTSTATUS
SfFsControlMountVolumeComplete(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PDEVICE_OBJECT NewDeviceObject
)
/*++
 
Routine Description:
 
    This does the post-Mount work and must be done at PASSIVE_LEVEL.
 
Arguments:
 
    DeviceObject - The device object for this operation,
 
    Irp - The IRP for this operation that we will complete once we are finished
        with it.
 
Return Value:
 
    Returns the status of the mount operation.
 
--*/
{
    PVPB vpb;
    PSFILTER_DEVICE_EXTENSION newDevExt;
    PIO_STACK_LOCATION irpSp;
    PDEVICE_OBJECT attachedDeviceObject;
    NTSTATUS status;
    BOOLEAN justAttached = FALSE;
 
    PAGED_CODE();
 
    newDevExt = NewDeviceObject->DeviceExtension;
    irpSp = IoGetCurrentIrpStackLocation(Irp);
 
    //
    //  Get the correct VPB from the real device object saved in our
    //  device extension.  We do this because the VPB in the IRP stack
    //  may not be the correct VPB when we get here.  The underlying
    //  file system may change VPBs if it detects a volume it has
    //  mounted previously.
    //
 
    vpb = newDevExt->NLExtHeader.StorageStackDeviceObject->Vpb;
 
    //
    //  Display a message when we detect that the VPB for the given
    //  device object has changed.
    //
 
    if (vpb != irpSp->Parameters.MountVolume.Vpb) {
        SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
            ("SFilter!SfFsControlMountVolume:              VPB in IRP stack changed   %p IRPVPB=%p VPB=%p\n",
                vpb->DeviceObject,
                irpSp->Parameters.MountVolume.Vpb,
                vpb));
    }
 
    //
    //  See if the mount was successful.
    //
 
    if (NT_SUCCESS(Irp->IoStatus.Status)) {
        SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
            ("SFilter!SfFsControlMountVolume:              Mount volume success for          %p \"%wZ\", status=%08x\n",
                DeviceObject,
                &newDevExt->NLExtHeader.DeviceName,
                Irp->IoStatus.Status));
 
        //
        //  Acquire lock so we can atomically test if we area already attached
        //  and if not, then attach.  This prevents a double attach race
        //  condition.
        //
 
        ExAcquireFastMutex(&gSfilterAttachLock);
 
        //
        //  The mount succeeded.  If we are not already attached, attach to the
        //  device object.  Note: one reason we could already be attached is
        //  if the underlying file system revived a previous mount.
        //
 
        if (!SfIsAttachedToDevice(vpb->DeviceObject, &attachedDeviceObject)) {
            //
            //  Attach to the new mounted volume.  The file system device
            //  object that was just mounted is pointed to by the VPB.
            //
            /// 绑定
            status = SfAttachToMountedDevice(vpb->DeviceObject, ///< 生成的卷设备对象
                NewDeviceObject); ///< 新的过滤设备对象
 
            if (NT_SUCCESS(status)) {
                justAttached = TRUE;
 
                SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
                    ("SFilter!SfFsControlMountVolume:              Mount volume - attached successfully to      %p \"%wZ\", status=%08x\n",
                        DeviceObject,
                        &newDevExt->NLExtHeader.DeviceName,
                        Irp->IoStatus.Status));
            }
            else {
                //
                //  The attachment failed, cleanup.  Since we are in the
                //  post-mount phase, we can not fail this operation.
                //  We simply won't be attached.  The only reason this should
                //  ever happen at this point is if somebody already started
                //  dismounting the volume therefore not attaching should
                //  not be a problem.
                //
 
                SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
                    ("SFilter!SfFsControlMountVolume:              Mount volume - attached to volume failed     %p \"%wZ\", status=%08x\n",
                        DeviceObject,
                        &newDevExt->NLExtHeader.DeviceName,
                        Irp->IoStatus.Status));
 
                SfCleanupMountedDevice(NewDeviceObject);
 
                IoDeleteDevice(NewDeviceObject);
            }
 
            ASSERT(NULL == attachedDeviceObject);
        }
        else {
            //
            //  We were already attached, handle it
            //
 
            SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
                ("SFilter!SfFsControlMountVolume               Mount volume - already attached to   %p \"%wZ\"\n",
                    ((PSFILTER_DEVICE_EXTENSION)attachedDeviceObject->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
                    &newDevExt->NLExtHeader.DeviceName));
 
            //
            //  Cleanup and delete the device object we created
            //
 
            SfCleanupMountedDevice(NewDeviceObject);
 
            IoDeleteDevice(NewDeviceObject);
 
            //
            //  Dereference the returned attached device object
            //
 
            ObDereferenceObject(attachedDeviceObject);
        }
 
        //
        //  Release the lock
        //
 
        ExReleaseFastMutex(&gSfilterAttachLock);
 
        //
        //  If we just successfully attached to the device and the appropriate
        //  debug flag is set, then get the DOS device name.  We couldn't
        //  do this above because a mutex was held.
        //
 
        if (justAttached && FlagOn(SfDebug, SFDEBUG_GET_DOS_NAMES) &&
            newDevExt->NLExtHeader.StorageStackDeviceObject != NULL) {
            NLGetDosDeviceName(NewDeviceObject,
                &newDevExt->NLExtHeader);
        }
    }
    else {
        //
        //  The mount request failed, handle it.
        //
 
        SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
            ("SFilter!SfFsControlMountVolume:              Mount volume failure for          %p \"%wZ\", status=%08x\n",
                DeviceObject,
                &newDevExt->NLExtHeader.DeviceName,
                Irp->IoStatus.Status));
 
        //
        //  Cleanup and delete the device object we created
        //
 
        SfCleanupMountedDevice(NewDeviceObject);
 
        IoDeleteDevice(NewDeviceObject);
    }
 
    //
    //  Complete the request.
    //  NOTE:  We must save the status before completing because after
    //         completing the IRP we can not longer access it (it might be
    //         freed).
    //
 
    status = Irp->IoStatus.Status;
 
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
 
    return status;
}
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
NTSTATUS
SfAttachToMountedDevice(
    IN PDEVICE_OBJECT DeviceObject,
    IN PDEVICE_OBJECT SFilterDeviceObject
)
 
/*++
 
Routine Description:
 
    This will attach to a DeviceObject that represents a mounted volume.
 
Arguments:
 
    DeviceObject - The device to attach to
 
    SFilterDeviceObject - Our device object we are going to attach
 
Return Value:
 
    Status of the operation
 
--*/
 
{
    PSFILTER_DEVICE_EXTENSION newDevExt = SFilterDeviceObject->DeviceExtension;
    NTSTATUS status;
    ULONG i;
 
    PAGED_CODE();
    ASSERT(IS_MY_DEVICE_OBJECT(SFilterDeviceObject));
#if WINVER >= 0x0501
    ASSERT(!SfIsAttachedToDevice(DeviceObject, NULL));
#endif
 
    //
    //  Propagate flags from Device Object we are trying to attach to.
    //  Note that we do this before the actual attachment to make sure
    //  the flags are properly set once we are attached (since an IRP
    //  can come in immediately after attachment but before the flags would
    //  be set).
    //
 
    if (FlagOn(DeviceObject->Flags, DO_BUFFERED_IO)) {
        SetFlag(SFilterDeviceObject->Flags, DO_BUFFERED_IO);
    }
 
    if (FlagOn(DeviceObject->Flags, DO_DIRECT_IO)) {
        SetFlag(SFilterDeviceObject->Flags, DO_DIRECT_IO);
    }
 
    ASSERT(newDevExt->NLExtHeader.ThisDeviceObject == SFilterDeviceObject);
 
    //
    //  It is possible for this attachment request to fail because this device
    //  object has not finished initializing.  This can occur if this filter
    //  loaded just as this volume was being mounted.
    //
 
    /// 函数不太可靠,所以循环了8次进行绑定
    for (i = 0; i < 8; i++) {
        LARGE_INTEGER interval;
 
        //
        //  Attach our device object to the given device object
        //  The only reason this can fail is if someone is trying to dismount
        //  this volume while we are attaching to it.
        //
 
        status = SfAttachDeviceToDeviceStack(SFilterDeviceObject,
            DeviceObject,
            &newDevExt->NLExtHeader.AttachedToDeviceObject);
        if (NT_SUCCESS(status)) {
            //
            //  Finished all initialization of the new device object,  so clear
            //  the initializing flag now.  This allows other filters to now
            //  attach to our device object.
            //
 
            ClearFlag(SFilterDeviceObject->Flags, DO_DEVICE_INITIALIZING);
 
            //
            //  Display the name
            //
 
            SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES,
                ("SFilter!SfAttachToMountedDevice:             Attaching %p to volume      %p \"%wZ\"\n",
                    SFilterDeviceObject,
                    newDevExt->NLExtHeader.AttachedToDeviceObject,
                    &newDevExt->NLExtHeader.DeviceName));
 
            return STATUS_SUCCESS;
        }
 
        //
        //  Delay, giving the device object a chance to finish its
        //  initialization so we can try again
        //
 
        interval.QuadPart = (500 * DELAY_ONE_MILLISECOND);
        KeDelayExecutionThread(KernelMode, FALSE, &interval);
    }
 
    return status;
}
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
NTSTATUS
SfFsControlCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
)
 
/*++
 
Routine Description:
 
    This routine is invoked for the completion of an FsControl request.  It
    signals an event used to re-sync back to the dispatch routine.
 
Arguments:
 
    DeviceObject - Pointer to this driver's device object that was attached to
            the file system device object
 
    Irp - Pointer to the IRP that was just completed.
 
    Context - Pointer to the event to signal
 
--*/
 
{
    UNREFERENCED_PARAMETER(DeviceObject);
    UNREFERENCED_PARAMETER(Irp);
 
    ASSERT(IS_MY_DEVICE_OBJECT(DeviceObject));
    ASSERT(Context != NULL);
    /// XP及以后版本
#if WINVER >= 0x0501
    if (IS_WINDOWSXP_OR_LATER()) {
        //
        //  On Windows XP or later, the context passed in will be an event
        //  to signal.
        //
        /// 只需要对事件进行设置即可
        KeSetEvent((PKEVENT)Context, IO_NO_INCREMENT, FALSE);
    }
    else {
#endif
        //
        //  For Windows 2000, if we are not at passive level, we should
        //  queue this work to a worker thread using the workitem that is in
        //  Context.
        //
 
        if (KeGetCurrentIrql() > PASSIVE_LEVEL) {
            //
            //  We are not at passive level, but we need to be to do our work,
            //  so queue off to the worker thread.
            //
            /// 把绑定函数放在工作者线程中执行
            ExQueueWorkItem((PWORK_QUEUE_ITEM)Context,
                DelayedWorkQueue);
        }
        else {
            PWORK_QUEUE_ITEM workItem = Context;
 
            //
            //  We are already at passive level, so we will just call our
            //  worker routine directly.
            //
            /// 直接在这里完成绑定
            (workItem->WorkerRoutine)(workItem->Parameter);
        }
 
#if WINVER >= 0x0501
    }
#endif
 
    return STATUS_MORE_PROCESSING_REQUIRED;
}
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
NTSTATUS
SfCreate(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
)
 
/*++
 
Routine Description:
 
    This function filters create/open operations.  It simply establishes an
    I/O completion routine to be invoked if the operation was successful.
 
Arguments:
 
    DeviceObject - Pointer to the target device object of the create/open.
 
    Irp - Pointer to the I/O Request Packet that represents the operation.
 
Return Value:
 
    The function value is the status of the call to the file system's entry
    point.
 
--*/
 
{
    NTSTATUS status;
    PNAME_CONTROL fileName = NULL;
    PSFILTER_DEVICE_EXTENSION devExt = (PSFILTER_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
    BOOLEAN cacheName;
 
    PAGED_CODE();
 
    //
    //  If this is for our control device object, don't allow it to be opened.
    //
 
    if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {
        //
        //  Sfilter doesn't allow for any communication through its control
        //  device object, therefore it fails all requests to open a handle
        //  to its control device object.
        //
        //  See the FileSpy sample for an example of how to allow creates to
        //  the filter's control device object and manage communication via
        //  that handle.
        //
 
        //Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
        Irp->IoStatus.Status = STATUS_SUCCESS;
 
        Irp->IoStatus.Information = 0;
 
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
 
        //return STATUS_INVALID_DEVICE_REQUEST;
        return STATUS_SUCCESS; ///< 控制设备对象,说明客户端正在加载驱动,返回STATUS_SUCCESS,客户端就可打开驱动设备对象
    }
 
    ASSERT(IS_MY_DEVICE_OBJECT(DeviceObject)); /// 断言表示必须为真,不然就蓝屏
 
    //
    //  If debugging is enabled, do the processing required to see the packet
    //  upon its completion.  Otherwise, let the request go with no further
    //  processing.
    //
 
    if (!FlagOn(SfDebug, SFDEBUG_DO_CREATE_COMPLETION | ///< SfDebug一般是从注册表中读出来的,这里硬编码了
        SFDEBUG_GET_CREATE_NAMES |
        SFDEBUG_DISPLAY_CREATE_NAMES)) {
        //
        //  We don't want to get filenames, display filenames, or
        //  call our completion routine.  Don't put us on the stack
        //  and call the next driver.
        //
 
        IoSkipCurrentIrpStackLocation(Irp);
 
        return IoCallDriver(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
            Irp);
    }
 
    if (FlagOn(SfDebug, SFDEBUG_GET_CREATE_NAMES |
        SFDEBUG_DISPLAY_CREATE_NAMES) &&
        !FlagOn(devExt->Flags, SFDEVFL_DISABLE_VOLUME)) {
        //
        //  Debugging specifies that we need to get the filename
        //
 
        NAME_LOOKUP_FLAGS LookupFlags = 0x00000000;
 
        //
        //  If DosName has been set, indicate via flags that we
        //  want to use it when getting the full file name.
        //
 
        if (devExt->NLExtHeader.DosName.Length != 0) {
            SetFlag(LookupFlags, NLFL_USE_DOS_DEVICE_NAME);
        }
 
        //
        //  Indicate we are in pre-create
        //
 
        SetFlag(LookupFlags, NLFL_IN_CREATE);
 
        if (FlagOn(irpSp->Parameters.Create.Options, FILE_OPEN_BY_FILE_ID)) {
            //
            //  The file is being opened by ID, not file name.
            //
 
            SetFlag(LookupFlags, NLFL_OPEN_BY_ID);
        }
 
        if (FlagOn(irpSp->Flags, SL_OPEN_TARGET_DIRECTORY)) {
            //
            //  The file's parent directory should be opened
            //
 
            SetFlag(LookupFlags, NLFL_OPEN_TARGET_DIR);
        }
 
        //
        //  Retrieve the file name.  Note that in SFilter we don't do any name
        //  caching.
        //
        /// 为拿到文件的名字,先分配一个结构
        status = NLAllocateNameControl(&fileName, &gSfNameBufferLookasideList);
 
        if (NT_SUCCESS(status)) {
            //
            //  We are okay not checking the return value here because
            //  the GetFullPathName function will set the Unicode String
            //  length to 0. So either way, in an error it will print an empty string
            //
            /// 拿到文件的全路径
            status = NLGetFullPathName(irpSp->FileObject,
                fileName,
                &devExt->NLExtHeader,
                LookupFlags,
                &gSfNameBufferLookasideList,
                &cacheName);
        }
    }
 
    if (FlagOn(SfDebug, SFDEBUG_DISPLAY_CREATE_NAMES |
        SFDEBUG_DO_CREATE_COMPLETION) &&
        !FlagOn(devExt->Flags, SFDEVFL_DISABLE_VOLUME)) {
        //
        //  Debugging flags indicate we must do completion.
        //  Note that to display file names we must do completion
        //  because we don't know IoStatus.Status and IoStatus.Information
        //  until post-create.
        //
 
        KEVENT waitEvent;
 
        //
        //  Initialize an event to wait for the completion routine to occur
        //
 
        KeInitializeEvent(&waitEvent, NotificationEvent, FALSE);
 
        //
        //  Copy the stack and set our Completion routine
        //
 
        IoCopyCurrentIrpStackLocationToNext(Irp);
 
        IoSetCompletionRoutine(
            Irp,
            SfCreateCompletion,
            &waitEvent,
            TRUE,
            TRUE,
            TRUE);
 
        //
        //  Call the next driver in the stack.
        //
 
        status = IoCallDriver(devExt->NLExtHeader.AttachedToDeviceObject, Irp);
 
        //
        //  Wait for the completion routine to be called
        //
 
        if (STATUS_PENDING == status) {
            NTSTATUS localStatus = KeWaitForSingleObject(&waitEvent,
                Executive,
                KernelMode,
                FALSE,
                NULL);
            ASSERT(STATUS_SUCCESS == localStatus);
        }
 
        //
        //  Verify the IoCompleteRequest was called
        //
 
        ASSERT(KeReadStateEvent(&waitEvent) ||
            !NT_SUCCESS(Irp->IoStatus.Status));
 
        //
        //  If debugging indicates we should display file names, do it.
        //
        /// 打印文件的名字
        if (irpSp->Parameters.Create.Options & FILE_OPEN_BY_FILE_ID) {
            SF_LOG_PRINT(SFDEBUG_DISPLAY_CREATE_NAMES,
                ("SFilter!SfCreate: OPENED        fo=%p %08x:%08x   %wZ (FID)\n",
                    irpSp->FileObject,
                    Irp->IoStatus.Status,
                    Irp->IoStatus.Information,
                    &fileName->Name));
        }
        else {
            SF_LOG_PRINT(SFDEBUG_DISPLAY_CREATE_NAMES,
                ("SFilter!SfCreate: OPENED        fo=%p st=%08x:%08x   %wZ\n",
                    irpSp->FileObject,
                    Irp->IoStatus.Status,
                    Irp->IoStatus.Information,
                    &fileName->Name));
        }
 
        //
        //  Release the name control structure if we have
        //
 
        if (fileName != NULL) {
            NLFreeNameControl(fileName, &gSfNameBufferLookasideList);
        }
 
        //
        //  Save the status and continue processing the IRP
        //
 
        status = Irp->IoStatus.Status;
 
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
 
        return status;
    }
    else {
        //
        //  Free the name control if we have one
        //
 
        if (fileName != NULL) {
            NLFreeNameControl(fileName, &gSfNameBufferLookasideList);
        }
 
        //
        //  Debugging flags indicate we did not want to display the file name
        //  or call completion routine.
        //  (ie SFDEBUG_GET_CREATE_NAMES && !SFDEBUG_DO_CREATE_COMPLETION)
        //
 
        IoSkipCurrentIrpStackLocation(Irp);
 
        return IoCallDriver(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
            Irp);
    }
}

过滤驱动与IRP处理方式

  • 1.IoCopyxxX+完成例程下发
    • IoCopyCurrentlrpStackLocationToNext+完成例程:把IRP下发之后,上层驱动在某个事件上进行等待。下一层驱动把IRP完成之后就会调用完成例程设置事件为有事件状态来通知上层驱动
1
2
3
4
5
6
7
8
9
10
/// 拿到当前IRP的栈
/// 拿到下一层IRP的栈
/// 把当前IRP的栈拷贝到下一层IRP的栈上,因为当前IRP还没下发,所以下一层IRP的栈是空的,所以不存在下层IRP栈原来有数据被覆盖的问题
#define IoCopycurrentIrpStackLocationToNext (Irp){\
    PIO_STACK_LOCATION __irpSp;\
    PI0_STACK_LOCATION __nextIrpSp;\
    __irpsp = IoGetcurrentIrpstackLocation((Irp));\
    __nextIrpSp =IoGetNextIrpstackLocation((Irp));\
    RtlCopyMemory( __nextIrpSp,__irpSp,FIELD__OFFSET(IO_STACK_IOCATIONCompletionRoutine));\
    nextIrpSp->control = O;}
  • 2.IoSkip+IoCall直接下发
    • IoSkipCurrentIrpStackLocation,下层设备拿到的IO_STACKLOCATION和当前的一样
1
2
3
4
5
6
7
8
9
#define IoskipcurrentIrpStackLocation(Irp){\
    (Irp)->currentLocation++; \
    ...
    /// 相互抵消,相当于IRP栈没有发生变化,下一层驱动对应的IRP栈和当前驱动用的是同一个栈,好像IRP从来没有到达过当前的驱动层一样
    /// 为什么不用像第一种方式一样需要拷贝栈数据?是因为当前驱动不需要知道下一层的IRP结果,就不需要保存当前驱动对应IRP栈的的空间了
    /// 为什么不直接不加不减?因为下发必然会减(IoCallDriver),为保持下一层使用当前驱动对应的IRP栈,所以需要先加
 
    IoskipcurrentIrpstackLocation(Irp);//location+1
    IoCallDriver(deviceExtension->nextLower,Irp);//location-1
  • 3.结束IRP不下发
1
2
3
4
5
/// 一般用于拒绝的时候
PIO_STACK_LOCATION irpStack= IoGetCurrentlrpStackLocation(Irp);
Irp->loStatus.Status = STATUS_SUCCESS;//STATUS_ACCESS_DENIED
irp->loStatus.Information = O
IoCompleteRequest(lrp, IO_NO_INCREMENT);
IRP注意事项
  • 1.在驱动程序将IRP传递个下一个驱动之后,就不再拥有这个IRP,并且不能试图再去访问它。否则会导致系统崩溃。
    • 那个IRP会被其它的驱动或者线程释放或完成
    • 如果驱动需要访问一个已经在栈里传下去的IRP,这个驱动必须实现并设置IoCompletion例程。
    • 当I/O管理器调用IoCompletion例程时,这个驱动就能够在IoCompletion 例程执行期间重新获得对这一IRP的所有权。如此,loCompletion例程就能够访问IRP中的域。
  • 2.若是驱动的分发例程也还需要在IRP被后面的驱动处理完成之后再处理它,这个IoCompletion例程必须返回STATUS_MORE_PROCESSING_REQUIRED,以将IRP 的所有权返回给分发例程
    • 如此一来,I/O 管理器会停止IRP的处理,将最终完成IRP的在务留给分发例程。分发例程能够在之后调用IoCompleteRequest 来完成这个IRP,或者还能将这个IRP标记为等候进一步处理。
      IoCopyxxX+完成例程下发
1
2
3
4
5
6
7
8
9
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
48
49
50
/// IoCopyxxX+完成例程 下发
 
/// 分发例程
KEVENT event;
KelnitializeEvent(&event, NotificationEvent,FALSE); ///< 设置一个事件
IoCopyCurrentlrpStackLocationToNext(Irp); ///把当前驱动对应的IRP栈数据拷贝到下一层驱动上IRP栈上
/// 为当前IRP设置一个完成例程
/// 驱动需要访问一个已经在栈里传下去的IRP,这个驱动必须实现并设置`IoCompletion`例程。
IoSetCompletionRoutine(Irp,
    IoCompRoutine,
    &event,
    TRUE,TRUE,TRUE
    );
/// 将IRP往下发
status = IoCallDriver(DeviceObject,Irp);
/// 如果IRP处于pending状态,就在这个事件上等待
if (status == STATUS_PENDING)
{
    status = KeWaitForSingleObject(&event,
        Executive,
        KernelMode,
        FALSE,
        NULL
        );
 
ASSERT(NT_SUCCESS(status));
/// 驱动的分发例程也还需要在IRP被后面的驱动处理完成之后再处理它,一般在驱动程序将IRP传递个下一个驱动之后,就不再拥有这个IRP,并且不能试图再去访问它。否则会导致系统崩溃。因为那个IRP会被其它的驱动或者线程`释放或完成`。这里可以访问是因为设置了完成例程,并且这个IoCompletion例程返回了`STATUS_MORE_PROCESSING_REQUIRED`
status = Irp->IoStatus.Status;
IoFreelrp(Irp) ///< 原本有完成例程负责Irp的销毁,这时候就需要交给分发例程来做了
}
 
 
/// 完成例程
NTSTATUS IoCompRoutine(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
{
    PKEVENT event = Context;
    /// 当I/O管理器调用`IoCompletion`例程时,这个驱动就能够在IoCompletion `例程执行期间`重新获得对这一IRP的所有权。
    /// 如此,IoCompletion例程就能够访问IRP中的域。
    Irp->UserIosb->Status = Irp->IoStatus.Status;
    Irp->UserIosb->Infomation = Irp->IoStatus.Information;
    KeSetEvent(event,IO_NO_INCREMENT, FALSE);
    //IoFreelrp(Irp) ///< 原本有完成例程负责Irp的销毁,这时候就需要交给分发例程来做了
    return STATUS_MORE_PROCESSING_REQUIRED;
    /// 返回STATUS_MORE_PROCESSING_REQUIRED,以将IRP 的所有权返回给`分发例程`
    /// I/O 管理器会停止IRP的处理,将最终完成IRP的在务留给分发例程。分发例程能够在之后调用`IoCompleteRequest` 来完成这个IRP,或者还能将这个IRP标记为等候进一步处理。
}
IoSkip+IoCall直接下发
  • 对IRP没有任何改动的时候,比如放行
1
2
3
4
5
6
7
8
PDEVICE_EXTENSION deviceExtension;
IoSkipCurrentlrpstackLocation(lrp);
/// 拿到保存在设备扩展里的下层设备deviceExtension
(PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
/// 下发
/// 在驱动程序将IRP传递个下一个驱动之后,就不再拥有这个IRP,并且不能试图再去访问它。否则会导致系统崩溃。
/// 因为这个IRP会被其它的驱动或者线程`释放或完成`。
return IoCallDriver(deviceExtension->TargetDeviceObject,Irp);
错误的下发方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
/// 没有设置完成例程(并且在完成例程中返回STATUS_MORE_PROCESSING_REQUIRED,以将IRP 的所有权返回给`分发例程`)
// Forward request to next driver
IoCopyCurrentlrpStackLocationToNext(Irp)
// Send the lRP down
status IoCallDriver(nextDevice,Irp);
// The following is an error because thisdriver
// no longer owns the IRP
if(status == STATUS_PENDING)
{
    IoMarklrpPending(Irp); //错误,无权操作Irp了
}
// Return the lower driver's status
return status;

基于Sfilter的HIPS

  • 监控敏感目录(全盘监控效率低)
  • 防勒索工具

    分发过滤函数

  • FilterCreate(创建)
    • 病毒和木马进入系统肯定会生成一些文件,比如释放.sys,.dll文件或者一个链接等等都属于文件创建操作
    • 全局监控太耗性能,只监控一些敏感的区域比较好
  • FilterRead (一般不拦,加解密处理)
    • 读操作不会修改文件,但是如果作为隐私保护器,那需要拦截,防止别人读取文件
    • 加解密中也要拦截, 读操作这时候是解密
  • FilterWrite(修改,小心加解密处理)
    • 修改文件肯定要拦截
    • 加解密中也要拦截, 写操作这时候是加密
  • FilterSetInfo(删,重命名)
    • 删,重命名对应IRP_MJ_SET_INFORMATION下的次功能号,所以也需要拦
  • FilterClose (一般不拦)
  • FilterClean(写关闭等)
    • 写关闭:写操作完成之后关闭,要拦截。
    • 比如,一个病毒特征码是foul,要把病毒写到文件中去,直接写foul,在写的时候就会被杀毒软件匹配并拦截到。但每次写的时候只写一部分,第1次写f,第2次写o,第3次写u,第4次写l,写完之后文件保存就是foul,绕过了杀毒软件的查杀。所以要在每次写完的关闭之前都需要扫描一遍文件是不是匹配病毒特征码,所以写关闭是需要拦截的。
    • 写关闭有时候无法直接区分,因为打开文件再关闭,关闭之前不知道是读还是写,在xp里面,关闭的时候information是2则表示是写,win7及以后版本则没有了这些标志。
    • 所以在Sfilter里面,往往是通过文件的修改时间来判断,如果是在1min之内则认为是写关闭。
    • Minifilter里面有上下文,写操作在打开文件的时候我们是知道的,因为打开文件的时候带写权限,把信息记录下来通过上下文的形式传给关闭操作的。
  • 拦截创建
  • 拦截重命名
  • 拦截删除

    FilterCreate

  1. 放行
  • 本进程
1
2
3
4
5
6
7
8
9
10
//如果是自己进程下发的IRP请求(发给控制设备对象)直接返回
if (IS_MY_CONTROL_DEVICE_OBJECT(lpDevice))
{
    lpIrp->IoStatus.Status = Status;
    lpIrp->IoStatus.Information = ulInfomation;
 
    IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
 
    return Status;
}
  • 内核过来的
    • lrp->RequestorMode == KernelMode
  • 本进程的
    • FilterDeviceloctrl中 PsGetCurrentProcessld()
  • 系统进程的
    • DriverEntry里:PsGetCurrentProcessld()
  • 文件夹
1
2
ulOptions = Irp Stack->Parameters.Create.Options;
FlagOn(IrpStack->FileObject->Flags,FO_VOLUME_OPEN)||FlagOn(ulOptions,FILE_DIRECTORY_FILE)||FlagOn(lrpStack->Flags,SL_OPEN_PAGING_FILE)
  • KeGetCurrent(lrql)>APC_LEVELS
  • 文件带缓存IO:
    • 预先读入文件和延迟写入文件。在读/写文件的时候,为了提高效率,文件带缓冲进行IO操作,读的时候提前读入目标旁边的数据,写的时候会延迟写(集中起来一次性写入),因为磁盘是低速设备,减少IO次数是能提高效率的。
    • 当ReadFile时,会调用NtReadFile()系统调用,它会构造一个IRP下发到FSD,FSD会检查这个IRP看是不是可以缓存的,是的话,如果还没有为此文件建立缓存的话,就会调用CclnitializeCacheMap()函数建立缓存,它里面会调用内存管理器(VMM)函数建立一个节对象。
    • 当用到时,会把这个节对象(和文件关联)映射到内核空间。如果IRP是可缓存的,则调用CcCopyRead函数进行从缓存中读入文件。
    • 如果此文件还没有在内存中,则会产生页面错误,交给MmAccessFault()函教处理,它会调用loPageRead iO分配一个不缓存的IRP(IRP_PAGING_lO),但是它会走FSD,不会调用缓存的函数,而是最终调用磁盘驱动进行真实的磁盘读写读入到内存。
    • 之后CcCopyRead()再不会产生错误了,会从缓存复制到用户Buffer中
  • IRP_PAGING_IO:
    • 情况1.IRP_NOCACHE && 非IRP_XXX_ PAGING _IO,也就是用户程序设置FILE_NO_INTERMEDIATE_BUFFERING,流程是App->IO->FSD->DISK
    • 情况2.IRP_CACHE&& 非IRP_XXX_PAGING_IO,也就是用户程序默认设置, 流程是APP->IO->FSD-CC(Cache Manger)->MM(->FSD-DISK)
    • IRP_PAGING_IO:在情况2中:MM会发起一个IRP并标记为IRP_XXX_PAGING_IO,流程是MM->FSD->DISK(on behalt of vm),所以IRP_PAGING_IO不是由用户程序发起的,而是由内存管理器发起的,所以不需要监控。
    • 如果设置了IRP_XXX_PAGING_IO,那就是MM内部用的IRP,CACHE标记此时没有意义(on behalft of vmn)
    • 发给磁盘的机会:
    • 1.FILE_NO_INTERMEDIATE_BUFFERING&&非IRR_XXX_PAGING_IO的时候会发给DISK,即App->IO->FSD->DISK
    • 2.IRP_XXX_PAGING_IO时候会发给DISK,即MM->FSD->DISK
1
2
3
4
5
6
7
8
9
10
11
if (
    lpIrp->RequestorMode == KernelMode ||  //来自内核态
    IsDir(lpIrpStack) == TRUE || //进程操作是个文件夹
    PsGetCurrentProcessId() == g_hSystemProcID || //系统进程的创建
    FlagOn(lpIrpStack->Flags, SL_OPEN_TARGET_DIRECTORY) ||
    (lpIrp->Flags & IRP_PAGING_IO) ||
    (lpIrp->Flags & IRP_SYNCHRONOUS_PAGING_IO)) //paging_io放行,不是应用层程序发起的IO,而是内核由内存管理器发起的IO
{
    bSkipped = TRUE; //放行
    goto _EXIT;
}
  1. 拿文件名/长短名转化,盘符转化。
  2. 匹配规则
  3. 弹框
  • 拦(STATUS ACCESS_DENIED)
  • 放(IoCopyxxX+完成例程下发,因为后面需要根据创建的结果决定是否将文件名插入到hash表(如果HASH表中没有的话,将获得的文件名放入HASH表中),供其它Filter函数使用
    • 文件名必须在Create里拿
    • 必须在Create请求完成后才是正确的
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
sfCreate(PDEVICE_OBJECT lpDevice, PIRP lpIrp)
{
    PLIST_ENTRY                        CurrentList = NULL;
    USER_RESULT                        R3UserResult = User_Pass;
    NAME_LOOKUP_FLAGS                LookupFlags = 0;
    PNAME_CONTROL                    lpNameControl = NULL;
    NTSTATUS                        ntStatus = STATUS_SUCCESS;
    ULONG                            ulInfo = 0;
    BOOLEAN                            bSkipped = FALSE;
    ULONG                            ulDisposition = 0;
    KEVENT                            waitEvent = { 0 };
    IO_STACK_LOCATION* lpIrpStack = IoGetCurrentIrpStackLocation(lpIrp);
    PFILE_OBJECT                    lpFileObject = lpIrpStack->FileObject;
    PTWOWAY                            pTwoWay = NULL;
    WCHAR                            wszLongName[MAX_PATH] = { 0 };
    WCHAR                            wszFileName[MAX_PATH] = { 0 };
    WCHAR                            wszDeviceName[MAX_PATH] = { 0 };
    UNICODE_STRING                    ustrDeviceName = { 0 };
    UNICODE_STRING                    ustrRule = { 0 };
    BOOLEAN                            bPopWindow = TRUE;
    /// 1.放行
    /// 如果是自己进程下发的IRP请求(发给控制设备对象)直接返回
    if (IS_MY_CONTROL_DEVICE_OBJECT(lpDevice))
    {
        lpIrp->IoStatus.Status = ntStatus;
        lpIrp->IoStatus.Information = ulInfo;
 
        IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
 
        return ntStatus;
    }
    else if (!IS_MY_DEVICE_OBJECT(lpDevice))
    {
        ///非法参数
        lpIrp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER;
        lpIrp->IoStatus.Information = ulInfo;
 
        IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
 
        return ntStatus;
    }
    else
    {
        /// 发给过滤设备的IRP
        PSFILTER_DEVICE_EXTENSION lpDevExt = (PSFILTER_DEVICE_EXTENSION)(lpDevice->DeviceExtension);
        PIO_SECURITY_CONTEXT securityContext = lpIrpStack->Parameters.Create.SecurityContext;
 
        if (
            lpIrp->RequestorMode == KernelMode ||  //来自内核态
            IsDir(lpIrpStack) == TRUE || //进程操作是个文件夹
            PsGetCurrentProcessId() == g_hSystemProcID || //系统进程的创建
            FlagOn(lpIrpStack->Flags, SL_OPEN_TARGET_DIRECTORY) ||
            (lpIrp->Flags & IRP_PAGING_IO) ||
            (lpIrp->Flags & IRP_SYNCHRONOUS_PAGING_IO)) //paging_io,不是应用层程序发起的IO,而是内核由内存管理器发起的IO
        {
            bSkipped = TRUE; //放行
            goto _EXIT;
        }
 
        ulDisposition = (lpIrpStack->Parameters.Create.Options >> 24) & 0xFF; ///取下高8
 
        if (ulDisposition == FILE_CREATE || ulDisposition == FILE_OVERWRITE || ulDisposition == FILE_OVERWRITE_IF)
        {
            //修改打开?
        }
        else
        {
            bPopWindow = FALSE; //只读的情况下不弹窗
        }
 
        //在create时获取文件路径
 
        if (FlagOn(lpIrpStack->Flags, SL_OPEN_TARGET_DIRECTORY))
        {
            SetFlag(LookupFlags, NLFL_OPEN_TARGET_DIR);
        }
 
        if (lpDevExt->NLExtHeader.DosName.Length != 0)
        {
            SetFlag(LookupFlags, NLFL_USE_DOS_DEVICE_NAME);
        }
 
        if (FlagOn(lpIrpStack->Parameters.Create.Options, FILE_OPEN_BY_FILE_ID))
        {
            SetFlag(LookupFlags, NLFL_OPEN_BY_ID);
        }
 
        SetFlag(LookupFlags, NLFL_IN_CREATE);
 
        /// 2. 拿文件名/长短名转化,盘符转化。
        ntStatus = GetFileNameFromObject(&lpNameControl, LookupFlags, lpIrpStack->FileObject, lpDevice);
 
        if (lpNameControl == NULL ||
            !NT_SUCCESS(ntStatus))
        {
            //允许
            bSkipped = TRUE;
            goto _EXIT;
        }
 
        if (IsShortNamePath(lpNameControl->Name.Buffer)) ///判断短名,即判断~
        {
            //实际上应该处理短格式文件名
            ConverShortToLongName(wszLongName, lpNameControl->Name.Buffer, sizeof(WCHAR) * MAX_PATH);
            RtlCopyMemory(lpNameControl->Name.Buffer, wszLongName, sizeof(WCHAR) * MAX_PATH);
        }
 
        ustrDeviceName.Buffer = wszDeviceName;
        ustrDeviceName.Length = 0;
        ustrDeviceName.MaximumLength = sizeof(WCHAR) * MAX_PATH;
 
        RtlCopyUnicodeString(&ustrDeviceName, &lpNameControl->Name);
        //RtlCopyMemory(wszDeviceName, lpNameControl->Name.Buffer, MAX_PATH*sizeof(WCHAR));
 
        //ustrDeviceName:\device\harddiskvolume3\doc\1.txt
        //wszFileName:c:\doc\1.txt
        if (!GetNTLinkName(ustrDeviceName.Buffer, wszFileName)) ///文件名格式转换成符号链接的格式
        {
            bSkipped = TRUE;
            goto _EXIT;
        }
 
        //ustrDeviceName:c:\doc\1.txt
        RtlInitUnicodeString(&ustrDeviceName, wszFileName);
        RtlCopyUnicodeString(&lpNameControl->Name, &ustrDeviceName);
 
        /// 3. 匹配规则
        //ADD MODULE FOR RULES
        ///硬编码不可取,把规则放到.dat文件里,用客户端读取规则文件,通过IoConctorl下发到内核驱动中,组织到一个链表里,通过链表来匹配规则
        RtlInitUnicodeString(&ustrRule, L"C:\\WINDOWS\\SYSTEM32\\*\\*.SYS"); ///全部用大小,是因为下面匹配的时候TRUE表示忽略大小写,规则就需要全部是大写的的
 
        if (!IsPatternMatch(&ustrRule, &lpNameControl->Name, TRUE))
        {
            //bSkipped = TRUE;
            //goto _EXIT;
            bPopWindow = FALSE;
        }
        else
        {
            DbgPrint("File:%wZ\n", &lpNameControl->Name);
            DbgPrint("Noted: rule matched\n");
        }
        /// 4.弹窗
        if (bPopWindow)
        {
            R3UserResult = hipsGetResultFromUser(L"创建", lpNameControl->Name.Buffer, NULL, User_DefaultNon);
        }
 
        if (R3UserResult == User_Block)
        {
            //禁止
            lpIrp->IoStatus.Information = 0;
            lpIrp->IoStatus.Status = STATUS_ACCESS_DENIED;
            IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
            ntStatus = STATUS_ACCESS_DENIED;
            bSkipped = FALSE;
 
            goto _EXIT;
        }
        /// Q:为什么这里需要使用`IoCopyxxX+完成例程`往下发
        IoCopyCurrentIrpStackLocationToNext(lpIrp);
 
        KeInitializeEvent(&waitEvent,
            NotificationEvent,
            FALSE);
 
        IoSetCompletionRoutine(lpIrp,
            FilterCreateCompletion,
            &waitEvent,
            TRUE,
            TRUE,
            TRUE);
 
        ntStatus = IoCallDriver(((PSFILTER_DEVICE_EXTENSION)lpDevice->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
            lpIrp);
 
        if (ntStatus == STATUS_PENDING)
        {
            ntStatus = KeWaitForSingleObject(&waitEvent,
                Executive,
                KernelMode,
                FALSE,
                NULL);
        }
        /// A:因为这里需要知道IRP的执行结果
        ntStatus = lpIrp->IoStatus.Status;
 
        if (NT_SUCCESS(ntStatus))
        {
            /// 如果HASH表中没有的话,此处将获得的文件名放入HASH表中
            /// 文件名只由在CreateFile成功之后才是可靠的,在创建之前,write,Read,删掉拿到的文件名都是不可靠的
            /// 因为创建或者打开文件必须保证文件名是对的,不然无法成功,而且创建出来之后,不再依赖文件名了,以后操作都基于fileObjec,文件名完全可以被修改甚至抹除
            /// 所以一开始创建或者打开文件获得的文件名放入HASH表中,后续以fileObject为key查询hash表,拿到文件名才是可靠的
            {
                PHASHDATA lpData = ExAllocatePoolWithTag(NonPagedPool, sizeof(HASHDATA), 'HSAH');
                if (lpData)
                {
                    RtlZeroMemory(lpData, sizeof(HASHDATA));
                    lpData->lpNameControl = lpNameControl;
                    lpNameControl = NULL;
                    LockWrite(&g_HashTableLock);
                    Insert((DWORD)lpFileObject, lpData, g_pHashTable); ///hash key是文件的内核对象 value是包含文件名的lpData
                    UnLockWrite(&g_HashTableLock);
                }
            }
        }
 
        IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
    }
 
_EXIT:
    if (bSkipped)
    {
        IoSkipCurrentIrpStackLocation(lpIrp);
        ntStatus = IoCallDriver(((PSFILTER_DEVICE_EXTENSION)lpDevice->DeviceExtension)->NLExtHeader.AttachedToDeviceObject, lpIrp);
    }
 
    if (lpNameControl != NULL)
    {
        NLFreeNameControl(lpNameControl, &gSfNameBufferLookasideList);
    }
 
    return ntStatus;
}

FilterWrite

  1. 放行:
  • 本进程
1
2
3
4
5
6
7
8
9
10
//如果是自己进程下发的IRP请求(发给控制设备对象)直接返回
if (IS_MY_CONTROL_DEVICE_OBJECT(lpDevice))
{
    lpIrp->IoStatus.Status = Status;
    lpIrp->IoStatus.Information = ulInfomation;
 
    IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
 
    return Status;
}
  • Irql > APC_LEVEL
  • 系统进程
  • 分页IO
1
2
3
4
5
6
7
8
9
if (
    KeGetCurrentIrql() > APC_LEVEL || // Irql > APC_LEVEL
    PsGetCurrentProcessId() == g_hSystemProcID || //系统进程
    (lpIrp->Flags & IRP_PAGING_IO) ||
    (lpIrp->Flags & IRP_SYNCHRONOUS_PAGING_IO)) //paging_io
{
    bSkipped = TRUE; //放行
    goto _Exit;
}
  1. 从Hash表中以fileobject为key没有找到的文件名
1
2
3
4
5
6
7
8
//此处需要通过FileObject获得文件名
 
pTwoWay = Find((DWORD)lpFileObject, g_pHashTable);
if (pTwoWay == NULL)
{
    bSkipped = TRUE; //从表中根据file object没有找到的
    goto _Exit;
}
  1. 匹配规则
1
2
3
4
5
6
7
8
9
lpNameControl = pTwoWay->data.lpNameControl;
 
RtlInitUnicodeString(&ustrRule, L"C:\\WINDOWS\\SYSTEM32\\*\\*.SYS");
 
if (!IsPatternMatch(&ustrRule, &lpNameControl->Name, TRUE)) //匹配规则
{
    bSkipped = TRUE;
    goto _Exit;
}
  1. 弹框
    • 拦截(STATUS_ACCESS_DENIED)
    • 放(写入之后根本没办法管,所以使用IoSkip+IoCall直接下发)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
R3UserResult = hipsGetResultFromUser(L"写入", lpNameControl->Name.Buffer, NULL, User_DefaultNon);
 
        if (R3UserResult == User_Block)
        {
            //禁止
            lpIrp->IoStatus.Information = 0;
            lpIrp->IoStatus.Status = STATUS_ACCESS_DENIED;
            IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
            Status = STATUS_ACCESS_DENIED;
            bSkipped = FALSE;
 
            goto _Exit;
        }
 
        //允许
        bSkipped = TRUE;
        goto _Exit;
    }
 
_Exit:
    if (bSkipped)
    {
        IoSkipCurrentIrpStackLocation(lpIrp);
        Status = IoCallDriver(((PSfilter_DEVICE_EXTENSION)lpDevice->DeviceExtension)->NLExtHeader.AttachedToDeviceObject, lpIrp);
    }
  • 效率
    • 多次写?

      FilterSetInfo拦截删除,重命名等

  1. 放行:
  • 本进程
1
2
3
4
5
6
7
8
9
10
//如果是自己进程下发的IRP请求(发给控制设备对象)直接返回
if (IS_MY_CONTROL_DEVICE_OBJECT(lpDevice))
{
    lpIrp->IoStatus.Status = Status;
    lpIrp->IoStatus.Information = ulInfomation;
 
    IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
 
    return Status;
}
  • 系统进程
1
2
3
4
5
6
7
PSfilter_DEVICE_EXTENSION lpDevExt = (PSfilter_DEVICE_EXTENSION)(lpDevice->DeviceExtension);
 
if (PsGetCurrentProcessId() == g_hSystemProcID) //系统进程
{
    bSkipped = TRUE;
    goto _EXIT;
}
  1. 从Hash表中以fileobject为key没有找到的文件名,识别子功能号
1
2
3
4
5
6
7
8
9
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
//HASH表中获得文件名
pTwoWay = Find((DWORD)lpFileObject, g_pHashTable);
if (pTwoWay == NULL)
{
       bSkipped = TRUE;
       goto _EXIT;
}
lpNameControl = pTwoWay->data.lpNameControl;
 
if(lpIrpStack->Parameters.SetFile.FileInformationClass == FileRenameInformation ||
    lpIrpStack->Parameters.SetFile.FileInformationClass == FileBasicInformation ||
    lpIrpStack->Parameters.SetFile.FileInformationClass == FileAllocationInformation ||
    lpIrpStack->Parameters.SetFile.FileInformationClass == FileEndOfFileInformation ||
    lpIrpStack->Parameters.SetFile.FileInformationClass == FileDispositionInformation)    
     {
    switch (lpIrpStack->Parameters.SetFile.FileInformationClass)
    {
    case FileAllocationInformation:
    case FileEndOfFileInformation:
        szOper = L"设置大小";
        bSkipped = TRUE;
        goto _EXIT;
        // break;
    case FileRenameInformation:
        szOper = L"重命名";
        break;
    case FileBasicInformation:
        szOper = L"设置基础信息";
        bSkipped = TRUE;
        goto _EXIT;
        //break;
    case FileDispositionInformation:
        bNeedPostOp = TRUE;
        szOper = L"删除";
        break;
    }
}
else
{
    // 允许
    bSkipped = TRUE;
    goto _EXIT;
}

3.匹配

1
2
3
4
5
6
7
8
9
10
11
12
RtlInitUnicodeString(&ustrRule, L"C:\\WINDOWS\\SYSTEM32\\*\\*.SYS");
 
if (!IsPatternMatch(&ustrRule, &lpNameControl->Name, TRUE))
{
    bSkipped = TRUE; //没匹配上,放行
    goto _EXIT;
}
 
if (lpIrpStack->Parameters.SetFile.FileInformationClass == FileRenameInformation)
{
    ///@todo 重命名的目标路径?
}
  • 如何拿重命名之后的文件名?
    • lrpSp->Parameters.SetFile.FileObject
    • IrpSp->FileObject
    • Irp->AssociatedIrp.SystemBuffer;(从这里可以直接拿)
    • fileObject >DeviceObject
    • RtlVolumeDeviceToDosName
    • ObQueryNameString
    • loQueryFileDosDeviceName
  1. 弹框
    • 如果是删除,需要知道删除的结果是否成功,来决定是否更新hash表(把文件名删除),所以使用IoCopyxxX+完成例程将IRP下发
    • 如果是重命名,也需要知道删除的结果是否成功,来决定是否更新hash表(把文件名改了),所以使用IoCopyxxX+完成例程将IRP下发
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
    //如果是重名名,这里的NULL要改成重名后的数据
    R3UserResult = hipsGetResultFromUser(szOper, lpNameControl->Name.Buffer, NULL, User_DefaultNon);
 
    if (R3UserResult == User_Block)
    {
        // 禁止
        lpIrp->IoStatus.Information = 0;
        lpIrp->IoStatus.Status = STATUS_ACCESS_DENIED;
        IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
        Status = STATUS_ACCESS_DENIED;
        bSkipped = FALSE;
 
        goto _EXIT;
    }
 
    bSkipped = TRUE;
}
_EXIT:
    if (bSkipped)
    {
        KEVENT waitEvent;
 
        IoCopyCurrentIrpStackLocationToNext(lpIrp);
 
        KeInitializeEvent(&waitEvent,
            NotificationEvent,
            FALSE);
 
        IoSetCompletionRoutine(lpIrp,
            SetFilterCompletion,
            &waitEvent,
            TRUE,
            TRUE,
            TRUE);
 
        Status = IoCallDriver(
            ((PSFILTER_DEVICE_EXTENSION)lpDevice->DeviceExtension)->NLExtHeader.AttachedToDeviceObject,
            lpIrp);
 
        if (Status == STATUS_PENDING)
        {
            Status = KeWaitForSingleObject(&waitEvent,
                Executive,
                KernelMode,
                FALSE,
                NULL);
        }
 
        Status = lpIrp->IoStatus.Status;
        ///@todo to update the hash table if succeeded
 
        IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
    }

FilterCleanup

  1. 放行
  2. 从Hash表中以fileobject为key没有找到的文件名
  3. 匹配
  4. 弹窗
    • 删除结点
    • 所以使用IoSkip+IoCall直接下发

      优化

  • 规则硬编码不可取,把规则放到.dat文件里,用客户端读取规则文件,通过IoConctorl下发到内核驱动中,组织到一个链表里,通过链表来匹配规则

    Minifilter

    Minifilter与legacy filter(Sfilter)区别

  • 新一代的文件过滤驱动框架: Minifilter
  • 比sfilter加载顺序更易控制altitude被绑定到合适的位置。
  • 可卸载能力。
    • 系统不重启的情况下,HOOK不支持卸载,卸载可能会蓝屏,是因为有的IRP可能没有立即返回,会被pending起来,如果这时候把hook的函数卸载,当重新回来执行的时候,函数的地址就失效了,内存无效,蓝屏。
    • 解决方法:引用计数
    • minifiler驱动是向minifiler框架注册的,minifilter驱动卸载了,框架还在,就不会造成类似HOOK函数卸载后的问题
  • Callback模型仅需处理必要操作的能力(pre_create,post create)
    • 相当于把Sfilter的分发函数分为两部分:pre_create(IRP下发之前),post create(IRP处理完之后)
    • 如果不想处理IRP就不需要注册对应的回调函数(如果是Sfilter则需要为不处理的IRP注册一个通用的过滤分发函数)
    • 绑定过滤设备对象动作被隐藏
  • 兼容性更好
    • hook会打架,谁后生成谁优先
    • Sfiler不可卸载
    • Minifilter都没有上述问题
  • 名字处理更容易
    • Minifilter拿文件名只需直接调用函数就可以了
  • 安装方式(.inf或者用代码动态加载)
    • 动态安装,创建几个与之相关的注册表的键即可
  • 通信方式(port)
    • 基于端口通信,效率非常高,应用层可以多个线程处理内核层发送的请求,每个线程也可以处理多个请求。
  • 同样遵循IRQL,锁等内核开发通用机制
  • FltCreateFile/ZwCreateFile
    • Minifilter中不建议使用Zw*函数,而是使用Flt*函数,避免重入(死循环)

      Minifilter框架

      Minifilter
  • I/O Nanager:负责把应用层的IO请求封装成IRP包,发送给Filter Manager
  • Filter Manager Frame:把IRP重新组装成FLT_CALLBACK_DATA结构体,把这个结构体传给逐层传给Minifilter驱动A,B,C(Altitude值不一样,每次加载的时候相对关系是固定的,值大的在上层,越优先处理),即Minifilter中没有IRP这一说法了,处理IO数据的时候都是从FLT_CALLBACK_DATA结构体中拿数据。
    Minifilter与Sfilter共存
  • Legacy Filter Driver:指的是Sfilter驱动,虽然夹在两个Filter Manager Frame,但Sfilter驱动是没有高度的。Sfilter驱动可以在Filter Manager Frame的上面也可以在下面,二者之间没有说谁的级别比谁的级别高。
    Altitude值
  • 20000-429999
  • 每一个minifilter驱动必须有一个叫做altitude唯一标识符。一个minifilter驱动的altitude定义了它加载时在I/O栈中相对其他minifilter驱动的位置。值越小,栈中位置就越低。
  • FSFilter Anti-Virus 320000-329999
    • 此组包括在文件I/O期间探测并杀毒的过滤驱动。
    • 反病毒在加减密之前是合理的,如果加减密在前面,把文件加密了,反病毒就无法识别病毒特征码了。
  • FSFilter Encryption 140000-149999
    • 此组包括在文件I/O期间加密解密数据的过滤驱动。
  • Altitude值在注册表中的位置

    Minifilter结构

  • FLT_REGISTRATION的定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/managing-contexts-in-a-minifilter-driver
const FLT_REGISTRATION fileMonitorRegistration =
{
    sizeof( FLT_REGISTRATION), ///< Size
    FELT_REGISTRATION_VERSION, ///< Version
    0,                         ///< Flags
    ContextRegistration,       ///< ContextRegistration
    fileMonitorCallbacks,      ///< Operation callbacks,结构体数组,每一个结构体封装了IRP和与之对应的回调函数,注册的回调函数就是放在这个结构体数组里
    fileMonUnload,             ///< FilterUnload,在Minifilter卸载时调用,做一些清理工作
    fileMonInstanceSetup,      ///< InstanceSetup,instance类似Sfilter中的过滤设备对象,有多少的卷设备就生成多个实例与之绑定,生成的过程是不可见的(看不到生成实例的代码,是由框架去做的),instance的数量和卷设备对象的数量相等的,一一对应。
    ///< 调用时机:把Minifilter的实例绑定到卷设备对象上会调用这个函数
    ///< 作用:可以记录下instance对应卷设备的一些属性,比如说是C盘还是D盘,文件类型是FAT32还是NTFS,卷的扇区大小是512B还是4KB。存到一个上下文里面(是个缓存),下次用到的时候,之间从缓存里面拿数据既可以了,提高效率。
    NULL,                      ///< InstanceQuery Teardown
    fileMonInstanceTeardownStart, ///< InstanceTeardownStart
    NULL,                         ///< InstanceTeardownComplete
    NULL,                         ///< GenerateFileName
    NULL,                         ///< GenerateDestinationFileName
    NULL                          ///< NormalizeNameComponent
};
  • fileMonitorCallbacks结构体数组的定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const FLT_OPERATION_REGISTRATION fileMonitorCallbacks[]=
{
    {
        IRP_MJ_CREATE, ///表示创建或者打开IRP的回调函数
        FLTFL_OPERATION_REGISTRATION_SKIP_PAGlNG_IO, // Flage,忽略掉paging_io
        HOOK_PreNtCreateFile, ///可以缺省其中一个
        HOOK_PostNtCreateFile ///可以缺省其中一个
    },
    {
        IRP_MJCLEANUP,
        0
        HOOK_PreNtCleanup,
        NULL
    },
    {
        IRP_MJ_WRITE,
        0,
        HOOK_PreNtWriteFile,
        HOOK_PostNtWriteFile
    },
    {
        lRP_MJ_SETLINF ORMATION,
        0,
        HOOK_PreNtSetInformationFile,
        HOOK_PostNtSetInformationFile
    },
        IRPZMJ_ORERATION_END
    }
};
  • 文件带缓存IO:
    • 预先读入文件和延迟写入文件。在读/写文件的时候,为了提高效率,文件带缓冲进行IO操作,读的时候提前读入目标旁边的数据,写的时候会延迟写(集中起来一次性写入),因为磁盘是低速设备,减少IO次数是能提高效率的。
    • 当ReadFile时,会调用NtReadFile()系统调用,它会构造一个IRP下发到FSD,FSD会检查这个IRP看是不是可以缓存的,是的话,如果还没有为此文件建立缓存的话,就会调用CclnitializeCacheMap()函数建立缓存,它里面会调用内存管理器(VMM)函数建立一个节对象。
    • 当用到时,会把这个节对象(和文件关联)映射到内核空间。如果IRP是可缓存的,则调用CcCopyRead函数进行从缓存中读入文件。
    • 如果此文件还没有在内存中,则会产生页面错误,交给MmAccessFault()函教处理,它会调用loPageRead iO分配一个不缓存的IRP(IRP_PAGING_lO),但是它会走FSD,不会调用缓存的函数,而是最终调用磁盘驱动进行真实的磁盘读写读入到内存。
    • 之后CcCopyRead()再不会产生错误了,会从缓存复制到用户Buffer中
  • IRP_PAGING_IO:
    • 情况1.IRP_NOCACHE && 非IRP_XXX_ PAGING _IO,也就是用户程序设置FILE_NO_INTERMEDIATE_BUFFERING,流程是App->IO->FSD->DISK
    • 情况2.IRP_CACHE&& 非IRP_XXX_PAGING_IO,也就是用户程序默认设置, 流程是APP->IO->FSD-CC(Cache Manger)->MM(->FSD-DISK)
    • IRP_PAGING_IO:在情况2中:MM会发起一个IRP并标记为IRP_XXX_PAGING_IO,流程是MM->FSD->DISK(on behalt of vm),所以IRP_PAGING_IO不是由用户程序发起的,而是由内存管理器发起的,所以不需要监控。
    • 如果设置了IRP_XXX_PAGING_IO,那就是MM内部用的IRP,CACHE标记此时没有意义(on behalft of vmn)
    • 发给磁盘的机会:
    • 1.FILE_NO_INTERMEDIATE_BUFFERING&&非IRR_XXX_PAGING_IO的时候会发给DISK,即App->IO->FSD->DISK
    • 2.IRP_XXX_PAGING_IO时候会发给DISK,即MM->FSD->DISK
  • HOOK_PreNtCreatelFile/HOOK_PostNtCreateFile定义
1
2
3
4
5
6
7
8
9
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
/// create执行之前调用
FLT_PREOP_CALLBACK_STATUS HOOK_PreNtCreateFile(
    PFLTCALLBACK_DATA Data, ///Filter Manager Frame将IRP重新组装成`FLT_CALLBACK_DATA`结构体
    PCFLT_RELATED_OBJECTS FltObjects, ///与Minifilter相关的对象
    PVOID *CompletionContext ///分配的一个context资源,可以传给Post函数处理,然后在Post函数释放掉context资源
)
    //sandbox
    //主防
    //杀毒引擎
    //加解密
    return xxx; ///这个返回值是返回给Minifilter管理器的,拿到返回值之后再决定要不要把操作继续往下发给Mnifilter驱动或者Sfilter驱动
    /// Data->IoStatus.Status = STATUS_ACCESS_DENIED; 这个才是返回给IO管理器的,即应用层
     /// PRE-OP的返回值:(和sfilter比较)
    /// FLT_PREOP_SUCCESS_WITH_CALLBACK // 告诉Minifilte管理器要把操作往下发,结束之后要调用Post,类似Sfilter中`IoCopyxxX+完成例程`将IRP下发
    /// FLTPREOP_SUCCESS_NO_CALLBACK // 告诉Minifilte管理器要把操作往下发,结束之后但不需要调用Post,类似Sfilter中`IoSkip+IoCall`直接下发
    /// FLT_PREOP_PENDING  // 挂起
    /// FLT PREOP DISALLOW_FASTIO // 禁用fastio
    /// FLT_PREOP_COMPLETE // 告诉Minifilte管理器要把操作完成之后不下发了,当前为止,不下发有拒绝(STATUS_ACCESS_DENIED),成功完成(STATUS_ACCESS)
    /// FLT_PREOP_SYNCHRONIZE // 同步
}
 
/// create完成之后创建
FLT_POSTOP_CALLBACK_STATUS HOOK_PostNtCreateFile(
    PFLT_CALLBACK_DATA Data,
    PCFLT RELATED_OBJECTS FltObjects,
    PVOID completionContext, //在PRE-OP里返回FLT_PREOPsuCcEss_wITH_CALLBACK 时获取里面的上下文,并最后释放Context资源
    FLT_POST_OPERATION_FLAGS Flags
)
{
    return xxx;
    // POST-OP的返回值:
    // FLT_POSTOP_FINISHED_PROCESSING // 最终完成处理
    // FLT_POSTOP_MORE_PROCESSING_REQUIRED // post处理完之后,还需要更多处理,一般发生在Post里面,如果Irql比较高,比如处于DISPATCH_LEVEL,这样需要做一些操作的时候,是需要开一个工作者线程去做,这时候就需要返回一个FLT_POSTOP_MORE_PROCESSING_REQUIRED
 
}
FltObjects->volume,FltObjects->Instance,FltObjects->FileObject,
FltObjects->FileObject->DeviceObject
  • 判断Data是什么操作的宏
1
2
3
4
5
6
7
8
9
10
11
12
FLT_IS_IRP_OPERATION  ///应用下发的IRP
FLT_IS_FASTIO_OPERATION ///走缓存
FLJ IS_FS_FILTER_OPERATION ///其他Minifilter或者Sfilter下发的
 
// eg:禁用fastio
if(FLT_IS_FASTIO_OPERATION(Data)) ///为真则是Fasdtio操作
{
    ntStatus STATUS_FLT DISALLOW_FAST_I0;
    Data->loStatus.status = ntStatus;
    Data->loStatus.Information = 0;
    return FLT_PREOR_DISALLOW_FASTIO;
}
  • 参数数据的获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
PFLT_CALLBACK_DATA Data;
PEPROCESS processObject = Data->Thread ? loThreadToProcess(Data->Thread) : PsGetCurrentProcess(); ///把Thread转换成Exprocess结构
HandleToUlong(PsGetProcessldf processObject));///通过Exprocess结构拿到PID
 
Data->IoStatus.Status = ntStatus; ///pIrp->loStatus.Status=ntStatus;
Data->IoStatus.Information = 0; ///跟IRP类似
 
Eltobjects->Volume, ///卷设备对象
FltObjects->Instance, ///实例对象和Volume一一对应
FltObjects->FileObject, ///要被操作目标文件的内核对象
FltObjects-FileObject->DeviceObject ///文件所在的设备对象
 
PMDE pReadMdl = Data->lopb->Parameters.Read.MdlAddress; ///MdlAddress!=NULL使用的是direct io
PVOID pReadBuffer = Data->Iopb->Parameters.ReadBuffer;///MdlAddress==NULL使用的是readbuffer
ULONG uReadLength = Data->lopb->Parameters.Read.Length; ///读数据的长度
 
Data->Iopb->Parameters.create.SecurityContext->DesiredAccess ///如果是创建,创建的一些操作可以拿到,比如说以写的方式,读的方式还是读写的方式等
 
PVOID pQueryBuffer
Data>Iopb->Parameters.DirectoryControl.QueryDirectory.DineectoryBuffer;
///查询文件夹的buffer
ULONG uQueryBuffersize = Data->lopb->Parameters.DirectoryControl.QueryDirectory.Length; ///查询文件夹的buffer长度
 
/// 返回
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
Data->IoStatus.Information = 0;
return FLT_PREOP_COMPLETE;
  • 启动mifilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/// 注册
/// 自己封装的一个initFileMonitor
NTSTATUS initFileMonitor(PDRIVER_OBJECT DriverObject )
{
    // fileMonitorRegistration结构体传给FltRegisterFilter函数进行注册,得到句柄g_pFilter
    return FltRegisterFilter( DriverObject,
                             &fileMonitorRegistration,
                             &g_pFilter);
}
 
/// 启动
NTSTATUS startFileMonitor()
{
    if(g_pFilter)
        // 启动Minifilter驱动,以后所有与Write,READ,SET_INFORMATION等相关的IO操作依次会被 _Pre* 和 _Pos*函数拦截
        return FltStartFiltering(g_pFilter);
    return STATUS_INSUFFICIENT_RESOURCES,
}
 
/// 卸载
VOID stopFileMonitor()
{
    if(g_pFilter)
    {
 
        FltUnregisterFilter(g_pFilter); // 卸载时会调用fileMonitorRegistration.fileMonUnload来释放一些资源
        g_pFilter = NULL;
    }
}

NullFilter测试与安装

  • null表示没有为系统任何的Irp提供回调函数
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
/*++
 
Copyright (c) 1999 - 2002  Microsoft Corporation
 
Module Name:
 
    nullFilter.c
 
Abstract:
 
    This is the main module of the nullFilter mini filter driver.
    It is a simple minifilter that registers itself with the main filter
    for no callback operations.
 
Environment:
 
    Kernel mode
 
--*/
#pragma comment(lib, "FltMgr.lib")
extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; };
 
#include <fltKernel.h>
#include <dontuse.h>
#include <suppress.h>
 
#pragma prefast(disable:__WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "Not valid for kernel mode drivers")
 
//---------------------------------------------------------------------------
//      Global variables
//---------------------------------------------------------------------------
 
#define NULL_FILTER_FILTER_NAME     L"NullFilter"
 
typedef struct _NULL_FILTER_DATA {
    //
    //  The filter handle that results from a call to
    //  FltRegisterFilter.
    //
 
    PFLT_FILTER FilterHandle;
} NULL_FILTER_DATA, * PNULL_FILTER_DATA;
 
/*************************************************************************
    Prototypes for the startup and unload routines used for
    this Filter.
 
    Implementation in nullFilter.c
*************************************************************************/
 
DRIVER_INITIALIZE DriverEntry;
NTSTATUS
DriverEntry(
    __in PDRIVER_OBJECT DriverObject,
    __in PUNICODE_STRING RegistryPath
);
 
NTSTATUS
NullUnload(
    __in FLT_FILTER_UNLOAD_FLAGS Flags
);
 
NTSTATUS
NullQueryTeardown(
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __in FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
);
 
//
//  Structure that contains all the global data structures
//  used throughout NullFilter.
//
 
NULL_FILTER_DATA NullFilterData;
 
//
//  Assign text sections for each routine.
//
 
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, NullUnload)
#pragma alloc_text(PAGE, NullQueryTeardown)
#endif
 
//
//  This defines what we want to filter with FltMgr
//
 
CONST FLT_REGISTRATION FilterRegistration = {
    sizeof(FLT_REGISTRATION),         //  Size
    FLT_REGISTRATION_VERSION,           //  Version
    0,                                  //  Flags
 
    NULL,                               //  Context
    NULL,                               //  Operation callbacks 没有值,没有为系统的Irp提供回调,所以叫Nullfilter
 
    NullUnload,                         //  FilterUnload
 
    NULL,                               //  InstanceSetup
    NullQueryTeardown,                  //  InstanceQueryTeardown
    NULL,                               //  InstanceTeardownStart
    NULL,                               //  InstanceTeardownComplete
 
    NULL,                               //  GenerateFileName
    NULL,                               //  GenerateDestinationFileName
    NULL                                //  NormalizeNameComponent
};
 
/*************************************************************************
    Filter initialization and unload routines.
*************************************************************************/
 
NTSTATUS
DriverEntry(
    __in PDRIVER_OBJECT DriverObject,
    __in PUNICODE_STRING RegistryPath
)
/*++
 
Routine Description:
 
    This is the initialization routine for this miniFilter driver. This
    registers the miniFilter with FltMgr and initializes all
    its global data structures.
 
Arguments:
 
    DriverObject - Pointer to driver object created by the system to
        represent this driver.
    RegistryPath - Unicode string identifying where the parameters for this
        driver are located in the registry.
 
Return Value:
 
    Returns STATUS_SUCCESS.
 
--*/
{
    NTSTATUS status;
 
    UNREFERENCED_PARAMETER(RegistryPath);
 
    //
    //  Register with FltMgr
    //
    status = FltRegisterFilter(DriverObject,
        &FilterRegistration,
        &NullFilterData.FilterHandle);
 
    ASSERT(NT_SUCCESS(status));
 
    if (NT_SUCCESS(status)) {
        //
        //  Start filtering i/o
        //
        status = FltStartFiltering(NullFilterData.FilterHandle);
 
        if (!NT_SUCCESS(status)) {
            FltUnregisterFilter(NullFilterData.FilterHandle);
        }
    }
    DbgPrint("Minifilter started\n");
    return status;
}
 
NTSTATUS
NullUnload(
    __in FLT_FILTER_UNLOAD_FLAGS Flags
)
/*++
 
Routine Description:
 
    This is the unload routine for this miniFilter driver. This is called
    when the minifilter is about to be unloaded. We can fail this unload
    request if this is not a mandatory unloaded indicated by the Flags
    parameter.
 
Arguments:
 
    Flags - Indicating if this is a mandatory unload.
 
Return Value:
 
    Returns the final status of this operation.
 
--*/
{
    UNREFERENCED_PARAMETER(Flags);
 
    PAGED_CODE();
    /// 卸载
    FltUnregisterFilter(NullFilterData.FilterHandle);
    DbgPrint("Minifilter unloaded\n");
    return STATUS_SUCCESS;
}
 
NTSTATUS
NullQueryTeardown(
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __in FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
)
/*++
 
Routine Description:
 
    This is the instance detach routine for this miniFilter driver.
    This is called when an instance is being manually deleted by a
    call to FltDetachVolume or FilterDetach thereby giving us a
    chance to fail that detach request.
 
Arguments:
 
    FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
        opaque handles to this filter, instance and its associated volume.
 
    Flags - Indicating where this detach request came from.
 
Return Value:
 
    Returns the status of this operation.
 
--*/
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(Flags);
 
    PAGED_CODE();
 
    return STATUS_SUCCESS;
}
方式1:.inf格式安装:

  • 右键安装.inf
  • cmd输入:net start servicename/net stop name
    • 也可以直接调用API
1
2
3
4
5
6
7
;; 加载
SetupCopyOEMlnf
;; 或者
InstallHinfSection(NULL, NULL,TEXT("DefaultInstall128 .\\myfilter.inf"), 0);
 
;; 卸载
InstallHinfSection(NULL, NULL,TEXT("DefaultUninstall128 .\\upfilter.inf"), 0);
  • .inf
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
;;; 修改的时候只需要替换NullFilter替换成自己的Minifilter项目的名字
;;;
;;; NullFilter
;;;
;;;
;;; Copyright (c) 1999 - 2002, Microsoft Corporation
;;;
 
[Version]
Signature   = "$Windows NT$"
Class       = "ActivityMonitor"             ;This is determined by the work this filter driver does,行为监控
ClassGuid   = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2}    ;This value is determined by the Class,ClassGuid和ActivityMonitor一一对应,可以查询MSDN的文档得到
;@Todo: File System Filter Driver Classes and Class GUIDs
Provider    = %Msft%            ;Minifilter的提供者
DriverVer   = 06/16/2007,1.0.0.0  ;保持默认即可
CatalogFile = nullfilter.cat
 
 
[DestinationDirs]
DefaultDestDir          = 12            ;12代表%windir%\system32\drivers
NullFilter.DriverFiles  = 12            ;把.sys拷贝到%windir%\system32\drivers,
;;
;; Default install sections
;;
 
[DefaultInstall]
OptionDesc  = %ServiceDescription%
CopyFiles   = NullFilter.DriverFiles
 
[DefaultInstall.Services]
AddService  = %ServiceName%,,NullFilter.Service
 
;;
;; Default uninstall sections
;;
 
[DefaultUninstall]
DelFiles   = NullFilter.DriverFiles
 
[DefaultUninstall.Services]
DelService = %ServiceName%,0x200      ;Ensure service is stopped before deleting
 
;
; Services Section,实际上是在修改注册表
;
 
[NullFilter.Service]
DisplayName      = %ServiceName%            ;服务名
Description      = %ServiceDescription%     ;描述
ServiceBinary    = %12%\%DriverName%.sys    ;%windir%\system32\drivers\
Dependencies     = "FltMgr"                 ;依赖的服务
ServiceType      = 2                        ;SERVICE_FILE_SYSTEM_DRIVER
StartType        = 3                        ;SERVICE_DEMAND_START,是需要的时候动态加载,不会随着系统启动
ErrorControl     = 1                        ;SERVICE_ERROR_NORMAL
LoadOrderGroup   = "FSFilter Activity Monitor"
AddReg           = NullFilter.AddRegistry
 
;
; Registry Modifications,Minifilter共有的一部分
;
 
[NullFilter.AddRegistry]
HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance%
HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude%
HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags%
 
;
; Copy Files
;
 
[NullFilter.DriverFiles]
%DriverName%.sys
 
[SourceDisksFiles]
nullfilter.sys = 1,,
 
[SourceDisksNames]
1 = %DiskId1%,,,
 
;;
;; String Section
;;
 
[Strings]
Msft                    = "Microsoft Corporation"
ServiceDescription      = "NullFilter mini-filter driver"
ServiceName             = "NullFilter"
DriverName              = "NullFilter"
DiskId1                 = "NullFilter Device Installation Disk"
 
;Instances specific information.
DefaultInstance         = "Null Instance"
Instance1.Name          = "Null Instance"
Instance1.Altitude      = "370020"
Instance1.Flags         = 0x1          ; Suppress automatic attachments
方式2:动态加载

1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
269
270
271
// MinifilterInstall.cpp : Defines the entry point for the console application.
//
 
#include "stdafx.h"
#include <windows.h>
#include <winioctl.h>
#include <winsvc.h>
#include <stdio.h>
#include <conio.h>
 
//#include <fltuser.h>
//#include <dontuse.h>
 
 
#define DRIVER_NAME "nullfilter"
#define DRIVER_PATH ".\\nullfilter.sys"
#define DRIVER_ALTITUDE     "370020" // 这里没有使用UNICODE编码,导致安装不生效,是因为工程是宽字节的工程,如果是多字节工程那就没问题
 
// SYS文件跟程序放在同个目录下
// InstallDriver(DRIVER_NAME,DRIVER_PATH,DRIVER_ALTITUDE);
// 启动驱动服务 StartDriver(DRIVER_NAME);
// 停止驱动服务 StopDriver(DRIVER_NAME);
// 卸载服务 DeleteDriver(DRIVER_NAME);
 
BOOL InstallDriver(const char* lpszDriverName,const char* lpszDriverPath,const char* lpszAltitude)
{
    char    szTempStr[MAX_PATH];
    HKEY    hKey;
    DWORD    dwData;
    char    szDriverImagePath[MAX_PATH];   
 
    if( NULL==lpszDriverName || NULL==lpszDriverPath )
    {
        return FALSE;
    }
    //得到完整的驱动路径
    GetFullPathName(lpszDriverPath, MAX_PATH, szDriverImagePath, NULL);
 
    SC_HANDLE hServiceMgr=NULL;// SCM管理器的句柄
    SC_HANDLE hService=NULL;// NT驱动程序的服务句柄
 
    //打开服务控制管理器
    hServiceMgr = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
    if( hServiceMgr == NULL )
    {
        // OpenSCManager失败
        CloseServiceHandle(hServiceMgr);
        return FALSE;       
    }
 
    // OpenSCManager成功 
 
    //创建驱动所对应的服务
    hService = CreateService( hServiceMgr,
        lpszDriverName,             // 驱动程序的在注册表中的名字
        lpszDriverName,             // 注册表驱动程序的DisplayName 值
        SERVICE_ALL_ACCESS,         // 加载驱动程序的访问权限
        SERVICE_FILE_SYSTEM_DRIVER, // 表示加载的服务是文件系统驱动程序
        SERVICE_DEMAND_START,       // 注册表驱动程序的Start 值
        SERVICE_ERROR_IGNORE,       // 注册表驱动程序的ErrorControl 值
        szDriverImagePath,          // 注册表驱动程序的ImagePath 值
        "FSFilter Activity Monitor",// 注册表驱动程序的Group 值
        NULL,
        "FltMgr",                   // 注册表驱动程序的DependOnService 值
        NULL,
        NULL);
 
    if( hService == NULL )
    {       
        if( GetLastError() == ERROR_SERVICE_EXISTS )
        {
            //服务创建失败,是由于服务已经创立过
            CloseServiceHandle(hService);       // 服务句柄
            CloseServiceHandle(hServiceMgr);    // SCM句柄
            return TRUE;
        }
        else
        {
            CloseServiceHandle(hService);       // 服务句柄
            CloseServiceHandle(hServiceMgr);    // SCM句柄
            return FALSE;
        }
    }
    CloseServiceHandle(hService);       // 服务句柄
    CloseServiceHandle(hServiceMgr);    // SCM句柄
 
    //-------------------------------------------------------------------------------------------------------
    // SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances子健下的键值项
    //-------------------------------------------------------------------------------------------------------
    strcpy(szTempStr,"SYSTEM\\CurrentControlSet\\Services\\");
    strcat(szTempStr,lpszDriverName);
    strcat(szTempStr,"\\Instances");
    if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,szTempStr,0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,(LPDWORD)&dwData)!=ERROR_SUCCESS)
    {
        return FALSE;
    }
    // 注册表驱动程序的DefaultInstance 值
    strcpy(szTempStr,lpszDriverName);
    strcat(szTempStr," Instance");
    if(RegSetValueEx(hKey,"DefaultInstance",0,REG_SZ,(CONST BYTE*)szTempStr,(DWORD)strlen(szTempStr))!=ERROR_SUCCESS)
    {
        return FALSE;
    }
    RegFlushKey(hKey);//刷新注册表
    RegCloseKey(hKey);
 
 
    //-------------------------------------------------------------------------------------------------------
    // SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances\\DriverName Instance子健下的键值项
    //-------------------------------------------------------------------------------------------------------
    strcpy(szTempStr,"SYSTEM\\CurrentControlSet\\Services\\");
    strcat(szTempStr,lpszDriverName);
    strcat(szTempStr,"\\Instances\\");
    strcat(szTempStr,lpszDriverName);
    strcat(szTempStr," Instance");
    if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,szTempStr,0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,(LPDWORD)&dwData)!=ERROR_SUCCESS)
    {
        return FALSE;
    }
    // 注册表驱动程序的Altitude 值
    strcpy(szTempStr,lpszAltitude);
    if(RegSetValueEx(hKey,"Altitude",0,REG_SZ,(CONST BYTE*)szTempStr,(DWORD)strlen(szTempStr))!=ERROR_SUCCESS)
    {
        return FALSE;
    }
    // 注册表驱动程序的Flags 值
    dwData=0x0;
    if(RegSetValueEx(hKey,"Flags",0,REG_DWORD,(CONST BYTE*)&dwData,sizeof(DWORD))!=ERROR_SUCCESS)
    {
        return FALSE;
    }
    RegFlushKey(hKey);//刷新注册表
    RegCloseKey(hKey);
 
    return TRUE;
}
 
BOOL StartDriver(const char* lpszDriverName)
{
    SC_HANDLE        schManager;
    SC_HANDLE        schService;
 
    if(NULL==lpszDriverName)
    {
        return FALSE;
    }
 
    schManager=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
    if(NULL==schManager)
    {
        CloseServiceHandle(schManager);
        return FALSE;
    }
    schService=OpenService(schManager,lpszDriverName,SERVICE_ALL_ACCESS);
    if(NULL==schService)
    {
        CloseServiceHandle(schService);
        CloseServiceHandle(schManager);
        return FALSE;
    }
 
    if(!StartService(schService,0,NULL))
    {
        CloseServiceHandle(schService);
        CloseServiceHandle(schManager);
        if( GetLastError() == ERROR_SERVICE_ALREADY_RUNNING )
        {            
            // 服务已经开启
            return TRUE;
        }
        return FALSE;
    }
 
    CloseServiceHandle(schService);
    CloseServiceHandle(schManager);
 
    return TRUE;
}
 
BOOL StopDriver(const char* lpszDriverName)
{
    SC_HANDLE        schManager;
    SC_HANDLE        schService;
    SERVICE_STATUS    svcStatus;
    bool            bStopped=false;
 
    schManager=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
    if(NULL==schManager)
    {
        return FALSE;
    }
    schService=OpenService(schManager,lpszDriverName,SERVICE_ALL_ACCESS);
    if(NULL==schService)
    {
        CloseServiceHandle(schManager);
        return FALSE;
    }   
    if(!ControlService(schService,SERVICE_CONTROL_STOP,&svcStatus) && (svcStatus.dwCurrentState!=SERVICE_STOPPED))
    {
        CloseServiceHandle(schService);
        CloseServiceHandle(schManager);
        return FALSE;
    }
 
    CloseServiceHandle(schService);
    CloseServiceHandle(schManager);
 
    return TRUE;
}
 
BOOL DeleteDriver(const char* lpszDriverName)
{
    SC_HANDLE        schManager;
    SC_HANDLE        schService;
    SERVICE_STATUS    svcStatus;
 
    schManager=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
    if(NULL==schManager)
    {
        return FALSE;
    }
    schService=OpenService(schManager,lpszDriverName,SERVICE_ALL_ACCESS);
    if(NULL==schService)
    {
        CloseServiceHandle(schManager);
        return FALSE;
    }
    ControlService(schService,SERVICE_CONTROL_STOP,&svcStatus);
    if(!DeleteService(schService))
    {
        CloseServiceHandle(schService);
        CloseServiceHandle(schManager);
        return FALSE;
    }
    CloseServiceHandle(schService);
    CloseServiceHandle(schManager);
 
    return TRUE;
}
 
int main(int argc, char* argv[])
{
    printf("Print any key to install driver\n");
    getch();
    DeleteDriver(DRIVER_NAME);
    //安装驱动调用这个函数
    BOOL bRet = InstallDriver(DRIVER_NAME, DRIVER_PATH, DRIVER_ALTITUDE);
    if (bRet == FALSE)
    {
        printf("Driver install failed\n");
        return -1;
    }
    printf("Print any key to start driver\n");
    getch();
    //启动驱动调用这个函数
    bRet = StartDriver(DRIVER_NAME);
    if (bRet == FALSE)
    {
        printf("StartDriver failed\n");
        return -1;
    }
    printf("Print any key to stop driver\n");
    getch();
    //停止驱动调用这个
    StopDriver(DRIVER_NAME);
    //删除服务调用这个
    DeleteDriver(DRIVER_NAME);
 
 
    return 0;
}

路径获取

  • Minifilter获取文件路径就是如此简单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// 在postcreate里获得
PFLT_FILE_NAME_INFORMATON pNameInfo = NULL; ///Q:没有内存的指针,为什么可以直接传给FltGetFileNameInformation使用呢?
 
/// A:FltGetFileNameInformation是带缓存的,如果第一次查询文件的路径,函数会在内部为其分配内存,把文件名放到结构体的内存里面,其他驱动再次查询这个文件路径的时候,如果路径在缓存中,就直接把缓存给它,不再分配新内存。这么多人使用缓存,难以管理,做不到谁分配谁谁释放,所以这里使用了引用计数。
ntStatus = FltGetFileNameInformation(Data,
    FLT_FILE_NAME_NORMALIZED |
    FLT_FILE_NAME_QuERY_DEFAULT,
    &pNamelnfo); ///传二级指针或者指针的引用才能分配到内存
 
/// 解析
F1tParseFileNamelnformation(pNamelnfo);
 
pNamelnfo->Name //\\device\\harddiskvolume3\\doc\\x.dat
pNamelnfo->Volume
 
FltReleaseFileNameInformation(Namelnfo); // 并不是真正释放内存,而是将缓存的引用计数减1,当减到0的时候才释放内存
 
// 重命名路径的获得:
PFILE_RENAME_INFORMATION
pFileRenamelnfomation =(PENLE_RENAME_INFORMATION)Data->IopParameters.SetFilelnformationInfoBuffer;
 
// 或者通过这样获得重命名的路径
FltGetDestinationFileInation;

基于Minifilter的PASSTHROUGH

1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
//
//  operation registration
//
/// 几乎涵盖所有的IRP
/// 共用同一组PtPreOperationPassThrough和PtPostOperationPassThrough
CONST FLT_OPERATION_REGISTRATION Callbacks[] = {
    {IRP_MJ_CREATE,
     0,
     PtCreatePreOperationPassThrough,
     PtCreatePostOperationPassThrough}, // 除了这个修改了一下
 
    {IRP_MJ_CREATE_NAMED_PIPE,
     0,
     PtPreOperationPassThrough, // 直接返回
     PtPostOperationPassThrough},
 
    {IRP_MJ_CLOSE,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_READ,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_WRITE,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_QUERY_INFORMATION,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_SET_INFORMATION,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_QUERY_EA,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_SET_EA,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_FLUSH_BUFFERS,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_QUERY_VOLUME_INFORMATION,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_SET_VOLUME_INFORMATION,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_DIRECTORY_CONTROL,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_FILE_SYSTEM_CONTROL,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_DEVICE_CONTROL,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_INTERNAL_DEVICE_CONTROL,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_SHUTDOWN,
     0,
     PtPreOperationNoPostOperationPassThrough,
     NULL}, // post operations not supported
 
    {IRP_MJ_LOCK_CONTROL,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_CLEANUP,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_CREATE_MAILSLOT,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_QUERY_SECURITY,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_SET_SECURITY,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_QUERY_QUOTA,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_SET_QUOTA,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_PNP,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_RELEASE_FOR_SECTION_SYNCHRONIZATION,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_ACQUIRE_FOR_MOD_WRITE,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_RELEASE_FOR_MOD_WRITE,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_ACQUIRE_FOR_CC_FLUSH,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_RELEASE_FOR_CC_FLUSH,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_NETWORK_QUERY_OPEN,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_MDL_READ,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_MDL_READ_COMPLETE,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_PREPARE_MDL_WRITE,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_MDL_WRITE_COMPLETE,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_VOLUME_MOUNT,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_VOLUME_DISMOUNT,
     0,
     PtPreOperationPassThrough,
     PtPostOperationPassThrough},
 
    {IRP_MJ_OPERATION_END} };
1
2
3
4
5
6
7
8
9
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
FLT_PREOP_CALLBACK_STATUS
PtCreatePreOperationPassThrough(
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __deref_out_opt PVOID* CompletionContext)
    /*++
 
    Routine Description:
 
        This routine is the main pre-operation dispatch routine for this
        miniFilter. Since this is just a simple passThrough miniFilter it
        does not do anything with the callbackData but rather return
        FLT_PREOP_SUCCESS_WITH_CALLBACK thereby passing it down to the next
        miniFilter in the chain.
 
        This is non-pageable because it could be called on the paging path
 
    Arguments:
 
        Data - Pointer to the filter callbackData that is passed to us.
 
        FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
            opaque handles to this filter, instance, its associated volume and
            file object.
 
        CompletionContext - The context for the completion routine for this
            operation.
 
    Return Value:
 
        The return value is the status of the operation.
 
    --*/
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext);
 
    return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
FLT_POSTOP_CALLBACK_STATUS
PtCreatePostOperationPassThrough(
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __in_opt PVOID CompletionContext,
    __in FLT_POST_OPERATION_FLAGS Flags)
    /*++
 
    Routine Description:
 
        This routine is the post-operation completion routine for this
        miniFilter.
 
        This is non-pageable because it may be called at DPC level.
 
    Arguments:
 
        Data - Pointer to the filter callbackData that is passed to us.
 
        FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing
            opaque handles to this filter, instance, its associated volume and
            file object.
 
        CompletionContext - The completion context set in the pre-operation routine.
 
        Flags - Denotes whether the completion is successful or is being drained.
 
    Return Value:
 
        The return value is the status of the operation.
 
    --*/
{
    NTSTATUS ntStatus;
    PFLT_FILE_NAME_INFORMATION pNameInfo = NULL;
 
    UNREFERENCED_PARAMETER(Data);
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext);
    UNREFERENCED_PARAMETER(Flags);
 
    /*
    typedef struct _FLT_FILE_NAME_INFORMATION {
      USHORT                     Size;
      FLT_FILE_NAME_PARSED_FLAGS NamesParsed;
      FLT_FILE_NAME_OPTIONS      Format;
      UNICODE_STRING             Name;
      UNICODE_STRING             Volume;
      UNICODE_STRING             Share;
      UNICODE_STRING             Extension;
      UNICODE_STRING             Stream;
      UNICODE_STRING             FinalComponent;
      UNICODE_STRING             ParentDir;
    } FLT_FILE_NAME_INFORMATION, *PFLT_FILE_NAME_INFORMATION;
    */
 
    ntStatus = FltGetFileNameInformation(Data,
        FLT_FILE_NAME_NORMALIZED |
        FLT_FILE_NAME_QUERY_DEFAULT,
        &pNameInfo);
    if (NT_SUCCESS(ntStatus))
    {
        FltParseFileNameInformation(pNameInfo);
        DbgPrint("FileName:%wZ\n", &pNameInfo->Name);
        FltReleaseFileNameInformation(pNameInfo);
    }
 
    PT_DBG_PRINT(PTDBG_TRACE_ROUTINES,
        ("PassThrough!PtPostOperationPassThrough: Entered\n"));
 
    return FLT_POSTOP_FINISHED_PROCESSING;
}
  • 枚举MiniFilterFItEnumerateFilters,它会返回过滤器对象(FLT_FILTER)的地址根据过滤器对象地址,加上偏移,获得Minifilter中PreCall、 PostCallIRP等信息的结构体指针,可以Hook掉。

    应用:监控进程创建

    思路:
  • NtCreateSection(进程创建)
    • 对应的Irp是IRP_MJ ACQUIRE FOR_SECTION_SYNCHRONIZATION,在Passthrough中为这个Irp注册回调函数
    • 监控Data->lopb->Parameters.AcquireForSectionSynchronization.PageProtection ==PAGE_EXECUTE,如果等于就是在创建进程,return STATS_ACCESS_DENY
  • 优点:SSDT HOOK只支持x86(SSDT HOOK改内核,会触发patchguard机制,除非使用VT技术欺骗操作系统),Minifilter不会触发patchguard机制,所以x86和x64 都支持

    CALLBACK IRQL

  • 1.a preoperation callback routine can be called at IRQL_PASSIVE_LEVEL or at IRQL_APC_LEVELS Typically it is called at IRQL_PASSIVE_LEVEL
  • 2.lf a minifilter driver's preoperation callback routine returns
    FLT_PREOP_SYNCHRONIZE for an lRP-based l/O operation,the corresponding postoperation callback routine is called at lRQL <= APC_LEVEL, in the same thread context as the preoperation
    callback routine.
  • 3.The postoperation callback routine for a fast I/O operation is calledIRQL_PASSIVE_LEVEL, in the same thread context as the preoperation callback routine.
  • 4.Post-create callback routines are called at IRQL_PASSIVE_LEVEL
    in the context of the thread that originated the IRP_MJ_CREATE operation.
  • 总而言之,preoperation一定处于IRQL_APC_LEVELS或者IRQL_PASSIVE_LEVEL ;而postoperation根据具体情况具体分析:2-4中情况下,处于IRQL_PASSIVE_LEVEL,其他情况就不好说了。

    避免重入

  • Minifilter中不建议使用Zw*函数,而是使用Flt*函数,避免重入(死循环)
  • eg:不能使用zwCreateFile等函数,可能导致重入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
FltCreateFile
FltReadFile
FltWriteFile
FltClose
FltQueryXxxFltSetXxXFltGetXxx
FltPerformXxx
 
ntStatus = FltCreateFile(pFilter, ///这个参数是注册Minifilter返回的句柄
    pDstInstance, ///这个参数是实例的指针,从FltObjects中拿,这两个参数是FltCreateFile特有的,其他参数和FltReadFile、FltWriteFile等都一致
    &hDstFile,
    GENERIC_WRITE | SYNCHRONIZE,
    &objDstAttrib,
    &ioStatus,
    0,
    FILE_ATTRIBUTE_NORMAL,
    FILE_SHARE_READ |
    FILE_SHARE_WRITE |
    FILE_SHARE DELETE,
    FILECREATE,
    CreateOptions,
    NULL,
    0,
    0);

上下文

  • 设备扩展是什么?
    • 进程的创建和监视,曾经把进程的一些信息放入设备扩展里面
    • 在Sfilter创建过滤设备对象的时候,设备对象存放着下层设备对象的指针(过滤设备所在的栈,未绑定之前的栈顶上的设备对象指针)
    • 可以理解为设备扩展的''上下文'',
  • context上下文:其实就是附着在某个对象上的一段内存,这段内存缓存的相关数据由自己定义;
    • eg:
      1.对象: 人
      2.上下文: 口袋
      3.数据: 手机
      如果人没有口袋,在路上想付款的时候,发现没有身上手机,只能回家去取(相当于计算机重新获取数据的过程)
      如果有口袋,出门之前把手机放口袋里(一次性把数据查询出来,缓存到上下文里面),在路上想付款的时候,直接从口袋里里掏出手机(想要拿数据的时候,直接从上下文中拿数据,上下文可以提高计算机获取数据的效率)
      • 利用缓存提高程序执行的效率是非常重要的思想,包括在系统架构里面,经常使用Redis(NOSQL数据库)把平时查询的数据库缓存起来,来缓解关系数据库后台执行程序的压力.

        Minifilter上下文

  • 和中断、进程上下文区别:
    • 中断上下文:中断传过来的数据,中断前进程的上下文
    • 进程上下文:寄存器,栈,堆,描述进程的结构体,
  • Minifilter的上下文:其实就是附着在Minifilter中对象上的一段内存,这段内存缓存的相关数据由自己定义(其实是Mifnifilter运行期间的所在环境中的信息)
    • Minifilter中常见的对象
      FileObject,Instance等,可以给这些对象分配一块内存,存放自定义的数据
    • 分配上下文
1
2
FltAllocateContext // 分配上下文,可以指定上下文的类型,比如Stream Context,Stream Handle Context,Instance Contxt等等
FltReleaseContext // 释放上下文,其实就是把引用计数减1,直到引用计数变为0之后才释放
  • Minifilter上下文的应用:写关闭
    • 写关闭:以写的方式打开,得到文件的句柄,往这个文件写入数据,最后把文件关闭掉
    • (调用CteateFile()->ReadFile()->CloseFile())
    • 主防或者反病毒一般不关注读关闭(读关闭不会造成系统破坏,最多会造成隐私泄漏)
    • 读/写关闭在应用层都是调用CloseHandle(FileHandle),所以单从FileHanle这个参数是无法知道是读关闭还是写关闭(在XP系统,IRP_MJ_Clean这个IRP里面information等于2,就表示是写关闭,否则就是都关闭;但在vista中这个条件就不成立了)
    • 所以在Sfilter里面是根据文件最后修改时间,如果是1min之内,则认为是写关闭。
    • Minifilter则可以使用上下文来记录标记,比如打开文件文件的时候是可以知道R/W的(必须传一个读写的标志),然后把这个标记信息记录在文件(FileObject)的上下文里面,Clean的时候(调用CloseHandle(FileHandle)的时候),通过FilerHander找到对应的FileObject的上下文找到打开时记录的R/W的标志,如果是写的话,即写关闭就处理它(扫描一遍该文件,和病毒库的特征码匹配一遍,防止生成病毒)。

      Minifilter上下文的分类

  • 分类依据是什么?
    • Minifilter有很多种对象,根据对象不同分为不同的类。
    • 一个文件从磁盘打开加载到内存之后,会产生以下这些
  • Stream Context(流上下文),绑定到FCB (File control Block,文件控制块)的上下文,
    文件和FCB是一对一的关系。
1
2
FltGetStreamContext // 获取对象上的上下文
FltSetStreamContext // 将缓存重新设置到对象上,比如修改了上下文的数据,使用这个函数把上下文更新到对象上
  • Stream Handle Context (流句柄上下文),就是常见FO(FileObjec),文件和FileObjec是多对一的关系。
    • 写关闭
1
2
FltGetStreamHandleContext
FltSetStreamHandleContext
  • Instance Context(实例上下文),也就是过滤驱动在文件系统的设备堆栈上创建的一个过滤器实例;
1
2
FltGetlnstanceContext // 获取对象上的上下文
FltSetInstanceC ontext // 将缓存重新设置到对象上,比如修改了上下文的数据,使用这个函数把上下文更新到对象上
  • volume context(卷上下文),卷就是通常看到的C,D盘以及网络重定向器,一般情况下一个券对一个对滤器实例对象,在实际应用上经常用Instance Context来代替volume Context。(在启动Minifilter的时候有多少卷设备对象就生成多少个Minigilter实例,安装到卷设备上,安装的那一刻可以把卷设备的信息查询出来(卷的名字,卷的文件系统信息,文件系统类型,卷的扇区大小等))放到Instance Context,以后要想知道卷的信息,直接从Instance Context获取即可。
1
2
FltGetVolumeContext
FltSetvolumeContext
  • 文件上下文(vista之后)
1
2
FltGetFileContext
FltSetFileContext
  • 其中, Stream Handle ContextInstance Context使用频率最高
  • 注册Minifilter的时候除了一组用来拦截Irp的fileMonitorCallbacks数组,还有一组用来清理Context的回调函数数组
1
2
3
4
5
6
7
8
9
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
PFLT_FILTER g_pFilter = NULL;
const FLT_CONTEXT_REGISTRATION ContextRegistration[]=
    {
        /// 在释放context之前调用,可以在此释放context里的内存等
        FLT_INSTANCE_CONTEXT,
        0,
        CtxContextCleanup, ///每种类型的Context都共用同一个CtxContextCleanup
    ///Q:既然有FltReleaseContext来释放Context了,为什么这里还需要CtxContextCleanup?
    ///A:是为了针对Minifilter的Context中保存了一些其他资源或者指向其他内存的情况,可以用CtxContextCleanup释放掉,比如Minifilter的Context中存放了文件的句柄、锁、指针,得先用CtxContextCleanup释放掉,才可以用FltReleaseContext来释放Context(类似C++中的delete,释放内存之前调用析构函数把内存中的其他资源释放掉,再释放要内存)
        CTX_INSTANCE_CONTEXT_SIZE,
        CTX_INSTANCE_CONTEXT_TAG
    },
    {
        FLT_FILE_CONTEXT,
        0,
        CtxContextCleanup,
        CTX_FILE_CONTEXT_SIZE,
        CTX_FILE_CONTEXT_TAG
    },
    {
        FLT_STREAM_CONTEXT,
        0,
        CtxContextCleanup,
        CTX_STREAM_CONTEXT_SIZE,
        CTX_STREAM_CONTEXT_TAG
    },
    {
        FLT_STREAMHANDLE_CONTEXT,
        0,
        CtxContextCleanup,
        CTX_STREAMHANDLE_CONTEXT_SIZE,
        CTX_STREAMHANDLEICONTEXT_TAG
    },
    {
        FLT_CONTEXT_END
    }
}

Context使用例子

1
2
3
4
5
6
7
8
9
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
/// @Warring 多线环境下记得加锁
 
typedef struct _INSTANCE_CONTEXT{
    ... ///自定义的数据
}INSTANCE_CONTEXT,*PINSTANCECCONTEXT;
 
PINSTANCE_CONTEXT pContext = NULL;
 
/// 分配与设置
ntStatus = FltGetInstanceContext(FltObjects->Instance,&pContext); ///拿到上下文
 
if(NT_SUCCESS(ntStatus) == FALSE) ///失败说明还没有为其指定上下文
{
    /// 分配上下文,指定上下文的类型
    ntStatus = FltAllocateContexti(g_pFilter, ///Minifilter的句柄
        FLT_INSTANCE_CONTEXT, ///Context类型
        sizeof(INSTANCE_CONTEXT), ///Context大小
        PagedPool, ///从PagedPool中分配
        &pContext), ///分配的内存的首地址放在这个指针上
    if(NT_SUCCESS(Status) == FALSE){
        return ntStatus; ///如果分配失败,该函数直接return
    }
    RtZeroMemory(pContext,sizeof(INSTANCE_CONTEXT)); ///初始化为0
}
/// 结构体里面定义了设备类型和结构体类型
pContext->m_DeviceType = VolumeDeviceType;
pContext->m_FSType = volumeFilesystemType;
FltSetInstanceContext(FltQbjects->Instance,FLT_SET_CONTEXT_TREPLACE_IF_EXISTS,pContext,NLLL); ///把刚分配的上下文绑定到instance上去,引用计数+1
 
if (pContext) // 如果分配成功,需要对引用计数进行减1,因为实际上没有新的Context产生,为保持引用计数的数量,一加就需要一减
{
    FltReleaseContext(pContext); ///引用计数-1
}
 
/// 获取访问
PINSTANCE_CONTEXT pContext = NULL;
Status = FltGetInstanceContext{EItObjects->Instance,&pContext); ///引用计数+1
pContext->xxx->xxx;
FltReleaseConte // -1

RO-R3通信新途径

  • Minifilter框架提供了两套API,一套是在R3中调用的,另一套是在R0中调用的
    • R3:Filterxxx
    • R0:Fltxxx
  • 这些API不只局限于在Minifilter中使用,只要当前建立的工程是基于Minifilter框架的驱动,如果在Minifilter使用了HOOK等其他机制,都可以用这套API进行通信。

    R3(主动)->R0

  • 在NT驱动中:R3通过DeviceIoControl发数据给R0,R0通过DispatchIoctr来处理R3的下发的数据
  • 在Minifilter中:R3是通过FilterSendMessage发数据给R0,R0通过fnMessageFromClient处理从R3 FilterSendMessage的请求
    • 没有控制码了怎么办,自己加个控制码,实际上是简化了控制码,想怎么定义就怎么定义,反而更方便了
  • R3主动给R0发数据的情景
    • 下规则
    • R3告诉R0监控什么位置
    • 杀一个进程,删一个文件,枚举隐藏的进程,枚举hook的函数
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
/// R0创建端口,
/// @note 可以指定安全属性,比如可以指定为只允许管理员连接
const PWSTR ScannerPortName = L"\\ScannerPort";
//    status = FltBuildDefaultSecurityDescriptor(&sd, FLT_PORT_ALL_ACCESS); ///创建安全描述符,指定为只允许管理员连接
FltCreateCommunicationPort(g_pEilter,
    &g_pSeryerPort,
    &oa, ///设置PORT的名字
    NULL,
    ///获得R3端口g_pClientPort等
    fnConnectFromCRlient, ///有客户端连接R0端口的时候被调用
    // 作用1:这个函数执行的时候处在应用层当前进程的上下文中,可以记录下进程的信息,比如pid和Exprocess结构,以后发现由这个客户端发下来的请求,就可以把他放行了
    // 作用2:拿到R3的端口
    fnDisconnectFromclient, ///当客户端断开连接的时候被调用
    fnMessageFromClient, ///处理从R3 FilterSendMessage的请求,名字和随便起,只要保证接口一致,注册时一一对应即可
    1);
 
/// R3连接R0,即R3拿到R0的端口
FilterConnectCommunicationPort
 
/// R3主动发请求给RO
FilterSendMessage(Port, ///拿到的R0端口
                  &request, ///R3给R0发数据的缓存,自定义控制码放在这里
                  sizeof(REQUEST), //缓存的大小
                  &reply, ///R0处理完数据返回给R3的结果
                  sizeof(REPLY),
                  &dwRtn); ///实际上传输的字节数,对应IRP中的IOSTATUS.Information
 
/// R0处理从R3 FilterSendMessage的请求
NTSTATUS fnMessageFromClient(
    IN PVOID PortCookie, ///Port相关的私有数据
    IN PVOID InputBuffer OPTIONAL,
    IN ULONG InputBufferLength,
    OUT PVOLD OutputBuffer OPTIONAL,
    IN PULONG OutputBufferLength,
    OUT PULONG ReturnOutputEufferLength ///实际传输的长度
    )
{
    /// 这里通信方式使用的是neither io需要ProbeForRead和ProbeForWite
    /// 1.保证内存对齐,2.保证地址合法(保证时用户态的地址,应用层程序没有权限访问内核态的地址),避免提权
    __try
    {
        ProbeForRead(InputBuffer,InputBufferLength,sizeof(ULONG));
        //GET InputBuffer
        //Do something:
        ProbeForWite(OutputBuffer,OutputBufferLength,sizeof(ULONG));
        //Copy Result to Outputbuffer
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        return STATUS_NOT_IMPLEMENTED;
    }
 
    return STATUS_SUCCESS;
}

R0(主动)->R3->R0(弹窗模型)

  • R0监控到数据,R0通过FltSendMessage发数据给R3(FltSendMessage可以设置超时等待,比如内核层等待弹窗返回结果40s或者无限等待),R3开启多个线程(每个线程都可用来接受R0发送的数据),通过FilterGetMessage来处理从R0的请求(异步读,不管拿没拿到数据都会返回),拿到数据处理完之后通过FilterReplyMessage返回给R0
  • R0->R3
1
2
3
4
5
6
7
8
9
10
11
/// 发送消息给R3
timeout.QuadPart = (LONGLONG)40* -10000000i64; ///40 seconds,设为0不等待,设置为null无限等待
Status = FltSendMessage(g_pFilter,
                        &g_pClientPort, ///客户端的端口
                        &request,
                        sizeof(SCANNER_NOTIFICATION)
                        &reply,
                        replySize,
                        &timeout);
/// 关闭端口
FItCloseCommunicationPort(g_pServerPort);
  • R3->R0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// 响应R0的:FltSendMessage()
/// 一般放在后台的工作者线程中去执行,而且可以创建多线程,在多个线程中去处理这个部分代码,提高处理的效率
FilterConnectCommunicationPort(ScannerPortName, ///与R0端口名字一致
                               0,
                               NULL,
                               0,
                               NULL,
                               &Port); ///RO端口
 
/// 处理从R0来的请求,即R0调用FltSendMessage的请求
completion = CreateloCompletionPort(port,NULL,0,1); ///创建一个完成端口
FilterGetMessage(Port, ///异步读,拿没拿到数据都不等待,直接返回
                 &message->MessagelHleader, ///数据放在这里
                 FIELD_OFFSET(SANDBOX_MESSAGE,Ovlp),
                 &message->Ovlp);
while(1)
{
      GetQueuedCompletionStatus(completion,&outSize,&key,&pOvlp,INFINITE); ///在完成端口这里阻塞,一旦R0调用FltSendMessage(),completion完成端口有了数据之后,这个函数就会返回
    ... ///处理数据
 
    /// R3处理结果返回给内核层
    FilterReplyMessage(Port,
                       (PEILTER_REPLY_HEADER)
                       &replyMessage,
                       sizeof(replyMessage));
    /// 异步读,继续下一个循环
    FilterGetMessaget(Port,
                      &message->MessageHeader,
                      FIELD_OFFSET_SANDBOXMESSAGE,
                      Ovlp),
                      &message->Ovlp);
}

深入分析SCANNER

  • 微软WDK7600中的一个demo,纯c写的,比新的WDK的C++写的要好
  • 杀毒引擎,主防都可以基于这个项目进行二次开发

    编译运行

  • 编译运行Scanner
    • 编译:应用层程序需要包含以下头文件和库文件
1
2
#include <fltuser.h> //C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um
#pragma comment(lib,"fltLib.lib") //C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x86
  • 运行:先用.inf文件安装驱动,在启动驱动:net start scanner,在运行scanuser.exe
  • demo中只监控.txt,doc,.inf,.bat"foul"代表病毒的特征码
    • 扫描文件,发现正在写入病毒则拒绝写入,发现已存在的病毒则拒绝打开


  • notepad.exe保存的时候,是一次性写入,所以没办法模拟写关闭(逐次写入一个字符,直到完全写入foul),只能在应用层程序中编码openfile—>writefile->openfile->...

    代码分析

    驱动层
  • DriverEntry()
    • FltRegisterFilter()
      • FilterRegistration
        • ContextRegistration
        • Callbacks
          • ScannerPreCreate()
          • ScannerPostCreate()
            • ScannerpCheckExtension()
              • ScannerExtensionsToScan[]
              • FltSetStreamHandleContext()
              • ScannerpScanFileInUserMode()
                • FltGetVolumeFromInstance()
                • FltGetVolumeProperties()
                • SCANNER_NOTIFICATION[]
                • FltReadFile()
                • FltSendMessage()
          • ScannerPreWrite()
            • FltGetStreamHandleContext()
            • MmGetSystemAddressForMdlSafe()
            • SCANNER_NOTIFICATION[]
            • FltSendMessage()
          • ScannerPreCleanup()
            • FltGetStreamHandleContext()
            • ScannerpScanFileInUserMode()回扫
          • 删除重命名可以参考Sfilter的Hips来完善
    • FltBuildDefaultSecurityDescriptor()
    • FltCreateCommunicationPort()
      • ScannerPortConnect()
      • ScannerPortDisconnect()
        • FltCloseClientPort()
    • FltStartFiltering()
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
/*++
 
Copyright (c) 1999-2002  Microsoft Corporation
 
Module Name:
 
    scanner.c
 
Abstract:
 
    This is the main module of the scanner filter.
 
    This filter scans the data in a file before allowing an open to proceed.  This is similar
    to what virus checkers do.
 
Environment:
 
    Kernel mode
 
--*/
 
#include <fltKernel.h>
#include <dontuse.h>
#include <suppress.h>
#include "scanuk.h"
#include "scanner.h"
 
#pragma prefast(disable:__WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "Not valid for kernel mode drivers")
 
//
//  Structure that contains all the global data structures
//  used throughout the scanner.
//
 
SCANNER_DATA ScannerData;
 
//
//  This is a static list of file name extensions files we are interested in scanning
//
 
const UNICODE_STRING ScannerExtensionsToScan[] =
{ RTL_CONSTANT_STRING(L"doc"),
  RTL_CONSTANT_STRING(L"txt"),
  RTL_CONSTANT_STRING(L"bat"),
  RTL_CONSTANT_STRING(L"cmd"),
  RTL_CONSTANT_STRING(L"inf"),
    /*RTL_CONSTANT_STRING( L"ini"),   Removed, to much usage*/
    {0, 0, NULL}
};
 
//
//  Function prototypes
//
 
NTSTATUS
ScannerPortConnect(
    __in PFLT_PORT ClientPort,
    __in_opt PVOID ServerPortCookie,
    __in_bcount_opt(SizeOfContext) PVOID ConnectionContext,
    __in ULONG SizeOfContext,
    __deref_out_opt PVOID* ConnectionCookie
);
 
VOID
ScannerPortDisconnect(
    __in_opt PVOID ConnectionCookie
);
 
NTSTATUS
ScannerpScanFileInUserMode(
    __in PFLT_INSTANCE Instance,
    __in PFILE_OBJECT FileObject,
    __out PBOOLEAN SafeToOpen
);
 
BOOLEAN
ScannerpCheckExtension(
    __in PUNICODE_STRING Extension
);
 
//
//  Assign text sections for each routine.
//
 
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, ScannerInstanceSetup)
#pragma alloc_text(PAGE, ScannerPreCreate)
#pragma alloc_text(PAGE, ScannerPortConnect)
#pragma alloc_text(PAGE, ScannerPortDisconnect)
#endif
 
//
//  Constant FLT_REGISTRATION structure for our filter.  This
//  initializes the callback routines our filter wants to register
//  for.  This is only used to register with the filter manager
//
 
const FLT_OPERATION_REGISTRATION Callbacks[] = {
    { IRP_MJ_CREATE, ///拦截Create操作,即打开文件的时候看文件是否有"foul",打开文件前会去扫描文件(得到文件的签名,发云端去查询好与坏)一般这些耗时的任务都是放在R3去做的
      0,
      ScannerPreCreate,
      ScannerPostCreate},
 
    { IRP_MJ_CLEANUP,
      0,
      ScannerPreCleanup,
      NULL},
 
    { IRP_MJ_WRITE,
      0,
      ScannerPreWrite,
      NULL},
 
    { IRP_MJ_OPERATION_END}
};
 
const FLT_CONTEXT_REGISTRATION ContextRegistration[] = {
    { FLT_STREAMHANDLE_CONTEXT,
      0,
      NULL, ///上下文没有保存其他资源需要清理的,清理函数设置为null
      sizeof(SCANNER_STREAM_HANDLE_CONTEXT),
      'chBS' },
 
    { FLT_CONTEXT_END }
};
 
/**
*  @brief       这是一个代表 Student 的类.
*  @details     Student 有参数:name, age, and grade. 我们可以设置他们.
*/
const FLT_REGISTRATION FilterRegistration = {
    sizeof(FLT_REGISTRATION),           ///< Size
    FLT_REGISTRATION_VERSION,           ///< Version
    0,                                  ///< Flags
    ContextRegistration,                ///< ContextRegistration
    Callbacks,                          ///< Operation callbacks,结构体数组,每一个结构体封装了IRP和与之对应的回调函数,注册的回调函数就是放在这个结构体数组里
    ScannerUnload,                      ///< FilterUnload
    ScannerInstanceSetup,               ///< InstanceSetup,instance类似Sfilter中的过滤设备对象,有多少的卷设备就生成多个个实例与之绑定,生成的过程是不可见的(看不到生成实例的代码,是由框架去做的)
                                        ///< 可以记录下instance对应卷设备的一些属性,比如说是C盘还是D盘,文件类型是FAT32还是NTFS,卷的扇区大小是512还是4K。存到一个上下文里面(是个缓存),下次用到的时候,之间从缓存里面拿数据既可以了,提高效率。
    ScannerQueryTeardown,               ///< InstanceQuery Teardown
    NULL,                               ///< InstanceTeardownStart
    NULL,                               ///< InstanceTeardownComplete
    NULL,                               ///< GenerateFileName
    NULL,                               ///< GenerateDestinationFileName
    NULL                                ///< NormalizeNameComponent
};
 
////////////////////////////////////////////////////////////////////////////
//
//    Filter initialization and unload routines.
//
////////////////////////////////////////////////////////////////////////////
 
NTSTATUS
DriverEntry(
    __in PDRIVER_OBJECT DriverObject,
    __in PUNICODE_STRING RegistryPath
)
/*++
 
Routine Description:
 
    This is the initialization routine for the Filter driver.  This
    registers the Filter with the filter manager and initializes all
    its global data structures.
 
Arguments:
 
    DriverObject - Pointer to driver object created by the system to
        represent this driver.
 
    RegistryPath - Unicode string identifying where the parameters for this
        driver are located in the registry.
 
Return Value:
 
    Returns STATUS_SUCCESS.
--*/
{
    OBJECT_ATTRIBUTES oa;
    UNICODE_STRING uniString;
    PSECURITY_DESCRIPTOR sd;
    NTSTATUS status;
 
    UNREFERENCED_PARAMETER(RegistryPath);
 
    //
    //  Register with filter manager.
    //
 
    status = FltRegisterFilter(DriverObject,
        &FilterRegistration,
        &ScannerData.Filter); ///Minifilter句柄
 
    if (!NT_SUCCESS(status)) {
        return status;
    }
 
    //
    //  Create a communication port.
    //
 
    RtlInitUnicodeString(&uniString, ScannerPortName); ///初始化端口
 
    //
    //  We secure the port so only ADMINs & SYSTEM can acecss it.
    //
 
    status = FltBuildDefaultSecurityDescriptor(&sd, FLT_PORT_ALL_ACCESS); ///创建安全描述符,指定为只允许管理员连接
 
    if (NT_SUCCESS(status)) {
        InitializeObjectAttributes(&oa,
            &uniString,
            OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
            NULL,
            sd);
        /// @todo  完善回调函数
        status = FltCreateCommunicationPort(ScannerData.Filter,
            &ScannerData.ServerPort,
            &oa,
            NULL,
            ScannerPortConnect,
            ScannerPortDisconnect,
            NULL,///@todo:处理R3的请求,这里R3没有主动发消息给R0,都是R0主动发消息给R3的,所以传NULL
            1);
        //
        //  Free the security descriptor in all cases. It is not needed once
        //  the call to FltCreateCommunicationPort() is made.
        //
 
        FltFreeSecurityDescriptor(sd);
 
        if (NT_SUCCESS(status)) {
            //
            //  Start filtering I/O.
            //
 
            status = FltStartFiltering(ScannerData.Filter); ///启动Minifilter驱动
 
            if (NT_SUCCESS(status)) {
                return STATUS_SUCCESS;
            }
 
            FltCloseCommunicationPort(ScannerData.ServerPort);
        }
    }
 
    FltUnregisterFilter(ScannerData.Filter);
 
    return status;
}
 
/**
*  @brief       ScannerPortConnect 是加载驱动程序后调用的第一个驱动程序提供的例程,它负责初始化驱动程序;
*  @param[in]   pDriverObject 由IO管理器创建,标识驱动,创造设备对象,指向 DRIVER_OBJECT 结构;
*  @param[in]   pRegPath 驱动安装后在注册表中的路径,指向 UNICODE_STRING 结构的指针,
*                        该结构指定注册表中驱动程序的 Parameters 项的路径;
*  @return      ntStatus 成功返回0,否则为非0,如果例程成功,则它必须返回 STATUS_SUCCESS。
*                        否则,它必须返回在 ntstatus中定义的错误状态值之一;
*  @pre
*  @see         https://docs.microsoft.com/zh-cn/windows-hardware/drivers/wdf/driverentry-for-kmdf-drivers
*/
NTSTATUS
ScannerPortConnect(
    __in PFLT_PORT ClientPort,
    __in_opt PVOID ServerPortCookie,
    __in_bcount_opt(SizeOfContext) PVOID ConnectionContext,
    __in ULONG SizeOfContext,
    __deref_out_opt PVOID* ConnectionCookie
)
/*++
 
Routine Description
 
    This is called when user-mode connects to the server port - to establish a
    connection
 
Arguments
 
    ClientPort - This is the client connection port that will be used to
        send messages from the filter
 
    ServerPortCookie - The context associated with this port when the
        minifilter created this port.
 
    ConnectionContext - Context from entity connecting to this port (most likely
        your user mode service)
 
    SizeofContext - Size of ConnectionContext in bytes
 
    ConnectionCookie - Context to be passed to the port disconnect routine.
 
Return Value
 
    STATUS_SUCCESS - to accept the connection
 
--*/
{
    PAGED_CODE();
 
    UNREFERENCED_PARAMETER(ServerPortCookie);
    UNREFERENCED_PARAMETER(ConnectionContext);
    UNREFERENCED_PARAMETER(SizeOfContext);
    UNREFERENCED_PARAMETER(ConnectionCookie);
 
    ASSERT(ScannerData.ClientPort == NULL);
    ASSERT(ScannerData.UserProcess == NULL);
 
    //
    //  Set the user process and port.
    //
 
    ScannerData.UserProcess = PsGetCurrentProcess(); ///拿到当前进程的Exprocess结构
    ScannerData.ClientPort = ClientPort; ///拿到客户端的结构体
 
    DbgPrint("!!! scanner.sys --- connected, port=0x%p\n", ClientPort);
 
    return STATUS_SUCCESS;
}
 
VOID
ScannerPortDisconnect(
    __in_opt PVOID ConnectionCookie
)
/*++
 
Routine Description
 
    This is called when the connection is torn-down. We use it to close our
    handle to the connection
 
Arguments
 
    ConnectionCookie - Context from the port connect routine
 
Return value
 
    None
 
--*/
{
    UNREFERENCED_PARAMETER(ConnectionCookie);
 
    PAGED_CODE();
 
    DbgPrint("!!! scanner.sys --- disconnected, port=0x%p\n", ScannerData.ClientPort);
 
    //
    //  Close our handle to the connection: note, since we limited max connections to 1,
    //  another connect will not be allowed until we return from the disconnect routine.
    //
 
    FltCloseClientPort(ScannerData.Filter, &ScannerData.ClientPort); ///把应用层端口关闭
 
    //
    //  Reset the user-process field.
    //
 
    ScannerData.UserProcess = NULL;
}
 
NTSTATUS
ScannerUnload(
    __in FLT_FILTER_UNLOAD_FLAGS Flags
)
/*++
 
Routine Description:
 
    This is the unload routine for the Filter driver.  This unregisters the
    Filter with the filter manager and frees any allocated global data
    structures.
 
Arguments:
 
    None.
 
Return Value:
 
    Returns the final status of the deallocation routines.
 
--*/
{
    UNREFERENCED_PARAMETER(Flags);
 
    //
    //  Close the server port.
    //
 
    FltCloseCommunicationPort(ScannerData.ServerPort);
 
    //
    //  Unregister the filter
    //
 
    FltUnregisterFilter(ScannerData.Filter);
 
    return STATUS_SUCCESS;
}
 
NTSTATUS
ScannerInstanceSetup(
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __in FLT_INSTANCE_SETUP_FLAGS Flags,
    __in DEVICE_TYPE VolumeDeviceType,
    __in FLT_FILESYSTEM_TYPE VolumeFilesystemType
)
/*++
 
Routine Description:
 
    This routine is called by the filter manager when a new instance is created.
    We specified in the registry that we only want for manual attachments,
    so that is all we should receive here.
 
Arguments:
 
    FltObjects - Describes the instance and volume which we are being asked to
        setup.
 
    Flags - Flags describing the type of attachment this is.
 
    VolumeDeviceType - The DEVICE_TYPE for the volume to which this instance
        will attach.
 
    VolumeFileSystemType - The file system formatted on this volume.
 
Return Value:
 
  FLT_NOTIFY_STATUS_ATTACH              - we wish to attach to the volume
  FLT_NOTIFY_STATUS_DO_NOT_ATTACH       - no, thank you
 
--*/
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(Flags);
    UNREFERENCED_PARAMETER(VolumeFilesystemType);
 
    PAGED_CODE();
 
    ASSERT(FltObjects->Filter == ScannerData.Filter);
 
    //
    //  Don't attach to network volumes.
    //
 
    if (VolumeDeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM) {
        return STATUS_FLT_DO_NOT_ATTACH;
    }
 
    return STATUS_SUCCESS;
}
 
NTSTATUS
ScannerQueryTeardown(
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __in FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
)
/*++
 
Routine Description:
 
    This is the instance detach routine for the filter. This
    routine is called by filter manager when a user initiates a manual instance
    detach. This is a 'query' routine: if the filter does not want to support
    manual detach, it can return a failure status
 
Arguments:
 
    FltObjects - Describes the instance and volume for which we are receiving
        this query teardown request.
 
    Flags - Unused
 
Return Value:
 
    STATUS_SUCCESS - we allow instance detach to happen
 
--*/
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(Flags);
 
    return STATUS_SUCCESS;
}
 
FLT_PREOP_CALLBACK_STATUS
ScannerPreCreate(
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __deref_out_opt PVOID* CompletionContext
)
/*++
 
Routine Description:
 
    Pre create callback.  We need to remember whether this file has been
    opened for write access.  If it has, we'll want to rescan it in cleanup.
    This scheme results in extra scans in at least two cases:
    -- if the create fails (perhaps for access denied)
    -- the file is opened for write access but never actually written to
    The assumption is that writes are more common than creates, and checking
    or setting the context in the write path would be less efficient than
    taking a good guess before the create.
 
Arguments:
 
    Data - The structure which describes the operation parameters.
 
    FltObject - The structure which describes the objects affected by this
        operation.
 
    CompletionContext - Output parameter which can be used to pass a context
        from this pre-create callback to the post-create callback.
 
Return Value:
 
   FLT_PREOP_SUCCESS_WITH_CALLBACK - If this is not our user-mode process.
   FLT_PREOP_SUCCESS_NO_CALLBACK - All other threads.
 
--*/
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext);
 
    PAGED_CODE(); ///Minifilter也遵循Irql的准则,这个宏会发现当前的Irql级别是否大于APC_LEVEL,如果是,就蓝屏
 
    //
    //  See if this create is being done by our user process.
    //
 
    if (IoThreadToProcess(Data->Thread) == ScannerData.UserProcess) { ///当前线程对应的进程等于ScannerData.UserProcess,表示操作是自己的进程下发的请求
        DbgPrint("!!! scanner.sys -- allowing create for trusted process \n");
 
        return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }
 
    return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}
 
BOOLEAN
ScannerpCheckExtension(
    __in PUNICODE_STRING Extension
)
/*++
 
Routine Description:
 
    Checks if this file name extension is something we are interested in
 
Arguments
 
    Extension - Pointer to the file name extension
 
Return Value
 
    TRUE - Yes we are interested
    FALSE - No
--*/
{
    const UNICODE_STRING* ext;
 
    if (Extension->Length == 0) {
        return FALSE;
    }
 
    //
    //  Check if it matches any one of our static extension list
    //
 
    ext = ScannerExtensionsToScan; ///扩展名的数组
 
    while (ext->Buffer != NULL) {
        if (RtlCompareUnicodeString(Extension, ext, TRUE) == 0) {
            //
            //  A match. We are interested in this file
            //
 
            return TRUE;
        }
        ext++;
    }
 
    return FALSE;
}
 
FLT_POSTOP_CALLBACK_STATUS
ScannerPostCreate(
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __in_opt PVOID CompletionContext,
    __in FLT_POST_OPERATION_FLAGS Flags
)
/*++
 
Routine Description:
 
    Post create callback.  We can't scan the file until after the create has
    gone to the filesystem, since otherwise the filesystem wouldn't be ready
    to read the file for us.
 
Arguments:
 
    Data - The structure which describes the operation parameters.
 
    FltObject - The structure which describes the objects affected by this
        operation.
 
    CompletionContext - The operation context passed fron the pre-create
        callback.
 
    Flags - Flags to say why we are getting this post-operation callback.
 
Return Value:
 
    FLT_POSTOP_FINISHED_PROCESSING - ok to open the file or we wish to deny
                                     access to this file, hence undo the open
 
--*/
{
    PSCANNER_STREAM_HANDLE_CONTEXT scannerContext;
    FLT_POSTOP_CALLBACK_STATUS returnStatus = FLT_POSTOP_FINISHED_PROCESSING;
    PFLT_FILE_NAME_INFORMATION nameInfo;
    NTSTATUS status;
    BOOLEAN safeToOpen, scanFile;
 
    UNREFERENCED_PARAMETER(CompletionContext);
    UNREFERENCED_PARAMETER(Flags);
 
    //
    //  If this create was failing anyway, don't bother scanning now.
    //
 
    if (!NT_SUCCESS(Data->IoStatus.Status) || ///这次操作失败
        (STATUS_REPARSE == Data->IoStatus.Status)) { ///重定向操作
        return FLT_POSTOP_FINISHED_PROCESSING; ///不监控了
    }
 
    //
    //  Check if we are interested in this file.
    //
 
    status = FltGetFileNameInformation(Data,
        FLT_FILE_NAME_NORMALIZED |
        FLT_FILE_NAME_QUERY_DEFAULT,
        &nameInfo);
 
    if (!NT_SUCCESS(status)) {
        return FLT_POSTOP_FINISHED_PROCESSING;
    }
 
    FltParseFileNameInformation(nameInfo); ///拿文件路径
 
    //
    //  Check if the extension matches the list of extensions we are interested in
    //
 
    scanFile = ScannerpCheckExtension(&nameInfo->Extension);
 
    //
    //  Release file name info, we're done with it
    //
 
    FltReleaseFileNameInformation(nameInfo); ///存放文件的内存是用引用用计数来管理的
 
    if (!scanFile) { ///不是感兴趣的文件扩展名
        //
        //  Not an extension we are interested in
        //
 
        return FLT_POSTOP_FINISHED_PROCESSING;
    }
 
    (VOID)ScannerpScanFileInUserMode(FltObjects->Instance,
        FltObjects->FileObject, ///读一部分,1024字节发到R3中去,扫描病毒的特征码有特定的算法,不一定从文件头读起
        &safeToOpen); ///存放扫描的结果
 
    if (!safeToOpen) {
        //
        //  Ask the filter manager to undo the create.
        //
 
        DbgPrint("!!! scanner.sys -- foul language detected in postcreate !!!\n");
 
        DbgPrint("!!! scanner.sys -- undoing create \n");
 
        FltCancelFileOpen(FltObjects->Instance, FltObjects->FileObject);
 
        Data->IoStatus.Status = STATUS_ACCESS_DENIED;
        Data->IoStatus.Information = 0;
 
        returnStatus = FLT_POSTOP_FINISHED_PROCESSING;
    }
    else if (FltObjects->FileObject->WriteAccess) { ///后续需要判断写关闭,所以这里要判断是否是以写的方式打开的,把写操作记录下来,如果是则后续还需要重新扫描文件
        //
        //
        //  The create has requested write access, mark to rescan the file.
        //  Allocate the context.
        //
 
        status = FltAllocateContext(ScannerData.Filter,
            FLT_STREAMHANDLE_CONTEXT,///流句柄上下文保存在file_object
            sizeof(SCANNER_STREAM_HANDLE_CONTEXT), ///就只有一个布尔值,用来标记此次是以写方式打开,后续需要些关闭
            PagedPool,
            &scannerContext);
 
        if (NT_SUCCESS(status)) {
            //
            //  Set the handle context.
            //
 
            scannerContext->RescanRequired = TRUE;
 
            (VOID)FltSetStreamHandleContext(FltObjects->Instance, ///流句柄上下文绑定到Instance
                FltObjects->FileObject,
                FLT_SET_CONTEXT_REPLACE_IF_EXISTS,
                scannerContext,
                NULL);
 
            //
            //  Normally we would check the results of FltSetStreamHandleContext
            //  for a variety of error cases. However, The only error status
            //  that could be returned, in this case, would tell us that
            //  contexts are not supported.  Even if we got this error,
            //  we just want to release the context now and that will free
            //  this memory if it was not successfully set.
            //
 
            //
            //  Release our reference on the context (the set adds a reference)
            //
 
            FltReleaseContext(scannerContext);  ///前面set会增加引用计数,所以这里需要release一下
        }
    }
 
    return returnStatus;
}
 
FLT_PREOP_CALLBACK_STATUS
ScannerPreCleanup(
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __deref_out_opt PVOID* CompletionContext
)
/*++
 
Routine Description:
 
    Pre cleanup callback.  If this file was opened for write access, we want
    to rescan it now.
 
Arguments:
 
    Data - The structure which describes the operation parameters.
 
    FltObject - The structure which describes the objects affected by this
        operation.
 
    CompletionContext - Output parameter which can be used to pass a context
        from this pre-cleanup callback to the post-cleanup callback.
 
Return Value:
 
    Always FLT_PREOP_SUCCESS_NO_CALLBACK.
 
--*/
{
    NTSTATUS status;
    PSCANNER_STREAM_HANDLE_CONTEXT context;
    BOOLEAN safe;
 
    UNREFERENCED_PARAMETER(Data);
    UNREFERENCED_PARAMETER(CompletionContext);
 
    status = FltGetStreamHandleContext(FltObjects->Instance, ///拿上下文
        FltObjects->FileObject,
        &context);
 
    if (NT_SUCCESS(status)) {
        if (context->RescanRequired) {///写关闭
            (VOID)ScannerpScanFileInUserMode(FltObjects->Instance,
                FltObjects->FileObject,
                &safe);
 
            if (!safe) {
                DbgPrint("!!! scanner.sys -- foul language detected in precleanup !!!\n"); ///这里只是简单打印警告,一般应该隔离该文件或者弹窗提醒用户比较好
            }
        }
 
        FltReleaseContext(context);
    }
 
    return FLT_PREOP_SUCCESS_NO_CALLBACK;
}
 
FLT_PREOP_CALLBACK_STATUS
ScannerPreWrite(
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __deref_out_opt PVOID* CompletionContext
)
/*++
 
Routine Description:
 
    Pre write callback.  We want to scan what's being written now.
 
Arguments:
 
    Data - The structure which describes the operation parameters.
 
    FltObject - The structure which describes the objects affected by this
        operation.
 
    CompletionContext - Output parameter which can be used to pass a context
        from this pre-write callback to the post-write callback.
 
Return Value:
 
    Always FLT_PREOP_SUCCESS_NO_CALLBACK.
 
--*/
{
    FLT_PREOP_CALLBACK_STATUS returnStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
    NTSTATUS status;
    PSCANNER_NOTIFICATION notification = NULL;
    PSCANNER_STREAM_HANDLE_CONTEXT context = NULL;
    ULONG replyLength;
    BOOLEAN safe = TRUE;
    PUCHAR buffer;
 
    UNREFERENCED_PARAMETER(CompletionContext);
 
    //
    //  If not client port just ignore this write.
    //
 
    if (ScannerData.ClientPort == NULL) {
        return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }
 
    status = FltGetStreamHandleContext(FltObjects->Instance, ///拿上下文,Q:写操作为什么也需要访问上下文,不是只有写关闭才需要访问?
        FltObjects->FileObject,
        &context);
 
    if (!NT_SUCCESS(status)) {
        //
        //  We are not interested in this file
        //
 
        return FLT_PREOP_SUCCESS_NO_CALLBACK; ///放行,A:只有在文件扩展名属于扩展数组指定的扩展名,才会为其创建上下文,所以在写操作,根据是否有上下文来判断文件是否是属于扩展数组指定的扩展名,即是否需要监控的扩展名
    }
 
    //
    //  Use try-finally to cleanup
    //
 
    try {
        //
        //  Pass the contents of the buffer to user mode.
        //
 
        if (Data->Iopb->Parameters.Write.Length != 0) { ///写的字节数
            //
            //  Get the users buffer address.  If there is a MDL defined, use
            //  it.  If not use the given buffer address.
            //
 
            if (Data->Iopb->Parameters.Write.MdlAddress != NULL) { ///direct io
                buffer = MmGetSystemAddressForMdlSafe(Data->Iopb->Parameters.Write.MdlAddress, ///映射成内核态对应的虚拟地址
                    NormalPagePriority);
 
                //
                //  If we have a MDL but could not get and address, we ran out
                //  of memory, report the correct error
                //
 
                if (buffer == NULL) {
                    Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
                    Data->IoStatus.Information = 0;
                    returnStatus = FLT_PREOP_COMPLETE;
                    leave;
                }
            }
            else {
                //
                //  Use the users buffer
                //
                /// Callbacks对Irp进行了封装,在Minifilter管理器应该已经对Buffer进行了Probe,所以这里就可以直接使用buffer
                buffer = Data->Iopb->Parameters.Write.WriteBuffer; ///不是direct io
            }
 
            //
            //  In a production-level filter, we would actually let user mode scan the file directly.
            //  Allocating & freeing huge amounts of non-paged pool like this is not very good for system perf.
            //  This is just a sample!
            //
 
            notification = ExAllocatePoolWithTag(NonPagedPool, ///分配一个结构体
                sizeof(SCANNER_NOTIFICATION),
                'nacS');
            if (notification == NULL) {  ///初始化结构体
                Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
                Data->IoStatus.Information = 0;
                returnStatus = FLT_PREOP_COMPLETE;
                leave;
            }
 
            notification->BytesToScan = min(Data->Iopb->Parameters.Write.Length, SCANNER_READ_BUFFER_SIZE);
 
            //
            //  The buffer can be a raw user buffer. Protect access to it
            //
 
            try {
                RtlCopyMemory(&notification->Contents,
                    buffer,
                    notification->BytesToScan);
            } except(EXCEPTION_EXECUTE_HANDLER) {
                //
                //  Error accessing buffer. Complete i/o with failure
                //
 
                Data->IoStatus.Status = GetExceptionCode();
                Data->IoStatus.Information = 0;
                returnStatus = FLT_PREOP_COMPLETE;
                leave;
            }
 
            //
            //  Send message to user mode to indicate it should scan the buffer.
            //  We don't have to synchronize between the send and close of the handle
            //  as FltSendMessage takes care of that.
            //
 
            replyLength = sizeof(SCANNER_REPLY);
 
            status = FltSendMessage(ScannerData.Filter, ///发给R3
                &ScannerData.ClientPort,
                notification,
                sizeof(SCANNER_NOTIFICATION),
                notification,
                &replyLength,
                NULL);
 
            if (STATUS_SUCCESS == status) {
                safe = ((PSCANNER_REPLY)notification)->SafeToOpen; ///R3扫描的结果
            }
            else {
                //
                //  Couldn't send message. This sample will let the i/o through.
                //
 
                DbgPrint("!!! scanner.sys --- couldn't send message to user-mode to scan file, status 0x%X\n", status);
            }
        }
 
        if (!safe) {
            //
            //  Block this write if not paging i/o (as a result of course, this scanner will not prevent memory mapped writes of contaminated
            //  strings to the file, but only regular writes). The effect of getting ERROR_ACCESS_DENIED for many apps to delete the file they
            //  are trying to write usually.
            //  To handle memory mapped writes - we should be scanning at close time (which is when we can really establish that the file object
            //  is not going to be used for any more writes)
            //
 
            DbgPrint("!!! scanner.sys -- foul language detected in write !!!\n");
 
            if (!FlagOn(Data->Iopb->IrpFlags, IRP_PAGING_IO)) {
                DbgPrint("!!! scanner.sys -- blocking the write !!!\n");
 
                Data->IoStatus.Status = STATUS_ACCESS_DENIED;
                Data->IoStatus.Information = 0;
                returnStatus = FLT_PREOP_COMPLETE;
            }
        }
    }
    finally {
        if (notification != NULL) {
            ExFreePoolWithTag(notification, 'nacS');
        }
 
        if (context) {
            FltReleaseContext(context);
        }
    }
 
    return returnStatus;
}
 
//////////////////////////////////////////////////////////////////////////
//  Local support routines.
//
/////////////////////////////////////////////////////////////////////////
 
NTSTATUS
ScannerpScanFileInUserMode(
    __in PFLT_INSTANCE Instance,
    __in PFILE_OBJECT FileObject,
    __out PBOOLEAN SafeToOpen
)
/*++
 
Routine Description:
 
    This routine is called to send a request up to user mode to scan a given
    file and tell our caller whether it's safe to open this file.
 
    Note that if the scan fails, we set SafeToOpen to TRUE.  The scan may fail
    because the service hasn't started, or perhaps because this create/cleanup
    is for a directory, and there's no data to read & scan.
 
    If we failed creates when the service isn't running, there'd be a
    bootstrapping problem -- how would we ever load the .exe for the service?
 
Arguments:
 
    Instance - Handle to the filter instance for the scanner on this volume.
 
    FileObject - File to be scanned.
 
    SafeToOpen - Set to FALSE if the file is scanned successfully and it contains
                 foul language.
 
Return Value:
 
    The status of the operation, hopefully STATUS_SUCCESS.  The common failure
    status will probably be STATUS_INSUFFICIENT_RESOURCES.
 
--*/
 
{
    NTSTATUS status = STATUS_SUCCESS;
    PVOID buffer = NULL;
    ULONG bytesRead;
    PSCANNER_NOTIFICATION notification = NULL;
    FLT_VOLUME_PROPERTIES volumeProps;
    LARGE_INTEGER offset;
    ULONG replyLength, length;
    PFLT_VOLUME volume = NULL;
 
    *SafeToOpen = TRUE;
 
    //
    //  If not client port just return.
    //
 
    if (ScannerData.ClientPort == NULL) {
        return STATUS_SUCCESS;
    }
 
    try {
        //
        //  Obtain the volume object .
        //
 
        status = FltGetVolumeFromInstance(Instance, &volume); ///拿到卷设备对象
 
        if (!NT_SUCCESS(status)) {
            leave;
        }
 
        //
        //  Determine sector size. Noncached I/O can only be done at sector size offsets, and in lengths which are
        //  multiples of sector size. A more efficient way is to make this call once and remember the sector size in the
        //  instance setup routine and setup an instance context where we can cache it.
        //
 
        status = FltGetVolumeProperties(volume, ///获取卷设备对象的属性,扇区
            &volumeProps,
            sizeof(volumeProps),
            &length);
        //
        //  STATUS_BUFFER_OVERFLOW can be returned - however we only need the properties, not the names
        //  hence we only check for error status.
        //
 
        if (NT_ERROR(status)) {
            leave;
        }
 
        length = max(SCANNER_READ_BUFFER_SIZE, volumeProps.SectorSize); ///扇区大小,传统扇区大小是512B,大扇区(4K)是为了突破bios+MBR最大只支持2T磁盘的限制
 
        //
        //  Use non-buffered i/o, so allocate aligned pool
        //
 
        buffer = FltAllocatePoolAlignedWithTag(Instance,
            NonPagedPool,
            length,
            'nacS');
 
        if (NULL == buffer) {
            status = STATUS_INSUFFICIENT_RESOURCES;
            leave;
        }
 
        notification = ExAllocatePoolWithTag(NonPagedPool,
            sizeof(SCANNER_NOTIFICATION), ///存放从文件中读出length长度的数据发给R3
            'nacS');
 
        if (NULL == notification) {
            status = STATUS_INSUFFICIENT_RESOURCES;
            leave;
        }
 
        //
        //  Read the beginning of the file and pass the contents to user mode.
        //
 
        offset.QuadPart = bytesRead = 0;
        status = FltReadFile(Instance,
            FileObject,
            &offset,
            length,
            buffer,
            FLTFL_IO_OPERATION_NON_CACHED |
            FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET,
            &bytesRead,
            NULL,
            NULL);
 
        if (NT_SUCCESS(status) && (0 != bytesRead)) {
            notification->BytesToScan = (ULONG)bytesRead;
 
            //
            //  Copy only as much as the buffer can hold
            //
 
            RtlCopyMemory(&notification->Contents,
                buffer,
                min(notification->BytesToScan, SCANNER_READ_BUFFER_SIZE));
 
            replyLength = sizeof(SCANNER_REPLY);
 
            status = FltSendMessage(ScannerData.Filter,
                &ScannerData.ClientPort,
                notification,///request
                sizeof(SCANNER_NOTIFICATION),
                notification,///reply,和equest用同一块内存,由于不会同时对同一块内存进行修改,所以这里没有问题
                &replyLength,
                NULL); ///无限等待
 
            if (STATUS_SUCCESS == status) {
                *SafeToOpen = ((PSCANNER_REPLY)notification)->SafeToOpen;
            }
            else {
                //
                //  Couldn't send message
                //
 
                DbgPrint("!!! scanner.sys --- couldn't send message to user-mode to scan file, status 0x%X\n", status);
            }
        }
    }
    finally {
        if (NULL != buffer) {
            FltFreePoolAlignedWithTag(Instance, buffer, 'nacS');
        }
 
        if (NULL != notification) {
            ExFreePoolWithTag(notification, 'nacS');
        }
 
        if (NULL != volume) {
            FltObjectDereference(volume);
        }
    }
 
    return status;
}
应用层

  • main()
    • FilterConnectCommunicationPort()
    • CreateIoCompletionPort()
    • CreateThread()
    • FilterGetMessage()
    • ScannerWorker()
      • GetQueuedCompletionStatus()
      • CONTAINING_RECORD(pOvlp, SCANNER_MESSAGE, Ovlp);
      • ScanBuffer()
      • FilterReplyMessage()字符匹配
      • FilterGetMessage
  • 缺少R3下发给R0的部分
  • 移植到MFC中
  • Minifilter本质还是NT框架,所以往驱动中加入HOOK都是可以的
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
/*++
 
Copyright (c) 1999-2002  Microsoft Corporation
 
Module Name:
 
    scanUser.c
 
Abstract:
 
    This file contains the implementation for the main function of the
    user application piece of scanner.  This function is responsible for
    actually scanning file contents.
 
Environment:
 
    User mode
 
--*/
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <winioctl.h>
#include <string.h>
#include <crtdbg.h>
#include <assert.h>
#include <fltuser.h>
#include "scanuk.h"
#include "scanuser.h"
#include <dontuse.h>
 
#include <fltuser.h> //C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um
#pragma comment(lib,"fltLib.lib") //C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x86
//
//  Default and Maximum number of threads.
//
 
#define SCANNER_DEFAULT_REQUEST_COUNT       5
#define SCANNER_DEFAULT_THREAD_COUNT        2
#define SCANNER_MAX_THREAD_COUNT            64
 
UCHAR FoulString[] = "foul";
 
//
//  Context passed to worker threads
//
 
typedef struct _SCANNER_THREAD_CONTEXT {
    HANDLE Port;
    HANDLE Completion;
} SCANNER_THREAD_CONTEXT, * PSCANNER_THREAD_CONTEXT;
 
VOID
Usage(
    VOID
)
/*++
 
Routine Description
 
    Prints usage
 
Arguments
 
    None
 
Return Value
 
    None
 
--*/
{
    printf("Connects to the scanner filter and scans buffers \n");
    printf("Usage: scanuser [requests per thread] [number of threads(1-64)]\n");
}
 
BOOL
ScanBuffer(
    __in_bcount(BufferSize) PUCHAR Buffer,
    __in ULONG BufferSize
)
/*++
 
Routine Description
 
    Scans the supplied buffer for an instance of FoulString.
 
    Note: Pattern matching algorithm used here is just for illustration purposes,
    there are many better algorithms available for real world filters
 
Arguments
 
    Buffer      -   Pointer to buffer
    BufferSize  -   Size of passed in buffer
 
Return Value
 
    TRUE        -    Found an occurrence of the appropriate FoulString
    FALSE       -    Buffer is ok
 
--*/
{
    PUCHAR p;
    ULONG searchStringLength = sizeof(FoulString) - sizeof(UCHAR);
 
    for (p = Buffer;
        p <= (Buffer + BufferSize - searchStringLength);
        p++) {
        if (RtlEqualMemory(p, FoulString, searchStringLength)) {
            printf("Found a string\n");
 
            //
            //  Once we find our search string, we're not interested in seeing
            //  whether it appears again.
            //
 
            return TRUE;
        }
    }
 
    return FALSE;
}
 
DWORD
ScannerWorker(
    __in PSCANNER_THREAD_CONTEXT Context
)
/*++
 
Routine Description
 
    This is a worker thread that
 
Arguments
 
    Context  - This thread context has a pointer to the port handle we use to send/receive messages,
                and a completion port handle that was already associated with the comm. port by the caller
 
Return Value
 
    HRESULT indicating the status of thread exit.
 
--*/
{
    PSCANNER_NOTIFICATION notification;
    SCANNER_REPLY_MESSAGE replyMessage;
    PSCANNER_MESSAGE message;
    LPOVERLAPPED pOvlp;
    BOOL result;
    DWORD outSize;
    HRESULT hr;
    ULONG_PTR key;
 
#pragma warning(push)
#pragma warning(disable:4127) // conditional expression is constant
 
    while (TRUE) {
#pragma warning(pop)
 
        //
        //  Poll for messages from the filter component to scan.
        //
 
        result = GetQueuedCompletionStatus(Context->Completion, &outSize, &key, &pOvlp, INFINITE);
 
        //
        //  Obtain the message: note that the message we sent down via FltGetMessage() may NOT be
        //  the one dequeued off the completion queue: this is solely because there are multiple
        //  threads per single port handle. Any of the FilterGetMessage() issued messages can be
        //  completed in random order - and we will just dequeue a random one.
        //
 
        message = CONTAINING_RECORD(pOvlp, SCANNER_MESSAGE, Ovlp);
 
        if (!result) {
            //
            //  An error occured.
            //
 
            hr = HRESULT_FROM_WIN32(GetLastError());
            break;
        }
 
        printf("Received message, size %d\n", pOvlp->InternalHigh);
 
        notification = &message->Notification;
 
        assert(notification->BytesToScan <= SCANNER_READ_BUFFER_SIZE); ///待扫描的字节数
        __analysis_assume(notification->BytesToScan <= SCANNER_READ_BUFFER_SIZE); ///待扫描的数据
 
        ///这个地方,可以修改成弹窗的代码:result=PopupWindow(notification);
        result = ScanBuffer(notification->Contents, notification->BytesToScan);
 
        replyMessage.ReplyHeader.Status = 0;
        replyMessage.ReplyHeader.MessageId = message->MessageHeader.MessageId; ///Mifilfter框架生成的
 
        //
        //  Need to invert the boolean -- result is true if found
        //  foul language, in which case SafeToOpen should be set to false.
        //
 
        replyMessage.Reply.SafeToOpen = !result; ///扫描结果
 
        printf("Replying message, SafeToOpen: %d\n", replyMessage.Reply.SafeToOpen);
 
        hr = FilterReplyMessage(Context->Port,
            (PFILTER_REPLY_HEADER)&replyMessage,
            sizeof(replyMessage));
        //这个时候,执行完上面函数后,驱动中FltSendMessage()返回
 
        if (SUCCEEDED(hr)) {
            printf("Replied message\n");
        }
        else {
            printf("Scanner: Error replying message. Error = 0x%X\n", hr);
            break;
        }
 
        memset(&message->Ovlp, 0, sizeof(OVERLAPPED));
 
        hr = FilterGetMessage(Context->Port, ///异步读,开始下一轮
            &message->MessageHeader,
            FIELD_OFFSET(SCANNER_MESSAGE, Ovlp),
            &message->Ovlp);
 
        if (hr != HRESULT_FROM_WIN32(ERROR_IO_PENDING)) {
            break;
        }
    }
 
    if (!SUCCEEDED(hr)) {
        if (hr == HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE)) {
            //
            //  Scanner port disconncted.
            //
 
            printf("Scanner: Port is disconnected, probably due to scanner filter unloading.\n");
        }
        else {
            printf("Scanner: Unknown error occured. Error = 0x%X\n", hr);
        }
    }
 
    free(message);
 
    return hr;
}
 
//scanuser.exe reqno(5) threadno(2,MAX64)
//scanuser.exe 5 10     5表示程序一次性可以处理5个请求,10表示允许创建10个线程
//scanuser.exe
int _cdecl
main(
    __in int argc,
    __in_ecount(argc) char* argv[]
)
{
    DWORD requestCount = SCANNER_DEFAULT_REQUEST_COUNT;
    DWORD threadCount = SCANNER_DEFAULT_THREAD_COUNT;
    HANDLE threads[SCANNER_MAX_THREAD_COUNT];
    SCANNER_THREAD_CONTEXT context;
    HANDLE port, completion;
    PSCANNER_MESSAGE msg;
    DWORD threadId;
    HRESULT hr;
    DWORD i, j;
 
    //
    //  Check how many threads and per thread requests are desired.
    //
 
    if (argc > 1) {
        requestCount = atoi(argv[1]); ///一次最多处理请求的个数
 
        if (requestCount <= 0) {
            Usage();
            return 1;
        }
 
        if (argc > 2) {
            threadCount = atoi(argv[2]); ///允许创建线程的个数
        }
 
        if (threadCount <= 0 || threadCount > 64) {
            Usage();
            return 1;
        }
    }
 
    //
    //  Open a commuication channel to the filter
    //
 
    printf("Scanner: Connecting to the filter ...\n");
 
    hr = FilterConnectCommunicationPort(ScannerPortName, ///连接R0
        0,
        NULL,
        0,
        NULL,
        &port);
 
    if (IS_ERROR(hr)) {
        printf("ERROR: Connecting to filter port: 0x%08x\n", hr);
        return 2;
    }
 
    //
    //  Create a completion port to associate with this handle.
    //
 
    completion = CreateIoCompletionPort(port, ///创建完成端口
        NULL,
        0,
        threadCount);
 
    if (completion == NULL) {
        printf("ERROR: Creating completion port: %d\n", GetLastError());
        CloseHandle(port);
        return 3;
    }
 
    printf("Scanner: Port = 0x%p Completion = 0x%p\n", port, completion);
 
    context.Port = port;
    context.Completion = completion;
 
    //
    //  Create specified number of threads.
    //
 
    for (i = 0; i < threadCount; i++) { ///创建线程
        threads[i] = CreateThread(NULL,
            0,
            ScannerWorker,
            &context,
            0,
            &threadId);
 
        if (threads[i] == NULL) {
            //
            //  Couldn't create thread.
            //
 
            hr = GetLastError();
            printf("ERROR: Couldn't create thread: %d\n", hr);
            goto main_cleanup;
        }
 
        for (j = 0; j < requestCount; j++) { ///为每个线程分配requestCount个结构体
            //
            //  Allocate the message.
            //
 
#pragma prefast(suppress:__WARNING_MEMORY_LEAK, "msg will not be leaked because it is freed in ScannerWorker")
            msg = malloc(sizeof(SCANNER_MESSAGE));
 
            if (msg == NULL) {
                hr = ERROR_NOT_ENOUGH_MEMORY;
                goto main_cleanup;
            }
 
            memset(&msg->Ovlp, 0, sizeof(OVERLAPPED));
 
            //
            //  Request messages from the filter driver.
            //
 
            hr = FilterGetMessage(port,  ///异步读
                &msg->MessageHeader,
                FIELD_OFFSET(SCANNER_MESSAGE, Ovlp),
                &msg->Ovlp);
 
            if (hr != HRESULT_FROM_WIN32(ERROR_IO_PENDING)) {
                free(msg);
                goto main_cleanup;
            }
        }
    }
 
    hr = S_OK;
 
    WaitForMultipleObjectsEx(i, threads, TRUE, INFINITE, FALSE);
 
main_cleanup:
 
    printf("Scanner:  All done. Result = 0x%08x\n", hr);
 
    CloseHandle(port);
    CloseHandle(completion);
 
    return hr;
}

基于Minifilter的SANDBOX

  • 把病毒和木马的操作重定向到沙盒中进行
  • 沙盒的根目录
    • \device\harddiskvolume1\doc\hi.txt
    • 沙盒所在区域,可控的区域
  • 源路径
    • \device\harddiskvolume2\doc\hi.txt
    • 病毒和木马想要操作的真正路径
  • 内部路径
    • \device\harddiskvolume1\sandbox\harddiskvolume2\doc\hi.txt
    • 被重定向之后的路径,模拟了和外部一样的路径
  • 删除标记
    • \device\harddiskvolume1\sandbox\harddiskvolume2\doc\hi.txt.del
    • 当病毒和木马想要删除某个文件,拒绝删除,并返回成功,欺骗病毒木马
    • 当病毒和木马想要打开/遍历待删除标志的文件,就会返回一个拒绝,欺骗病毒木马

      重定向reparse

      Windows的I/O管理器提供了一个方便的方法来重定向一个文件对象。通常使用文件过滤驱动(在文件打开和文件创建的操作中)实现该方法。操作方法如下;
  • 1.在IRP_MJ_CREATE的分发函数中,获得FILE_OBJET的FileName属性。
  • 2.用目标文件的完整路径替换原有的文件名字。
    • 这个全名,包括卷设备对象的名字(例如,Device\HardDiskVolume0\Doc\1.txt)可以释放掉原有的FileName.Buffer,同时用自己定义义的缓冲区(buffer,以NonPagedPool方式申请)替换它
  • 3.设置loStatus的status字段为STATUS_REPARSE,然后设置Information字段为IO_REPARSE
  • 4.完成该IRP请求
  • 5.返回STATUS_REPARSE
    • I/O管理器接收到该返回后,使会触发另一个文件打开操作,并发一个IRP_MJ_CREATE的请求(带上新的重定向的路径),完成了重定向。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
///重定向
NTSTATUS
SbRedirectFile(
    IN    PFLT_CALLBACK_DATA         Data, ///Minifilter必须带的结构体
    IN    PCFLT_RELATED_OBJECTS    FltObjects,
    IN    PUNICODE_STRING            pUstrDstFileName ///重定向之后的路径
)
{
    PFILE_OBJECT pFileObject;
    pFileObject = Data->Iopb->TargetFileObject; ///1.拿到目标文件内核对象
    if (pFileObject == NULL)
        return STATUS_INVALID_PARAMETER;
 
    if (pFileObject->FileName.Length > 0 && pFileObject->FileName.Buffer != NULL)  ///2.把原来的Buffer释放掉
    {
        ExFreePool(pFileObject->FileName.Buffer);
        pFileObject->FileName.Buffer = NULL;
    }
 
    pFileObject->FileName = *pUstrDstFileName; ///绝对路径,重新指向新的路径
    pFileObject->RelatedFileObject = NULL; ///相对路径
 
    Data->IoStatus.Status = STATUS_REPARSE;   ///3.设置loStatus的status字段为`STATUS_REPARSE`,然后设置Information字段为`IO_REPARSE`.
    Data->IoStatus.Information = IO_REPARSE;
 
    FltSetCallbackDataDirty(Data); ///让IO管理器认识到已经把数据改了
 
    return STATUS_REPARSE; ///5.返回`STATUS_REPARSE`,I/O管理器接收到该返回后,使会触发另一个文件打开操作,并发一个`IRP_MJ_CREATE`的请求(带上新的重定向的路径),完成了重定向.
}

CreateFile重定向

  • 首先确定该进程是否需要SANDBOX
    • 获取文件名(insandbox and outsandbox filename)
    • 判断sandbox里是否含有该文性的.del文件。如果有则按照是否来自insandbox的请求和是否改变文件进行处理。
      • 创建或改变该文件,来自外面,则重定向到内部
      • 创建或改变该文件,来自里面,则删除该标志文件,并交给文件系统去创建
      • 只读操作该文件,含有删除标志,访问失败
  • 如果sandbox里存在该文件,且请求来自sandbox,交给文件系统处理;请求来自外面,reparse到里面。
  • 如果操作不改变文件,但是sandbox里没有这个文件并且请求来自sandbox,则reparse到外面;否则交给文件系统处理
  • 操作改变文件, sandbox里没有该文件,则准备路径;如果sandbox外面该必文件,则负责拷贝
  • 重定向reparse

    DeleteFile

  • IRP_MJ_SETL_INFORMATION 子功能号
    • FileDispositionInformation
  • 思路:
    • 获得文件名字(in and out sandbox name )
    • 判断是否来自sandbox里面还是外面
    • 如果是外面路径,则转换为里面的路径
      • 不删除外面的文件,在里面设置一个删除标志
    • 如果是里面路径,获得外面的路径
      • 如果外面不存在,直接删除里面的该文件
      • 如果外面存在;则在里面建立一个删除标志,并删除

        QueryDir重定向

  • RP_MJ_DIRECTORY_CONTROL
  • QueryDir
    • 首先确定该进程是否需要SANDBOX
    • 获取文件名 (in sandbox and outsandbox)
    • query dir in sandbox并去掉含删除标志的文件
    • query dir out sandbox并去掉含删除标志的文件
    • 合并两部分内容
  • eg:一个进程被沙盒了,进程想要遍历d:\doc\所有的文件
  • d:\doc\1.txt 2.txt 3.txt
  • c:\sandbox\harddiskvolume2\doc\1.txt.del,2.txt,4.txt,5.txt
  • 进程最终看到的是2,3,4,5
  • @todo 待完善
  • WriteFile不需要重定向,读写之前需要把文件打开,得到句柄后进行读写,然后再把文件关闭,所以在写之前,打开已经重定向了,所以WriteFile不需要重定向。

    如何判断自己被沙盒了?

  • 创建文件的时候,传入一个MAX_PATH长度的文件路径,如果返回失败(说明有沙合在前面加了一个沙盒的根目录导致重定向的路径长度超过了MAX_PATH,文件创建失败)

注册表回调框架

  • XP及其以下版本,用HOOK
  • VISTA及其以上版本,用回调
1
2
3
4
5
6
7
8
9
10
11
12
NTSTATUS DriverEntry(
    IN PDRIVER_OBJECTDriverObject,
    IN PUNICODE_STRINGRegistryPath)
{
    CmRegisterCallbackEx(
        MyRegCallback, ///回调函数
        &uAltitude, ///指定高度
        driverObject,
        NULL,
        &g_RegCookie, ///回调的handle
        NULL);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// 回调函数
NTSTATUS MyRegCallback(
    __in PVOID CallbackContext,
    __in_opt PVOID Argument1,///REG_NOTIFY_CLASS,标识注册表的操作
    __in_opt PVOID Argument2 ) ///KEY_INFORMATION,拿到相关的信息,比如文件路径
{
    switch( (REG_NOTIlFY_CLASS)Argument1)
    {
    case RegNtPreDeleteKey :
        return HOOK_PreNtDeleteKey(PREG_DELETE_KEY_INFORMATION)Argument2);
    case RegNtPreRenameKey:
        return HOoK_PreNtRenameKeyf(PREG_RENAME_KEY_INFORMATION)Argument2);
    case RegNtPreCreateKeyEx: ///pre操作
        return HOoK_PreNtCreatekeyEx((PREG_CREATE_KEY_INFORMATION)Argument2);
        case RegNtPostCreatekeyEx://post操作
        return HOOK_PostNtCreateKeyExt(PRGG_PoST_OPERATION_INFORMATION)Argument2);
    }
}

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 2
支持
分享
最新回复 (1)
雪    币: 446
活跃值: (550)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
 总结的很棒
2024-7-30 12:04
0
游客
登录 | 注册 方可回帖
返回
//