文件系統的過濾
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分發函數。
第三步:編寫一個文件系統變動回調函數,在其中綁定剛激活的文件系統的控制設備,並註冊這個回調函數。
第四步:編寫默認的分發函數。
第五步:處理文件系統的控制請求,在其中監控捲設備的掛載。
明日計劃
文件系統卷設備的綁定