文章摘要:我们在办公室周围发现了一台笔记本电脑,该笔记本电脑已启用BIOS密码。最重要的是,笔记本电脑的安全启动已打开。我们想运行一个未使用Microsoft密钥签名的操作系统,因此我们确实需要一种进入设置实用程序的方法。UEFI入门安全术语· SEC-安全· PEI-EFI之前的初始化· DXE-驱动程序执行环境· PEI模块/ DXE驱动程序/ UEFI应用程序-包含固件代码的Microsoft PE格
我们在办公室周围发现了一台笔记本电脑,该笔记本电脑已启用BIOS密码。最重要的是,笔记本电脑的安全启动已打开。我们想运行一个未使用Microsoft密钥签名的操作系统,因此我们确实需要一种进入设置实用程序的方法。
UEFI入门
安全术语
· SEC-安全
· PEI-EFI之前的初始化
· DXE-驱动程序执行环境
· PEI模块/ DXE驱动程序/ UEFI应用程序-包含固件代码的Microsoft PE格式化文件
· 协议-GUID标识的结构实例
· PCH-平台控制器中枢
引导过程
即使是当今的现代64位CPU也开始以16位模式执行。在UEFI中,这称为SEC阶段。SEC阶段配置最少的一组CPU寄存器,然后将CPU切换为32位模式。此模式切换标志着SEC阶段的结束和PEI阶段的开始。在过去的日子里,SEC阶段还充当了系统信任的基础,但是如今,该角色已分配给PCH。在CPU开始执行任何代码之前,PCH会验证SEC和PEI阶段的固件。PEI阶段配置一些非CPU平台组件,并可选地验证DXE阶段代码的完整性。验证之后,PEI阶段将CPU切换到64位模式,并开始DXE阶段。DXE阶段包含操作系统启动之前运行的所有驱动程序和应用程序,包括操作系统的“ 的引导程序。其中一些驱动程序即使在您的操作系统启动后仍然存在。
固件文件系统
UEFI定义了自己的文件系统格式以用于Flash映像。一个flash映像将包含一个或多个固件卷,每个卷将包含一个或多个固件文件。文件是由GUID而不是名称标识的,尽管某些文件类型定义了一种可选地提供名称的方式。文件也可以是另一个卷的容器,该容器启用嵌套卷(即一个卷在另一个卷中)。嵌套卷通常用于支持卷压缩。
Flash 介绍
flash 转储
从本质上讲,PC只是大型的,函数强大的嵌入式设备,并且像大多数嵌入式设备一样,它们具有可以转储和重写的flash芯片。flash芯片通常是板上封装最大的芯片。我们迅速确定了笔记本电脑主板上的flash芯片,并立即使用一些夹子将其连接到SPIflash编程器。
解析 Flash
flash的内容被格式化为Intel映像,可以很容易地由UEFITool解析
英特尔Flash映像分为几个区域。但是,我们关心的唯一区域是BIOS区域。BIOS区域由几个固件文件系统卷和一个NVRAM变量存储区组成。我们本来可以找到负责向用户显示设置屏幕并修补密码检查的文件,但这在此笔记本电脑上是不可能的,因为启用了基于硬件的固件安全性。
在上图中,标记为红色的区域受Intel BootGuard保护。在CPU执行任何指令之前,先计算红色区域的哈希值,然后对照存储在flash某处的签名进行检查。RSA公钥的哈希,用于验证OEM在制造过程中将签名融合到PCH中。青色标记的区域受OEM的代码验证机制保护。OEM必须在BootGuard之上实施其自己的验证机制,因为BootGuard仅保护SEC和PEI阶段。DXE阶段也需要受到保护,以免被修改。在大多数实现中,对青色区域进行哈希处理,然后将该哈希与存储在红色区域中文件中的哈希进行匹配。由于红色区域中的所有内容均已受到BootGuard的保护,因此不需要其他签名。
用NVRAM处理
除了NVRAM变量内部,我们都看不到安全启动启用标志或BIOS密码。因此,要尝试的第一件事是完全清除所有NVRAM变量,并使开发板对所有内容使用其默认值。如果幸运的话,安全启动将被禁用,BIOS密码也将消失。但是,当我们在清除变量存储后尝试启动板子时,出现以下错误消息:
12B4: Bad Checksum of Sec Settings in NVRAM variable.
我们在转储中搜索了该字符串的一部分,然后将包含它的DXE驱动程序加载到IDA中。加载后,我们将遵循外部参照来找到引用它的代码。似乎它将尝试获取具有特定GUID的协议的句柄,如果无法获取,则将获取日志记录协议的句柄并将错误消息发送给它。我们跟踪了实现所需协议的驱动程序,并查看了它尝试访问的所有NVRAM变量。其中一个具有SecConfig名称,因此我们尝试清除除该变量之外的所有NVRAM变量,并希望达到最佳效果。
主板成功启动,安全启动已禁用!但是,BIOS密码仍处于启用状态。不能将其存储在SecConfig 变量,因为在查看其内容后,我们确定它只是一堆启用/禁用标志。变量中没有足够的数据来包含密码或密码的哈希值。根据这些发现,我们得出结论,BIOS密码必须存储在NVRAM以外的其他位置,并且甚至可以将flash密码存储在完全不同的芯片上。
固件修改
加载到安装程序
通过退出正在运行的UEFI应用程序(在本例中为UEFI Shell的副本),启动到flash驱动器后,可以返回到启动设备选择菜单。但是,完成此操作后,从启动菜单进入设置菜单的选项将消失,我们查看了设置菜单的NVRAM引导项,并看到它正在引导到GUID为2AD48FB3-2E28-42F2-88D5-A73EC922DCBA的UEFI应用程序。通过在UEFITool中搜索该GUID,我们在固件中找到了该应用程序的可执行文件。我们将其提取并放在flash驱动器上,尝试从shell中执行该应用程序,但是由于某种原因,该可执行文件被标记为DXE驱动程序,而不是UEFI应用程序。我们设法通过使用load 命令而不是直接运行它,但是即使以这种方式启动它,也会看到密码提示。
在内存中修改固件
我们想跟踪处理密码检查逻辑的驱动程序,以便可以在内存中对其进行修补。想要制作的补丁程序会使其认为未设置密码。这不是一个永久性的解决方法,但是如果它能够正常工作,它将使我们能够进入设置菜单。除此之外,我们在固件映像中浏览了所有DXE的名称。BpwManager之所以特别,是因为我们认为Bpw可能是BIOS密码的缩写。我们将其加载到IDA中,然后查看其所有字符串。知道当我们看到正确的driver时 12AE: Sys Security - BIOS password entry failure count exceeded在字符串列表中。驱动程序注册了一个协议,该协议由多个函数指针组成。我们查看了设置实用程序使用该协议的所有地方,并找到了一个我们认为它正在确定是否启用BIOS密码的地方。它调用了协议提供的函数之一,如果返回值设置了最低位,它将对字符串进行Enabled处理,否则将对字符串进行Disabled处理。
((void (__fastcall *)(BpwProtocol *, _QWORD, char *))bpwProtocol->GetBPWFlags)(bpwProtocol, 0i64, &bpwFlags); v13 = L"Enabled"; if ( !(bpwFlags & 1) ) v13 = L"Disabled";
假定这与显示BIOS密码菜单项的代码有关,并且它所调用的GetFlags()函数是某种函数。该函数的代码只是从内存地址中读取一个值,然后将其返回。我们使用UEFI Shell编辑内存中的标志值并将其设置为0,然后尝试再次加载设置实用程序。我们甚至可以转到安全性选项卡并取消/重置BIOS密码!
可悲的是,在重新启动笔记本电脑并尝试正常进入设置实用程序之后,它仍然提示我们输入旧密码。
GUID
模拟EEPROM
BpwManager驱动程序中的几乎每个函数都调用了GUID为 9FFA2362-7344-48AA-8208-4F7985A71B51的协议。我们使用UEFITool的GUID搜索函数来查找对该协议的所有引用。一个引起我兴趣的是一个名为EmuSecEepromDxe的Driver。将其加载到IDA中,并确认这是注册有问题协议的驱动程序。该协议由三个函数指针组成,其中一个指针除了返回错误值外什么也不做。基于其余两个函数的十六进制输出以及如何在BpwManager驱动程序中使用它们,我们构造了此结构来描述协议:
struct EmuSecEepromProtocol { public: EFI_STATUS (*eepromRead)(EmuSecEepromProtocol *this, __int64 eepromBankID, __int64 byteIndex, unsigned char *b); EFI_STATUS (*eepromWrite)(EmuSecEepromProtocol *this, __int64 eepromBankID, __int64 byteIndex, unsigned char b); EFI_STATUS (*returnError)(); };
我们将仿真EEPROM分为几个部分,我们称之为存储区。有8个存储区,每个存储区包含0x80字节。每个eepromBankID都指向两个连续的存储体,第二个存储体用于大于0x80的字节索引。我们确定对于BpwManagerDXE 重要的信息存储在bank ID 0x57中。
我们编写了一个快速的UEFI应用程序,尝试从该bank ID中读取所有0x100字节,但我们进行的每次调用均eepromRead 返回前0x80字节的错误代码。这意味着我们无法从两个组中的第一个存储组中读取数据,跟踪了在IDA中引用该错误编号的位置。通读代码,我们发现bank ID 0x5C是所有bank 的访问权限数组。每次尝试从存储区读取或写入内容时,都会根据要访问的存储区号(而非ID号)检查存储区ID 0x5C中的字节。bank ID 0x57对应于bank 编号6和7,并确保有足够的bank 编号6已被设置为不允许读取或写入,而bank 编号7允许读取。这解释了为什么我们能够从后半部分而不是前半部分读取字节。
我们试图更改bank 编号6的允许字节,但这返回了另一个错误。我们发现权限字节中还有另一位锁定了进一步的权限更改。尝试修补导致错误返回代码的跳转指令,但这也不起作用。为了追踪它,我们遵循了所有读/写请求的路径,发现它们最终以CPU IO EFI协议告终,实际操作发生在CPU之外的某个地方。
Shenanigans Boot-Time
我猜想所有模拟的EEPROM操作实际上都是由嵌入式控制器处理的,但是我并没有花费太多时间来寻找实际处理它们的方法。对我而言,了解其原理并不是那么重要。板上几乎所有其他芯片都在BGA封装中,而我们不知道其引脚排列,因此,刷新任何存储在其中的芯片都是不切实际的。
我们知道在启动过程中的某个时刻,必须将权限设置为至少允许某些操作,因为要求键入密码的提示需要与之进行比较,并且设置实用程序必须能够更改密码。我们需要的提示是,如果启动到内置应用程序(例如诊断程序初始屏幕)然后退出,则启动菜单中仍会存在设置按钮。但是,如果启动到外部应用程序(例如flash驱动器上的UEFI Shell),则“ Enter Setup”按钮将消失,直到下一次重新启动。我们在转储中搜索了一个内置应用程序的名称,以尝试查看是否可以将其重定向到通常无法访问但内置的UEFI Shell。事实证明,它们完全是标准的NVRAM UEFI引导项,引导项的属性字段具有一个标志,表示该标志用于应用程序,并且变量包含内置应用程序的GUID,而不是要运行的文件的路径。
“问题”
我们读出了所有bank 的许可字节,发现它们允许每个bank 拥有所有许可。然后,确定了密码的哈希值位于EEPROM中的位置,并向其中写入0。如果将BpwManager密码的哈希值全部读取为0,它将认为未设置密码, 重新启动后,我们能够进入启动菜单,但是选择任何启动项都遇到此错误:
01240: Bad BPW data, stop boot.
该错误仅显示约3秒钟,然后系统立即关闭电源。与其直接在仿真的EEPROM中修补哈希,我们实际上应该做与之前绕过密码提示进入设置菜单并从那里进行更改的补丁相同的补丁。
JTAG
在这一点上,我们每个人唯一想保存主板的方法就是JTAG。即使主板没有JTAG连接器,大多数英特尔芯片组也支持通过USB的JTAG。他们称其为直接连接接口或DCI,有两种形式:DbC(USB D e b ug Class)和OOB。OOB在USB引脚上实现了完全不同的有线协议,并且需要一个特殊的适配器,只能通过与Intel签署NDA来获得。使用交叉的USB 3.0 AA电缆连接到要调试的电路板,它将作为USB设备枚举。要与该USB设备接口,可以使用Intel System Studio,无需NDA即可免费下载,它会提供一个普通的调试器界面。
找到接口
我们需要弄清楚如何启用DCI。在大多数主板上,可以访问的设置实用程序仅显示可用配置选项的一小部分。由于某些原因,其他选项通常仍会被编译,即使它们一直是被隐藏的。在UEFI中看到的几乎每个接口都是基于规范HII或人机界面基础结构的。HII接口由VFR 语言进行设计,并被编译为IFR。我们需要做的就是找到显示选项的DXE,然后从中提取IFR。拥有IFR之后,我们可以对其进行分解以使其易于阅读。幸运的是,有人已经完成了编写工具来完成所有这些工作的工作。我们用了 LongSoft的Universal-IFR-Extractor的分支。为了找到正确的DXE,我们只是在设置实用程序中搜索选项之一的名称,IFR提取器的输出是字节码的反汇编版本。
https://github.com/LongSoft/Universal-IFR-Extractor
有许多VarStore 定义如下的对象:
VarStoreEFI: VarStoreId: 0x5 [8D6355D7-9BD1-44FF-B02F-925BA85A0FAC], Attrubutes: 3, Size: 572, Name: PchSetup
每个VarStore变量对应一个NVRAM变量,由其名称和guid给出,该ID用于在选项中引用它。选项的定义如下:
One Of: DCI enable (HDCIEN), VarStoreInfo (VarOffset/VarName): 0x8, VarStore: 0x5, QuestionId: 0x2D8, Size: 1, Min: 0x0, Max 0x1, Step: 0x0 One Of Option: Disabled, Value (8 bit): 0x0 (default) One Of Option: Enabled, Value (8 bit): 0x1 End One Of {29 02}
DCI使选项存储在VarStore5中的字节偏移量8处,且长度为1个字节。将该字节设置为0表示禁用,将其设置为1表示启用。
这些是我们更改的所有选项:
· DCI启用(HDCIEN)->启用
· 调试界面->启用
· 调试接口锁定->禁用
· 启用/禁用IED(英特尔增强调试)->启用
在Flash中编辑NVRAM变量
此时,由于无法启动到除启动菜单以外的任何内容,因此编辑这些NVRAM变量的唯一选择是使用外部Flash编程器直接对其进行写入。从flash芯片中检索了一个新的转储,我们将只修改当前状态,而不是将其恢复为较早的状态,并有可能导致更多故障。使用UEFITool在flash中找到了变量的偏移量。每个变量都有一个header,因此需要跳过它才能获得变量的实际值。编辑完所有需要更改选项的变量之后,在UEFITool中重新加载转储,以验证我们没有意外损坏的数据并且变量的值实际上发生了变化。
之后,重新刷新它,连接交叉线,打开板上的电源。
这是我们第一次在基于Intel的计算机上使用JTAG,这非常令人兴奋,尤其是事实证明它是如此的容易。
解决“问题”
调试分析
我们进入启动菜单,然后进入调试器。不知道将代码分成哪一部分,更不用说我们感兴趣的代码或数据所在的位置。不幸的是,我们没有想到在“问题”发生并使电路板无法启动之前保存任何相关DXE模块的加载地址。调试器具有一个内置命令,该命令将列出所有已加载的DXE模块,但是它需要UEFI系统表的地址,并且自动扫描失败。
在大多数使用EDK-II构建的UEFI二进制文件中,执行驱动程序/应用程序的入口点函数时,系统表会存储在全局变量中。我们从RIP所在的地址中转储了几个字节,然后在UEFITool中搜索那些字节,以确定当前在其中执行代码的模块。将该模块加载到IDA中,并将数据库重新基于该模块的加载地址。通过在IDA数据库中搜索我们先前提取的字节并基于RIP的值计算偏移量来找出模块的加载地址。现在有了一个很好的对齐的IDA数据库,可以轻松地获得包含指向系统表的指针的全局变量的地址。从该变量中检索系统表的地址,并将其提供给调试器。现在,当列出DXE模块的命令时,它实际上给出了一个列表。输出类似于:
ModuleID Base Size Name 00001 0x00000000D5A32000 0x00018460 00002 0x00000000D5A4B000 0x00003B80 00003 0x00000000D5A4F000 0x00001200 00004 0x00000000D5A51000 0x00002F40 00005 0x00000000D5A54000 0x00000540 00006 0x00000000D5A55000 0x00001620 ...
在未加载调试信息的情况下,调试器不会给我们模块的名称甚至GUID。但是,可以使用模块的大小来缩小可能的基地址的范围。UEFITool提供了每个模块的大小,检查位于每个剩余基址的内存,并将其与从固件转储中提取的数据进行比较,以确定哪个基址对应于我们感兴趣的模块。
代码执行
我们考虑了一段时间执行任意代码的最佳方法。我们意识到,需要运行的所有代码都将导致状态更改,该更改将在重新启动后持续存在。这意味着我们不必将执行返回到固件,代码运行后,可以重新启动电路板。
我们回头看了BpwManager中的代码,并确定有两个字节的校验和尚未删除。我们认为将校验和清零将使电路板能够启动。要写入零,我们只需将RIP设置为eepromWrite函数的地址,然后将所有其他寄存器设置为提供正确的函数参数。我们在eepromWrite函数,以便执行不会返回到固件代码。返回固件代码可能会导致崩溃,因为通过修改寄存器,将电路板置于未定义状态,覆盖两个字节并重置板后,BIOS密码终于消失了!尝试通过设置实用程序设置我们自己的密码,以确保它实际上已经消失并且拥有完全控制权。[来源:嘶吼网]