-
-
[原创]文件注册表过滤驱动
-
发表于: 2023-2-7 08:32 9173
-
本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如有错漏,欢迎留言交流指正
文件,注册表过滤
分层驱动框架
- NT驱动框架:
单层
驱动,只能接受自己进程的IRP - Sifileter驱动框架:
多层
驱动,接受所有
进程的IRP- 过滤:分层驱动中
再加一层
而不影响它的上下层,以过滤它们之间的数据,对数据或行为进行安全控制。过滤是通过设备绑定实现的(有多少个文件卷设备就生成多少个文件过滤驱动设备对象一一绑定,这样,发给各个卷设备对象的IRP都会被监控到)。
- 过滤:分层驱动中
- 磁盘过滤驱动:用于文件还原
绑定与过滤
设备栈绑定的形式
:驱动自己生成一个设备(过滤设备
),调用系统提供的绑定API,绑定到自标设备上,并返回
一个在未绑定
之前目标设备所在设备栈的最顶层设备
。这样发往下层的IRP或者发往土层的数据都会被过滤设备截获。绑定的API
:- 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分发函数
- 如何区分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:
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中的域。
- 那个IRP会被其它的驱动或者线程
- 2.若是驱动的分发例程也还需要在IRP被后面的驱动处理完成之后再处理它,这个IoCompletion例程必须返回
STATUS_MORE_PROCESSING_REQUIRED
,以将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 | / / / 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 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中
- 预先读入文件和延迟写入文件。在读/写文件的时候,为了提高效率,文件带缓冲进行IO操作,读的时候提前读入目标旁边的数据,写的时候会延迟写(
- 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.
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; } |
- 拿文件名/长短名转化,盘符转化。
- 匹配规则
- 弹框
- 拦(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 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; } |
- 从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 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; } |
- 弹框
- 拦截(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); } |
- 放行:
- 本进程
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; } |
- 从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
- 弹框
- 拦
- 放
- 如果是删除,需要知道删除的结果是否成功,来决定是否
更新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
- 规则硬编码不可取,把规则放到
.dat
文件里,用客户端读取规则文件,通过IoConctorl下发到内核驱动中,组织到一个链表里,通过链表来匹配规则Minifilter
Minifilter与legacy filter(Sfilter)区别
- 新一代的文件过滤驱动框架: Minifilter
- 比sfilter
加载顺序更易控制
。altitude
被绑定到合适的位置。 可卸载
能力。- 系统不重启的情况下,HOOK不支持卸载,卸载可能会蓝屏,是因为有的IRP可能没有立即返回,会被
pending
起来,如果这时候把hook的函数卸载,当重新回来执行的时候,函数的地址就失效了,内存无效,蓝屏。 - 解决方法:引用计数
- minifiler驱动是向minifiler框架注册的,minifilter驱动卸载了,框架还在,就不会造成类似HOOK函数卸载后的问题
- 系统不重启的情况下,HOOK不支持卸载,卸载可能会蓝屏,是因为有的IRP可能没有立即返回,会被
- Callback模型
仅需处理必要操作
的能力(pre_create,post create)- 相当于把Sfilter的分发函数分为两部分:
pre_create
(IRP下发之前),post create
(IRP处理完之后) - 如果不想处理IRP就不需要注册对应的回调函数(如果是Sfilter则需要为不处理的IRP注册一个
通用
的过滤分发函数) - 绑定过滤设备对象动作
被隐藏
- 相当于把Sfilter的分发函数分为两部分:
兼容性
更好- hook会打架,谁后生成谁优先
- Sfiler不可卸载
- Minifilter都没有上述问题
名字处理
更容易- Minifilter拿文件名只需直接调用函数就可以了
- 安装方式(.inf或者用代码动态加载)
- 动态安装,创建几个与之相关的注册表的键即可
- 通信方式(port)
- 基于端口通信,效率非常高,应用层可以多个线程处理内核层发送的请求,每个线程也可以处理多个请求。
- 同样遵循IRQL,锁等内核开发通用机制
- FltCreateFile/ZwCreateFile
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期间
加密
和解密
数据的过滤驱动。
- 此组包括在文件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中
- 预先读入文件和延迟写入文件。在读/写文件的时候,为了提高效率,文件带缓冲进行IO操作,读的时候提前读入目标旁边的数据,写的时候会延迟写(
- 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.
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 atIRQL_PASSIVE_LEVEL
or atIRQL_APC_LEVELS
Typically it is called atIRQL_PASSIVE_LEVEL
- 2.lf a minifilter driver's
preoperation
callback routine returnsFLT_PREOP_SYNCHRONIZE
for an lRP-based l/O operation,the correspondingpostoperation
callback routine is called at lRQL <= APC_LEVEL
, in the same thread context as thepreoperation
callback routine. - 3.The
postoperation
callback routine for afast I/O
operation is calledIRQL_PASSIVE_LEVEL
, in the same thread context as the preoperation callback routine. - 4.
Post-create
callback routines are called atIRQL_PASSIVE_LEVEL
in the context of the thread that originated theIRP_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上下文
:其实就是附着在某个对象上的一段内存,这段内存缓存的相关数据由自己定义;和中断、进程上下文区别
:- 中断上下文:中断传过来的数据,中断前
进程的上下文
- 进程上下文:寄存器,栈,堆,描述进程的结构体,
- 中断上下文:中断传过来的数据,中断前
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有很多种对象,
根据对象不同
分为不同的类。 - 一个文件从磁盘打开加载到内存之后,会产生以下这些
- 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 Context
和Instance 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
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()
- ScannerpCheckExtension()
- ScannerPreWrite()
- FltGetStreamHandleContext()
- MmGetSystemAddressForMdlSafe()
- SCANNER_NOTIFICATION[]
- FltSendMessage()
- ScannerPreCleanup()
- FltGetStreamHandleContext()
- ScannerpScanFileInUserMode()回扫
- 删除重命名可以参考Sfilter的Hips来完善
- FilterRegistration
- FltBuildDefaultSecurityDescriptor()
- FltCreateCommunicationPort()
- ScannerPortConnect()
- ScannerPortDisconnect()
- FltCloseClientPort()
- FltStartFiltering()
- FltRegisterFilter()
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(¬ification - >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(¬ification - >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
- 被重定向之后的路径,模拟了和外部一样的路径
删除标记
:- 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
的请求(带上新的重定向的路径),完成了重定向。
- I/O管理器接收到该返回后,使会触发另一个文件打开操作,并发一个
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
andoutsandbox
filename) - 判断sandbox里是否含有该文性的
.del
文件。如果有则按照是否来自insandbox的请求和是否改变文件进行处理。- 创建或改变该文件,
来自外面
,则重定向到内部 - 创建或改变该文件,
来自里面
,则删除该标志文件,并交给文件系统去创建 只读操作
该文件,含有删除标志,访问失败
- 创建或改变该文件,
- 获取文件名(
- 如果sandbox里
存在
该文件,且请求来自sandbox,交给文件系统处理;请求来自外面,reparse
到里面。 - 如果操作
不改变
文件,但是sandbox里没有
这个文件并且请求来自
sandbox,则reparse
到外面;否则交给文件系统处理 - 操作
改变
文件, sandbox里没有
该文件,则准备路径;如果sandbox外面有
该必文件,则负责拷贝
。 - 重定向reparse
DeleteFile
- IRP_MJ_SETL_INFORMATION 子功能号
- FileDispositionInformation
- 思路:
- 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); } } |
赞赏
他的文章
- [原创]java和smali汇编 2677
- [原创]Android逆向前期准备(下) 4239
- [原创]native层逆向分析(上篇) 13789
- [原创]Java层逆向分析方法和技巧 7244
看原图
赞赏
雪币:
留言: