文件过滤驱动实现目录重定向(一)

谈论这个问题前,先看看一个情况:

比如你上班的公司,可能有多个文件服务器,这些文件服务器通过FTP或者远程共享目录方式提供目录共享。
而你可能会经常性的从一个文件服务器切换到另一个文件服务器上去找资料。
而且如果是FTP的话,还得准备一个FTP客户端。
这么折腾也许觉得有点麻烦,也许就会想:有没有办法让所有这些文件服务器目录共享到我本地一个目录下边,
等我需要的时候,我直接打开这个本地目录,然后操作这些文件就跟完全操作我本地机器上的文件一样方便。

其实早在 win2003 server的系统中,就提供了类似的叫DFS的分布式文件系统的功能。
可惜只在windows服务器版本才有这个功能。

因此为了在所有windows版本中实现此功能,或者是为了更深入的理解文件系统,
有必要掌握如何利用文件过滤驱动来达到目录重定向的功能。
这里讨论的目录重定向,不是简单的在文件过滤驱动里的IRP_MJ_CREATE里修改文件路径到新位置,
然后返回 STATUS_REPARSE来重定向;
也不是在应用层用DeviceIoControl执行FSCTL_SET_REPARSE_POINT 来映射目录,
也不是调用subst命令把某个本地目录映射成新盘符;
而是拦截有关 文件操作的所有 IRP,然后把文件请求的所有数据转向到别的地方处理。
大致过程:
首先定义一个需要重定向的目录,比如 D:\Redir, 创建一个文件过滤驱动挂载到 D: 盘。
当IRP_MJ_CREATE进入到过滤驱动,通过分析这个请求的文件名,判断是不是在我们监控的目录里,
如果是,则把 这个IRP的文件对象,就是FILE_OBJECT保存起来(一般通过MAP结构进行关联保存),
并把这个创建的IRP重定向到真正的文件打开操作,
以后除开 IRP_MJ_CRAETE的所有IRP,通过判断是否在关联结构里找到这个FILE_OBJECT, 
来确定是否是属于这个监控目录的文件请求,如果是则把IRP转向处理,
在IRP_MJ_CLOSE中,删除在关联结构中保存的FILE_OBJECT对象。
原理看起来并不复杂,可是真正要处理这些转向的IRP的细节,可就不那么轻松了。

文件过滤驱动拦截的IRP主要包括以下几个:
IRP_MJ_CREATE,文件创建操作,文件的任何操作,都是从这里开始的。
IRP_MJ_CLEANUP,文件的HANDLE句柄全部关闭会触发这个消息
IRP_MJ_CLOSE,文件对象 FILE_OBJECT引用减为0,文件对象即将被删除时触发。
IRP_MJ_READ、IRP_MJ_WRITE, 文件的读写操作
IRP_MJ_QUERY_INFORMATION 查询文件信息,比如文件创建修改时间,文件大小等等。
IRP_MJ_SET_INFORMATION 设置文件信息,比如删除文件,重名文件,更改文件大小等等。
IRP_MJ_DIRECTORY_CONTROL 查询某个目录下的文件和子目录信息。

还有几个也需要处理的,如果不处理可能某些文件操作不正常。
IRP_MJ_QUERY_VOLUME_INFORMATION ,查询目录所在的卷设备信息。
IRP_MJ_QUERY_SECURITY 查询安全信息,在win7系统得处理这个,否则exe程序无法执行。
(还包括其他需要处理的IRP)。

看起来处理的IRP不算多,我当初也是这么想的,可是处理的细节却挺多。
(查看 WDK的实例里边的 fastfat目录 ,微软实现FAT32文件系统的源代码,那里边除了代码还是代码,而且还是异常的多)

我在前几篇文章中讲过虚拟磁盘驱动, 虚拟磁盘驱动加载的时候,会在Windows磁盘管理器出现一个新的磁盘,把这个磁盘分区格式化之后,可以正常的把他当成一个真正的磁盘来操作,可以在上边存放,删除文件目录等。
这些操作,经过Windows系统重重解析处理,最后会转换成对虚拟磁盘驱动单纯的对磁盘扇区的偏移读写操作,
然后把对扇区的偏移读写,通过多种途径比如是一个真正的文件,保存起来。操作这个虚拟磁盘,最终就变成操作一个单纯文件。
类似的概念,比如 Windows自带的vhd或vhdx格式的虚拟磁盘文件。
虚拟磁盘驱动,应该说可以很方便的把服务器端的一个单纯的文件,映射到客户端形成一个或者多个磁盘。
但是如果有这样的需求,假设服务端保存的不是一个单纯的文件,而是一个或者多个目录,
你需要把这些目录映射到客户端的一个新的盘符。或者映射到某个现有磁盘的某个目录下边。
(我觉得映射到现有的一个目录更酷,就跟Linux系统那样就一个根目录,下边可以挂载多个不同的文件系统)
这种要求,虚拟磁盘驱动就无法解决问题了。
映射到新的盘符,当然可以通过windows自带的映射网络驱动器的功能,把服务端目录映射到客户端的一个新的盘符中。
但是如果我们想从原理上来理解windows究竟做了些什么,以及我们自己想更加灵活和随心所欲的控制映射行为,
那解决办法就是上文提到的利用文件过滤驱动来拦截具体的IRP进行处理了。
如果你只是映射远程目录到一个新盘符,你可以不使用文件过滤驱动,
而是自己开发一个自定义的文件系统,再开发一个分区驱动,然后把分区格式化成自己的文件系统,
这样也能达到把远程目录映射到本地新盘符的目的。详细办法后面章节会陆续提到。

先简单说说Windows文件系统的工作流程。
当某个新的磁盘被加到硬件上,Windows系统检测到有新的磁盘硬件被添加,当然首先 就是加载这个磁盘对应的驱动,
然后就会在磁盘管理器里边识别出这个磁盘设备,最后经过我们对这个磁盘的一系列的磁盘初始化操作之后,
他会把已经安装到windows中的文件系统(NTFS,FAT32,CDFS,RAWFS,当然可能还包括我们自己开发的文件系统),
并且是你在分区格式化时候指定的文件系统(现在一般常用的是NTFS驱动)Attach到这个新磁盘的驱动之上,
我们在应用层调用WIN32 API函数,CreateFile, ReadFile,WriteFile等文件操作函数来读写这个新磁盘的数据。
调用CreateFile,进入ntdll的NtCreateFile,然后进入到内核的NtCreateFile函数,内核的NtCreateFile里边,
创建一个 IRP_MJ_CREATE 的IRP,并填写相关参数,
然后调用NTFS系统的顶层驱动(顶层驱动下边可能有一个或多个文件过滤驱动),
如果过滤驱动没有拦截,NTFS文件系统驱动最终解析这个请求,
并转换成对这个磁盘驱动的 对扇区的读写请求,然后调用下层的磁盘驱动进行磁盘扇区的读写操作。
(如果磁盘驱动是虚拟磁盘驱动,这就又回到刚才上边提到的虚拟磁盘驱动的内容了)。
完成磁盘读写之后,然后一层层的返回,最终回到应用层的CreateFile。
ReadFile和WriteFile等WIN32 API函数基本也是这样的流程(当然还包括FastIO和Cache IO过程)。

显然的,如果我们在文件过滤驱动中拦截了某个目录下的所有文件操作的IRP,
那么对这个目录下的文件操作的行为就不再下发到底层的NTFS文件系统,自然也不会进入到真正的磁盘IO操作。
如果我们再把拦截到的IRP重新解析处理,让他返回我们自己的数据,这样就达到了这个目录重定向的目的。

要实现目录重定向,我们需要熟悉文件过滤驱动的框架。
可以借鉴sfilter源代码,也许sfilter代码挺多,研究起来挺烦。
其实很简单的一句话,sfilter就是标准的 NT式过滤驱动,外加一些如何处理FASTIO和动态挂载文件系统的代码。
FASTIO本身的函数也多,所以懒惰的时候,直接返回 FALSE即可,反正对系统影响不算太大。

如果你对文件动态挂载的代码不感兴趣,那你完全可以用简单的方式快速的实现一个文件过滤驱动框架。
(假如你给固定的 D: 盘挂载文件过滤驱动)
伪代码如下:

///首先打开 D:\根目录,获得 hFile 句柄.
OBJECT_ATTRIBUTES oa;
UNICODE_STRING us; RtlInitUnicodeString(&us, L"\\??\\D:\\");
InitializeObjectAttributes(&oa, &us, ....);
HANDLE hFile = ZwCreateFile( &oa ,....);

////从句柄获得 FILE_OBJECT 对象
PFILE_OBJECT fileObj;
ObReferenceObjectByHandle(hFile, FILE_READ_ATTRIBUTES, // FILE_READ_DATA
  NULL, KernelMode, (PVOID*)&fileObj, NULL);
从FILE_OBJECT获得文件系统设备对象, 这个设备就是我们即将Attach的设备.
fileSystemDeviceObject = IoGetRelatedDeviceObject(fileObj);

myFilterDeviceObject =IoCreateDevice(...);
.....别忘需要继承源设备的设备属性

IoAttachDeviceToDeviceStackSafe(fileSystemDeviceObject,。。。)
就这么简单,一个过滤设备就创建好了。

当然别忘在DriverEntry中把所有派遣函数替换成我们的派遣函数
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; ++i){
  ///
  DriverObject->MajorFunction[i] = sfPassThrough; //派遣函数
  /////
 }

还必须记得填写 FAST_IO_DISPATCH 。否则会造成windows罢工蓝屏。
PFAST_IO_DISPATCH fastIoDispatch = (PFAST_IO_DISPATCH)ExAllocatePoolWithTag(NonPagedPool, sizeof(FAST_IO_DISPATCH), 'FXSD');
。。。。填写许多的FASTIO的回调函数

DriverObject->FastIoDispatch = fastIoDispatch; //////

还必须知道一点就是,由于windows历史原因,PFAST_IO_DISPATCH中有6个函数:
AcquireFileForNtCreateSection
ReleaseFileForNtCreateSection
AcquireForModWrite
ReleaseForModWrite
AcquireForCcFlush
ReleaseForCcFlush
不提供支持,也就是这六个函数即使提供有效的函数地址,也不会被调用。
在我们的目录重定向驱动中,这是一个糟糕的事情,为什么呢?
因为我们必须保证被重定向的目录内所有的文件操作请求,不能下传给下层驱动,
否则很可能被下层的驱动识别成无效的请求,甚至蓝屏死机。
幸好在WINXP以上的系统中,可以调用 FsRtlRegisterFileSystemFilterCallbacks 来解决这六个函数问题
而且还可以在他注册的回调函数中,
直接返回 STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY,表示我们已经处理了这个请求,让他别再下传给下层驱动。
至于 STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY 这个状态码,没有文档说明达到这种效果,
但是 测试之后(包括最新的win8系统)确实是这样的。

如果你还想加入动态挂载文件系统的功能:
首先在DriverEntry调用 IoRegisterFsRegistrationChange 注册一个回调函数fsNotification,
fsNotification 函数原型如下,
VOID fsNotification(PDEVICE_OBJECT filesystemCtrlDevice, BOOLEAN FsActive);
filesystemCtrlDevice 就是对应的文件系统控制设备(比如NTFS文件系统),FsActive为TRUE表示加载,否则是卸载。
当有新的文件系统被加载进来或卸载,这个函数就会被调用。
加载新文件系统的时候我们创建一个过滤设备,挂载到文件系统控制设备上,
通过抓获这个过滤设备的 IRP_MJ_SYSTEM_CONTROL就能知道某个卷设备(就是我们看到的一个个盘符)何时被加载,何时被卸载。
然后我们再创建过滤设备挂载到这个卷设备上,这就等于是文章上边讲到的如何挂载到一个固定盘符比如D:盘的功能了。

如果卷设备在我们过滤驱动之前被加载,那么即使我们挂载过滤设备到文件系统控制设备上,也不会得到卷设备挂载的通知。
如何解决这个问题呢? 
这就需要在 fsNotification 回调函数中,在创建过滤设备Attach到文件系统控制设备之后,枚举所有已经被挂载的卷设备。
枚举伪代码如下:

IoEnumerateDeviceObjectList(filesystemCtrlDevice ->DriverObject, ....); 
//从文件系统控制设备所在的驱动对象,获取这个驱动下的所有设备

for(IoEnumerateDeviceObjectList获得的每个设备对象){
      首先判断设备类型对不对,
      接着调用 IoGetDeviceAttachmentBaseRef 获得设备栈最底层的文件系统设备,然后获取底层设备的名字,
            如果有名字说明是控制设备,而不是卷设备,只有没有名字的才算卷设备。
     最后 调用 IoGetDiskDeviceObject 函数,查看这个文件卷设备是否挂载到真正的存储卷设备上。
           如果是则说明这是个有效的卷设备,然后就是如上所述的创建过滤设备挂载到这个卷设备上。
}


就这样,一个文件过滤驱动的框架就做好了。,
(这里略过FASTIO的内容,因为他的函数太多,也费事,有兴趣可查看 微软的FastFat代码关于FastIO调用的例子。)

而重点就是如何处理 sfPassThrough 派遣函数,让他达到我们的重定向目录的功能。

待续。。。。


本文在 CSDN上BLOG:
    http://blog.csdn.net/fanxiushu/article/details/43636575 以及后续章节

本文在CSDN上提供的程序:
http://download.csdn.net/detail/fanxiushu/8448785

本文在 CSDN上提供的源代码工程:
    http://download.csdn.net/detail/fanxiushu/8545567


2017-01更新,完整版本程序下载地址:

CSDN的下载地址:

http://download.csdn.net/detail/fanxiushu/9719017

GITHUB上的下载地址:

https://github.com/fanxiushu/xFsRedir/raw/master/xFsRedir-1.0.0.1.zip

这个是1.0.0.1版本,如果寻找新版本,请关注:

https://github.com/fanxiushu/xFsRedir

或发送邮件 [email protected] 如发现BUG,可在CSDN或者GitHUB上提出来,或者发送邮件 [email protected]

猜你喜欢

转载自blog.csdn.net/snowfoxmonitor/article/details/80724813