Windows驱动开发入门-文件系统过滤与监控

碎碎念

sfilter貌似已经过时,建议使用Minifilter写文件系统过滤。

当要求区分目录、涉及文件系统的要求时,存储设备驱动很难解决问题,需要使用文件系统过滤。FAT32的驱动为fastfat.sys,NTFS的驱动叫ntfs.sys,这俩都在Windows的drivers目录下。像这样的FS文件系统生成两类设备,一类CDO控制对象,用来修改这个驱动内部配置,一个文件系统只对应一个CDO;另一类文件系统卷设备,一个卷对应一个逻辑盘。例如逻辑盘“C:”只是个符号链接,真正的设备名叫“\\Device\\HarddiskVolume1”。卷设备时卷管理器生成的,没名字,不能直接绑定,要不绑定的不是系统生成的卷设备而是真正的卷设备。对文件的读写IRP都发送到卷设备,不论卷文件系统是什么,一个卷一个卷设备。绑定卷设备前先绑定文件系统控制设备。

在文件过滤中,按照国际惯例应该把控制设备生成到“\\FileSystem\\Filters”下,但有些早期Windows系统没这个路径,所以直接生成到“\\FileSystem”下。

这节代码太复杂了,不打算格式化了,可读性可能很低。这一节东西理解就好,用的时候大多不用修改。

设备对象&分发函数

控制设备生成

驱动入口DriverEntry的主要工作:

  • 生成一个CDO并指定名字。
  • 设置普通分发函数、快速IO分发函数。
  • 实现一个文件系统变动回调函数,在其中绑定刚激活的FS CDO,并使用IoRegsiterFsRegistrationChange注册这个回调函数。
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
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath){
PFAST_IO_DISPATCH fastIoDispatch;
UNICODE_STRING nameString;
UNICODE_STRING path2K;
UNICODE_STRING pathXP;
WCHAR nameBuffer[MY_DEV_MAX_PATH] = { 0 };
UNICODE_STRING userNameString;
WCHAR userNameBuffer[MY_DEV_MAX_NAME] = { 0 };
UNICODE_STRING syblnkString;
WCHAR syblnkBuffer[MY_DEV_MAX_NAME] = { 0 };
UNICODE_STRING dosDevicePrefix;
UNICODE_STRING dosDevice;
WCHAR dosDeviceBuffer[MY_DEV_MAX_NAME] = { 0 };
NTSTATUS status;
ULONG i;
#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 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);
RtlInitEmptyUnicodeString(&nameString, nameBuffer, MY_DEV_MAX_PATH);
RtlInitEmptyUnicodeString(&userNameString, userNameBuffer, MY_DEV_MAX_NAME);
RtlInitEmptyUnicodeString(&syblnkString, syblnkBuffer, MY_DEV_MAX_NAME);
RtlInitUnicodeString(&pathXP, L"\\FileSystem\\Filters\\");
RtlInitUnicodeString(&path2K, L"\\FileSystem\\");
RtlInitUnicodeString(&dosDevicePrefix, L"\\DosDevices\\");
RtlInitEmptyUnicodeString(&dosDevice, dosDeviceBuffer, MY_DEV_MAX_NAME);
RtlCopyUnicodeString(&dosDevice, &dosDevicePrefix);
status = OnSfilterDriverEntry(DriverObject, RegistryPath, &userNameString, &syblnkString, &gUserExtensionSize);
if (!NT_SUCCESS(status))
return status;
RtlCopyUnicodeString(&nameString, &pathXP);
RtlAppendUnicodeStringToString(&nameString, &userNameString);
// Create the Control Device Object (CDO). This object represents this driver. Note that it does not have a device extension.
// 这是生成控制设备。
if (g_cdo_for_all_users)
status = IoCreateDevice(DriverObject, 0, &nameString, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &gSFilterControlDeviceObject); //没有设备扩展
else {
// 以下生成一个可以被任何用户打开读写的设备。但是这个guid是我手写固定的。不清楚如果在有多个基于本sfilter模块的驱动同时安装的时候,会不会导致出现guid重复的问题。
UNICODE_STRING sddlString;
RtlInitUnicodeString(&sddlString, L"D:P(A;;GA;;;WD)");
status = IoCreateDeviceSecure(DriverObject, 0, &nameString, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &sddlString, (LPCGUID)&SFGUID_CLASS_MYCDO, &gSFilterControlDeviceObject);
};
if (status == STATUS_OBJECT_PATH_NOT_FOUND) { //路径没找到 生成失败
RtlInitEmptyUnicodeString(&nameString, nameBuffer, MY_DEV_MAX_PATH);
RtlCopyUnicodeString(&nameString, &path2K);
RtlAppendUnicodeStringToString(&nameString, &userNameString);
// 这是再次生成控制设备。
if (g_cdo_for_all_users)
status = IoCreateDevice(DriverObject, 0, &nameString, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &gSFilterControlDeviceObject); //has no device extension
else {
// 以下生成一个可以被任何用户打开读写的设备。但是这个guid是我手写固定的。不清楚如果在有多个基于本sfilter模块的驱动同时安装的时候,会不会导致出现guid重复的问题。
UNICODE_STRING sddlString;
RtlInitUnicodeString(&sddlString, L"D:P(A;;GA;;;WD)");
status = IoCreateDeviceSecure(DriverObject, 0, &nameString, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &sddlString, (LPCGUID)&SFGUID_CLASS_MYCDO, &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;
};
RtlAppendUnicodeStringToString(&dosDevice, &syblnkString);
IoDeleteSymbolicLink(&dosDevice);
status = IoCreateSymbolicLink(&dosDevice, &nameString);
if (!NT_SUCCESS(status)) {
KdPrint(("SFilter!DriverEntry: Error creating syblnk object \"%wZ\", status=%08x\n", &syblnkString, status));
IoDeleteDevice(DriverObject->DeviceObject);
return status;
};
// Initialize the driver object with this device driver's entry points.
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
DriverObject->MajorFunction[i] = SfPassThrough;
// We will use SfCreate for all the create operations
DriverObject->MajorFunction[IRP_MJ_CREATE] = SfCreate;
DriverObject->MajorFunction[IRP_MJ_CREATE_NAMED_PIPE] = SfCreate;
DriverObject->MajorFunction[IRP_MJ_CREATE_MAILSLOT] = SfCreate;
DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = SfFsControl;
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 filtersby 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);
if (!fastIoDispatch) {
IoDeleteDevice(gSFilterControlDeviceObject);
return STATUS_INSUFFICIENT_RESOURCES;
};
RtlZeroMemory(fastIoDispatch, sizeof(FAST_IO_DISPATCH));
fastIoDispatch->SizeOfFastIoDispatch = sizeof(FAST_IO_DISPATCH);
fastIoDispatch->FastIoCheckIfPossible = SfFastIoCheckIfPossible;
fastIoDispatch->FastIoRead = SfFastIoRead;
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;
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;
ExFreePool(fastIoDispatch);
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 notenumerate the file systems that were loaded before this filter was loaded.
status = IoRegisterFsRegistrationChange(DriverObject, SfFsNotification); //注册一个文件系统变动回调函数 当文件系统被激活或卸载时使用
if (!NT_SUCCESS(status)) {
KdPrint(("SFilter!DriverEntry: Error registering FS change notification, status=%08x\n", status));
DriverObject->FastIoDispatch = NULL;
ExFreePool(fastIoDispatch);
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);
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
NTSTATUS SfPassThrough(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
NTSTATUS status;
SF_RET ret;
PVOID context;
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.
// 所有的处理例程首先要判断是否是CDO的请求
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {
status = OnSfilterCDODispatch(DeviceObject, Irp);
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
};
if (!IS_MY_DEVICE_OBJECT(DeviceObject)) {
PVOID context = NULL;
ret = OnSfilterIrpPre(DeviceObject, NULL, NULL, Irp, &status, &context);
ASSERT(context == NULL);
ASSERT(ret == SF_IRP_COMPLETED);
return status;
};
// Get this driver out of the driver stack and get to the next driver as quickly as possible.
// 对于本地文件系统我们只过滤卷设备
if ((DeviceObject->DeviceType == FILE_DEVICE_DISK_FILE_SYSTEM) && (((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->StorageStackDeviceObject == NULL)) {
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp);
};
ret = OnSfilterIrpPre(DeviceObject, ((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->AttachedToDeviceObject, (PVOID)(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->UserExtension), Irp, &status, &context);
switch (ret) {
case SF_IRP_PASS: {
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp);
};
case SF_IRP_COMPLETED:
return status;
default: {
KEVENT waitEvent;
KeInitializeEvent(&waitEvent, NotificationEvent, FALSE);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp, SfCreateCompletion, &waitEvent, TRUE, TRUE, TRUE);
status = IoCallDriver(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp);
if (STATUS_PENDING == status) {
NTSTATUS localStatus = KeWaitForSingleObject(&waitEvent, Executive, KernelMode, FALSE, NULL);
ASSERT(STATUS_SUCCESS == localStatus);
};
ASSERT(KeReadStateEvent(&waitEvent) || !NT_SUCCESS(Irp->IoStatus.Status));
OnSfilterIrpPost(DeviceObject, ((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->AttachedToDeviceObject, (PVOID)(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->UserExtension), Irp, status, context);
status = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
};
};
};

快速IO分发函数

一大堆东西,在driver->FastIoDispatch中没有空间,需要自己用ExAllocatePoolWithTag来分配非分页内存。串口、键盘、硬盘等驱动不需要注册这些回调函数,上层根本不会调用,但文件系统上层会,不注册就蓝屏。但有个方法可以糊弄过去,每个快速IO分发函数返回FALSE,这些请求都会通过IRP重新发送给普通分发函数来捕获,但有效率损失,但不大:)。

其中一个快速IO分发函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BOOLEAN SfFastIoCheckIfPossible(IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN BOOLEAN Wait, IN ULONG LockKey, IN BOOLEAN CheckForReadOperation, OUT PIO_STATUS_BLOCK IoStatus, IN PDEVICE_OBJECT DeviceObject) {
PDEVICE_OBJECT nextDeviceObject;
PFAST_IO_DISPATCH fastIoDispatch;
PAGED_CODE();
// 如果是控制设备,不允许
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject))
return FALSE;
// 如果不是我的设备(影子设备可能发生这种情况)
if (!IS_MY_DEVICE_OBJECT(DeviceObject))
return FALSE;
if (DeviceObject->DeviceExtension) {
ASSERT(IS_MY_DEVICE_OBJECT(DeviceObject));
// Pass through logic for this type of Fast I/O
nextDeviceObject = ((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->AttachedToDeviceObject; //得到本驱动绑定的设备
ASSERT(nextDeviceObject);
fastIoDispatch = nextDeviceObject->DriverObject->FastIoDispatch; //得到目标设备FastIo分发函数接口
if (VALID_FAST_IO_DISPATCH_HANDLER(fastIoDispatch, FastIoCheckIfPossible))
return (fastIoDispatch->FastIoCheckIfPossible)(FileObject, FileOffset, Length, Wait, LockKey, CheckForReadOperation, IoStatus, nextDeviceObject); //直接调用
};
return FALSE;
};

其他快速IO分发函数介绍:

  • FastIoCheckIfPossible:被FsRtl系列函数调用,用于确认读写操作是否可以用快速IO接口进行。
  • FastIoRead/FastIoWrite:读写处理。
  • FastIoQueryBasicInfoFastIoQueryStandardInfo:获取各种文件信息。
  • FastIoLockFastIoUnlockSingleFastIoUnlockAll/FastIoUnlockAllByKey:文件锁定操作。
  • FastIoDeviceControl:提供NtDeviceIoControlFile的支持。
  • AcquireFileForNtCreateSection/ReleaseFileForNtCreateSection:NTFS映射文件内容到内存页面前进行的操作。
  • FastIoDetachDevice:当可移除介质被拿走后把上层设备和将要销毁的设备解除绑定。
  • FastIoQueryNetworkOpenInfo:CIFS(网上邻居/网络重定向驱动)在NT4时传输协议增加了一个FileNetworkOpenInformation网络文件请求,FSD增加了这个接口用于在一次操作中获得所有文件信息。
  • FastIoAcquireForModWrite:Modified Page Writer调用这个接口获取文件锁,实现这个接口时使文件锁范围减小到调用的指定位置。不实现这个接口时整个文件将被锁。
  • FastIoPrepareMdlWrite:FSD提供MDL,向MDL写入数据代表向文件写入数据,参数中有用于描叙要写的目标文件。
  • FastIoMdlWriteComplete:写操作完成,FSD回收MDL。
  • FastIoReadCompressed:读压缩后的数据,调用者负责解压。
  • FastIoWriteCompressed:将数据压缩后存储。
  • FastIoMdlReadCompressed/FastIoMdlReadCompleteCompressed:MDL版本压缩读,后者接口被调用时MDL必须释放。
  • FastIoMdlWriteCompressed/FastIoMdlWriteCompleteCompressed:同上,压缩写。
  • FastIoQueryOpen:打开文件、获取文件基本信息并关闭文件。
  • FastIoReleaseForModWrite:释放FastIoAcquireForModWrite占有的锁。
  • FastIoAcquireForCcFlush/FastIoReleaseForCcFlush:FsRtl调用此接口,在延迟写线程将要把修改后文件数据写入前调用,获取文件锁。

设备绑定

动态绑定函数

例如IoAttachDeviceToDeviceStackSafe等函数在低版本系统中不存在,所以这里使用内核函数动态加载的方式。当动态获取某个函数指针时为空,则改为使用IoAttachDeviceToDeviceStack函数。MmGetSystemRoutineAddress可动态寻找一个内核函数指针,原型为:

1
NTKERNELAPI PVOID MmGetSystemRoutineAddress(IN PUNICODE_STRING SystemRoutineName); //例如L"IoAttachDeviceToDeviceStackSafe"

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
NTSTATUS SfAttachDeviceToDeviceStack(IN PDEVICE_OBJECT SourceDevice, IN PDEVICE_OBJECT TargetDevice, IN OUT PDEVICE_OBJECT* AttachedToDeviceObject) {
PAGED_CODE();
#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);
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
VOID SfFsNotification(IN PDEVICE_OBJECT DeviceObject, IN BOOLEAN FsActive) {
UNICODE_STRING name;
WCHAR nameBuffer[MAX_DEVNAME_LENGTH];
PAGED_CODE();
// Init local name buffer
RtlInitEmptyUnicodeString(&name, nameBuffer, sizeof(nameBuffer));
SfGetObjectName(DeviceObject, &name);
// Display the names of all the file system we are notified of
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES, ("SFilter!SfFsNotification: %s %p \"%wZ\" (%s)\n", (FsActive) ? "Activating file system " : "Deactivating file system", DeviceObject, &name, GET_DEVICE_TYPE_NAME(DeviceObject->DeviceType)));
// Handle attaching/detaching from the given file system.
if (FsActive)
SfAttachToFileSystemDevice(DeviceObject, &name); //绑定文件系统控制设备
else
SfDetachFromFileSystemDevice(DeviceObject); //注销 解除绑定
return;
};

文件系统识别器

只关心磁盘文件系统、光盘、网络文件系统:

1
2
//  Macro to test for device types we want to attach to
#define IS_DESIRED_DEVICE_TYPE(_type) (((_type) == FILE_DEVICE_DISK_FILE_SYSTEM) || ((_type) == FILE_DEVICE_CD_ROM_FILE_SYSTEM) || ((_type) == FILE_DEVICE_NETWORK_FILE_SYSTEM))

当新的物理存储媒介进入系统后,IO管理器会加载各种文件系统的文件系统识别器对他进行识别,识别成功后加载真正的对应文件系统驱动,并卸载掉文件系统识别器。文件系统识别器的CDO很像文件系统的CDO,绑定他可能出现问题,最好放弃绑定。Windows标注文件系统识别器由驱动“\\FileSystem\\Fs_Rec”生成,当然也有些不是,这些文件系统控制请求过滤中有相应处理。

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
NTSTATUS SfAttachToFileSystemDevice(IN PDEVICE_OBJECT DeviceObject, IN PUNICODE_STRING DeviceName) {
PDEVICE_OBJECT newDeviceObject;
PSFILTER_DEVICE_EXTENSION devExt;
UNICODE_STRING fsrecName;
NTSTATUS status;
UNICODE_STRING fsName;
WCHAR tempNameBuffer[MAX_DEVNAME_LENGTH];
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;
// always init NAME buffer
RtlInitEmptyUnicodeString(&fsName, tempNameBuffer, sizeof(tempNameBuffer));
// See if we should attach to the standard file system recognizer device or not
RtlInitUnicodeString(&fsrecName, L"\\FileSystem\\Fs_Rec");
SfGetObjectName(DeviceObject->DriverObject, &fsName);
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.
if (RtlCompareUnicodeString(&fsName, &fsrecName, TRUE) == 0)
return STATUS_SUCCESS; //放弃绑定 直接返回成功即可
// We want to attach to this file system. Create a new device object we can attach with.
status = IoCreateDevice(gSFilterDriverObject, sizeof(SFILTER_DEVICE_EXTENSION) + gUserExtensionSize, NULL, DeviceObject->DeviceType, 0, FALSE, &newDeviceObject); //生成新的设备 准备绑定目标设备
if (!NT_SUCCESS(status))
return status;
if (!OnSfilterAttachPre(newDeviceObject, DeviceObject, &fsName, (PVOID)(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->UserExtension))) //最后一节讲 询问用户是否要绑定
return STATUS_UNSUCCESSFUL;
// 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);
// Do the attachment
devExt = newDeviceObject->DeviceExtension;
status = SfAttachDeviceToDeviceStack(newDeviceObject, DeviceObject, &devExt->AttachedToDeviceObject); //绑定
if (!NT_SUCCESS(status))
goto ErrorCleanupDevice;
// Set the name
devExt->TypeFlag = SFLT_POOL_TAG;
RtlInitEmptyUnicodeString(&devExt->DeviceName, devExt->DeviceNameBuffer, sizeof(devExt->DeviceNameBuffer)); //设备名字记录到设备扩展中
RtlCopyUnicodeString(&devExt->DeviceName, DeviceName); //Save Name
// Mark we are done initializing
ClearFlag(newDeviceObject->Flags, DO_DEVICE_INITIALIZING);
// Display who we have attached to
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES, ("SFilter!SfAttachToFileSystemDevice: Attaching to file system %p \"%wZ\" (%s)\n", DeviceObject, &devExt->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, &fsName); //枚举所有的卷 逐个绑定
if (!NT_SUCCESS(status)) {
IoDetachDevice(devExt->AttachedToDeviceObject);
goto ErrorCleanupDevice;
};
};
#endif
OnSfilterAttachPost(newDeviceObject, DeviceObject, devExt->AttachedToDeviceObject, (PVOID)(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->UserExtension), STATUS_SUCCESS); //最后一节讲 绑定后处理回调函数
return STATUS_SUCCESS;
// Cleanup error handling
ErrorCleanupDevice: //错误处理
SfCleanupMountedDevice(newDeviceObject);
IoDeleteDevice(newDeviceObject);
OnSfilterAttachPost(newDeviceObject, DeviceObject, NULL, (PVOID)(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->UserExtension), status);
return status;
};

FS-CDO绑定

FS-CDO绑定

sfilter过滤设备的设备扩展定义:

1
2
3
4
5
6
7
8
9
typedef struct _SFILTER_DEVICE_EXTENSION { //文件过滤系统驱动的设备扩展
ULONG TypeFlag;
PDEVICE_OBJECT AttachedToDeviceObject; //绑定的文件系统设备(真实设备)
PDEVICE_OBJECT StorageStackDeviceObject; //与文件系统设备相关的真实设备(磁盘) 在绑定时用
UNICODE_STRING DeviceName; //如果绑定了一个卷 那么这是物理磁盘卷名 否则是绑定的CDO名
WCHAR DeviceNameBuffer[MAX_DEVNAME_LENGTH]; //保存名字的缓冲区
// The extension used by other user.
UCHAR UserExtension[1];
} SFILTER_DEVICE_EXTENSION, * PSFILTER_DEVICE_EXTENSION;

为了让系统看起来过滤驱动和文件系统一样的话,有些标志位需要复制:

1
2
3
4
5
6
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);

上文的SfAttachToFileSystemDevice即为代码。

利用文件系统控制请求

当有卷设备被挂载或解挂载时,SfFsControl会被系统回调。从这个回调函数中获取卷设备相关信息并绑定它,才能捕获各种针对文件的IRP,从而获得监控各种文件操作的能力。主功能号为IRP_MJ_FILE_SYSTEM_CONTROL时,下面这几个次功能号IRP要被处理:

  • IRP_MN_MOUNT_VOLUMN:一个卷被挂载,这时要调用SfFsControlMountVolume来绑定一个卷。
  • IRP_MN_LOAD_FILE_SYSTEM:文件系统识别器要求加载真正的文件系统。
  • IRP_MN_USER_FS_REQUEST:此时可以从irpSp->Parameters.FileSystemControl.FsControlCode中获得一个控制码,当控制码为FSCTL_DISMOUNT_VOLUME时说明是个磁盘解挂载。捕获U盘手工拔出太复杂了,但解挂载后sfilter不删除过滤设备也问题不大,除了一点内存泄露。

代码:

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
NTSTATUS SfFsControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
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));
if (!IS_MY_DEVICE_OBJECT(DeviceObject)) {
PVOID context = NULL;
NTSTATUS status;
SF_RET ret = OnSfilterIrpPre(DeviceObject, NULL, NULL, Irp, &status, &context);
ASSERT(context == NULL);
ASSERT(ret == SF_IRP_COMPLETED);
return status;
};
// 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->AttachedToDeviceObject, &devExt->DeviceName));
break;
};
};
break;
};
};
// Pass all other file system control requests through.
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->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
NTSTATUS SfFsControlLoadFileSystem(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
PSFILTER_DEVICE_EXTENSION devExt = DeviceObject->DeviceExtension;
NTSTATUS status;
PFSCTRL_COMPLETION_CONTEXT completionContext;
PAGED_CODE();
// This is a "load file system" request being sent to a file system recognizer device object. This IRP_MN code is only sent to file system recognizers.
// NOTE: Since we no longer are attaching to the standard Microsoft file system recognizers we will normally never execute this code. However, there might be 3rd party file systems which have their own recognizer which may still trigger this IRP.
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES, ("SFilter!SfFscontrolLoadFileSystem: Loading File System, Detaching from \"%wZ\"\n", &devExt->DeviceName));
// VERSION NOTE:
// On Windows 2000, we cannot simply synchronize back to the dispatch routine to do our post-load filesystem processing. We need to do this work at passive level, so we will queue that work to a worker thread from the completion routine.
// For Windows XP and later, we can safely synchronize back to the dispatch routine. The code below shows both methods. Admittedly, the code would be simplified if you chose to only use one method or the other, but you should be able to easily adapt this for your needs.
#if WINVER >= 0x0501
if (IS_WINDOWSXP_OR_LATER()) {
KEVENT waitEvent;
KeInitializeEvent(&waitEvent, NotificationEvent, FALSE);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp, SfFsControlCompletion, &waitEvent, TRUE, TRUE, TRUE); //context parameter
status = IoCallDriver(devExt->AttachedToDeviceObject, Irp);
// Wait for the operation to complete
if (STATUS_PENDING == status) {
status = KeWaitForSingleObject(&waitEvent, Executive, KernelMode, FALSE, NULL);
ASSERT(STATUS_SUCCESS == status);
};
// Verify the IoCompleteRequest was called
ASSERT(KeReadStateEvent(&waitEvent) || !NT_SUCCESS(Irp->IoStatus.Status));
status = SfFsControlLoadFileSystemComplete(DeviceObject, Irp);
}
else {
#endif
// Set a completion routine so we can delete the device object when the load is complete.
completionContext = ExAllocatePoolWithTag(NonPagedPool, sizeof(FSCTRL_COMPLETION_CONTEXT), SFLT_POOL_TAG);
if (completionContext == NULL) {
// If we cannot allocate our completion context, we will just pass through the operation. If your filter must be present for data access to this volume, you should consider failing the operation if memory cannot be allocated here.
IoSkipCurrentIrpStackLocation(Irp);
status = IoCallDriver(devExt->AttachedToDeviceObject, Irp);
}
else {
ExInitializeWorkItem(&completionContext->WorkItem, SfFsControlLoadFileSystemCompleteWorker, completionContext);
completionContext->DeviceObject = DeviceObject;
completionContext->Irp = Irp;
completionContext->NewDeviceObject = NULL;
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp, SfFsControlCompletion, completionContext, TRUE, TRUE, TRUE);
// Detach from the file system recognizer device object.
IoDetachDevice(devExt->AttachedToDeviceObject);
// Call the driver
status = IoCallDriver(devExt->AttachedToDeviceObject, Irp);
};
#if WINVER >= 0x0501
};
#endif
return status;
};

绑定文件系统卷

VPB

在主功能号IRP_MJ_FILE_SYSTEM_CONTROL,次功能号为IRP_MN_MOUNT_VOLUME的IRP请求处理中,调用SfFsControlMountVolume绑定卷设备。指针irpSp->Parameters.MountVolume.Vpb是个VBP,VBP为卷参数块,作用是把实际存储媒介设备对象和文件系统卷设备对象联系起来。卷设备在irpSp->Parameters.MountVolume.Vpb->DeviceObject,存储设备对象在irpSp->Parameters.MountVolume.Vpb->RealDevice

但这里有个问题,irpSp->Parameters.MountVolume.Vpb->DeviceObject在这个请求完成前还没有意义,所以要先保存irpSp->Parameters.MountVolume.Vpb->RealDevice,然后再从这个存储设备对象中获取卷设备才对。

代码下面再讲。

设置IRP完成函数

等待IRP完成的方法同之前几篇。拷贝当前栈空间后向下发送请求,在此之前给IRP分配一个完成函数,IRP完成后自动调用完成函数。完成函数在Dispatch中断级,但有些系统调用需要在Passive中断级调用,具体哪些系统调用可以参看WDK,有的API会标注“irq level=PASSIVE”。

如果代码执行是由于应用程序/上层调用,则在Passive中断级,例如上层发来IRP的分发函数。如果代码执行时由于下层硬件引发,则在Dispatch中断级,例如网卡的OnReceive和硬盘读写完毕返回回调函数等。这不是绝对的,但应随时做好最坏的准备。

卷影和磁盘数据恢复相关的特殊设备,可过滤也可不过滤。

同步方法:初始化一个KEVENT事件,并通过上下文传递给完成回调函数,完成函数设置这个事件,本函数等待这个事件。

完成函数的中断级为Dispatch,不适合直接绑定设备,应该使用工作任务将某个函数插入到某个线程中执行,这时拥有Passive中断级。用ExInitializeWorkItem初始化一个工作任务:

1
2
3
4
5
VOID ExInitializeWorkItem(
IN PWORK_QUEUE_ITEM Item, //要初始化的工作任务
IN PWORKER_THREAD_ROUTINE Routine, //有待执行的函数
IN PVOID Context //上下文指针 完全用户自定义
)

再用ExQueueWorkItem将某个工作任务插入到某个队列中,Windows内核依次执行这些队列中的工作任务,且确保中断级别为Passive:

1
2
3
4
VOID ExQueueWorkItem(
IN PWORK_QUEUE_ITEM WorkItem, //工作任务指针
IN WORK_QUEUE_TYPE QueueType //CriticalWorkQueue执行优先级高 DelayedWorkQueue优先级自动调整 Passive中断级一般就DelayedWorkQueue
)

代码:

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
NTSTATUS SfFsControlMountVolume(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
PSFILTER_DEVICE_EXTENSION devExt = DeviceObject->DeviceExtension;
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
PDEVICE_OBJECT newDeviceObject;
PDEVICE_OBJECT storageStackDeviceObject;
PSFILTER_DEVICE_EXTENSION newDevExt;
NTSTATUS status;
BOOLEAN isShadowCopyVolume;
PFSCTRL_COMPLETION_CONTEXT completionContext;
PAGED_CODE();
ASSERT(IS_MY_DEVICE_OBJECT(DeviceObject));
ASSERT(IS_DESIRED_DEVICE_TYPE(DeviceObject->DeviceType));
// Get the real device object (also known as the storage stack device object or the disk device object) pointed to by the vpb parameter because this vpb may be changed by the underlying file system. Both FAT and CDFS may change the VPB address if the volume being mounted is one they recognize from a previous mount.
storageStackDeviceObject = irpSp->Parameters.MountVolume.Vpb->RealDevice; //保存下来RealDevice
// Determine if this is a shadow copy volume. If so don't attach to it.
// NOTE: There is no reason sfilter shouldn't attach to these volumes, this is simply a sample of how to not attach if you don't want to
status = SfIsShadowCopyVolume(storageStackDeviceObject, &isShadowCopyVolume); //判断是否是卷影
if (NT_SUCCESS(status) && isShadowCopyVolume && !FlagOn(SfDebug, SFDEBUG_ATTACH_TO_SHADOW_COPIES)) { //不打算绑定卷影就跳过去
UNICODE_STRING shadowDeviceName;
WCHAR shadowNameBuffer[MAX_DEVNAME_LENGTH];
// Get the name for the debug display
RtlInitEmptyUnicodeString(&shadowDeviceName, shadowNameBuffer, sizeof(shadowNameBuffer));
SfGetObjectName(storageStackDeviceObject, &shadowDeviceName);
SF_LOG_PRINT(SFDEBUG_DISPLAY_ATTACHMENT_NAMES, ("SFilter!SfFsControlMountVolume Not attaching to Volume %p \"%wZ\", shadow copy volume\n", storageStackDeviceObject, &shadowDeviceName));
// Go to the next driver
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(devExt->AttachedToDeviceObject, Irp);
};
// This is a mount request. Create a device object that can be attached to the file system's volume device object if this request is successful. We allocate this memory now since we can not return an error in the completion routine.
// Since the device object we are going to attach to has not yet been created (it is created by the base file system) we are going to use the type of the file system control device object. We are assuming that the file system control device object will have the same type as the volume device objects associated with it.
status = IoCreateDevice(gSFilterDriverObject, sizeof(SFILTER_DEVICE_EXTENSION) + gUserExtensionSize, NULL, DeviceObject->DeviceType, 0, FALSE, &newDeviceObject); //预先生成过滤设备 虽然没到绑定的时候
if (!NT_SUCCESS(status)) {
// If we can not attach to the volume, then don't allow the volume to be mounted.
KdPrint(("SFilter!SfFsControlMountVolume: Error creating volume device object, status=%08x\n", status));
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
};
// We need to save the RealDevice object pointed to by the vpb parameter because this vpb may be changed by the underlying file system. Both FAT and CDFS may change the VPB address if the volume being mounted is one they recognize from a previous mount.
newDevExt = newDeviceObject->DeviceExtension; //填写设备扩展 RealDevice被保存在这里
newDevExt->StorageStackDeviceObject = storageStackDeviceObject;
newDevExt->TypeFlag = SFLT_POOL_TAG;
// Get the name of this device
RtlInitEmptyUnicodeString(&newDevExt->DeviceName, newDevExt->DeviceNameBuffer, sizeof(newDevExt->DeviceNameBuffer));
SfGetObjectName(storageStackDeviceObject, &newDevExt->DeviceName);
// VERSION NOTE:
// On Windows 2000, we cannot simply synchronize back to the dispatch routine to do our post-mount processing. We need to do this work at passive level, so we will queue that work to a worker thread from the completion routine.
// For Windows XP and later, we can safely synchronize back to the dispatch routine. The code below shows both methods. Admittedly, the code would be simplified if you chose to only use one method or the other, but you should be able to easily adapt this for your needs.
#if WINVER >= 0x0501
if (IS_WINDOWSXP_OR_LATER()) {
KEVENT waitEvent;
KeInitializeEvent(&waitEvent, NotificationEvent, FALSE); //初始化事件
IoCopyCurrentIrpStackLocationToNext(Irp); //因为要等待完成 所以拷贝当前调用栈
IoSetCompletionRoutine(Irp, SfFsControlCompletion, &waitEvent, TRUE, TRUE, TRUE); //设置完成函数 waitEvent当作上下文传入
status = IoCallDriver(devExt->AttachedToDeviceObject, Irp); //发送IRP并等待事件完成
// Wait for the operation to complete
if (STATUS_PENDING == status) {
status = KeWaitForSingleObject(&waitEvent, Executive, KernelMode, FALSE, NULL);
ASSERT(STATUS_SUCCESS == status);
};
// Verify the IoCompleteRequest was called
ASSERT(KeReadStateEvent(&waitEvent) || !NT_SUCCESS(Irp->IoStatus.Status));
status = SfFsControlMountVolumeComplete(DeviceObject, Irp, newDeviceObject); //请求完成 调用函数绑定卷
}
else {
#endif
// Initialize our completion routine
completionContext = ExAllocatePoolWithTag(NonPagedPool, sizeof(FSCTRL_COMPLETION_CONTEXT), SFLT_POOL_TAG);
if (completionContext == NULL) {
// If we cannot allocate our completion context, we will just pass through the operation. If your filter must be present for data access to this volume, you should consider failing the operation if memory cannot be allocated here.
IoSkipCurrentIrpStackLocation(Irp);
status = IoCallDriver(devExt->AttachedToDeviceObject, Irp);
}
else {
ExInitializeWorkItem(&completionContext->WorkItem, SfFsControlMountVolumeCompleteWorker, completionContext); //初始化工作任务
completionContext->DeviceObject = DeviceObject; //写入上下文
completionContext->Irp = Irp;
completionContext->NewDeviceObject = newDeviceObject;
IoCopyCurrentIrpStackLocationToNext(Irp); //拷贝调用栈
IoSetCompletionRoutine(Irp, SfFsControlCompletion, &completionContext->WorkItem, TRUE, TRUE, TRUE); //context parameter
// Call the driver
status = IoCallDriver(devExt->AttachedToDeviceObject, Irp);
};
#if WINVER >= 0x0501
};
#endif
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
NTSTATUS SfFsControlCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context) {
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
ASSERT(IS_MY_DEVICE_OBJECT(DeviceObject));
ASSERT(Context != NULL);
#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) //中断级别过高 工作任务放到DelayedWorkQueue队列中执行
// 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;
};

绑定卷

SfFsControlMountVolumeCompleteWorker就是调用SfFsControlMountVolumeComplete绑定卷设备:

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
NTSTATUS SfFsControlMountVolumeComplete(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PDEVICE_OBJECT NewDeviceObject) {
PVPB vpb;
PSFILTER_DEVICE_EXTENSION newDevExt;
PIO_STACK_LOCATION irpSp;
PDEVICE_OBJECT attachedDeviceObject;
NTSTATUS status;
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->StorageStackDeviceObject->Vpb; //获取之前保存的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)) {
// 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)) {
// 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.
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 failure for %p \"%wZ\", already attached\n", ((PSFILTER_DEVICE_EXTENSION)attachedDeviceObject->DeviceExtension)->AttachedToDeviceObject, &newDevExt->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);
}
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->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
NTSTATUS SfAttachToMountedDevice(IN PDEVICE_OBJECT DeviceObject, IN PDEVICE_OBJECT SFilterDeviceObject) {
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
if (!OnSfilterAttachPre(SFilterDeviceObject, DeviceObject, NULL, (PVOID)(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->UserExtension))) //最后一节讲 询问用户是否要绑定
return STATUS_UNSUCCESSFUL;
// 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);
// 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.
for (i = 0; i < 8; i++) { //反复8次尝试绑定
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->AttachedToDeviceObject);
if (NT_SUCCESS(status)) {
OnSfilterAttachPost(SFilterDeviceObject, DeviceObject, newDevExt->AttachedToDeviceObject, (PVOID)(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->UserExtension), 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 to volume %p \"%wZ\"\n", newDevExt->AttachedToDeviceObject, &newDevExt->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); //delay 1/2 second
KeDelayExecutionThread(KernelMode, FALSE, &interval); //这个线程延迟500ms后再继续
};
OnSfilterAttachPost(SFilterDeviceObject, DeviceObject, NULL, (PVOID)(((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->UserExtension), status); //最后一节讲 绑定后处理回调函数
return status;
};

操作过滤

注意:以下部分没有实例代码,看看就行了。

读请求

从硬盘上获得数据,必须请求结束后才存在于输出缓冲区。比写请求麻烦,写请求数据由上层程序填写,请求完成前已存在于输入缓冲区中。

先要判断设备对象是个啥,如果是绑定在文件系统卷设备上,则这确实是个读写请求。如果绑定在FS-CDO上的就不是。这一点通过设备扩展中的StorageDev字段判断。

DriverEntry里加一条:

1
DriverObject->MajorFunction[IRP_MJ_READ] = SfRead;

判断是否为卷设备:

1
2
3
PSFILTER_DEVICE_EXTENSION devExt = DeviceObject->DeviceExtension;
if (devExt->StorageDev != NULL)
//卷设备 对文件的写操作

写操作处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
NTSTATUS SfRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
PFILE_OBJECT file_object = irpsp->FileObject;
PSFILTER_DEVICE_EXTENSION devExt = DeviceObject->DeviceExtension;
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) { //控制设备操作直接返回失败
Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
};
if (devExt->StorageDev != NULL) //对文件系统其他设备的操作 直接下发
return SfPassThrough(DeviceObject, Irp);
//到这里说明是对卷设备的操作
};

解析读请求

读请求中读取的偏移量和读取的文件内容长度:

1
2
3
4
5
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
LARGE_INTEGER offset;
ULONG length;
offset.QuadPart = irpsp->Parameters.Read.ByteOffset.QuadPart;
length = irpsp->Parameters.Read.Length;

如果是写请求:

1
2
3
4
5
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
LARGE_INTEGER offset;
ULONG length;
offset.QuadPart = irpsp->Parameters.Write.ByteOffset.QuadPart;
length = irpsp->Parameters.Write.Length;

总结一下处理IRP请求的四种方式:

  • 如果对IRP完成后的事情无兴趣,直接用IoSkipCurrentIrpStackLocation来忽略当前IO_STACK_LOCATION,然后向下传递并返回IoCallDriver的返回状态。
  • 如果对IRP完成后的事情无兴趣且不打算继续传递,打算立即返回成功或失败时,直接填写IRP的状态参数后调用IoCompleteRequest并返回自己想返回的结果。
  • 如果对IRP完成后的事情有兴趣,并打算在完成函数中处理时,先使用IoCopyCurrentIrpStackLocationToNext来拷贝当前IO_STACK_LOCATION,然后完成指定函数,返回IoCallDriver的返回状态。(在完成函数中没必要调用IoCompleteRequest,直接返回IRP状态即可)。
  • 同上一个情况时,还有可能把任务塞进系统工作线程中或希望在另外的线程中完成IRP,那么完成函数应返回STATUS_MORE_PROCESSING_REQUIRED,并在完成时调用IoCompleteRequest。另一种方法在分发函数中等待,在完成函数中设置事件,操作相同。

至于怎么获取读取到的内容,IRP缓冲区放在哪儿取决于这个操作的IO方式:BufferIO方式、DirectIO方式、NeitherIO方式。

  • BufferIO方式:在文件操作读写请求中不会出现,但会在串口过滤中出现。把发出请求的应用程序的用户空间的缓冲区拷贝一份到内核空间,解决了不同进程间地址失效问题。但因为要拷贝,所以既费时又费力。
  • DirectIO方式:使用MDL传递缓冲区,大约就是直接把用户空间范围直接映射到内核空间。缓冲区MDL指针在Irp->MdlAddress中,再用MmGetSystemAddressForMdl转换得到实际缓冲区指针。
  • NeitherIO方式:直接把用户空间指针传进来,即Irp->UserBuffer。这可以在请求处理回调函数中直接使用,但不能在其他线程中处理。

读操作代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
KEVENT waitEvent;
PVOID buf;
ULONG length;
KeInitializeEvent(&waitEvent, NotificationEvent, FALSE);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp, SfReadCompletion, &waitEvent, TRUE, TRUE, TRUE);
status = IoCallDriver(devExt->AttachedToDeviceObject, Irp);
if (STATUS_PENDING == status) {
status = KeWaitForSingleObject(&waitEvent, Executive, KernelMode, FALSE, NULL);
ASSERT(STATUS_SUCCESS == status);
};
if (irp.IoStatus.Status == STATUS_SUCCESS) { //获取到的内容在buf
if (irp->MdlAddress != NULL)
buf = MmGetSystemAddressForMdl(Irp->MdlAddress);
else
buf = Irp->UserBuffer;
length = IoStatus.Information; //内容长度的获取
};

次功能号获取:

1
2
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
UCHAR minor_code = irpsp->MinorFunction;

主功能号为IRP_MJ_READ时,次功能号有这些情况:

  • IRP_MN_NORMAL:缓冲区在Irp->MdlAddressIrp->UserBuffer中返回。
  • IRP_MN_MDL:收到这个次功能号时,上述两个缓冲区都为空,要求分配一个MDL并返回上层,当之后收到IRP_MN_MDL_COMPLETE时,可以释放MDL了。

分配、释放MDL方法:

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
_inline PMDL MyMdlAllocate(PVOID buf, ULONG length) { //分配MDL buf必须用户预先分配好
PMDL pmdl = IoAllocateMdl(buf, length, FALSE, FALSE, NULL);
if (pmdl == NULL)
return NULL;
MmBuildMdlForNonPagedPool(pmdl);
return pmdl;
};
_inline PMDL MyMdlMemoryAllocate(ULONG length) { //分配MDL 并带有一块内存
PMDL mdl;
PVOID buffer = ExAllocatePool(NonPagedPool, length);
if (buffer == NULL)
return NULL;
mdl = MyMdlAllocate(buffer, length);
if (mdl == NULL) {
ExFreePool(buffer);
return NULL;
};
return mdl;
};
_inline VOID MyMdlMemoryFree(PMDL mdl) { //释放MDL 并释放MDL所带内存
PVOID buffer = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority);
IoFreeMdl(mdl);
ExFreePool(buffer);
return;
};

读请求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
switch (irpsp->MinorFunction) {
case IRP_MN_NORMAL: { //先保留文件的偏移位置
PVOID buffer;
if (irp->MdlAddress != NULL)
buffer = MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);
else
buffer = Irp->UserBuffer;
//如果有资源 就往buffer中写入
irp->IoStatus.Information = length;
irp->IoStatus.Status = STATUS_SUCCESS;
irp->FileObject->CurrentByteOffset.Quat = offset.Quat + length;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
};
case IRP_MN_MDL: {
//先分配MDL
PMDL mdl = MyMdlMemoryAllocate(length);
if (mdl == NULL) {
//资源不足
};
irp->MdlAddress = mdl;
//如果有资源 就往MDL的buffer中写入
irp->IoStatus.Information = length;
irp->IoStatus.Status = STATUS_SUCCESS;
irp->FileObject->CurrentByteOffset.Quat = offset.Quat + length;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
};
case IRP_MN_MDL_COMPLETE: {
//释放MDL
irp->IoStatus.Information = length;
irp->IoStatus.Status = STATUS_SUCCESS;
irp->FileObject->CurrentByteOffset.Quat = offset.Quat + length;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
};
default: {
//其他情况不过滤
};
};

其他操作

文件对象生存周期

应用层使用的文件句柄直接对应于内核中的文件对象,二者可以互相转换,一个文件对象可以获得多个文件句柄。文件对象在主功能号为IRP_MJ_CREATE的IRP完成后诞生的(获得IRP但没完成时文件对象指针可用但没真正打开文件,是个无效的文件对象),在IRP_MJ_CLOSE的IRP完成后被销毁。这样从IRP中获得文件对象指针:

1
2
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
PFILE_OBJECT file_obj = irpsp->FileObject;

文件的打开与关闭

判断一个已存在的文件对象是一个文件还是一个目录:收到文件打开IRP时先获得当前IO_STACK_LOCATION,其中irpsp->Parameters.Create的结构如下,这些参数与应用层CreateFile的参数一一对应:

1
2
3
4
5
6
7
struct {
PIO_SECURITY_CONTEXT SecurityContext;
ULONG Options;
USHORT FileAttributes;
USHORT ShareAccess;
ULONG EaLength;
};

可能生成或打开时该文件对象已经存在了,这时无法判断是个文件还是个目录。所以要手动你维护一个哈希表(或者链表、数组等),新打开的判断是否为目录并插入哈希表,这样所有打开的目录都被保存。改动读请求的完成函数:

1
2
3
4
5
6
7
8
9
NTSTATUS SfCreateComplete(IN PDEVICE_OBJECT DeviceObject, IN PIRP irp, IN PVOID context) {
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
PFILE_OBJECT file = irpsp->FileObject;
UNREFERENCED_PARAMETER(DeviceObject);
if (NT_SUCCESS(irp->IoStatus.Status))
if (file != NULL && (irpsp->Parameters.Create.Options & FILE_DIRECTORY_FILE) != 0)
MyAddObjToSet(file); //成功则把这个文件对象记录到集合里 这是刚刚打开或生成的目录
return irp->IoStatus.Status;
};

文件的删除

分三步走:

  • 发送一个请求打开文件,打开时需设置为有删除的访问权限,打开失败时将直接导致无法删除文件。
  • 发出一个IRP_MJ_SET_INFORMATION设置请求,表示这个文件将被删除。
  • 关闭时文件被系统删除。

有些临时文件在打开时便被设置了关闭时删除,不需要再设置请求,关闭后被删除。

文件关闭前这个文件依然存在,但对文件的大部分操作(读、写、查询)等都被返回文件再删除中错误。

上述过程为彻底删除,移动到回收站只是一种改名操作。主功能号也是IRP_MJ_SET_INFORMATION,但Irpsp->Paramters.SetFile.FileInformationClass不同。

在上述irpsp->Parameters.Create结构的第一个域的结构如下:

1
2
3
4
5
6
typdef struct _IO_SECURITY_CONTEXT {
PSECURITY_QUALITY_OF_SERVICE SecurityQos;
PACCESS_STATE AccessState;
ACCESS_MASK DesiredAccess;
ULONG FullCreateOptions;
} IO_SECURITY_CONTEXT, *PIO_SECURITY_CONTEXT;

具体步骤如下:

  • 将上述的DesiredAccess设置DELETE位。

  • 发送一个IRP_MJ_SET_INFORMATION,其中irpsp->Parameters.SetFile.FileInformationClass应该为FileDispositioninformation,然后irp->AssociatedIrp.SystemBuffer指向这个结构,其中DeleteFile应为TRUE:

    1
    2
    3
    typedef struct _FILE_DISPOSITION_INFORMATION {
    BOOLEAN DeleteFile;
    } FILE_DISPOSITION_INFORMATION;

路径过滤

根据文件路径决定是否要过滤这个文件。

打开成功后获取路径

ObQueryNameString通过文件对象获取文件路径。

1
2
3
4
5
6
NTSTATUS ObQueryNameString(
IN PVOID Object, //文件对象
OUT POBJECT_NAME_INFORMATION ObjectNameInfo, //接收返回名字信息
IN ULONG Length,
OUT PULONG ReturnLength
);

其中OBJECT_NAME_INFORMATION结构长这样:

1
2
3
typedef struct _OBJECT_NAME_INFORMATION{
UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;

这个也要用调用两次的方法,先第一次调用试探缓冲区长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
UCHAR buf[64] = { 0 }; //猜测只要64字节
PUCHAR new_buf = NULL;
ULONG length = 64, ret_length;
POBJECT_NAME_INFORMATION name_infor = (POBJECT_NAME_INFORMATION)buf;
NTSTATUS status = ObQueryNameString(file_obj, name_infor, length, &ret_length); //第一次调用用默认的64字节
if (status == STATUS_INFO_LENGTH_MISMATCH) {
new_buf = ExAllocatePool(nonPagedPool, ret_length); //第二次调用 重新分配长度
if (new_buf == NULL) {
//内存不足 错误处理
}
else {
name_infor = (POBJECT_NAME_INFORMATION)new_buf;
status = ObQueryNameString(file_obj, name_infor, length, &ret_length);
//这里当status为STATUS_SUCCESS时 name_infor->Name就是要获得的名字了
};
};
if (new_buf != NULL)
ExFreePool(new_buf);

例如获取文件“MyFile.Name”文件结果为“\\Device\\HardDiskVolume1\\MyDirectory\\MyFile.Name”,但必须在IRP_MJ_CREATE完成后进行,否则只能获得盘符“\\Device\\HardDiskVolume1”,且ObQueryNameString只能在IRP_MJ_CREATE、IRP_MJ_CLEANUP、IRP_MJ_CLOSE的处理中使用,否则Windows有个bug会锁死。

在其他时候,如改名、查询、设置、读、写时没有特别稳定的方法,有两种说法如下。最好在打开时进行哈希表等方法记录,然后在任意时刻即可查询。

  • 可以直接向下层设备发送IRP_MJ_QUERY_INFORMATION,但这个IRP具体怎么写Windows没有公开。
  • 存在FileObject->FileName,但任何过滤层都可以修改它,所以在文件被打开后就认为失效了。

打开请求完成前获取路径

如何在IRP_MJ_CREATE处理前得到路径名。

只能通过FileObject->FileName来获取,但当FileObject->RelatedObject不为空时,FileNameRelatedObject目录的相对路径。用ObQueryNameString查询全路径后要和FileName组合,组合时还要判断要不要插入一个“\\”。

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
#define MEM_TAG "mymt"
ULONG MyFileFullPathPreCreate(PFILE_OBJECT file, PUNICODE_STRING path) {
NTSTATUS status;
POBJECT_NAME_INFORMATION obj_name_info = NULL;
WCHAR buf[64] = { 0 };
PVOID obj_ptr;
ULONG length = 0;
BOOLEAN need_split = FALSE;
ASSERT(file != NULL);
if (file == NULL)
return 0;
if (file->FileName.Buffer == NULL)
return 0;
obj_name_info = (POBJECT_NAME_INFORMATION)buf;
do {
if (file->RelatedFileObject != NULL)
obj_ptr = (PVOID)file->RelatedFileObject;
else
obj_ptr = (PVOID)file->DeviceObject;
status = ObQueryNameString(obj_ptr, obj_name_info, 64 * sizeof(WCHAR), &length);
if (status == STATUS_INFO_LENGTH_MISMATCH) {
obj_name_info = ExAllocatePoolWithTag(NonPagedPool, length, MEM_TAG);
if (obj_name_info == NULL)
return STATUS_INSUFFICIENT_RESOURCES;
RtlZeroMemory(obj_name_info, length);
status = ObQueryNameString(obj_ptr, obj_name_info, length, &length);
};
if (!NT_SUCCESS(status)) //失败了直接跳出
break;
if (file->FileName.Length > 2 && file->FileName.Buffer[0] != TEXT('\\') && obj_name_info->Name.Buffer[obj_name_info->Name.Length / sizeof(WCHAR) - 1] != TEXT('\\')) //FileName第一个字符不是斜杠 obj_name_info最后一个不是斜杠
need_split = TRUE;
length = obj_name_info->Name.Length + file->FileName.Length; //获取总体名字长度 长度不足直接返回
if (need_split)
length += sizeof(WCHAR);
if (path->MaximumLength < length)
break;
RtlCopyUnicodeString(path, &obj_name_info->Name); //设备名
if (need_split)
RtlAppendUnicodeToString(path, TEXT("\\")); //追加斜杠
RtlAppendUnicodeStringToString(path, &file->FileName); //追加FileName
} while (0);
if ((PVOID)obj_name_info != (PVOID)buf) //分配过空间就释放掉
ExFreePool(obj_name_info);
return length;
};

短名转长名

FileObject->FileName获得的文件名有可能是DOSF风格短名,但没有简单的方法能够短名转长名。

例如短名路径“\\aaaaaa~1\\bbbbbb~1\\cccccc~1\\dddddd~1.txt”,先分解为:

1
2
3
4
5
\
aaaaaa~1
bbbbbb~1
cccccc~1
dddddd~1.txt

ZwCreateFile打开第一个目录,这不可能需要长短转换,再调用ZwQueryDirectoryFile枚举下面所有文件和目录,用FileIdBothDirectoryInformation查询类别进行查询,会得到一组FILE_ID_BOTH_DIR_INFORMATION,代表下面每个文件和目录,结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct _FILE_ID_BOTH_DIR_INFORMATION {
ULONG NextEntryOffset;
ULONG FileIndex;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttribtues;
ULONG FileNameLength;
ULONG EaSize;
CCHAR ShortNameLength;
WCHAR ShortName[12]; //短名
LARGE_INTEGER FileId;
WCHAR FileName[1]; //长名
} FILE_ID_BOTH_DIR_INFORMATION, *PFILE_ID_BOTH_DIR_INFORMATION;

这个代码自己去写。

sfilter的封装

sfilter太复杂了,要是改起来就太抽象了。一般将sfilter原封不动编译成.lib,导出一些接口后链接生成用。这节讲sfilter怎样导出了哪些接口。

回调

初始化回调:

1
2
3
4
5
6
7
extern NTSTATUS OnSfilterDriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath,
OUT PUNICODE_STRING userNameString, //控制设备的名字
OUT PUNICODE_STRING syblnkString, //控制设备符号链接名
OUT PULONG extensionSize //每个设备的设备扩展长度
);

这个回调函数的插入在上文最开始DriverEntry中实现了。

绑定回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extern BOOLEAN OnSfilterAttachPre( //每当有设备要被绑定时调用 用户返回TRUE表示同意绑定 反之不同意绑定
IN PDEVICE_OBJECT ourDevice, //已生成好的过滤设备
IN PDEVICE_OBJECT theDeviceToAttach, //要被绑定的真实设备
IN PUNICODE_STRING DeviceName, //设备名
IN PVOID extension //设备扩展指针 设备扩展大小在OnSfilterDriverEntry中决定
);

extern VOID OnSfilterAttachPost( //用户决定绑定 绑定完成后无论成功与否都调用这个函数
IN PDEVICE_OBJECT ourDevice, //参数都同上
IN PDEVICE_OBJECT theDeviceToAttach,
IN PDEVICE_OBJECT theDeviceToAttached,
IN PVOID extension,
IN NTSTATUS status //STATUS_SUCCESS绑定成功
);

插入请求回调

请求预处理为在IRP还未下发前过滤这个IRP,请求后处理为在IRP完成后修改结果。

请求预处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef enum {
SF_IRP_GO_ON = 0, //本驱动要继续处理这个IRP 下发后OnSfilterIrpPost会被调用
SF_IRP_COMPLETED = 1, //本驱动完成了这个请求 不会继续发送了
SF_IRP_PASS = 2 //这个IRP将下发并不调用完成后处理函数 本驱动不管了
} SF_RET;

extern SF_RET OnSfilterIrpPre(
IN PDEVICE_OBJECT DeviceObject,
IN PDEVICE_OBJECT NextObject,
IN PVOID extension,
IN PIRP Irp,
OUT PNTSTATUS status,
PVOID* context //上下文指针 会被传递到后处理函数中
);

请求后处理:

1
2
3
4
5
6
7
8
extern VOID OnSfilterIrpPost(
IN PDEVICE_OBJECT DeviceObject,
IN PDEVICE_OBJECT NextObject,
IN PVOID extension,
IN PIRP Irp,
IN NTSTATUS status,
PVOID context
);

插入请求回调在sfilter中的实现在SfPassThrough中。

静态链接库

编译发行版本的驱动必须链接发行版本的库,编译调试版本的驱动必须链接调试版本的库,库和驱动编译时指定的目标操作系统必须一样,编译用的WDK或DDK也必须一致。

该静态链接库的使用方法:

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
#include <ntifs.h>
#include "..\inc\sfilter\sfilter.h"

SF_RET OnSfilterIrpPre(IN PDEVICE_OBJECT dev, IN PDEVICE_OBJECT next_dev, IN PVOID extension, IN PIRP irp, OUT PNTSTATUS status, PVOID* context) {
// 获得当前调用栈
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
PFILE_OBJECT file = irpsp->FileObject;
// 我仅仅过滤文件请求。 FileObject不存在的情况一律passthru.
if (file == NULL)
return SF_IRP_PASS;
KdPrint(("IRP: file name = %wZ major function = %x\r\n", &file->FileName));
return SF_IRP_PASS;
};

VOID OnSfilterIrpPost(IN PDEVICE_OBJECT dev, IN PDEVICE_OBJECT next_dev, IN PVOID extension, IN PIRP irp, IN NTSTATUS status, PVOID context) {
// 什么都不用做
return;
};

NTSTATUS OnSfilterDriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath, OUT PUNICODE_STRING userNameString, OUT PUNICODE_STRING syblnkString, OUT PULONG extensionSize) {
UNICODE_STRING user_name, syb_name;
NTSTATUS status = STATUS_SUCCESS;
// 确定控制设备的名字和符号链接。
RtlInitUnicodeString(&user_name, L"sflt_smpl_cdo");
RtlInitUnicodeString(&syb_name, L"sflt_smpl_cdo_syb");
RtlCopyUnicodeString(userNameString, &user_name);
RtlCopyUnicodeString(syblnkString, &syb_name);
// 设置控制设备为所有用户可用
sfilterSetCdoAccessForAll();
return STATUS_SUCCESS;
};

VOID OnSfilterDriverUnload(VOID) {
// 没什么要做的...;
return;
};

NTSTATUS OnSfilterCDODispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
return STATUS_UNSUCCESSFUL;
};

BOOLEAN OnSfilterAttachPre(IN PDEVICE_OBJECT ourDevice, IN PDEVICE_OBJECT theDeviceToAttach, IN PUNICODE_STRING DeviceName, IN PVOID extension) {
// 直接返回TRUE,所有设备都绑定
return TRUE;
};

VOID OnSfilterAttachPost(IN PDEVICE_OBJECT ourDevice, IN PDEVICE_OBJECT theDeviceToAttach, IN PDEVICE_OBJECT theDeviceToAttached, IN PVOID extension, IN NTSTATUS status) {
// 不需要做什么。
return;
};