您的位置:首页 > 其它

sfilter开源驱动分析(一)--文件过滤的基本步骤

2017-10-24 10:32 225 查看

NT的I/O管理器支持分层的驱动程序模式。

        当应用层的CreateFile() (R3层)  --> || NtCreateFile() [I/O管理器在这一层,ntdll.dll]  -->||
内核驱动层(R0层) ,这里讨论的主要是针对最后的内核驱动层的处理。
         当每个IRP被处理的时候,它会经过驱动层中的各个驱动程序,直到最终被某个驱动程序调用IoCompleteRequest()完成了这个IRP为止。 因此一个第三方的驱动程序可以轻易的把自己插入到存在的调用层中而得到处理IRP请求包的机会。
文件系统的过滤驱动的基本步骤
1.  首先在该驱动的DriverEntry()中,创建自己驱动的控制设备(包含设备以及link符号等)
2.  完成文件系统的各个派遣函数例程的初始化,包含了MajorFunction(正常的非缓存的访问,都在这里), fastIoDispatch(直接访问NT缓存管理器,如果缓存中没有的,会出现缺页错误,此时VMM还会发起非缓存的请求,跑到MajorFunction去正常的获取磁盘的内容,然后获取到以后,就进入缓存,再通过FastIO给用户),  如果是XP版本以上,还会有fsFilterCallbacks(主要是注册延迟写,预读功能之类的函数), 差不多就包含了这些。
3.  这里基本初始步骤比较重要的一点,通过IoRegisterFsRegistrationChange()注册一个SfFsNotification()函数,用于驱动加载时候,进行过滤设备的挂载(PS: 对应的卸载IoUnregisterFsRegistrationChange(), 可以用一个函数SfFsNotification()进行卸载处理),  在驱动加载时,系统会自动调用SfFsNotification(DeviceObject,
TRUE)
这样的参数,其中DeviceObject就是一个文件系统驱动的位于设备栈的最顶层的设备, 该设备是该文件系统控制设备对象CDO(Control Device Object)TRUE表示该系统是激活的状态,可以通过ObQueryNameString(DeviceObject)来查找到该文件系统驱动控制设备CDO对象,该设备对象的名称,例如:
"\Ntfs"--硬盘文件系统CDO名称,
"\Device\Mup“
--网络文件系统CDO名称, ”\Cdfs“ ---CDRom的文件系统CDO名称,等等。(注意,后面每个文件系统还有不同的卷,最终是挂载到卷上)
4.  初始化标记清除,表示驱动DriverEntry()已经初始化完成,可以工作了。
          附加(Attach)到目标设备对象:
1.  接着上面,当过滤驱动被加载时候,此时因为注册了SfFsNotification(DeviceObject, TRUE)函数,所以,系统启动后,会根据已经激活的文件系统,每个都会自动调用一次该通知函数,DeviceObject为File
System CDO的设备栈

2.  在该函数中,一开始可以根据DeviceObject->DeviceType来判断是否是自己需要挂载过滤设备的设备类型,例如我们只是关心硬盘文件系统,那么一旦这个DeviceObject->DeviceType=FILE_DEVICE_NETWORK_FILE_SYSTEM, 这种就可以直接返回STATUS_SUCCESS, 表示我们不关心。
3. 另外还有一个需要提的是文件识别器,也会进入到该通知函数,这个也不是我们需要attach的设备,我们根据:
ObQueryNameString(DeviceObject->DriverObject, &fsName), 注意是DriverObject的Name, 一旦发现我们获取到的fsName="\FileSystem\Fs_Rec", 那么就表示该激活的文件系统驱动是系统的文件识别器,也直接返回STATUS_SUCCESS, 表示我们不关心。
(文件识别器的相关知识,可以参考: http://www.wxphp.com/wxd_9tsze79et39emep57bmd_1.html)
4. 要成功的把你的驱动程序附加到目标设备对象上有一些简单的步骤必须执行:
a. 得到”目标设备对象“的指针。
b. 创建将在附加操作中使用的你自己的设备对象 ,也叫”源设备对象“
        c. 确保你的驱动程序已经准备好处理I/O请求,即你的驱动程序已经注册了各种要处理的派遣例程,因为一旦你附加成功后,原来发送给目标设备对象的请求,现在将重定向发送到你的设备对象上。
d. 确保你的设备对象中的域已经正确设置,以便向那些正常调用目标驱动程序的模块维持完全透明。
例如,创建出的你的过滤设备对象的标记,需要进行如下设置。

    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 );

    }

e. 请求IO管理器在这两个设备之间创建一个附加, IoAttachDeviceToDeviceStackSafe(), 该函数会将调用方的设备对象附加到设备对象链中的最高层,并返回之前在设备对象链中最高的设备对象。并不是所有的设备都有设备名字,所以依靠IoAttachDevice()无法绑定没有名字的设备。过滤设备对象绑定到File
System CDO的设备栈最顶层.
5. 一旦附加操作完成,I/O管理器将开始重定向I/O请求到你的设备对象来代替发送请求到管理目标设备对象的驱动程序上。
挂载到设备的卷上:
1.  前面只是把我们的过滤设备(newDeviceObject)附加到了文件系统CDO最顶层的设备栈上,这样,我们的newDeviceObject就可以接收到发送到File System CDO的IRP_MJ_FILE_SYSTEM_CONTROL请求了。 以后,程序就可以去绑定刚刚挂载上来的卷了.
2.  IoEnumerateDeviceObjectList(DeviceCDO->DriverObject),遍历该文件系统驱动下的当前存在的所有挂载了的设备,并且绑定他们。

* IoEnumerateDeviceObjectList函数枚举这个驱动下的设备对象列表。这个函数将被调用2次。
* 第1次调用: 获取设备列表中的设备对象的数量。
* 第2次调用:  根据第1次的结果numDevices值来开辟设备对象的存放空间,从而得到设备链devList。
3.  依次遍历各个设备对象, 判断如果该设备对象是该文件系统CDO(原先的最顶层的设备),或者是不符合类型的,或者是已经绑定过的设备,我们都不进行处理了,该绑定卷的代码如下:

NTSTATUS  SfEnumerateFileSystemVolumes( IN PDEVICE_OBJECT FSDeviceObject, IN PUNICODE_STRING Name ) 
{
/*
参数说明:
   
    FSDeviceObject:    它指向文件系统的控制设备对象(CDO)。即 被激活或则撤消的File System CDO
Name:
  它是文件系统的名字

*/

    PDEVICE_OBJECT newDeviceObject;
    PSFILTER_DEVICE_EXTENSION newDevExt;
    PDEVICE_OBJECT *devList;
    PDEVICE_OBJECT storageStackDeviceObject;
    NTSTATUS status;
    ULONG numDevices;
    ULONG i;
    BOOLEAN isShadowCopyVolume;

    PAGED_CODE();

/*
* IoEnumerateDeviceObjectList函数枚举这个驱动下的设备对象列表。这个函数将被调用2次。
* 第1次调用: 获取设备列表中的设备对象的数量。
* 第2次调用:  根据第1次的结果numDevices值来开辟设备对象的存放空间,从而得到设备链devList。
*/
status = (gSfDynamicFunctions.EnumerateDeviceObjectList)(
                    FSDeviceObject->DriverObject,
                    NULL,
                    0,
                    &numDevices);

if (!NT_SUCCESS( status ))
{
ASSERT(STATUS_BUFFER_TOO_SMALL == status);
numDevices += 8;  //为已知的设备开辟内存空间进行存储。额外增加8字节。

    
devList = ExAllocatePoolWithTag( NonPagedPool, (numDevices * sizeof(PDEVICE_OBJECT)), SFLT_POOL_TAG );
if (NULL == devList)
{
return STATUS_INSUFFICIENT_RESOURCES;
}

ASSERT( NULL != gSfDynamicFunctions.EnumerateDeviceObjectList );
status = (gSfDynamicFunctions.EnumerateDeviceObjectList)(
                        FSDeviceObject->DriverObject,
                        devList,
                        (numDevices * sizeof(PDEVICE_OBJECT)),
                        &numDevices);

if (!NT_SUCCESS( status ))
{
ExFreePool( devList );
return status;
}

//依次遍历各个设备对象
for (i=0; i < numDevices; i++)
{
storageStackDeviceObject = NULL;
try {
 
//如果设备对象是文件系统CDO,或者是不符合类型的,或者是已经绑定的
if ((devList[i] == FSDeviceObject) || (devList[i]->DeviceType != FSDeviceObject->DeviceType) || SfIsAttachedToDevice( devList[i], NULL ))
{
leave;
}

// 获得最底层设备名字
SfGetBaseDeviceObjectName( devList[i], Name );
//如果有名字,就是个控制设备,不进行绑定, 我们要绑定的是该文件系统下的磁盘卷设备, 磁盘卷设备是没有名字的
if (Name->Length > 0)
{
leave;
}

/*
调用IoGetDiskDeviceObject函数来获取一个与文件系统设备对象有关的磁盘设备对象。只绑定已经拥有一个磁盘设备对象的文件系统设备对象。
*/
ASSERT( NULL != gSfDynamicFunctions.GetDiskDeviceObject );
                status = (gSfDynamicFunctions.GetDiskDeviceObject)( devList[i], &storageStackDeviceObject );

                if (!NT_SUCCESS( status ))
{
leave;
}

status = SfIsShadowCopyVolume ( storageStackDeviceObject, &isShadowCopyVolume );

                if (NT_SUCCESS(status) &&
                    isShadowCopyVolume &&
                    !FlagOn(SfDebug,SFDEBUG_ATTACH_TO_SHADOW_COPIES)) 
{

                    UNICODE_STRING shadowDeviceName;
                    WCHAR shadowNameBuffer[MAX_DEVNAME_LENGTH];

RtlInitEmptyUnicodeString( &shadowDeviceName, shadowNameBuffer, sizeof(shadowNameBuffer) );
SfGetObjectName( storageStackDeviceObject, &shadowDeviceName );

KdPrint(("SFilter!SfEnumerateFileSystemVolumes         Not attaching to Volume    %p \"%wZ\", shadow copy volume\n",
                                   storageStackDeviceObject,
                                   &shadowDeviceName) );

leave;
}

KdPrint(("SFilter!Found the Device Object Volume ok!\n"));

// 是一个磁盘设备对象,创建新的设备对象,准备绑定。
status = IoCreateDevice( gSFilterDriverObject,
                                         sizeof( SFILTER_DEVICE_EXTENSION ),
                                         NULL,
                                         devList[i]->DeviceType,
                                         0,
                                         FALSE,
                                         &newDeviceObject );
if (!NT_SUCCESS( status ))
{
leave;
}

newDevExt = newDeviceObject->DeviceExtension;
                newDevExt->StorageStackDeviceObject = storageStackDeviceObject;
RtlInitEmptyUnicodeString( &newDevExt->DeviceName, newDevExt->DeviceNameBuffer,sizeof(newDevExt->DeviceNameBuffer) );
SfGetObjectName( storageStackDeviceObject, &newDevExt->DeviceName );

/*
在绑定最后的时候,再测试下,该设备是否被绑定过。这里加了一个锁。如果没被绑定,则执行下面的绑定过程,否则,直接返回。
*/
ExAcquireFastMutex( &gSfilterAttachLock );
if (!SfIsAttachedToDevice( devList[i], NULL ))
{
status = SfAttachToMountedDevice( devList[i], newDeviceObject );
if (!NT_SUCCESS( status ))
{
SfCleanupMountedDevice( newDeviceObject );
IoDeleteDevice( newDeviceObject );
}
}
else
{
SfCleanupMountedDevice( newDeviceObject );
IoDeleteDevice( newDeviceObject );
}

ExReleaseFastMutex( &gSfilterAttachLock );
}

finally {

/*
减少设备对象的计数,这个计数是由函数IoGetDiskDeviceObject增加的。成功绑定后,就减少该设备对象的计数。
一旦成功绑定到devList[i],I/O管理器会确定设备栈的下层设备,会一直存在,一直到这个文件系统栈被卸掉。
*/
if (storageStackDeviceObject != NULL)
{
ObDereferenceObject( storageStackDeviceObject );
}

//减少设备对象的计数,这个计数是由函数IoEnumerateDeviceObjectList增加的。
ObDereferenceObject( devList[i] );
            }
        }

status = STATUS_SUCCESS;
ExFreePool( devList );
}

return status;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: