首页
社区
课程
招聘
[翻译]标准和隔离型微过滤驱动介绍
2024-2-9 23:44 7043

[翻译]标准和隔离型微过滤驱动介绍

2024-2-9 23:44
7043

标准和隔离型微过滤驱动介绍

过滤驱动是Windows I/O子系统中最强大的架构特性之一。通过简单地将自身附加到现有设备上,过滤驱动就可以给设备增加新的功能。并且,过滤设备不需要对底层设备的驱动程序进行任何更改。

 

在典型的Windows系统中,过滤驱动程序可以安装在多个层级上。例如,有标准的Windows提供的在卷层级上的过滤驱动程序,用以提供卷快照功能(用于支持备份),以及可选的完整卷加密功能(以支持Windows Bitlocker)。还有制造商特定的过滤驱动程序,例如用于鼠标或键盘的过滤驱动程序,为特定型号添加对独特按钮的支持。

 

在Windows系统中,在众多插入过滤器的地方中,其中最常见且最强大的一个地方是在文件系统上。文件系统过滤器在I/O操作(来自应用程序和系统本身)到达文件系统之前截取这些I/O操作。这使它们能够在文件系统看到它们之前监视、跟踪、管理、操作甚至接受或拒绝I/O操作。大多数人熟悉的文件系统过滤器类型可能是杀毒软件过滤器。这种类型的过滤器通常截取文件打开请求,将请求挂起,并在过滤器(或更可能是在用户模式下运行的关联服务)扫描正在打开的文件以查找病毒。如果发现任何病毒,打开请求可以被取消。如果没有发现病毒,打开请求可以继续正常完成。

 

文件系统过滤器通常用于各种用途,从如上所述的杀毒和恶意软件扫描,到软件许可证跟踪和管理,到文件的审核和更改跟踪,到透明的数据加密和解密。文件系统过滤器还可以用于其他不太明显的用途。例如,由于它们可以看到哪些文件被创建和写入,文件系统过滤器通常在备份产品和分层存储子系统中发挥关键作用。而且,因为文件系统过滤器可以成为应用程序看到的文件系统“命名空间”的第一个解释器,它们还可以执行强大的文件重定向操作,例如将远程文件(例如存储在云中的文件)显示为本地文件。

 

自Windows XP SP2引入以来,文件系统Minifilter模型已成为实现文件系统过滤器的首选机制。这是有充分理由的,因为Minifilter模型为文件系统过滤驱动程序的开发提供了出色的组织和支持框架。有了相当不错的文档和一系列重要示例(如GitHub上的示例),许多开发人员认为编写文件系统Minifilter是完全可行的。如果他们遵循一定的限制,他们是正确的。

 

本文描述了Windows文件系统Minifilter的基本架构概念。然后描述了两种不同类型的Minifilter:标准Minifilter和隔离Minifilter。最后,它描述了为开发文件系统隔离Minifilter的项目相对于开发标准Minifilter的项目而言更加复杂的原因。

 

标准Minifilters vs 隔离Minifilters

在本文中,我们澄清了Windows文件系统社区中使用的一些常见术语。

 

标准Minifilter

标准Minifilter是一个Windows文件系统Minifilter驱动程序,用于监视或跟踪文件系统数据。大多数杀毒扫描程序都是标准Minifilter。

 

隔离Minifilter

隔离Minifilter是一个Windows文件系统Minifilter驱动程序,将文件数据的视图与同一文件的实际底层数据分离开来。典型的隔离Minifilter的例子是透明加密/解密过滤器。隔离Minifilter使用“相同的堆栈”概念,并通过为每个视图提供独特的缓存区域来提供不同的视图。

 

过滤管理器和高度

文件系统Minifilter模型的基础是过滤管理器,它是一个标准的Windows组件。过滤管理器是作为传统的文件系统过滤器实现的,并过滤所有文件系统实例。因为可以在任何给定的文件系统实例上有多个过滤器(Windows 10的默认安装中包含不少于九个标准文件系统Minifilter!),过滤管理器提供了一个“高度”系统,允许开发人员决定他们的Minifilter应该安装在过滤器层级中的哪个位置。见图1。

 

图 1 – 带有三个文件系统过滤器的过滤器管理器

 

在图1中,您可以看到Windows过滤管理器(标记为FLTMGR)正在过滤作为C盘挂载的NTFS文件系统实例。在图中,过滤管理器加载了三个文件系统Minifilter:MiniFilter A、MiniFilter B和MiniFilter C。这些过滤器的顺序,MiniFilter A位于MiniFilter B之上,MiniFilter B位于MiniFilter C之上,不是随机的。相反,这是由分配给每个Minifilter的高度确定的。高度对于每个Minifilter都是唯一的,并在Minifilter安装过程中指定。

 

高度是文件系统过滤器的重要属性,因为在正确的高度进行过滤对于正常运行非常关键。例如,考虑可能安装在任意文件系统上的两个Minifilter:一个透明数据加密Minifilter和一个杀毒软件Minifilter。为了使杀毒软件Minifilter能够正常工作,它需要在文件的解密内容上进行操作。因此,杀毒软件Minifilter需要在高度上较高(即高于)数据加密Minifilter。

 

Minifilter回调函数

当Minifilter向过滤管理器注册时,除了其他事情外,它可以选择接收特定I/O操作的PreOperation和/或PostOperation回调。在将指定类型的每个I/O操作传递给被过滤的文件系统(或者实际上是下一个较低高度的Minifilter)之前,会调用PreOperation回调。在文件系统(和任何较低的Minifilter)处理特定类型的I/O操作之后,会调用PostOperation Minifilter回调。

 

过滤管理器对回调的支持非常周到。例如,当给定的Minifilter接收到PreOperation回调时,该过滤器可以:

l   完全完成操作。这将导致较低高度的Minifilter(如果有的话),甚至被过滤的文件系统都看不到此I/O操作。

l   完成操作,将其传递给较低高度的Minifilter(如果有的话)和底层文件系统,并选择在操作完成时进行回调(PostOperation回调)。

l   完成操作,将其传递给较低高度的Minifilter(如果有的话)和底层文件系统,但选择在操作完成时不进行回调(这是PostOperation回调)。

l   返回操作正在进行中。在这种情况下,Minifilter稍后会向过滤管理器报告操作的状态,包括是否需要调用任何底层实体以及是否需要PostOperation回调。

 

在其PreOperation和/或PostOperation回调中,Minifilter可以执行几乎任何操作,包括检查或修改涉及到的数据。因此,关键是理解这些操作的具体含义。

 

Win32 API vs Native Windows API

Filter Manager框架用于编写Minifilter是如此强大和周到,以至于新的Minifilter开发人员很容易被误导,认为文件系统过滤很容易。事实是:它可能很容易,但也可能会变得令人惊讶地迅速变得混乱。

 

固有的问题是,无论Filter Manager模型有多么强大或令人愉快,Windows文件系统的世界本质上是复杂的。其中一些复杂性来自于Win32 API与I/O子系统实际使用的native Windows API有时非常不同。一个简单的例子是我们在OSR经常遇到的Win32 API CopyFile。开发人员通常会惊讶地发现,这个函数没有本机的对应函数。实际上,内部的CopyFile函数打开源文件,打开目标文件,并且如果这两个操作都成功,就会从源文件读取一系列数据并将其写入目标文件,直到文件被复制完成。然后关闭源文件和目标文件。

 

一个稍微有趣一些的例子是Win32函数DeleteFile。如果你不是一个文件系统开发人员,你可能会想“删除能有多复杂?你只需要…删除文件,对吗?”在Windows中,不是这样的。本机的Windows API实际上没有一个特定的删除文件操作。相反,通过发出一个设置信息操作(ZwSetInformationFile)来指示删除文件的意图,设置文件的Disposition(FILE_DISPOSITION_INFORMATION)。这允许调用者指定当文件关闭时是否要删除文件。然而,重要的是要注意,直到文件的最后一个句柄关闭,文件才会实际上被删除。这意味着应用程序可以打开一个文件并调用Win32的DeleteFile API,但这实际上并不删除文件。它只是将文件的Disposition设置为在关闭时删除。即使应用程序随后关闭文件,这仍然不一定意味着文件会被实际删除。想象一下,如果我们的示例应用程序在另一个应用程序打开文件时打开了该文件,会发生什么。该应用程序也可以随时设置文件的Disposition。如果在我们的示例应用程序设置文件的Disposition为在关闭时删除之后,其他应用程序将文件的Disposition设置为不再标记为在关闭时删除,那么文件将不会被删除。是的…这种情况确实发生过。

 

虚拟内存系统集成

文件系统Minifilter的另一个复杂性来源于文件系统、Windows缓存管理器和Windows内存管理器之间的密切关系。我们都知道,当我们调用ReadFile时,数据从指定的数据缓冲区中读取,该数据缓冲区指示了文件句柄所表示的文件。我们中的大多数人可能也至少知道,这里通常涉及一些缓存。也就是说,我们对ReadFile的每个调用并不总是会直接从媒介中读取数据。

 

有趣的是,除非使用非缓存方式来显式打开文件,文件系统通常对处理应用程序的ReadFile调用几乎没有做太多工作,只是调用缓存管理器来处理它。在文件系统的请求下,缓存管理器将数据从与该文件打开实例相关联的缓存区复制到应用程序的数据缓冲区中。这在图2中有所说明。

图2 – 文件系统使用缓存管理器来处理读取操作

 

在图2中,文件系统调用Windows缓存管理器来处理应用程序的ReadFile请求。文件系统使用应用程序在其ReadFile调用中提供的文件句柄来标识正在访问的文件的打开实例。文件句柄"hFile1"是一个指向被缓存管理器用于定位与文件系统缓存的一部分相关的缓存数据段的文件对象的句柄。

 

有趣的是,存储在缓存中的数据是通过Windows内存管理器通过页错误处理从磁盘读入内存的。这种页面读取操作也会经过文件系统。

 

文件系统Minifilter当然可以参与读取和写入操作的处理,这些操作既可以源自应用程序,也可以源自内存管理器。

保持简单:标准Minifilter

最常见的文件系统Minifilter类型是监视、跟踪或记录在文件系统级别执行的各种操作。一些Minifilter,例如杀毒软件扫描器,甚至可能批准或不批准某些操作。然而,这些过滤器不会涉及更改它们过滤的文件中数据的视图或大小。我们将这样的过滤器称为标准Minifilter,因为它们代表了存在的绝大多数文件系统Minifilter。

 

标准Minifilter的开发人员面临的主要挑战包括:

1.        正确理解和处理native Windows I/O子系统语义。

2.        正确保留所有native文件系统操作的行为,即使它们并不一定了解或关心这些操作。

 

当然,这还不包括编写任何Windows内核模式驱动程序所固有的基本挑战。

 

克服第一个挑战,即正确理解和处理native Windows I/O子系统语义,只需要时间和经验。微软提供了许多示例可以用作起点。并且关于编写基本文件系统Minifilter的文档非常好 - 假设你花时间阅读和理解它。还有一些在线资源,比如我们的NTFSD列表,可以通过回答你在学习过程中可能遇到的问题来帮助你。我还应该提一下,我们将在2018年1月开始教授如何编写标准Minifilter的研讨会。

 

上面提到的B项的复杂性在很大程度上取决于你编写的Minifilter的类型。你保持操作越简单,过滤范围越有限,事情就会变得越容易。例如,一个常见的错误是试图去过滤你的过滤器并不特别关心的I/O操作。文件系统Minifilter的HLK测试将对你评估你在保留过滤的文件系统行为方面的成功非常有帮助。一定要运行这些测试!

 

因此,如果你需要编写一个标准Minifilter - 即监视文件系统操作的Minifilter - 你将面临合理的挑战。

 

指数级难度:隔离Minifilter

一种非常不常见的文件系统Minifilter类型是隔离Minifilter。隔离过滤器将文件数据的视图与文件系统实际存储的底层数据分开(或“隔离”)。编写这种类型的Minifilter通常与编写标准的Windows文件系统一样复杂,因为它涉及到Minifilter、Windows缓存管理器和Windows内存管理器之间的直接密切交互。实际上,一些经验丰富的开发者认为隔离Minifilter比文件系统开发更难,因为在编写隔离Minifilter时,你实际上需要在Filter Manager提供的API中“嵌入”Windows文件系统的实现。因此,除了标准Minifilter的开发人员面临的挑战外,隔离Minifilter的开发人员还面临着许多更复杂的问题。

 

为了说明“将文件数据的视图与实际底层数据分开”的含义,让我们考虑一个实现实时透明加密/解密的隔离Minifilter。一个应用程序打开并调用ReadFile来读取一个以加密形式存储在磁盘上的文件。在这种情况下,加密/解密的Minifilter的工作是透明地解密呈现给应用程序的数据。支持这种类型活动的一般方案如图3所示。

图3 – 隔离 Minifilter

 

图3显示了两个与同一个文件关联的文件对象。图中上面的文件对象(绿色)是与应用程序相关联的打开实例。这个文件对象代表应用程序对文件的视图。请注意,文件对象引用的缓存部分包含解密的数据。这些数据是由隔离过滤器与缓存管理器合作将数据放入缓存中的。

 

图中下面的文件对象(蓝色)是由隔离Minifilter创建的,代表Minifilter(以及底层文件系统)对文件的视图。请注意,该文件对象的缓存部分中的数据是加密的。隔离Minifilter使用下面的文件对象与底层文件系统进行交互。

 

因此,当用户应用程序调用ReadFile时,隔离Minifilter将接收到该请求(作为其先前讨论的读取PreOperation处理的一部分),并与缓存管理器进行交互以填充缓存数据。在处理后续的分页读取操作(由内存管理器发送,以实现缓存管理器尝试获取缓存数据)时,隔离Minifilter发出一个引用下面(蓝色)文件对象的ReadFile操作。底层文件系统与缓存管理器合作,完成此请求并将数据放入指定的缓存部分。

 

当考虑到大多数透明数据加密系统通常使用某种元数据头(用于保存密钥或访问信息)时,情况变得更加复杂。这个头通常存储在文件本身中。因此,应用程序用于读取操作的文件偏移量可能与隔离Minifilter在访问存储在磁盘上的文件时需要使用的文件偏移量不同。

 

但实际上的复杂性往往比我们到目前为止讨论的还要多。假设加密/解密的Minifilter根据每个应用程序的情况确定在进行读取时向应用程序提供解密数据还是加密数据。例如,当Microsoft Word访问加密文档时,隔离Minifilter可能会自动解密数据。然而,当同一个加密文档从备份应用程序访问时,隔离Minifilter可以提供文件的原始加密内容。更有趣的是,一个设计良好的隔离Minifilter可以让不同的应用程序同时读取文件时,提供文件内容的解密视图和加密视图。如果其中一个应用程序在另一个应用程序打开文件的不同视图时向文件写入数据,会发生什么呢?让我们只是说情况会变得“有趣”起来。

 

隔离Minifilter:不仅仅是加密

我们应该迅速补充一点,隔离Minifilter模型不仅仅用于透明数据加密和解密系统。隔离Minifilter模型在以下情况下使用:

 

l   应用程序对数据的视图与文件系统上存储的底层文件存在差异。

l   应用程序对数据的视图与文件数据的底层位置存在差异。

l   不同的应用程序实例需要对文件系统上存储的相同底层数据具有各自独特的视图。

 

例如,考虑一种变更跟踪和版本控制的隔离Minifilter。一个Word副本可能正在主动编辑文件的最新版本,而另一个Word副本(可能由不同的用户运行)可能正在主动编辑文件的旧版本。

 

再例如,考虑一个存储在云端并且根据文件访问方式只能逐位下载的文件。对于应用程序来说,该文件可能完全驻留在本地文件系统中。然而,一个隔离过滤器可以根据需要从远程存储访问和补充数据。

 

总结一下:

Windows MiniFilter模型非常灵活且强大。在足够的时间和经验下,大多数开发人员都能编写标准的Minifilter。这些过滤器监控和/或跟踪文件操作,并且可能对底层文件系统中存储的文件的访问进行授权。开发标准Minifilter的主要挑战在于理解native Windows文件系统语义,并透明地将底层文件系统提供的所有功能传递给应用程序。这两个挑战都是可解决的,并且可以在没有大量Windows文件系统专业知识的情况下完成。

 

与标准Minifilter相比,隔离Minifilter则是另一种情况。这些Minifilter将应用程序接收到的文件视图与同一文件的实际底层数据分开。开发隔离Minifilter更像是开发完整的Windows文件系统,而不是开发标准Minifilter,因为它需要与Windows缓存管理器和内存管理器进行密切的交互。因此,开发隔离Minifilter不太可能成为大多数开发人员能够成功完成的任务,除非他们还希望成为Windows文件系统专家。

 

原文链接


阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回