UEFI安全漏洞的挖掘、防御与检测之道(下篇)
2022-5-1 11:50:0 Author: www.4hou.com(查看原文) 阅读量:22 收藏

在这篇文章中,我们继续为读者分享与SMM漏洞相关的知识、工具和方法。

(接上文)

TOCTOU攻击

类型说明

有时,即使在嵌套指针上调用SmmIsBufferOutsideSmmValid()也不足以保证SMI处理程序的安全。其原因是,SMM在设计时没有考虑到并发性,因此,它受到一些固有的竞态条件漏洞的影响,最突出的是针对通信缓冲区的TOCTOU攻击。因为通信缓冲区本身驻留在SMRAM之外,因此,它的内容可以在SMI处理程序执行时发生改变。这一事实具有严重的安全影响,因为它意味着从它那里两次获取的值不一定相同。

为了解决这个问题,多进程环境中的SMM采用了所谓的“SMI会合(SMI rendezvous)”技术。简而言之,一旦某个CPU进入SMM,一个专门的软件序言将向系统中所有其他处理器发送一个处理器间中断(IPI)。这个IPI将使它们也进入SMM,并在那里等待SMI的完成。只有这样,第一个处理器才能安全地调用处理函数来实际服务SMI。

该方案在防止其他处理器在使用通信缓冲区时干扰通信缓冲区方面非常有效,但当然,CPU并不是唯一有权访问内存总线的实体。正如任何操作系统入门课程所教导的那样,现在许多硬件设备都能够作为DMA代理运行,这意味着它们可以直接读/写内存而根本不需要通过CPU。从性能上讲,这些都是好消息,但就固件的安全性而言,却是一个可怕的坏消息。

1.jpg

DMA感知硬件可以在SMI执行时修改通信缓冲区的内容

为了了解DMA操作如何帮助成为漏洞利用的帮凶的,让我们来看看以下摘自实际SMI处理程序中的代码片段:

1.jpg

易受TOCTOU攻击的SMI处理程序

可以看到,这个处理程序至少在3个不同的位置引用了一个我们命名为field_18的嵌套指针:

首先,从通信缓冲区中检索其值,并将其保存到SMRAM中的局部变量中。

然后,对局部变量调用SmmIsBufferOutsideSmmValid函数,以确保该变量不与SMRAM重叠。

如果被认为是安全的,则从通信缓冲区中重新读取嵌套指针,然后将其作为目标参数传递给CopyMem函数。

正如前面提到的,没有什么能保证从通信缓冲区中连续读取的内容一定会产生相同的值。这意味着,攻击者可以通过让指针引用SMRAM外部的、完全安全的位置来处理该SMI:

1.jpg

处理SMI时通信缓冲区的初始布局

但是,就在SMI验证嵌套指针之后、再次获取它之前,存在一个小的机会窗口,DMA攻击可以修改其值,使其指向其他地方。由于攻击者知道,这个指针很快就会传递给CopyMem()函数,因此,可以设法让指针指向要破坏的SMRAM中的地址。

1.jpg

恶意DMA设备可以修改CommBuffer中的指针,使其指向其他地方,当然,也可以指向SMRAM内存

缓解措施

如果固件配置正确的话,SMRAM应该可以防止被DMA设备所篡改。为了确保您的机器上的配置是正确的,请通过CHIPSEC运行smm_dma模块。

1.jpg

检查系统是否为SMRAM提供了针对DMA攻击的保护

因此,可以通过将数据从通信缓冲区复制到位于SMRAM中的局部变量来缓解TOCTOU漏洞。

1.jpg

将数据从通信缓冲区复制到SMRAM中的局部变量中

一旦以这种方式将所需的全部数据都复制到SMRAM中,DMA攻击就无法影响SMI处理程序的执行流了:

1.jpg

如果配置正确,SMRAM就能免受DMA设备的篡改

检测方法

为了检测SMI处理程序中的TOCTOU漏洞,首先需要重建通信缓冲区的内部布局,然后计算每个字段被提取的次数。如果同一个字段被同一个执行流程取用了两次或更多,那么相应的处理程序就有可能容易受到这种攻击的影响。这些问题的严重性在很大程度上取决于各个字段的类型,其中指针字段是最严重的。同样,正确地重建通信缓冲区的结构对评估潜在的风险也是有很大帮助的。

仅能感知CSEG的处理程序

类型说明

正如本系列文章之前提到的,SMRAM内存的事实标准位置是“顶部内存段”,通常缩写为TSEG。然而,在许多机器上,由于兼容性的原因,通常会有一个单独的SMRAM区域被称为CSEG(兼容段),并且是与TSEG并存的。与TSEG不同,它在物理内存中的位置可以由BIOS以编程方式确定,而CSEG区域的位置则被固定在0xA0000-0xBFFFF的地址范围。一些传统的SMI处理程序在设计时只考虑了CSEG,这一事实可能会被攻击者所滥用。下面就是这样的处理程序的例子。

1.jpg

具有一些CSEG特定保护的SMI处理程序

与我们到目前为止审查的处理程序不同,这个SMI处理程序并不是通过通信缓冲区来获得其参数的。相反,它使用EFI_SMM_CPU_PROTOCOL从SMM的状态保存区读取相应的寄存器,该状态是由CPU在进入SMM时自动创建的。因此,在这个例子中,潜在的攻击面并非通信缓冲区,而是CPU的通用寄存器,在处理SMI之前,其值几乎可以随意设置。

处理程序的操作过程如下所示:

    首先,它从状态保存区读取ES和EBX寄存器的值。

    然后,利用上面的值计算线性地址,相应的公式为:16 * ES + (EBX & 0xFFFF)。

    最后,它检查计算出来的地址是否位于CSEG的范围内。如果该地址被认为是安全的,则将其作为参数传递给0x3020处的函数。

请注意,该处理程序基本上重新实现了常见的实用函数,如SmmIsBufferOutsideSmmValid(),只是它的实现方式很差,完全忽略了CSEG之外的SMRAM段。理论上讲,攻击者可以设置ES和BX寄存器,使计算出的线性地址指向一些其他的SMRAM区域,如TSEG,从而绕过处理程序所施加的安全检查。

然而,在实践中,这种漏洞很可能无法被实际利用。这是因为,我们能达到的最大线性地址为16*0xFFFF+0xFFFF==0x10FFF,而经验表明,TSEG通常位于更高的地址。尽管如此,了解这样的处理程序及其带来的危害还是有好处的。

缓解措施

缓解这些漏洞的措施,完全取决于SMI处理程序的开发人员。

检测方法

确定这些漏洞的一个不错的策略是寻找SMI处理程序,这些处理程序通常会利用“魔术数字”来表示CSEG的一些独特特征,其中包括直接值,如0xA0000(CSEG的物理基址)、0x1FFFF(其大小)和0xBFFFF(最后一个可寻址字节)。根据我们的经验,使用两个或更多这些值的函数可能具有某些特定于CSEG的行为,必须仔细检查以评估其潜在风险。

基于SetVariable()函数的信息泄露

类型说明

到目前为止,所有描述的漏洞类别都集中在劫持SMM执行流程和破坏SMM内存方面。然而,另一个非常重要的漏洞类别是围绕着泄露SMRAM的内容展开的。众所周知,SMRAM不能从SMM的外部读取,这就是为什么它有时被固件用来存储必须对外界保密的秘密。除此之外,泄露SMRAM的内容还可以帮助利用其他需要准确了解内存布局的漏洞。

当SMM代码试图更新NVRAM变量的内容时,就会容易发生SMRAM泄露。在UEFI中,更新NVRAM变量的操作并不是一个原子操作,而是由以下步骤组成的复合操作:

    分配一个堆栈缓冲区,用来存放与该变量相关的数据。

    使用GetVariable()服务,将变量的内容读入堆栈缓冲区。

    对堆栈缓冲区进行所有必要的修改。

    使用SetVariable()服务将修改后的堆栈缓冲区写回NVRAM。

1.jpg

用于更新UEFI变量的UEFI代码

当调用GetVariable()时,注意第4个参数是作为输入输出参数使用的。在进入该函数时,这个参数表示调用方要读取的字节数,而在返回时,它被设置为实际从NVRAM读取的字节数。如果变量的实际大小与预期的一致,两个值应该是一样的。

当开发者隐含地假设一个变量的大小是不可改变的,问题就出现了。由于这种假设,他们完全忽略了GetVariable()读取的字节数,而只是在写入更新的内容时将一个硬编码的大小传递给SetVariable()函数。

 1.jpg

上面的代码隐含地假设CpuSetup的大小总是0x101A,所以,它并没有检查GetVariable()实际读取的字节数。

由于一些NVRAM变量(至少是那些具有EFI_VARIABLE_RUNTIME_ACCESS属性的变量)的内容可以从操作系统中进行修改,它们可以被滥用来触发SMM中的信息泄露漏洞,同时也可以同时作为渗出渠道。让我们看看在实践中是如何做到这一点的。

首先,攻击者会使用操作系统提供的API函数,如SetFirmwareEnvironmentVariable()来截断该变量,从而使其比预期的短。然后,它将继续触发易受攻击的SMI处理程序。SMI处理程序将:

    分配基于堆栈的缓冲区。像其他基于堆栈的内存分配一样,这个缓冲区默认是未初始化的,这意味着它保存了以前发生在SMM中的函数调用的剩余部分。

 1.jpg

NVRAM变量和堆栈缓冲区的并列示意图(第1阶段)

调用GetVariable()服务,将变量的内容读入堆栈缓冲区。通常情况下,变量的大小等于堆栈缓冲区的大小,但由于攻击者刚刚截断了NVRAM中的变量,缓冲区肯定会更长。这又意味着,即使在GetVariable()返回后,它还会继续保存一些未初始化的字节。

 1.jpg

NVRAM变量和堆栈缓冲区的并列示意图(第2阶段)

    修改内存中的堆栈缓冲区。

 1.jpg

NVRAM变量和堆栈缓冲区的并列示意图(第3阶段)

调用SetVariable()服务,将修改后的堆栈缓冲区写回NVRAM中。因为这个调用是使用堆栈缓冲区的硬编码、恒定大小来完成的,所以它也会将其未初始化的部分写入NVRAM中。

 1.jpg

NVRAM变量和堆栈缓冲区的并列示意图(第4阶段)

为了完成这个过程,攻击者现在可以使用API函数,如GetFirmwareEnvironmentVariable()来完全泄露变量的内容,包括来自未初始化部分的字节。

缓解措施

这个故事的寓意是,不能盲目相信NVRAM变量,在分析处理程序的攻击面时应考虑到这一点。如果可以的话,最好使用相应的编译器标志,如InitAll,以确保堆栈缓冲区将被零初始化。更有技巧的是,当更新NVRAM变量的内容时,代码必须始终考虑到变量的实际大小,而不是依赖静态的、预先计算的值。

然而,缓解这些问题的另一个可能方向是限制对NVRAM变量的访问。这可以通过完全删除EFI_VARIABLE_RUNTIME_ACCESS属性或使用EDKII_VARIABLE_LOCK_PROTOCOL等协议来实现,使变量成为只读的。

检测方法

我们有理由认为,NVRAM变量的更新操作会在一个函数的执行过程中发生。这意味着我们通常可以忽略一个函数读取变量而另一个函数写入变量的场景。要找到这些函数,可以在用efiXplorer分析完输入文件后,导航到“services”选项卡,搜索SetVariable()后面紧跟着GetVariable()的调用对。

1.jpg

搜索由GetVariable()和SetVariable()组成的调用对

对于每一对这样的调用,请检查是否满足下列条件:

    两个调用都来自同一个函数;

    两个调用都对同一个NVRAM变量进行操作;

    传递给SetVariable()的大小参数是一个立即值。

1.jpg

检测SMRAM信息泄漏的简单启发式方法

识别库函数

这篇文章大量地引用了诸如FreePool()和SmmIsBufferOutsideSmmValid()等库函数,并天真地假设我们可以不费吹灰之力地找到它们。问题是这些函数是静态链接到二进制文件中的,通常SMM映像在发送给终端用户之前会去除所有的调试符号。因此,在IDA数据库中定位它们是相当具有挑战性的。

在我们的工作过程中,我们研究了多种方法来解决这个问题,包括使用Diaphora进行自动比对,以及使用一些不太知名的插件进行实验,如rizzo和fingermatch。最终,我们决定坚持KISS原则,考虑到目标函数的一些独特特征,我们决定使用简单明了的启发式方法进行匹配。下面是一些用于匹配前面提到的函数的经验法则。注意,我们假设二进制文件已经被efiXplorer分析过了,这可以让事情变得更轻松。

FreePool函数

识别FreePool()是非常简单的。只需要扫描IDA数据库满足下列条件的函数即可:

    接收一个整型参数;

    有条件地调用gBs->FreePool()或gSmst->FreePool()中的一个(但绝不会同时调用);

    将其输入参数转发给这两个服务。

1.jpg

准确定位FreePool()的简单启发式方法

SmmIsBufferOutsideSmmValid函数

对SmmIsBufferOutsideSmmValid()函数来说,识别起来就有点棘手了。为了成功地完成这个任务,我们需要掌握一些名为EFI_SMM_ACCESS2_PROTOCOL的UEFI协议的背景信息。这个协议被用来管理和查询平台上SMRAM的可见性。因此,它提供了相应的方法来打开、关闭和锁定SMRAM。

 1.jpg

EFI_SMM_ACCESS2_PROTOCOL的接口定义

除了这些,这个协议还导出了一个叫做GetCapabilities()的方法,客户端可以用它来弄清SMRAM在物理内存中的确切位置。

1.jpg

GetCapabilities()函数的说明文档

返回时,该函数会填充一个EFI_SMRAM_DESCRIPTOR结构体数组,告诉调用方SMRAM的哪些区域是可用的,它们的大小和状态是什么,等等。

1.jpg

使用EFI_SMM_ACCESS2_PROTOCOL查询SMRAM范围的示例程序的输出

在EDK2中,通常的做法是将这些EFI_SMRAM_DESCRIPTORS存储为全局变量,以便其他函数在未来可以轻松地访问它们。正如你可能猜到的,这些函数之一不是别的,而是SmmIsBufferOutsideSmmValid(),它用以遍历描述符列表,以确定调用方提供的缓冲区是否安全。

1.jpg

SmmIsBufferOutsideSmmValid的源代码

考虑到这一点,我们识别SmmIsBufferOutsideSmmValid()函数的策略将是反向查找:首先,我们要找到由EFI_SMM_ACCESS2_PROTOCOL初始化的全局SMRAM描述符,然后才根据使用它们的函数,推断哪个最可能是SmmIsBufferOutsideSmmValid()函数。

从技术上讲,只要按照下面的简单步骤就可以做到这一点:

进入efiXplorer的“protocols”选项卡标签,双击EFI_SMM_ACCESS2_PROTOCOL。这样,IDA将跳到使用这个GUID的位置(通常是对LocateProtocol的调用)。

1.jpg 

在IDA中搜索EFI_SMM_ACCESS2_PROTOCOL

点击协议的接口指针(EFISmmAccess2Protocol),点击“x”来搜索其交叉引用。

 1.jpg

列出EfiSmmAccess2Protocol的交叉引用

对于每个对GetCapabilities()的调用,检查第3个参数(SMRAM描述符)是否是一个全局变量。如果是的话,执行下列操作:

        点击“n”,根据一些命名惯例(例如,SmramDescriptor_XXX,其中XXX是一个序号)重新命名它,以便于将来参考。

        点击“y”,将其变量类型设置为EFI_SMRAM_DESCRIPTOR *。

1.jpg

重命名并设置SMRAM描述符的类型

    现在检查数据库中的每个函数是否满足以下标准:

        该函数必须接收两个整数参数。

        该函数必须返回一个布尔值。从反编译器的角度来看,布尔值只是普通的整数,所以为了做出这种区分,我们应该检查函数中的所有返回语句,并检查返回值是否是集合如果这三个条件都满足,那么当前的函数很有可能就是SmmIsBufferOutsideSmmValid()。

 1.jpg

使用简单的启发式方法在编译后的SMM二进制文件中查找SmmIsBufferOutsideSmmValid()函数

ami_smm_buffer_validation_protocol函数

目前,由于efiXplorer并没有提供现成的AMI_SMM_BUFFER_VALIDATION_PROTOCOL定义,所以,我们必须单独导入该协议的定义。

1.jpg

AMI_SMM_BUFFER_VALIDATION不支持开箱即用

为此,请执行以下步骤:

    从GitHub下载协议头文件并保存到本地。

    打开IDAPython提示符,运行下面的代码片段。

 1.jpg

用C语言定义导入协议头文件的宏

    这是必要的,因为头文件使用了多个宏和类型定义,必须在导入前以手动方式通过#defined进行定义:

    导航到File->Import C header file菜单来导入头文件。

 1.jpg

导入头文件

    再次运行efiXplorer(热键:CTRL+ALT+E),注意反编译输出是如何变化的。

1.jpg

现在已经能够识别AMI_SMM_BUFFER_VALIDATION了

小结

"见得越多,懂得越多。"

    —罗伯特-M-普尔西格,《禅与摩托车维修的艺术》。

固件级攻击似乎对安全社区构成了重大挑战。作为攻击者和防御者之间永恒的猫鼠游戏的一部分,威胁者开始将他们的注意力转移到固件上,许多人认为这是IT堆栈的软肋。近年来,人们对固件威胁的认识在不断提高,一些有希望的方法正在涌现,以对抗这些威胁。

硬件供应商,如英特尔,正在不断为每一个新的CPU系列增加更多的安全功能。这些功能的重要优势是,它们被植入硬件,能够从根本上消除某些漏洞类别(或至少使利用难度大大增加)。这种方法的缺点是,由于行业的分散性,不是每一个硬件支持的功能都能从软件方面得到广泛的采用。虽然某些功能,如安全启动、启动防护和BIOS防护非常流行,并且可以在大多数商品机中找到,但其他功能,如STM(SMI传输监控,一种旨在取消SMM特权的技术)仅仅被作为一个PoC存在。

微软等操作系统供应商正在与领先的OEM厂商紧密合作,以帮助弥合固件安全和操作系统安全之间的差距,鉴于他们利用虚拟化来保护每台Windows机器的长期愿景,这是一个强制性的举措。这些努力的成果是Secured-Core PC系列,它预装了安全功能和配置,旨在缩小固件攻击面,并在遭遇攻击时降低损害程度。

EDR供应商也做出了自己的贡献,并开始利用固件,并提供对SPI闪存和EFI系统分区的可见性。这种方法对于发现已知的固件植入物的IOC是很有帮助的,但不幸的是,在检测最初导致感染的潜在漏洞时,这种方法的作用相当有限。

即使面对这些进步,固件安全仍然面临诸多的问题、设计缺陷,当然还有许多尚未曝光的漏洞。安全社区成功实现这一目标的能力取决于三个基本支柱:知识、工具和勤奋。

在这篇文章中,我们专注于通过对陌生领域的介绍来促进知识的普及。在下一篇文章中,我们将介绍:

    我们是如何将挖掘漏洞的过程自动化,以至于把挖掘SMM漏洞过程变为运行Python脚本。

    我们发现的一些现实生活中的漏洞例子,这些漏洞涉及业内大多数知名的OEM。

至于勤奋,不幸的是,没有已知的方法可以培养出这样的人类品质。因此,我们每一个人都有责任尽我们最大的努力,确保在这个令人兴奋和富有挑战性的领域中全力以赴。

https://www.sentinelone.com/labs/zen-and-the-art-of-smm-bug-hunting-finding-mitigating-and-detecting-uefi-vulnerabilities/如若转载,请注明原文地址


文章来源: https://www.4hou.com/posts/PWOn
如有侵权请联系:admin#unsafe.sh