文档加密软件核心技术:文件系统微过滤驱动(minifilter)

文档加密软件产品目前使用的技术有两种,一种是应用层的API HOOK,另一种则是文件系统的微过滤驱动技术。Ping32文档加密软件使用的是文件系统的微过滤驱动技术。在聊文件系统的微过滤(minifilter)之前,我们先聊聊通用的过滤驱动概念。

过滤驱动概念是Windows I/O子系统最强大的功能特性之一。过滤驱动可以简单地通过将自身绑定到指定的设备上,来实现一些特定的功能。而且,过滤驱动不需要改变底层设备地驱动程序逻辑。Ping32的文档加密驱动亦是如此。除此之外,Ping32除文档加密外,很多其他功能也用到了过滤的思想,如:网络流量的过滤。

在典型的Windows系统中,过滤驱动可以安装在多个层级。例如,在卷级别有标准的Windows提供的过滤驱动,可提供卷快照功能(以支持备份),以及可选的完整卷数据加密(支持BitLocker),这个我们之前的文章亦有介绍。也可能有特定于制造商的过滤驱动,如:鼠标或键盘过滤驱动,以实现独有的按钮、按键功能支持。

在Windows系统中,插入过滤驱动最常见也是最强大的位置之一就是在文件系统上。文件系统过滤驱动在I/O操作到达文件系统之前拦截I/O操作(来自应用程序和系统本身)。这允许他们在文件系统捕获它们之前监控、跟踪、管理、操作甚至允许或拒绝I/O操作。想想文档加密软件的核心逻辑:在数据写入到磁盘之前,被透明加密了,从磁盘中读取返回到用户程序之前,又被透明解密了。大多数人熟悉的文件系统过滤驱动类型可能是杀毒软件的过滤驱动。这种类型的过滤驱动通常会拦截文件的打开请求并挂起他们,同时过滤驱动或者应用层运行的相关程序扫描正在打开的文件是否有病毒。如果发现任何病毒,可以取消打开请求。如果没有发现病毒,则可以允许打开请求正常完成。

文件系统过滤驱动使用的场景是非常广泛的,除了用于类似Ping32文档加密软件的数据透明加密和解密外,刚刚也介绍了杀毒软件的使用场景。除此之外,文档的审计和跟踪也会用到,如微软SysInternals的ProcMon工具。另外,文件系统过滤驱动也可能用于一些不太明显的目的,如:因为它们可以捕获创建和写入了哪些文件,文件系统过滤驱动通常在备份产品和分层存储子系统中发挥了关键作用。它们也可以实现重定向功能来实现一个用户空间的文件系统(类似FUSE),比如将云上的文件显示成本地文件(想想OneDrive)。

从Windows XP SP2开始,文件系统微过滤驱动(minifilter)已成为实现文件系统过滤驱动的首先方案。因为minifilter模型为文件系统过驱动的开发提供了出色的组织和支持框架。网络上也有非常多的开源示例,许多开发人员认为编写minifilter完全在他们的能力范围之内。这也许是对的,但是也有例外……

本文介绍了Windows minifilter的基本架构概念。然后描述了两种不同类型的minifilter:标准minifilter和隔离minifilter。Ping32文档加密软件即使通过隔离minifilter实现的。

本文中,我们会使用几个Windows文件系统开发的常见术语。

标准minifilter
标准minifilter是Windows文件系统常见的过滤驱动,往往用来监控或跟踪文件系统数据。大多数杀毒软件都会使用标准微过滤驱动。

隔离minifilter
隔离minifilter也是Windows文件系统的过滤驱动,它将文件数据的视图与相关文件的底层真实数据隔离开。隔离minifilter的典型使用场景是文档加密产品的透明加密和透明解密的实现。隔离minifilter使用“相同堆栈”的概念,并通过为每个视图提供独特的缓存来提供不同的视图。

过滤管理器和高度
文件系统minifilter的基础是过滤管理器Filter Manager,它是一个标准的Windows组件。过滤管理器作为一个原生的系统过滤器的实现,它会过滤所有的文件系统实例。因为,系统允许有多个过滤器位于给定的文件系统实例上(Windows 10系统本身就默认安装不少于9个minifilter),过滤管理器提供了一个高度参数,它允许开发人员决定将他们的minifilter安装在过滤管理器系统的哪个层次上。高度是文件系统过滤驱动的一个重要属性,因为在正确的高度进行过滤至关重要。例如:Ping32的文档加密minifilter和一个杀毒软件的minifilter。为了让杀毒软件的minifilter正常工作,它需要对文件的解密内容进行操作。因此杀毒软件的minifilter需要在更高的位置,Ping32文档加密的minifilter则在更底层的位置。

minifilter的回调
当minifilter向过来管理器注册时,除了一些基本的设置,它还可以选择接收指定的I/O操作的PreOperation和PostOperation回调。PreOperation回调在指定类型的每个I/O操作被传递到文件系统之前被调用。PostOperation则在文件系统处理特定类型的I/O操作后调用。

  • 过滤管理器对回调的支持是经过精心设计的。例如,当给定的minifilter收到PreOperation回调时,它可以:
  • 完全完成操作。这会导致较低高度的minifilter,甚至被过滤的文件系统看不到这个I/O操作。
  • 完成操作,将I/O操作传递给较低高度的过滤驱动和文件系统,并选择在操作完成时进行回调,触发PostOperation。
  • 完成操作,将I/O操作传递给较低高度的过滤驱动和文件系统,但选择在操作完成时不进行回到,不处罚PostOperation。
  • 返回正在进行的操作。这种情况下,minifilter稍后会通知过滤管理器操作的状态,包括是否需要调用更底层的实例以及是否需要触发PostOperation回调。

在PreOperation和PostOperation中,minifilter几乎可以执行任何操作,包括查看或修改操作数据。

Win32 API与Windows Native API
在一些通过API HOOK技术实现的文档加密产品中,HOOK的API都是Win32 API或者Native API (NtXXX)。如果这两种API的基本概念都无法理解的读者,可以先查找一些相关资料,做一些基础的了解。谈这个是因为,尽管Windows的过滤管理器框架是经过精心设计的也是非常强大的,但是实际上应用程序使用的Win32 API语义可能和I/O子系统使用的Native API非常不同。举个例子,很多Win32的文件API都可找到与之对应的Native API,比如:CreateFile和NtCreateFile,WriteFile和NtWriteFile,ReadFile和NtReadFile,但是也并非都能找到对应,如:CopyFile这个函数,我们就无法找到NtCopyFile,因为在内部,CopyFile拷贝文件时,流程是打开源文件,打开目标文件,如果两个操作都成功,则从源文件发出一系列读取操作,并写入目标文件,指导文件被复制,然后管理源文件和目标文件。另一个稍微有趣的例子是Win32 API DeleteFile。这个函数同样也找不到对应的NtDeleteFile。相反,删除文件的操作是通过NtSetInformationFile来设置文件的标记(FILE_DISPOSITION_INFORMATION)来实现的。这允许调用者指定他是否希望文件在关闭时删除文件。但是要注意的是,在文件最后的一个句柄被关闭前,文件实际上不会被删除。

与虚拟内存系统的集成
minifilter的另一个复杂特性来源于文件系统、Windows缓存管理器和Windows内存管理器之间的密切协作。我们都知道,调用ReadFile时,会从文件句柄关联的文件中读取指定的数据放入缓冲区。我们大多数人可能也知道,这通常涉及到一些缓存。也就是说,每次ReadFile的调用不一定总是直接从物理磁盘中读取数据。有趣的是,除了在没有缓存的情况下显示地打开文件,文件系统通常很少处理应用程序对ReadFile地调用,除了调用缓存管理器来处理它。为了响应文件系统地请求,缓存管理器将数据从与该文件的打开实例关联地缓冲区复制到应用程序地数据缓冲区。