文件系统的过滤
11.4 文件系统控制设备的绑定
11.4.1 生成文件系统控制设备的过滤设备
接下来要生成过滤设备,这里再次用到设备拓展。下面给出过本过滤设备设备拓展的数据结构。
typedef struct _SFILTER_DEVICE_EXTENSION{
//绑定的文件系统设备(真实设备)
PDEVICE_OBJECT AttachedToDeviceObject;
//与文件系统设备相关的真实设备,这个在绑定时使用
PDEVICE_OBJECT StorageStackDeviceObject;
//如果绑定了一个卷,那么这是物理磁盘卷名;否则这是绑定的控制设备名
UNICODE_STRING DeviceName;
//用来保存名字字符串的缓冲区
WCHAR DeviceNameBuffer[MAX_DEVNAME_LENGTH];
}SFILTER_DEVICE_EXTENSION,*PSFILTER_DEVICE_EXTENSION;
这个数据结构完全是自定义的,这里我们只想记得自己绑定在那个设备上,如果想记录更多信息完全可以自定义再添加。这里我们在得到设备对象指针后,只需要使用下面代码就可以知道这个设备绑定的原始设备。
nextDeviceObject = ((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension->AttachedToDeviceObject);
回顾以前的内容,为了让系统看起来过滤设备和原来的设备没什么区别,必须设置该设备的一些标志位与所绑定的设备相同。
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_BUFFER_IO 和 DO_DIRECT_IO 对应着前面介绍过的缓冲IO和直接IO,两种方式的不同之处在于,外部向这些设备发送读写请求时,所用的缓冲地址将有所不同。
11.4.2 绑定文件系统控制设备
下面直接给出完整函数并注释解读。只需要在文件系统变动回调函数中调用这个函数,就可以完成对文件系统控制设备的绑定。注意这个函数仅仅能用来绑定文件系统控制设备,而不能用来绑定文件系统卷。
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();
//检查设备类型
if (!IS_DESIRED_DEVICE_TYPE(DeviceObject->DeviceType)) {
return STATUS_SUCCESS;
}
RtlInitEmptyUnicodeString( &fsName,
tempNameBuffer,
sizeof(tempNameBuffer) );
RtlInitUnicodeString( &fsrecName, L"\\FileSystem\\Fs_Rec" );
SfGetObjectName( DeviceObject->DriverObject, &fsName );
//根据我们是否要绑定识别器,来决定是否跳过文件识别器
if (!FlagOn(SfDebug,SFDEBUG_ATTACH_TO_FSRECOGNIZER)) {
//如果决定不绑定
if (RtlCompareUnicodeString( &fsName, &fsrecName, TRUE ) == 0) {
//则在识别为文件识别器时结束
return STATUS_SUCCESS;
}
}
//生成一个新的设备,准备绑定目标设备。
//首先创建设备
status = IoCreateDevice( gSFilterDriverObject,
sizeof( SFILTER_DEVICE_EXTENSION ) + gUserExtensionSize,
NULL,
DeviceObject->DeviceType,
0,
FALSE,
&newDeviceObject );
if (!NT_SUCCESS( status )) {
return status;
}
//复制各种标志
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 );
}
//使用上一节提供的函数进行绑定
devExt = newDeviceObject->DeviceExtension;
//之前提到的动态绑定函数
status = SfAttachDeviceToDeviceStack( newDeviceObject,
DeviceObject,
&devExt->AttachedToDeviceObject );
if (!NT_SUCCESS( status )) {
goto ErrorCleanupDevice;
}
//将设备名记录在设备拓展中
RtlInitEmptyUnicodeString( &devExt->DeviceName,
devExt->DeviceNameBuffer,
sizeof(devExt->DeviceNameBuffer) );
RtlCopyUnicodeString( &devExt->DeviceName, DeviceName );
ClearFlag( newDeviceObject->Flags, DO_DEVICE_INITIALIZING );
//版本兼容,当目标操作系统版本大于0x0501时候,
//Windows内核一定有EnumerateDeviceObjectList 等函数。
//这个时候可以枚举所有的卷,进行逐个绑定。
//如果操作系统版本较低,则无法绑定已经加载的卷
#if WINVER >= 0x0501
if (IS_WINDOWSXP_OR_LATER()) {
ASSERT( NULL != gSfDynamicFunctions.EnumerateDeviceObjectList &&
NULL != gSfDynamicFunctions.GetDiskDeviceObject &&
NULL != gSfDynamicFunctions.GetDeviceAttachmentBaseRef &&
NULL != gSfDynamicFunctions.GetLowerDeviceObject );
status = SfEnumerateFileSystemVolumes( DeviceObject, &fsName );
if (!NT_SUCCESS( status )) {
IoDetachDevice( devExt->AttachedToDeviceObject );
goto ErrorCleanupDevice;
}
}
#endif
return STATUS_SUCCESS;
//错误处理
ErrorCleanupDevice:
SfCleanupMountedDevice( newDeviceObject );
IoDeleteDevice( newDeviceObject );
return status;
}
11.4.3 利用文件系统控制请求
文件系统控制设备已经被绑定,绑定的目的是为了获得发送给文件系统控制设备的文件系统控制请求。这些IRP的主功能号为 IRP_MJ_FILE_SYSTEM_CONTROL,每个主功能号下又有许多次功能号。
从这些IRP中能得到足够的信息。确定过一个卷被挂载,这样才有可能去绑定文件系统的卷设备。之前在设置普通分发函数时候设计的对应的SfFsControl函数被设置为该请求的分发哈桑农户。
当有卷被挂载或者解挂载时,这个函数就会被系统回调。现在的任务就是在这个函数中获得卷设备的相关信息并对它实施绑定,才能捕获各种针对文件的IRP,从而获得监控各种文件的能力。
该主功能好的次功能号为:
- IRP_MN_MOUNT_VOLUME。说要一个卷被挂载,应该调用SfFsControlMountVolume 来绑定一个卷。该函数在后续讲解。
- IRP_MN_LOAD_FILE_SYSTEM。这个请求比较特殊,他一般出现在文件系统识别器需要加载真正的文件系统时候,说明之前绑定了一个文件系统才会截获到这个请求,因此不考虑。
- IRP_MN_USER_FS_REQUEST。此时可以从irpSp->Parameters.FileSystemControl.FsControlCode 得到一个控制码。当控制码为 FSCTL_DISMOUNT_VOLUME 时,说明一个磁盘被解挂载。(U盘的拔出并不会导致该请求,U盘拔出是更复杂的过程)
NTSTATUS SfFsControl(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
//获取当前IRP栈
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation( Irp );
PAGED_CODE();
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;
}
switch (irpSp->MinorFunction) {
//处理卷的挂载
case IRP_MN_MOUNT_VOLUME:
return SfFsControlMountVolume( 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;
}
}
//其他的请求都跳过,发送给真实设备
IoSkipCurrentIrpStackLocation( Irp );
return IoCallDriver( ((PSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp );
}
接下来必须要完成的函数,SfFsControlMountVolume才能完全监控所有的卷。这在下一节描述。
但是我们发现,在解挂载命令时候,并没有做销毁和解除绑定等操作。实际上,这个请求似乎根本不会出现,只是理论上存在。要真正准确的捕获解挂载操作是很困难的。
所以在sfilter中,并不解除和销毁设备。因为该设备被解挂载后,多多余的设备并没有影响。另外,解挂载行为并不会频繁,所以内存泄漏也不会明显。(此处为作者猜测)。
再次小结
第一步: 生成一个控制设备。当然,此前必须给给控制设备指定名词
第二步:设置分发函数与IO分发函数。
第三步:编写一个文件系统变动回调函数,在其中绑定刚激活的文件系统的控制设备,并注册这个回调函数。
第四步:编写默认的分发函数。
第五步:处理文件系统的控制请求,在其中监控卷设备的挂载。
明日计划
文件系统卷设备的绑定