通过Windows I/O管理器实现本地权限提升:变体发现协作

本文详细分析了Google Project Zero研究员James Forshaw发现的Windows内核及驱动程序中的新型漏洞类别,涉及I/O管理器的权限检查机制缺陷,探讨了Microsoft的修复策略及第三方驱动开发者的防护建议。

通过Windows I/O管理器实现本地权限提升:变体发现协作

Microsoft安全响应中心(MSRC)调查所有影响Microsoft产品和服务的漏洞报告,以帮助客户和全球在线社区更加安全。我们感谢安全社区定期向我们报告的优秀漏洞研究,并认为与这些研究人员合作是一种荣幸。

Google Project Zero的James Forshaw是一位持续向我们报告高质量、有趣漏洞的研究员。James的大部分工作集中在Windows内部的复杂逻辑错误,特别是在权限提升和沙箱逃逸领域。

这篇博客文章涵盖了James与MSRC团队在Windows内核及其部分驱动程序中发现的新型漏洞类别的合作,Microsoft工程团队如何修复这些漏洞,以及第三方驱动开发者如何避免引入类似漏洞。

背景

在Windows中,当从用户模式线程进行系统调用时,系统调用处理程序通过将其PreviousMode字段设置为UserMode来在线程对象中记录这一点。如果系统调用是使用Zw前缀函数从内核模式或系统线程进行的,则线程的PreviousMode将设置为KernelMode。这种区分用户模式和内核模式调用者的方法有助于确定调用的参数是来自受信任还是不受信任的源,以及因此内核需要验证它们的程度。

当用户模式应用程序创建或打开文件时,这会调用NtCreateFile或NtOpenFile系统调用。内核模式代码有更广泛的API函数可供选择:NtCreateFile/NtOpenFile及其Zw前缀等效项、来自I/O管理器的IoCreateFile函数以及来自过滤器管理器的FltCreateFile函数。

如上图所示,所有这些最终都会到达I/O管理器内部函数IopCreateFile。线程的PreviousMode被分配给变量AccessMode,在IopCreateFile中,该变量用于决定是否检查有效参数和缓冲区,然后传递给对象管理器在调用ObOpenObjectByNameEx时使用。稍后,在IopParseDevice中,AccessMode用于访问检查——如果是UserMode,则对设备对象执行权限检查。接下来,IopParseDevice构建一个I/O请求包(IRP),将其RequestorMode字段设置为AccessMode,并使用IofCallDriver将控制传递给设备的IRP_MJ_CREATE分发函数。

IopCreateFile有一个Options参数,该参数不向NtCreateFile和NtOpenFile的调用者公开,但向仅可从内核模式访问的API函数公开。如果设置了IO_NO_PARAMETER_CHECKING标志,它会覆盖AccessMode,使其设置为KernelMode而不是线程的先前模式,从而绕过参数验证。这也会导致稍后在IopParseDevice中的权限检查被豁免。

请注意,IoCreateFileEx始终设置IO_NO_PARAMETER_CHECKING标志。由于FltCreateFile、FltCreateFileEx和FltCreateFile2通过此函数调用I/O管理器,因此这些函数也始终设置了IO_NO_PARAMETER_CHECKING。

然而,有时必须覆盖此行为并强制进行访问检查。例如,内核模式驱动程序(可能通过IOCTL)打开由用户模式应用程序指定的对象名称。

如果IopCreateFile的Options参数设置了IO_FORCE_ACCESS_CHECK标志,这有两个效果:首先,它导致I/O管理器在IopParseDevice中执行访问检查,就像AccessMode是UserMode一样(但不将其设置为UserMode)。其次,在IRP的IRP_MJ_CREATE堆栈位置中,它导致在Flags字段中设置SL_FORCE_ACCESS_CHECK。IRP_MJ_CREATE请求的处理程序应在其自己的访问检查中使用此标志,以覆盖IRP的RequestorMode。

在Windows XP的开发过程中,很明显,在对象命名空间中操作的其他API函数(例如,用于\Registry的ZwOpenKey)需要某种强制访问检查的方法,因此引入了新标志OBJ_FORCE_ACCESS_CHECK。这设置在请求对象的属性上,并导致对象管理器(而不是I/O管理器)将请求者的访问模式设置为UserMode。这优先于任何已设置的访问模式——特别是,它会覆盖IopCreateFile中设置IO_NO_PARAMETER_CHECKING的效果,将其设置回KernelMode。

总结以上内容:

  • 在决定是否执行访问检查时,IRP_MJ_CREATE处理程序不仅必须检查IRP的RequestorMode是否为UserMode,还必须检查是否设置了SL_FORCE_ACCESS_CHECK标志。
  • 内核模式调用者对IoCreateFile或FltCreateFile API函数有两种可能的方法来指定应执行访问检查:
    • 通过I/O管理器,通过设置Options中的IO_FORCE_ACCESS_CHECK标志,这反过来在IRP堆栈位置Flags中设置SL_FORCE_ACCESS_CHECK标志。
    • 通过对象管理器,通过设置OptionAttributes->Attributes中的OBJ_FORCE_ACCESS_CHECK标志,这导致IRP的RequestorMode设置为UserMode。

漏洞

在他的研究中,James发现Windows附带了各种内核模式驱动程序,在处理IRP_MJ_CREATE请求时,检查IRP的RequestorMode,但不检查SL_FORCE_ACCESS_CHECK。此外,这些可能通过内核模式代码被利用,这些代码表面上似乎在创建或打开文件时正确设置了IO_FORCE_ACCESS_CHECK。攻击者通过来自用户模式的某些请求获得对文件创建/打开调用的参数足够控制,可以使用此功能发送RequestorMode为KernelMode的IRP_MJ_CREATE请求。如果RequestorMode检查用于安全决策,这可能导致本地权限提升漏洞。

更多细节,包括James如何发现此漏洞类别以及此类代码在Windows内核和驱动程序中的示例,可以在他在Google Project Zero博客上的帖子中找到。

James指定了两种内核模式代码模式——“发起者”,进行文件创建/打开调用,和“接收者”,处理IRP_MJ_CREATE请求。这些定义如下:

“发起者”包括:

  • 对文件打开API函数(IoCreateFile或FltCreateFile)的调用,其中:
    • Options中的IO_NO_PARAMETER_CHECKING标志被设置(或者,调用是从系统线程进行的)。
      • 这会将IRP的RequestorMode设置为KernelMode。
      • 对于IoCreateFileEx和FltCreateFile*,IO_NO_PARAMETER_CHECKING是隐式设置的。
    • Options中设置了IO_FORCE_ACCESS_CHECK标志,表示意图进行访问检查。
    • ObjectAttributes中未设置OBJ_FORCE_ACCESS_CHECK属性。
      • 如果设置了此属性,它将覆盖IRP的RequestorMode,将其设置为UserMode。
    • 攻击者对此调用有一定程度的控制。

“接收者”包括:

  • IRP_MJ_CREATE请求的处理程序,其中:
    • 使用IRP的RequestorMode进行安全决策。
    • 在执行此操作时,未测试其IRP堆栈位置中的Flags是否为SL_FORCE_ACCESS_CHECK。

攻击者需要能够引导发起者打开由接收者处理的设备对象。接收者中的安全检查被绕过,因为Irp->RequestorMode将是KernelMode,但未检查SL_FORCE_ACCESS_CHECK标志。

在他的调查中,James发现了发起者和接收者的实例,但没有发现任何直接导致权限提升的链式组合。我们选择与他合作进行进一步研究,看看我们能一起发现什么。

变体发现

对于Windows附带的first-party驱动程序(由Microsoft编写的驱动程序)和Windows内核本身,我们使用Semmle QL(之前在此博客中讨论过)搜索源代码中上述漏洞代码模式。

为了找到发起者代码模式,我们使用自定义数据流分析来跟踪传递给内部函数IopCreateFile时Options和ObjectAttributes->Attributes的标志组合。如上所述,这是各种文件打开API函数最终到达的点。此结果集被过滤以仅显示设置了IO_FORCE_ACCESS_CHECK和IO_NO_PARAMETER_CHECKING但未设置OBJ_FORCE_ACCESS_CHECK的调用。我们拒绝了攻击者无法控制对象名称的发起者。

为了发现接收者代码模式,我们检查了受IRP对象的RequestorMode字段影响的控制表达式(即,用于控制流语句如if和switch的表达式),并且可从IRP_MJ_CREATE分发或过滤器函数到达。这些被过滤以排除涉及SL_FORCE_ACCESS_CHECK宏和IO_STACK_LOCATION对象的Flags字段的某些访问的表达式。在手动跟进中,少量RequestorMode检查被拒绝,因为它们没有安全影响(例如,用于排除内核模式调用者而不是允许它们)。

此初步分析在Windows源代码中总共找到了11个潜在发起者和16个潜在接收者,包括James向我们报告的那些。

Windows还附带许多“inbox驱动程序”——对启动某些设备至关重要或启用开箱即用完全功能安装的第三方驱动程序。我们过滤每个驱动程序二进制文件的导入表以获得进一步分析的子集。对于发起者,这些是IoCreateFile或FltCreateFile的导入,对于接收者,这是IoCreateDevice或FltRegisterFilter,因为我们只关心可通过设备对象或其过滤器到达的代码。使用IDA Pro检查了剩余的驱动程序二进制文件集。此分析未找到其他发起者或接收者。

利用这些潜在漏洞需要兼容的发起者和接收者。特别是,发起者必须向攻击者提供对最终IopCreateFile调用的足够控制,以便他们可以利用接收者。

我们发现接收者分为两类:

  • 需要提供特定的扩展属性,要么到达RequestorMode检查,要么在绕过它后在利用方面做有用的事情。
  • 需要将文件句柄传递回攻击者以到达其其他IRP分发函数中可能可利用的代码。

幸运的是,我们分析中检测到的发起者都没有给攻击者足够的能力来做这两件事。

在我们分析的下一步,我们进行了更广泛的搜索,包括所有对内核模式文件创建/打开API的调用,包括对ZwCreateFile和ZwOpenFile的调用,以及对IoCreateFile和FltCreateFile的调用,其中设置了IO_NO_PARAMETER_CHECKING(无论是否设置了IO_FORCE_ACCESS_CHECK)。在排除了所有设置了OBJ_FORCE_ACCESS_CHECK的调用后,内核和驱动程序代码中仍有数百个结果,因此我们通过关注两个接收者类别来过滤这些结果。

首先,我们过滤了EaBuffer参数非NULL的调用,以显示可以传入扩展属性的地方。其次,我们过滤了未设置OBJ_KERNEL_HANDLE的调用,以查看可能将可用对象句柄传递回用户模式的地方。这将结果减少到可管理数量以进行手动分析。然而,我们在此结果集中未找到任何可用作兼容发起者的代码。

深度防御安全措施

总结James和MSRC的联合调查,目前支持的Windows版本中似乎没有可用于开箱即用本地权限提升的发起者和接收者组合。

尽管如此,我们选择在未来版本的Windows中解决这些问题作为深度防御措施。大多数这些修复计划在Windows 10 19H1中发布,少数被保留以进行进一步的兼容性测试和/或因为它们存在的组件已弃用且默认禁用。

我们确实考虑了一个广泛的修复以防止发起者实例发生,通过进行API更改,以便如果Options中设置了IO_FORCE_ACCESS_CHECK,IRP的RequestorMode自动设置为UserMode,就像设置了OBJ_FORCE_ACCESS_CHECK属性一样。然而,破坏可能依赖现有行为的第三方驱动程序功能的兼容性风险被认为太高。

驱动程序开发者的信息

第三方驱动程序存在一些易受此漏洞类别影响的风险,我们敦促所有内核驱动程序开发者审查其代码,以确保正确处理IRP请求和防御性使用文件打开API。

推荐的更改应该相对简单。

在IRP_MJ_CREATE分发处理程序中,不要依赖IRP的RequestorMode值而不检查SL_FORCE_ACCESS_CHECK标志。例如,而不是:

1
if (Irp->RequestorMode != KernelMode) { // reject user mode requestors Status = STATUS_ACCESS_DENIED; }

使用类似这样的东西:

1
2
3
4
5
6
7
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);

if ((Irp->RequestorMode != KernelMode) || (IrpSp->Flags & SL_FORCE_ACCESS_CHECK))
{
// reject user mode requestors
Status = STATUS_ACCESS_DENIED;
}

其次,在Options中已经设置了IO_FORCE_ACCESS_CHECK标志的情况下,我们强烈建议也在ObjectAttributes中设置OBJ_FORCE_ACCESS_CHECK标志。例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
InitializeObjectAttributes(
&ObjectAttributes,
FileName,
(OBJ_CASE_INSENSITIVE | OBJ_FORCE_ACCESS_CHECK),
NULL,
NULL);
Status = IoCreateFileEx(
&ObjectHandle,
GENERIC_READ | SYNCHRONIZE,
&ObjectAttributes,
&IoStatusBlock,
NULL,
0,
0,
FILE_OPEN,
0,
NULL,
0,
CreateFileTypeNone,
NULL,
IO_FORCE_ACCESS_CHECK);

更一般地说,在可能代表用户模式请求进行文件创建/打开调用的情况下,不要假设线程的先前模式是UserMode或这将延续到IRP的请求者模式——在ObjectAttributes中设置OBJ_FORCE_ACCESS_CHECK标志以明确这一点。

致谢

我们要感谢James Forshaw与我们合作进行此漏洞调查,以及他与MSRC分享的许多其他高质量漏洞报告。

还要感谢Paul Brookes、Dileepa Kidambi Sudarsana和Michelle Chen在将静态分析扩展到整个Windows代码库方面的协助。

Steven Hunter,MSRC漏洞与缓解团队

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计