文件系统的过滤与监控
11.3 设备的绑定前期工作
11.3.1 动态地选择绑定函数
? sfilter 有一个有趣的地方是,使用了动态加载的方法来使用内核函数。只有高版本的Windows系统上才有IoAttachDeviceToDeviceStackSafe 这个函数。如果我们直接使用这个函数,那么在低版本的Windows系统上就会直接加载失败。因此,使用动态加载此类函数的好处就是,即使在低版本的Windows上也能成功加载。
? 在动态加载该函数时,如果失败则会使指针为NULL,而此时即使为NULL,我们也可以检测替换为函数IoAttachDeviceToDeviceStack,这样驱动就可以兼容不同版本的Windows了。
? 使用MmGetSystemRoutineAddress 可以动态地寻找一个内核函数的指针。该API函数原型如下:
NTKERNELAPI
PVOID MmGetSystemRoutineAddress(IN PUNICODE_STRING SystemRoutineName);
? **SystemRoutineName **是一个字符串,是要动态寻找地址的内核函数的名字。
? 下面代码中的“#if WINVER>=0X0501”,类似的代码在本书后面的代码中以让常常看到,这里是编译时的判断指令。当编译结果期望适用的Windows版本高于0x0501时,使用动态加载函数的方式;当编译的结果期望适用于的Windows版本比这个低的似乎后,没有必要尝试动态加载高级版本的函数。
NTSTATUS SfAttachDeviceToDeviceAttack(IN PDEVICE_OBJECT SourceDevice,IN PDEVICE_OBJECT TargetDevice
)
{PAGED_CODE();
//并不是当Windows版本高于该版本时候就运行一下代码
//应该理解为;当编译的期望目标操作系统版本高于0x0501时
//编译以下代码,反之不编译
#if WINVER >= 0x0501if (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
}
? 不知道从现在的操作系统驱动编程中,还需不需要对这个绑定函数进行动态加载了。不过可能有其他的函数需要动态加载吧。
11.3.2 注册文件系统变动回调
? 根据前面的知识,我们已经知道过滤对文件系统卷的请求方法,就是将他绑定。虽然这些设备卷都没有设备名,但是要找到这些对象也很简单。
? 如果不想处理动态的文件系统卷,完全可以这样简单的绑定。但是sfilter的要求更高,他要求当一个U盘插入USB扣,一个“J:” 之类的卷动态生成时,sfilter依然可以捕获这个时间,并生成一个过滤设备来绑定它。
? 一个新的存储媒介被系统发现并在文件系统内生成一个卷的过程称为挂载(Mount),与之相反的过程称为解挂载(Diskmount)。该过程开始时,文件系统的控制设备将得到一个IRP,其主功能好为IRP_MJ_FILE_SYSTEM_CONTROL ,次功能号为 IRP_MN_MOUNT。换一句话说,如果过滤驱动中已经生成了一个设备绑定文件系统的CDO,那么程序中就可以得到这样的一个IRP:在其中若知道一个新的卷正在解挂载,这时候程序就可以执行上面所说的操作了。
? 那么现在的问题就是如何知道系统中有哪些文件系统,还有就是应该在什么时候绑定它们的控制设备。
? IoRegisterFsRegistrationChange 是一个非常有用的系统调用。该调用注册一个回调函数,当系统中有任何文件系统被激活或者注销时,注册过的回调函数就会被调用。这种回调函数称为文件系统的变动回调。
? 文件系统的激活和挂载是两回事。所谓文件系统的激活,是指当系统中没有任何卷采用了NTFS文件系统时,Window并没有加载NTFS文件系统驱动,此时可以称为NTFS未激活;当一个新的使用了NTFS的的卷被加载到系统时候,则NTFS就被加载了,此时就可以说NTFS被激活。第二次加载新的NTFS的卷时,就和文件系统的激活没什么关系了,因为对应的文件系统已经被激活在系统中了。与文件系统的激活相反的过程则称为文件系统的卸载。
? IoRegisterFsRegistrationChange 注册的回调函数,只有文件系统被激活或者注销才会回调,和新加卷或者拔出卷没有直接关系。
? 下面看sfilter的DriverEqntry中对这个函数的调用,代码如下:
// SfFsNotification 是一个回调函数,这个函数有固定的原型status = IoRegisterFsRegistrationChange( DriverObject, SfFsNotification );if (!NT_SUCCESS( status )) {//如果失败了,前面分匹配的FastIo函数就没用了,释放掉KdPrint(( "SFilter!DriverEntry: Error registering FS change notification, status=%08x\n", status ));DriverObject->FastIoDispatch = NULL;ExFreePool( fastIoDispatch );//前面生成的控制设备也一并删除IoDeleteDevice( gSFilterControlDeviceObject );return status;}
11.3.3 文件系统变动回调的一个实现
? 有必要为此写一个回调函数 SfFsNotification 。请注意这个回调函数的原型必须符合下面的原型。
? 第一个参数是一个设备对象指针,这个设备对象就是文件系统的控制设备(请注意文件系统的控制设备可能已经被别的文件过滤驱动绑定,此时,这个设备的对象指针总是指向当前设备栈顶的那个设备)。第二个参数是一个BOOLEAN值,如果为TRUE则表示文件系统的激活,反之则表示文件系统的卸载。
VOID sfFsNotification(IN PDEVICE_OBJECT DeviceObject,IN BOOLEAN FsActive
)
{UNICODE_STRING name;WCHAR nameBuffer[MAX_DEVNAME_LENGTH];PAGED_CODE();//下面的获取设备和SF_LOG_PRINT没有什么实际意义主要是为了获取一些log,让我们知道绑定了哪些设备RtlInitEmptyUnicodeString( &name, nameBuffer, sizeof(nameBuffer) );SfGetObjectName( DeviceObject, &name );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)) );//如果文件系统被激活,那么绑定文件系统的控制设备if (FsActive) {SfAttachToFileSystemDevice( DeviceObject, &name );} else {//反之则解除绑定 绑定函数和接触绑定函数都是后面给出的。SfDetachFromFileSystemDevice( DeviceObject );}
}
? 这里涉及一些关于驱动加载方式的问题。驱动的动态加载是按照第一章介绍的加载驱动的办法,我们用工具或者输入命令行命令手动让驱动启动。
? 驱动的静态加载则一般是用inf文件安装驱动,并设置启动模式为Windows启动时自启动。
? IoRegisterFsRegistrationChange可以注册对激活文件系统的回调,但是对调用这个函数时候早已经激活的文件系统,回调是否被调用呢?早期不会,而在Windows 2000 以后则会。这里为了避免麻烦,不再学习早期的东西。
? 回顾一下我们在DriverEntry中实现的东西
第一步: 生成一个控制设备,当然此前必须给控制设备指定名称。
第二步:设置普通分发函数。
第三步:设置快速IO分发函数。
第四步:编写一个文件系统变通回调函数,在其中绑定刚激活的文件系统的控制设备。
第五步: 使用 IoRegisterFsRegistrationChange 进行注册。
? 那么应该如何绑定一个文件系统的控制设备呢,也就是如何实现函数SfAttachToFileSystemDevice呢?在后面进行描述。
11.3.4 文件系统识别器
? 上一节最好的一个问题是如何绑定一个刚刚被激活的文件系统控制设备。前面实现过SfAttachDeviceToStack 用来绑定这个设备,但是并不是每次文件系统变动回调发现有新的文件系统激活就直接绑定它。
? 首先需要判断这个文件系统是否我是我们关心的。过滤驱动可能只对文件系统的CDO的设备类型中的某些感兴趣。假设我们只关心磁盘文件系统、光盘文件系统和网络文件系统,那么就只需要注意这三种类型即可。下面的代码定义了一个宏来判断一个设备的类型是否是本驱动所关心的。
#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))
下一个问题是必须跳过文件系统识别器。所谓文件系统识别器,是文件系统驱动的一个很小的替身。为了避免没有使用到的文件系统驱动占据内核内存,Windwos系统不加载这些大驱动,而以该文件系统驱动对应的文件系统识别器来代替。当新的物理存储媒介进入系统后,IO管理器会依次尝试各种文件系统对它进行识别,若识别成功,则立即加载真正的文件系统驱动,对应的文件系统识别器则被卸载掉。文件系统识别器的控制设备看起来就像一个文件系统的控制设备,绑定它的话可能会出错,所以跳过它是最好的选择。虽然有的时候我们又必须选择绑定它。因此,我们需要学会判断它。
? 通过驱动的名字可以分辨出Windows 的标准文件系统识别器,Windows的标准文件系统识别器似乎都是由驱动“FileSystem\Fs_Rec” 生成的,所以直接判断驱动的名字就可以解决问题的一部分。下面的代码完成这个判断,如果识别为文件系统识别器则放弃绑定。
RtlInitUnicodeString(&fsrecName,L"FileSystem\Fs_Rec");
SfGetObjectNmae(DeviceObject->DriverObject,&fsName);
if(RtlCompareUnicodeString(&fsName,fsrecName,TRUE)==0)
{
return STATUS_SUCCESS;
}
? 上面这段代码可以放在文件系统变动回调函数中,也可以放在绑定文件系统控制设备的函数中。但是如果有一些非微软规定的文件系统识别器,没有生成在FileSystem\Fs_Rec 下,则该方法不可取。
明日计划
文件系统控制设备的绑定