以进程为控制粒度的文档加密驱动介绍

本文介绍了基于文件系统微过滤(minifilter)框架实现的对文件进行动态透明加解密驱动的开发方案及实现细节,标题中基于授信进程是指对加密文件的原始视图和解密视图的访问限制可以以进程为单位粒度进行限制。

Windows中常见的几种文档加密解决方案

EFS(加密文件系统)和Bitlocker是Windows内置的标准加密解决方案。

Bitlocker提供完整的扇区级分区加密服务。Bitlocker驱动程序(fvevol.sys)位于文件系统堆栈中的ntfs.sys驱动程序下方。它自动加密、解密由NTFS从物理驱动器写入、读取的数据块。因此,物理NTFS驱动器看起来像是解密的,但是如果你试图绕开NTFS读取磁盘原始内容,那么你读取的将是加密过的数据。应该注意的是,使用Bitlocker最安全的方式应该是——借助TPM加密协处理器,这也是Windows 11的硬件要求之一。不过Bitlocker依然可以在没有TPM的计算机上运行,不过这种情况如果想实现重启保护,往往需要借助USB闪存设备来引导系统。要了解的是,Bitlocker可以防范由于计算机设备丢失等场景带来的数据泄漏风险,并无法保证用户数据免受在Windows中运行的恶意程序(比如:勒索病毒)的侵害,也不能防范用户使用计算机时主动将文件通过网盘、QQ、微信、电子邮件、可移动存储等途径主动引发的数据泄漏。

加密文件系统(EFS)是一种标准的NTFS机制,它可以为逻辑驱动器的不同部分进行加密。EFS对应用程序透明地运行。EFS加密基于特定的帐户密码。当在有权限解密的用户会话中运行应用程序访问数据时,数据会被自动地加密和解密。因此EFS和Bitlocker一样,无法保证数据免受恶意程序地侵害,因为在指定用户会话中运行的恶意程序同样也有解密的权限。此外,EFS也无法防范用户主动引发地数据泄漏。

以进程为控制粒度的文档加密解决方案

我们已经阐述过,上述的两个方案都无法防范用户主动引发的数据泄漏。如果想满足这样的需求,须开发自定义的内核驱动程序。以便允许以进程为控制粒度,将进程划分为授信进程和非授信进程。这样将保证,在统一用户会话中读取数据时,一些应用程序读取的是文件的解密视图,而另外的应用程序读取的依然是加密视图。

使用minifilter实现文档加密

minifilter的原理以及和其他驱动的关联

minifilter的原理很简单,它连接到选定的系统分区,包括可移动存储,并过滤、查看、修改对位于这些分区中的文件对象的操作。

因此,无论应用程序执行什么文件操作,minifilter都会捕获到,并修改操作参数。例如,它可以阻止打开文件或启动应用程序;重定向文件操作到另一个文件;加密、解密或只是在读取、写入文件时检查数据。驱动可以保存每个分区的特定信息,然后用于每个操作。例如,此功能可用于为每个分区设置不同的加密参数。驱动程序可以为每个系统分区使用不同逻辑。特定于分区或其他文件系统对象的信息位于成为context的对象中。

minifilter实现的通用加密方案

没有进程的访问限制

要开始加密minifilter的开发,我们需要了解解决方案中采用的通用加密方案的原理。为了加密用户数据,minifilter必须分别注册几个回调处理器:IRP_MJ_READ 和 IRP_MJ_WRITE。

文件系统中的几种读写操作类型:
1、从文件缓存中读取:文件系统驱动从缓存中读取数据。如果缓存中没有数据,缓存管理器将文件中的数据读取到缓存中,然后将读取的数据返回到文件系统。
2、分页I/O读取:文件系统驱动直接从驱动器中读取数据。如果这个操作是由缓存管理器发起的,那么数据被移动到缓存中,如果是由应用程序发起的,那么数据不会被移动到文件缓存中。
3、Fast IO读取:直接从缓存中读取数据,不创建IRP。
4、缓存写入文件:数据写入缓存,缓存管理器不保证数据会理解写入驱动器上的文件。
5、分页I/O写入:数据直接写入驱动器上的文件。如果这个操作是由缓存管理器发起的,那么会导致刷新,如果是由应用程序发起的,那么数据将写入文件而不是移动到缓存中。
6、Fasr IO写入:数据直接写入缓存,绕过文件系统。

缓存管理器通常使用分页I/O读写操作将数据读入缓存或从缓存刷新到文件。这样,分页IO读取后,数据被移动到缓存中,分页IO写入后,数据直接写入驱动器上的文件。例如,如果缓存中有加密数据,并且应用程序进行了文件映射,则应用程序将直接引用缓存区域,该区域是来自文件的数据所在的区域。因此,minifilter不会接收和解密读取操作。为了处理这种情况,32位系统上,我们挂钩了Nt函数,64位系统上,这个行为是被禁止的。连接到分区并注册处理回调后,minifilter会看到上述所有的操作类型(Fast IO除外)。但是对于每个从缓存管理器写入的分页IO的数据加密和从缓存管理器读取的每个分页IO的解密就足以实现通用的透明文件加密,而没有每个进程的访问限制。因此,所有应用程序和缓存中总会有解密的数据,并且驱动器上的文件中总会有加密的数据,

进程存在不同访问限制的复杂性

不同的进程存在不同的访问限制,使动态文件加密驱动的开发变得更加复杂。在 minifilter 中处理操作时,会确定该进程是被允许还是被阻止。为了做到这一点,可以使用与每个 FileObject 相关的上下文。此上下文是在打开/创建文件操作的处理程序中创建的。此时打开目标文件的过程已经清楚了。有关进程的信息可以存储在此上下文中。在 FileObject 关闭操作期间可以删除上下文。此外,使用 Per-file 上下文也很重要。 顾名思义,此上下文直接链接到驱动器上的文件,而不管打开的 FileObject 的数量如何。对于为特定文件打开的所有文件对象,此上下文是唯一的,并且可以通过任何文件对象接收。当最后一个 FileObject 关闭时,系统将删除此上下文。注意:每个文件的上下文与文件系统加密驱动程序中的 FCB 结构相关,并且由于该结构存在直到任何打开的 FileObject 存在,因此上下文也是如此。诸如文件是否被加密、其解密大小、文件修改标志以及用于加密实现的其他数据等信息将存储在该上下文中。

此框架的问题

使用minifilter方法开发文件加密驱动程序有一个很大的缺点,即没有经验的开发人员可能没有意识到这一点。在使用文件映射时,禁止解密文件的进程仍然可以访问该文件的解密数据。发生这种情况是因为在文件映射的情况下,应用程序可以直接访问文件视图所在的缓存部分。因此,minifilter无法检测文件映射操作。在不改变加密方案架构的情况下,这个问题可以通过使用文件映射函数钩子来解决。在 x64 操作系统中,它只能是用户模式挂钩。这种方式涉及流程,非原生,会导致大量潜在的应用问题,很难预见。由于目前可用的钩子引擎的不完善以及需要考虑钩子实现的大量细微差别,这变得越来越复杂。操作系统最原生的解决方案(就设计而言)使用自定义微过滤器驱动程序和自定义文件系统驱动程序。