三、安装文件系统过滤驱动
对于Windows XP和后续操作系统来说,可以通过INI文件或安装应用程序来安装文件系统过滤驱动(对于Windows 2000和更早的操作系统,过滤驱动通常通过服务控制管理器Service Control Manager来进行安装)。
四、初始化文件系统过滤驱动
与设备驱动类似,文件系统过滤驱动也使用DriverEntry例程进行初始化工作。在驱动程序加载后,加载驱动相同的组件将通过调用驱动程序的 DriverEntry例程来对驱动程序进行初始化工作。对于文件系统过滤驱动来说,加载和初始化过滤驱动的系统组件为I/O管理器。
DriverEntry例程运行于系统线程上下文中,其IRQL = PASSIVE_LEVEL。本例程可分页,详细信息参见MmLockPagableCodeSection。
DriverEntry例程定义如下:
NTSTATUS
DriverEntry (
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
本例程有两个输入参数。第一个参数,DriverObject为系统在文件系统过滤驱动加载时所创建的驱动对象;第二个参数,RegistryPath为包含驱动程序注册键路径的Unicode字符串。
文件系统过滤驱动按如下顺序执行DriverEntry例程:
01、创建控制设备对象:
文件系统过滤驱动的DriverEntry例程通常以创建控制设备对象作为该例程的起始。创建控制设备对象的目的在于允许应用程序即使在过滤驱动加载到文件系统或卷设备对象之前也能够直接与过滤驱动进行通信。
注意:文件系统也会创建控制设备对象。当文件系统过滤驱动将其自身附加到文件系统之上时(而不是附加到某一特定文件系统卷),过滤驱动同样将其自身附加到文件系统的控制设备对象之上。
在FileSpy驱动范例中,控制设备对象按如下方式创建:
RtlInitUnicodeString(&nameString, FILESPY_FULLDEVICE_NAME);
status = IoCreateDevice(
DriverObject, //DriverObject
0, //DeviceExtensionSize
&nameString, //DeviceName
FILE_DEVICE_DISK_FILE_SYSTEM, //DeviceType
FILE_DEVICE_SECURE_OPEN, //DeviceCharacteristics
FALSE, //Exclusive
&gControlDeviceObject); //DeviceObject
RtlInitUnicodeString(&linkString, FILESPY_DOSDEVICE_NAME);
status = IoCreateSymbolicLink(&linkString, &nameString);
与文件系统不同,文件系统过滤驱动并不是一定要为其控制设备对象命名。如果传递给DeviceName参数一个非空(Non-NULL)值,该值将作为控制设备对象的名称。接下来,在前面的代码范例中DriverEntry可以调用IoCreateSymbolicLink例程来将该对象的核心模式名称与应用程序可见的用户模式名称关联到一起(同样可以通过调用IoRegisterDeviceInterface来使设备对象对应用程序可见)。
注意:由于控制设备对象是唯一不会附加到设备堆栈中的设备对象,因此控制设备对象是唯一的可安全命名的设备对象。由此,是否为文件系统过滤驱动的控制设备对象是否命名是可选的。
注意:文件系统的控制设备对象必须命名。过滤设备对象从不命名。
参数DeviceType代表某种设备类型,其可能的取值均以常量形式定义在ntifs.h中,例如: FILE_DEVICE_DISK_FILE_SYSTEM。
如果向DeviceName传递了一个非空值(Non-NULL),DeviceCharacteristics标识必须包括 FILE_DEVICE_SECURE_OPEN。该标识指示I/O管理器对所有发送到控制设备对象的Open请求进行安全检测。
文件系统过滤驱动在分派例程中识别其自身控制设备对象的有效方式为将设备指针与前期存储的全局控制设备对象指针进行比较。因此FileSpy驱动范例将 IoCreateDevice所返回的设备对象指针存储到了全局变量gControlDeviceObject中。
02、注册IRP分派例程:
过滤驱动DriverEntry例程中的DriverObject参数提供了一个指向过滤驱动的驱动对象的指针。为了注册I/O请求包(IRP)的分派例程,必须为主功能码注册分派例程的入口点。例如:FileSpy驱动范例按下列方式设置分派例程入口点:
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
{
DriverObject->MajorFunction[i] = SpyDispatch;
}
DriverObject->MajorFunction[IRP_MJ_CREATE] = SpyCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = SpyClose;
DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = SpyFsControl;
注意:在上面的For循环为每个IRP主功能码分派例程都分派了默认的分派例程。这是一个比较好的做法,因为,默认情况下,I/O管理器完成未知IRP并返回STATUS_INVALID_DEVICE_REQUEST。文件系统过滤驱动在这种方式下不会拒绝未知的IRP,这些请求将发送给低层驱动。由于这个原因,默认分派例程仅向下层传递IRP。
03、注册Fast I/O分派例程:
过滤驱动DriverEntry例程的DriverObject参数提供了指向过滤驱动驱动对象的指针。
为了注册文件系统过滤驱动的Fast I/O分派例程,必须分配并初始化Fast I/O分派表,向该表中存储Fast I/O分派例程,然后将该分派表的地址存储到驱动对象的FastIoDispatch成员中。
例如:FileSpy驱动范例按下述方式为Fast I/O分派例程设置入口点:
RtlZeroMemory(fastIoDispatch, sizeof(FAST_IO_DISPATCH));
fastIoDispatch->SizeOfFastIoDispatch = sizeof(FAST_IO_DISPATCH);
fastIoDispatch->FastIoCheckIfPossible = SpyFastIoCheckIfPossible;
fastIoDispatch->FastIoRead = SpyFastIoRead;
fastIoDispatch->FastIoWrite = SpyFastIoWrite;
fastIoDispatch->FastIoQueryBasicInfo = SpyFastIoQueryBasicInfo;
fastIoDispatch->FastIoQueryStandardInfo = SpyFastIoQueryStandardInfo;
fastIoDispatch->FastIoLock = SpyFastIoLock;
fastIoDispatch->FastIoUnlockSingle = SpyFastIoUnlockSingle;
fastIoDispatch->FastIoUnlockAll = SpyFastIoUnlockAll;
fastIoDispatch->FastIoUnlockAllByKey = SpyFastIoUnlockAllByKey;
fastIoDispatch->FastIoDeviceControl = SpyFastIoDeviceControl;
fastIoDispatch->FastIoDetachDevice = SpyFastIoDetachDevice;
fastIoDispatch->FastIoQueryNetworkOpenInfo = SpyFastIoQueryNetworkOpenInfo;
fastIoDispatch->MdlRead = SpyFastIoMdlRead;
fastIoDispatch->MdlReadComplete = SpyFastIoMdlReadComplete;
fastIoDispatch->PrepareMdlWrite = SpyFastIoPrepareMdlWrite;
fastIoDispatch->MdlWriteComplete = SpyFastIoMdlWriteComplete;
fastIoDispatch->FastIoReadCompressed = SpyFastIoReadCompressed;
fastIoDispatch->FastIoWriteCompressed = SpyFastIoWriteCompressed;
fastIoDispatch->MdlReadCompleteCompressed = SpyFastIoMdlReadCompleteCompressed;
fastIoDispatch->MdlWriteCompleteCompressed = SpyFastIoMdlWriteCompleteCompressed;
fastIoDispatch->FastIoQueryOpen = SpyFastIoQueryOpen;
DriverObject->FastIoDispatch = fastIoDispatch;
04、注册FsFilter回调例程:
FsFilter通知回调例程在下层文件系统执行某些操作之前或之后调用。如果需要获取更多有关于FsFilter回调例程相关信息,可参见FsRtlRegisterFileSystemFilterCallbacks例程
为了注册FsFilter的通知回调例程必须分配并初始化FS_FILTER_CALLBACKS结构体,然后向该结构体中促出FsFilter回调例程,并将存储有Callbacks parameter到FsRtlRegisterFileSystemFilterCallbacks中。
例如:FileSpy驱动范例按如下方式注册FsFilter回调。
fsFilterCallbacks.SizeOfFsFilterCallbacks = sizeof(FS_FILTER_CALLBACKS);
fsFilterCallbacks.PreAcquireForSectionSynchronization = SpyPreFsFilterOperation;
fsFilterCallbacks.PostAcquireForSectionSynchronization = SpyPostFsFilterOperation;
fsFilterCallbacks.PreReleaseForSectionSynchronization = SpyPreFsFilterOperation;
fsFilterCallbacks.PostReleaseForSectionSynchronization = SpyPostFsFilterOperation;
fsFilterCallbacks.PreAcquireForCcFlush = SpyPreFsFilterOperation;
fsFilterCallbacks.PostAcquireForCcFlush = SpyPostFsFilterOperation;
fsFilterCallbacks.PreReleaseForCcFlush = SpyPreFsFilterOperation;
fsFilterCallbacks.PostReleaseForCcFlush = SpyPostFsFilterOperation;
fsFilterCallbacks.PreAcquireForModifiedPageWriter = SpyPreFsFilterOperation;
fsFilterCallbacks.PostAcquireForModifiedPageWriter = SpyPostFsFilterOperation;
fsFilterCallbacks.PreReleaseForModifiedPageWriter = SpyPreFsFilterOperation;
fsFilterCallbacks.PostReleaseForModifiedPageWriter = SpyPostFsFilterOperation;
status = FsRtlRegisterFileSystemFilterCallbacks(DriverObject, &fsFilterCallbacks);
05、执行其它必要的初始化工作:
在注册完IRP和Fast I/O分派例程之后,文件系统过滤驱动的DriverEntry例程需要初始化其它该驱动所需的全局变量和数据结构。
06、注册回调例程【可选】:
过滤驱动可以通过调用IoRegisterFsRegistrationChange例程来注册用来侦听在文件系统调用 IoRegisterFileSystem或IoUnregisterFileSystem注册或卸载自身时所触发事件的回调例程。过滤驱动通过注册这个回调函数来发觉新的文件系统加载事件,然后,过滤驱动将自身附加到这个新的文件系统之上。
注意:文件系统过滤驱动不可能调用IoRegisterFileSystem或IoUnregisterFileSystem例程。这两个例程都是专为文件系统提供服务的。
过滤驱动并不是在调用IoRegisterFsRegistrationChange时加载到卷之上,它必须在侦测到卷以后才能进行加载(例如,通过一个用户模式应用程序)。注意:过滤驱动使用该例程来获得在卷装在后立即附加到该卷之上的能力。使用该例程时,并不能保证过滤驱动将直接附加到卷设备对象之上。其余部分未翻译,如下: But it does ensure that such a filter attaches before (and thus below) any filter that instead waits for a command from a user-mode application, because filters can attach only at the top of the current file system volume device stack.
07、存储注册表路径字符串拷贝【可选】:
注意:本步骤是过滤驱动在执行DriverEntry例程之后,需要使用注册表路径时所必须的。
其余部分未翻译,如下:Save a copy of the RegistryPath string that was passed as input to DriverEntry. This parameter points to a counted Unicode string that specifies a path to the driver's registry key,
\Registry\Machine\System\CurrentControlSet\Services\DriverName, where
DriverName is the name of the driver. If the RegistryPath string will be needed later, DriverEntry must save a copy of it, not just a pointer to it, because the pointer is no longer valid after the DriverEntry routine returns.
08、返回状态:
文件系统过滤驱动的DriverEntry例程通常会返回STATUS_SUCCESS。然而,如果驱动初始化失败,DriverEntry会向返回一个适当的状态值。
如果DriverEntry例程返回了一个指示未成功的状态值,系统将卸载该驱动。因此,DriverEntry例程必须在返回错误代码之前释放那些自己所分配的内存和所获取的诸如设备对象之类系统资源。
五、附加过滤驱动到文件系统或卷之上
文件系统过滤驱动将其自身附加到一个或多个已装载卷之上,并过滤发送给这些卷的I/O操作。下面将以Windows Driver Kit (WDK)中的范例来说明通常会采用的两种方式:
· 文件系统过滤驱动可以附加到终端用户指定需要过滤的卷,比如键入卷的驱动符。用户命令将传递给过滤驱动一个私有的IRP_MJ_DEVICE_CONTROL请求。FileSpy驱动范例在全局变量gFileSpyAttachMode设置为FILESPY_ATTACH_ON_DEMAND时,采用这种方式(默认情况下, gFileSpyAttachMode将设置为FILESPY_ATTACH_ALL_VOLUMES)。
· 文件系统过滤驱动可以附加到一个或多个文件系统驱动之上,监听 IRP_MJ_FILE_SYSTEM_CONTROL、IRP_MN_MOUNT_VOLUME请求,并附加到文件系统所装载的卷之上。SFilter 就驱动范例使用这种方式。FileSpy驱动范例在全局变量gFileSpyAttachMode被设置为 FILESPY_ATTACH_ALL_VOLUMES(默认值)采取这种方式。
注意:通常需要假定卷和文件系统驱动的关系为一对多,而不是一对一。这是由于某些高级存储功能,例如动态卷和卷装载点等所造成的。
注意:不要将定文件系统会一直以同步方式处理IRP_MN_MOUNT_VOLUME请求。例如,辅助存储器可能异步的进行装载。因此,过滤驱动需要在Mount Completion例程中传递PendingReturned标识。如果需要获取更多信息,可查阅DDK在线文档的“PendingReturned Flag”部分。
文件系统过滤驱动可以附加并过滤文件系统卷。但是它们无法直接附加到诸如磁盘驱动或分区等存储设备之上,同样他们也无法附加到单独的目录或文件之上。
如果需要获取更多信息,可参见下面章节:
01、创建过滤设备对象
调用IoCreateDevice例程来创建用来附加到卷或文件系统堆栈上的过滤设备对象,在FileSpy范例中,以下述方式进行该工作:
status = IoCreateDevice(
gFileSpyDriverObject, //DriverObject
sizeof(FILESPY_DEVICE_EXTENSION), //DeviceExtensionSize
NULL, //DeviceName
DeviceObject->DeviceType, //DeviceType
0, //DeviceCharacteristics
FALSE, //Exclusive
&newDeviceObject); //DeviceObject
在上面的代码片段中,DeviceObject为指向过滤驱动所需要附加到的目标设备的设备对象,而newDeviceObject为指向过滤驱动自身的设备对象的指针。
为了为过滤设备对象的设备扩展数据结构体分配存储空间,因此需要将DeviceExtensionSize参数设置为sizeof (FILESPY_DEVICE_EXTENSION)。新创建的过滤设备对象的设备扩展成员将设置为指向该数据结构的指针。文件系统过滤驱动通常为每个过滤设备对象定义并分配设备扩展。设备扩展的数量和结构均由驱动指定。然而,在MS Windows XP以及后续操作系统之上,过滤驱动所定义的设备扩展结构DEVICE_EXTENSION至少需要包含下述成员:PDEVICE_OBJECT AttachedToDeviceObject。
在上面调用IoCreateDevice时,由于过滤设备对象并没有命名,因此将DeviceName参数设置为NULL。由于过滤设备对象需要附加到文件系统或卷设备堆栈之上,因此,为过滤设备对象分配一个名字将造成系统安全漏洞。
DeviceType参数必须始终设置为过滤设备对象所附加到的目标(文件系统或过滤)设备对象。按照此方法传递设备类型是非常重要的,这是因为它将由I/O Manager使用,并传回给应用程序。
注意:文件系统和文件系统过滤驱动从来不会将DeviceType参数设置为FILE_DEVICE_FILE_SYSTEM。该值并不是一个有效参数值(FILE_DEVICE_FILE_SYSTEM常量仅在定义FSCTL时使用)。
DeviceType参数非常重要的其它原因是,许多过滤驱动仅仅附加到某些特定的文件系统之上。例如,一个特殊的过滤驱动将会附加到本地磁盘文件系统之上,而并不附加到CD-ROM文件系统或远程文件系统之上。这些过滤驱动测试最文件系统或卷设备堆栈中最高层设备对象的DeviceType参数。在大多数情况下,最高层设备对象为一个过滤设备对象。这就是为什么过滤驱动的Device Type参数需要同下层文件系统或卷设备对象的该参数一致的原因。
02、将过滤设备对象附加到目标设备对象之上
通过调用IoAttachDeviceToDeviceStackSafe来将过滤设备对象附加到目标文件系统或卷的过滤设备堆栈之中。
devExt = filespyDeviceObject->DeviceExtension;
status = IoAttachDeviceToDeviceStackSafe(
filespyDeviceObject, //SourceDevice
DeviceObject, //TargetDevice
&devext->AttachedToDeviceObject); //AttachedToDeviceObject
注意:在有其它过滤驱动已经附加到目标设备对象之上时,AttachedToDeviceObject输出参数接收到的设备对象指针可以与TargeDevice不同。
通过名称附加到文件系统之上
每种文件系统需要创建一个或多个已命名控制设备对象。如果需要直接附加到文件系统之上,文件系统过滤驱动需要传递给 IoGetDeviceObjectPointer传递该特定文件系统控制设备对象的名称来获取该设备对象的指针。下列代码范例显示了如何获取RAW文件系统的两个控制设备对象中的一个的方法:
RtlInitUnicodeString(&nameString, L"\\Device\\RawDisk");
status = IoGetDeviceObjectPointer(
&nameString, //ObjectName
FILE_READ_ATTRIBUTES, //DesiredAccess
&fileObject, //FileObject
&rawDeviceObject); //DeviceObject
if (NT_SUCCESS(status)) {
ObDereferenceObject(fileObject);
}
如果调用IoGetDeviceObjectPointer成功,文件系统过滤驱动调用IoAttachDeviceToDeviceStackSafe方法来附加到前述方法返回的控制设备对象之上。
注意:除了控制设备对象指针(rawDeviceObject)以外,IoGetDeviceObjectPointer还会返回一个指向向用户模式应用表现该控制设备对象的文件对象的指针(fileObject)。在上述代码范例中,并不需要文件对象,因此通过调用ObDereferenceObject关闭了该文件对象。下面没有翻译:It is important to note that decrementing the reference count on the file object returned by IoGetDeviceObjectPointer causes the reference count on the device object to be decremented as well. Thus the fileObject and rawDeviceObject pointers should both be considered invalid after the above call to ObDereferenceObject, unless the reference count on the device object is incremented by an additional call to ObReferenceObject before ObDereferenceObject is called for the file object.
03、传递DO_BUFFERED_IO和DO_DIRECT_IO标识
在过滤设备对象成功的附加到文件系统或卷之上以后,下面没有翻译:always be sure to set or clear the DO_BUFFERED_IO and DO_DIRECT_IO flags as needed so that they match the values of the next-lower device object on the driver stack. (For more information about these flags, see Methods for Accessing Data Buffers.) In the FileSpy sample, this is done as follows:
if (FlagOn( DeviceObject->Flags, DO_BUFFERED_IO ))
{
SetFlag( filespyDeviceObject->Flags, DO_BUFFERED_IO );
}
if (FlagOn( DeviceObject->Flags, DO_DIRECT_IO ))
{
SetFlag( filespyDeviceObject->Flags, DO_DIRECT_IO );
}
在上述代码片段中,DeviceObject为一个过滤设备对象附加到的设备对象的指针, filespyDeviceObject为指向过滤设备对象自身的指针。
04、传递FILE_DEVICE_SECURE_OPEN标识
在过滤设备对象成功附加到文件系统(并非卷)设备对象之上以后,下面没有翻译:always be sure to set the FILE_DEVICE_SECURE_OPEN flag on the filter device object as needed to so that it matches the value of the next-lower device object on the driver stack. (For more information about this flag, see Specifying Device Characteristics in the Kernel Architecture Design Guide and DEVICE_OBJECT in the Kernel Reference.)在FileSpy驱动范例中,实现方式如下:
if (FlagOn( DeviceObject->Characteristics, FILE_DEVICE_SECURE_OPEN ))
{
SetFlag( filespyDeviceObject->Characteristics, FILE_DEVICE_SECURE_OPEN );
}
在上述代码片段中,DeviceObject为一个过滤设备对象附加到的设备对象的指针, filespyDeviceObject为指向过滤设备对象自身的指针。
05、清除DO_DEVICE_INITIALIZING标记:
在过滤设备对象附加到文件系统或卷之上以后,,需要确保清除过滤设备对象的DO_DEVICE_INITIALIZING标识。在FileSpy驱动范例中,以如下方式实现:
ClearFlag(NewDeviceObject->Flags, DO_DEVICE_INITIALIZING);
在过滤设备对象创建以后,IoCreateDevice为设备对象设置DO_DEVICE_INITIALIZING标识。在过滤驱动成功进行附加以后,这个标识必须被清除掉。注意:如果本标识没有被清除,其它过滤驱动将无法再次附加到该过滤链中,因为,此时调用IoAttachDeviceToDeviceStackSafe将失败。
注意:在DriverEntry例程中创建的设备对象,并不需要必须清除DO_DEVICE_INITIALIZING标识,这是因为这个工作将会由I/O管理器自动完成。然而,如果创建了其它设备对象,则需要进行该清除工作。