第一篇中,已经解读了DriverEntry这个函数,函数里面就是做了一些初始化工作,创建用于通信的设备,设置各种回调,注册文件系统变动回调等等。
今天接着看filespy.c中的SpyFsNotification函数。该函数是IoRegisterFsRegistrationChange注册的文件系统变动回调函数。先看这个的理由如下:
在没看任何代码前,我的思维是:首先绑定系统中的所有物理卷;其次过滤文件操作;再其次,卸载过滤驱动的时候,我解除绑定的物理卷。代码做的话,我会在DriverEntry里面直接枚举所有的物理卷设备,然后绑定他们。这只是我的思路,文件过滤驱动里木有我这样的思路,因为我这个思路是不懂的前提下设想的。
文件过滤驱动的一个理论:先绑定文件系统控制设备对象CDO,然后绑定卷设备对象。
为什么要这么做,第4篇中说。
然而很遗憾,filespy里面没有直接绑定CDO,而是调用IoRegisterFsRegistrationChange注册了一个文件系统变动回调函数。该函数会在文件系统(例如:NTFS,FAT32等)激活或在注销的时候调用该回调函数。在2K SP4和XP以后的OS,即使在过滤驱动加载之前,已经被激活的文件系统,依然会被重新激活一下。2K SP4以前的OS,已经加载过的文件系统是不会被重新激活的。
IoRegisterFsRegistrationChange注册的文件系统变动回调函数,会绑定或解除绑定文件卷设备(这里的卷设备,指的是文件系统为每个物理卷对应生成的文件系统卷设备,文件系统卷设备是在文件驱动中生成的。例如:NTFS是个文件系统,我的C盘,D盘是NTFS,E盘是FAT32。那个物理卷C盘,D盘,在文件系统NTFS中,有文件系统卷设备对之各个相对应)。
VOID
SpyFsNotification (__in PDEVICE_OBJECT DeviceObject, __in BOOLEAN FsActive)
{
//
// PNAME_CONTROL 结构体NAME_CONTROL指针,位于:namelookup.h中
//
PNAME_CONTROL devName;
//
// NLGetAndAllocateObjectName从参数2中开辟空间存放参数1对应的名称。
// NLGetAndAllocateObjectName函数中调用函数NLAllocateNameControl(namelookup.c中)从函数NLGetAndAllocateObjectName的参数
// 2中开辟内存空间,调用函数NLGetObjectName(namelookup.c中)来获取NLGetAndAllocateObjectName参数1DeviceObject
// 所对应的文件名,而这个DeviceObject的文件名是通过:ObQueryNameString来获得的。这里需要注意下:ObQueryNameString在文件
// 过滤驱动中,直接询问设备的名称是非常容易引起重入的(这是其他高手的文章里面提到的,没有深究)。为什么FileSpy要直接使用
// ObQueryNameString来直接询问呢?理由如下:文件系统控制设备CDO,是SpyFsNotification的第一个参数DeviceObject,
// 它是文件系统的控制设备对象,是底层设备对象。所以可以直接询问这个设备对象的名称。
//
//
// 这里说下重入的问题:这个问题不大懂,只知道一点概念,举例更可能明白。
// 文件过滤驱动中使用Zw系列函数来读写文件的时候,最终会发出一个IRP包。这个包有I/O管理器丢出来又会进入我们的文件过滤驱动中,
// 我们自己要读写文件,IRP结果又回到我们的过滤驱动中了。
//
devName = NLGetAndAllocateObjectName( DeviceObject, &gFileSpyNameBufferLookasideList );
if (devName == NULL) {
SPY_LOG_PRINT( SPYDEBUG_DISPLAY_ATTACHMENT_NAMES,
("FileSpy!SpyFsNotification: Not attaching to %p, insufficient resources.\n",
DeviceObject) );
return;
}
SPY_LOG_PRINT( SPYDEBUG_DISPLAY_ATTACHMENT_NAMES,
("FileSpy!SpyFsNotification: %s %p \"%wZ\" (%s)\n",
(FsActive) ? "Activating file system " : "Deactivating file system",
DeviceObject,
&devName->Name,
GET_DEVICE_TYPE_NAME(DeviceObject->DeviceType)) );
//
// 根据SpyFsNotification函数的第2个参数,确定是绑定文件系统控制设备对象CDO,还是解除绑定。
//
if (FsActive) {
//
// 绑定文件系统控制设备对象CDO
//
SpyAttachToFileSystemDevice( DeviceObject, devName );
} else {
//
// 解除绑定文件系统控制设备对象CDO
//
SpyDetachFromFileSystemDevice( DeviceObject );
}
//
// 释放设备名称的空间,因为SpyAttachToFileSystemDevice已经将名称拷贝到了设备拓展里面。
//
NLFreeNameControl( devName, &gFileSpyNameBufferLookasideList );
}
上面已经看到绑定文件系统控制设备对象CDO,是在函数:SpyAttachToFileSystemDevice中进行的,那么现在就来读读这个函数,到底怎么绑定的文件系统控制设备对象CDO以及卷设备VDO的。
//
// SpyAttachToFileSystemDevice绑定文件控制设备对象。
//
NTSTATUS
SpyAttachToFileSystemDevice (
__in PDEVICE_OBJECT DeviceObject,
__in PNAME_CONTROL DeviceName
)
{
PDEVICE_OBJECT filespyDeviceObject;
PFILESPY_DEVICE_EXTENSION devExt;
UNICODE_STRING fsrecName;
NTSTATUS status;
PNAME_CONTROL fsName;
PAGED_CODE();
//
// 检测这个文件系统设备类型,是否是我们所要关心的。磁盘文件系统,CDROM文件系统和Network文件系统。
// #define IS_SUPPORTED_DEVICE_TYPE(_type) \
// (\
// ((_type) == FILE_DEVICE_DISK_FILE_SYSTEM) || \
// ((_type) == FILE_DEVICE_CD_ROM_FILE_SYSTEM) || \
// ((_type) == FILE_DEVICE_NETWORK_FILE_SYSTEM) \
// )
//
if (!IS_SUPPORTED_DEVICE_TYPE(DeviceObject->DeviceType)) {
return STATUS_SUCCESS;
}
//
// 检测是否是微软的文件系统识别器设备(查看这个驱动的名称是否是FS_REC,因为Windows的标准文件系统识别器都是由驱动
// "\\FileSystem\\Fs_Rec"生成的)。如果是文件系统识别器,跳过,不需要绑定。我们需要绑定真正的文件系统。
//
RtlInitUnicodeString( &fsrecName, L"\\FileSystem\\Fs_Rec" );
//
// 这个函数前面已经提到过,就是从参数2开辟空间,询问参数1的名称,并通过返回值返回。
//
fsName = NLGetAndAllocateObjectName( DeviceObject->DriverObject,
&gFileSpyNameBufferLookasideList );
if (fsName == NULL) {
//
// 获取名称失败,不绑定。
//
SPY_LOG_PRINT( SPYDEBUG_ERROR,
("FileSpy!SpyAttachToFileSystemDevice: Error retrieving name, may attach to FS recognizer, status=%08x\n",
STATUS_INSUFFICIENT_RESOURCES) );
} else if (RtlCompareUnicodeString( &fsName->Name,
&fsrecName, TRUE ) == 0) {
//
// 通过驱动名称的比较,是文件系统识别器,不绑定。
//
NLFreeNameControl( fsName, &gFileSpyNameBufferLookasideList );
return STATUS_SUCCESS;
}
NLFreeNameControl( fsName, &gFileSpyNameBufferLookasideList );
//
// 是我们关心的文件系统,且不是微软的文件系统识别器的设备,创建一个设备绑定这个设备对象。
//
status = IoCreateDevice( gFileSpyDriverObject,
sizeof( FILESPY_DEVICE_EXTENSION ),
(PUNICODE_STRING) NULL,
DeviceObject->DeviceType,
0,
FALSE,
&filespyDeviceObject );
if (!NT_SUCCESS( status )) {
SPY_LOG_PRINT( SPYDEBUG_ERROR,
("FileSpy!SpyAttachToFileSystemDevice: Error creating volume device object for \"%wZ\", status=%08x\n",
DeviceName ? &DeviceName->Name : &gEmptyUnicode,
status) );
return status;
}
//
// #define FlagOn(_F,_SF) ((_F) & (_SF))
// #define SetFlag(_F,_SF) ((_F) |= (_SF))
// 设置filespyDeviceObject->Flags为DO_BUFFERED_IO、DO_DIRECT_IO、DO_SUPPORTS_TRANSACTIONS
// FlagOn第2个参数,是3个值进行or操作获得3个操作的组合值,FlagOn再进行&,则与这3个操作组合值相等的保留下。
//
SetFlag( filespyDeviceObject->Flags,
FlagOn( DeviceObject->Flags,
(DO_BUFFERED_IO |
DO_DIRECT_IO |
DO_SUPPORTS_TRANSACTIONS) ));
SetFlag( filespyDeviceObject->Characteristics,
FlagOn( DeviceObject->Characteristics,
(FILE_DEVICE_SECURE_OPEN) ));
//
// 获得DeviceObject的设备拓展,初始化设备拓展里面的NLExtHeader头。函数:NLInitDeviceExtensionHeader(namelookup.c中)
// SpyInitDeviceNamingEnvironment(fspyCtx.c中)初始化设备拓展里面的CtxList和CtxLock,Flags成员
//
devExt = filespyDeviceObject->DeviceExtension;
NLInitDeviceExtensionHeader( &devExt->NLExtHeader,
filespyDeviceObject,
NULL );
SpyInitDeviceNamingEnvironment( filespyDeviceObject );
devExt->Flags = 0;
#if WINVER >= 0x0600
InitializeListHead( &devExt->TxListHead );
ExInitializeFastMutex( &devExt->TxListLock );
#endif
//
// 设置设备拓展成员NLExtHeader力的DeviceName,通过函数:NLAllocateAndCopyUnicodeString(namelookup.c中)
// 该函数作用:从非分页内存中,开辟内存,将参数2中的值拷入参数1。
//
status = NLAllocateAndCopyUnicodeString( &devExt->NLExtHeader.DeviceName,
&DeviceName->Name,
FILESPY_DEVNAME_TAG );
if (!NT_SUCCESS(status)) {
goto ErrorCleanupDevice;
}
//
// 绑定设备,函数:SpyAttachDeviceToDeviceStack(fspyLib.c中),参数1绑定到参数2,绑定函数返回的设备存储在参数3中。
// 这个函数就是调用DriverEntry.c里面动态获取的函数SpyLoadDynamicFunctions里面获取的函数地址。
//
status = SpyAttachDeviceToDeviceStack( filespyDeviceObject,
DeviceObject,
&devExt->NLExtHeader.AttachedToDeviceObject );
if (!NT_SUCCESS( status )) {
SPY_LOG_PRINT( SPYDEBUG_ERROR,
("FileSpy!SpyAttachToFileSystemDevice: Could not attach FileSpy to the filesystem control device object \"%wZ\".\n",
&DeviceName->Name) );
goto ErrorCleanupDevice;
}
//
// 网络设备对象的功能既是控制设备对象CDO,又是卷设备对象VDO,所有将网络文件系统的CDO设备也插入到被绑定的
// 设备链表里面,方便以后我们枚举它。
//
if (FILE_DEVICE_NETWORK_FILE_SYSTEM == DeviceObject->DeviceType) {
ExAcquireFastMutex( &gSpyDeviceExtensionListLock );
InsertTailList( &gSpyDeviceExtensionList, &devExt->NextFileSpyDeviceLink );
ExReleaseFastMutex( &gSpyDeviceExtensionListLock );
SetFlag(devExt->Flags,ExtensionIsLinked);
}
//
// 清除设备的初始化Flag
// #define ClearFlag(_F, _SF) ((_F) &= ~(_SF))
//
ClearFlag( filespyDeviceObject->Flags, DO_DEVICE_INITIALIZING );
//
// 显示我们绑定了哪个设备对象。
//
SPY_LOG_PRINT( SPYDEBUG_DISPLAY_ATTACHMENT_NAMES,
("FileSpy!SpyAttachToFileSystemDevice: Attaching to file system %p \"%wZ\" (%s)\n",
DeviceObject,
&devExt->NLExtHeader.DeviceName,
GET_DEVICE_TYPE_NAME(filespyDeviceObject->DeviceType)) );
//
// 在Windows XP中,I/O管理器提供了API可以安全地枚举给定的驱动DriverObject下的所有设备对象DeviceObject。
// 在卷设备被挂载后的某个时间,允许过滤驱动绑定给定的文件系统下的所有的已经挂载了的卷设备,这个功能函数在2K不支持。
//
// 多OS版本编译选项。
//
#if WINVER >= 0x0501
if (IS_WINDOWSXP_OR_LATER()) {
# define FSDEnumErrorMsg "FileSpy!SpyAttachToFileSystemDevice: Error attaching to existing volumes for \"%wZ\", status=%08x\n"
ASSERT( NULL != gSpyDynamicFunctions.EnumerateDeviceObjectList &&
NULL != gSpyDynamicFunctions.GetStorageStackDeviceObject &&
NULL != gSpyDynamicFunctions.GetDeviceAttachmentBaseRef &&
NULL != gSpyDynamicFunctions.GetLowerDeviceObject );
//
// 函数SpyEnumerateFileSystemVolumes(fileLib.c中),枚举给定的文件系统下的当前存在的所有挂载了的设备,并且绑定他们,
// 这样做的目的,是因为过滤驱动可能随时被加载,但是加载过滤驱动的时候,文件系统已经挂载了卷设备。既是:让过滤驱动加
// 后,随时都能绑定已经存在或刚刚挂载上来的文件系统卷设备。
//
status = SpyEnumerateFileSystemVolumes( DeviceObject );
if (!NT_SUCCESS( status )) {
SPY_LOG_PRINT( SPYDEBUG_ERROR,
(FSDEnumErrorMsg,
DeviceName ? &DeviceName->Name : &gEmptyUnicode,
status) );
IoDetachDevice( devExt->NLExtHeader.AttachedToDeviceObject );
goto ErrorCleanupDevice;
}
}
#endif
return STATUS_SUCCESS;
/
// 调用失败进行清除一些操作。
// SpyCleanupMountedDevice(fileLib.c中),调用SpyCleanupDeviceNamingEnvironment和NLCleanupDeviceExtensionHeader
// 释放空间,清除链表中的特定数据。主要的操作,就是在SpyAttachToFileSystemDevice中设置的数据,开辟的空间进行清理。
/
ErrorCleanupDevice:
SpyCleanupMountedDevice( filespyDeviceObject );
IoDeleteDevice( filespyDeviceObject );
return status;
}