Windows 中 UEFI 编程的搭便车指南

admin 2024年6月17日15:20:49评论5 views字数 13568阅读45分13秒阅读模式

Windows 中 UEFI 编程的搭便车指南

介绍

UEFI(统一可扩展固件接口)开发主要使用 EDK II(EFI 开发工具包 II)存储库完成。EFI 最初是英特尔内部的一个项目,旨在取代 BIOS。1998 年在 Tiano 项目下开始努力,它包括一个规范和一个实现它的固件。2004 年,英特尔开源了其 EFI 固件的基础代码。2005 年,计算机行业的主要参与者成立了 UEFI 论坛以接管规范的所有权,并于 2006 年发布了 UEFI 2.0 规范。开源代码随后演变成 EDK,最后演变成今天的 EDK II。一个名为 TianoCore 的开发人员社区在 GitHub 上维护源代码。

在 Windows 上进行 UEFI 开发曾经是一个复杂的过程,需要大量手动步骤。它实现了自定义构建工具链,并且不为通过 IDE 进行开发提供任何支持。虽然可以在网上找到各种指南,并且从那时起通过使用容器改进了构建过程,但它仍然远不及 IDE 的便利性。此外,关于如何安装/部署 UEFI 可执行文件的资源很少,关于如何在启用安全启动的情况下运行它们的资源更少。

linux高级usb安全开发与源码分析视频教程

Windows 中 UEFI 编程的搭便车指南

linux

Windows 中 UEFI 编程的搭便车指南

  • Windows 中 UEFI 编程的搭便车指南

  • Windows 中 UEFI 编程的搭便车指南

  • windows

  • Windows 中 UEFI 编程的搭便车指南

  • windows()

  • Windows 中 UEFI 编程的搭便车指南

  • USB()

  • Windows 中 UEFI 编程的搭便车指南

  • ()

  • Windows 中 UEFI 编程的搭便车指南

  • ios

  • Windows 中 UEFI 编程的搭便车指南

  • windbg

  • Windows 中 UEFI 编程的搭便车指南

  • ()

  • Windows 中 UEFI 编程的搭便车指南Windows 中 UEFI 编程的搭便车指南Windows 中 UEFI 编程的搭便车指南

  • Windows 中 UEFI 编程的搭便车指南

  • Windows 中 UEFI 编程的搭便车指南

  • Windows 中 UEFI 编程的搭便车指南

因此,本指南将尝试填补这些空白,并希望帮助那些寻求部署 UEFI 可执行文件的人,无论是供他们自己使用还是在生产环境中。

读者应对以下内容有基本的了解:

  • 什么是UEFI

  • C语言编程

  • Visual Studio IDE

安装和构建

鉴于上述开发挑战,Alex Ionescu(Windows Internals 的作者之一)创建了 VisualUefi,将 EDK II 的源代码封装到 Visual Studio 解决方案中。构建是在 IDE 中完成的,完全绕过了 EDK II 的自定义构建工具链。开发自己的 UEFI 可执行文件是通过第二种解决方案完成的。

按照 VisualUefi 存储库中的说明进行操作。

运行

UEFI 可执行文件不能直接在操作系统中运行;相反,它们只能在操作系统启动之前的 UEFI 环境中运行。因此,VisualUEFI 使用 QEMU 和 EDK II 的 OVMF(开放式虚拟机固件)模块来模拟固件执行环境。VisualUEFI 集成了 QEMU 和 OVMF,使得 UEFI 可执行文件几乎可以像常规命令行可执行文件一样运行。

从 EFI shell 运行

本部分介绍与 VisualUefi 中的步骤完全相同的步骤,只是添加了屏幕截图。

在解决方案中,按 启动 QEMU。它直接启动到 EFI shell,然后可用于执行示例 UEFI 可执行文件:SamplesCtrl+F5

Windows 中 UEFI 编程的搭便车指南

使用也有效,但这会启动 Visual Studio 调试器,该调试器在这里毫无用处,因为调试器既不能附加到 QEMU 也不能附加到 UEFI 可执行文件。F5

解决方案的生成输出目录作为虚拟卷装载在标签下。列出其内容显示的文件和文件夹与在 Windows 中看到的文件和文件夹相同:VisualUefisamplesx64Releasefs1:

Windows 中 UEFI 编程的搭便车指南

一、负载:UefiDriver.efi

Windows 中 UEFI 编程的搭便车指南

使用该命令检查驱动程序是否已加载。该标志一次显示一个屏幕的输出。加载前后的输出比较:devtree -b-b

Windows 中 UEFI 编程的搭便车指南

该命令也可以同样使用:drivers

Windows 中 UEFI 编程的搭便车指南

然后运行应用程序:

Windows 中 UEFI 编程的搭便车指南

从新的启动选项运行 — 安装应用程序

启动 QEMU 时,按住直到它进入固件菜单。或者,在 EFI shell 中输入命令以退出固件菜单。Escexit

Windows 中 UEFI 编程的搭便车指南

选择。Boot Maintenance Manager

Windows 中 UEFI 编程的搭便车指南

选择。Boot Options

Windows 中 UEFI 编程的搭便车指南

选择。Add Boot Option

Windows 中 UEFI 编程的搭便车指南

选择第一个选项。

Windows 中 UEFI 编程的搭便车指南

选择。UefiApplication.efi

Windows 中 UEFI 编程的搭便车指南

输入任何描述;这将是新启动选项的显示名称。该字段指定传递给 UEFI 可执行文件的参数。将其留空,因为示例应用程序不使用它。Optional Data

保存更改并返回固件菜单页面。选择选项:Boot Manager

Windows 中 UEFI 编程的搭便车指南

新添加的引导选项显示为最后一个条目。只需选择它即可运行。应用程序结束后,将立即返回固件菜单,因此输出将一闪而过。要使输出持久化,必须更改应用程序的代码。

一种方法是让应用程序等待用户击键后再返回。这可以通过 中的函数实现。但是,我们无法使用它,因为此库不包含在 EDK-II 解决方案中。在 VisualUEFI 中创建 Visual Studio 项目以生成新库超出了本指南的范围,因此现在只需将代码复制到应用程序中即可。WaitForKeyStrokeedk2MdeModulePkgLibraryCustomizedDisplayLibCustomizedDisplayLibInternal.c

EFI_STATUS
WaitForKeyStroke(
OUT EFI_INPUT_KEY* Key
)
{
EFI_STATUS Status;
UINTN Index;
while (TRUE) {
Status = gST->ConIn->ReadKeyStroke(gST->ConIn, Key);
if (!EFI_ERROR(Status)) {
break;
}
if (Status != EFI_NOT_READY) {
continue;
}
gBS->WaitForEvent(1, &gST->ConIn->WaitForKey, &Index);
}
return Status;
}

国际电话区号:

Print(L"Press any key to continue...n");
EFI_INPUT_KEY keyInput;
efiStatus = WaitForKeyStroke(&keyInput);
if (EFI_ERROR(efiStatus))
{
Print(L"Failed to get keystroke: %lxn", efiStatus);
goto Exit;
}

请注意,示例应用程序的代码存在 3 个问题:

  • 第 94 行:

efiStatus = ShellInitialize();

这仅在 EFI shell 中有效。由于我们现在首先运行我们的应用程序而不是 shell,因此会失败。ShellInitialize()

  • 第 104 行:

efiStatus = ShellOpenFileByName(L"fs1:\UefiApplication.efi",

它假定卷标始终可用,并且可执行文件始终保存在那里。但是,分配卷标的是 EFI shell,因此没有它就没有并且会失败。fs1:fs1:ShellOpenFileByName()

  • 第 134 行:

efiStatus = gBS->LocateProtocol(&gEfiSampleDriverProtocolGuid, NULL, &sampleProtocol);

它尝试访问示例驱动程序,但我们尚未安装驱动程序,因此这也将失败。

代码将始终处于失败状态,因此应在顶部附近插入代码以读取用户的击键,以确保它被执行。goto Exit;

为了避免总是进入引导管理器 UI,可以更改引导顺序以首先引导新选项而不是 EFI shell。为此,请转到:Boot Maintenance Manager > Boot Options > Change Boot Order

Windows 中 UEFI 编程的搭便车指南

在这种情况下,应用程序将退出 EFI shell,而不是固件菜单。这是因为在引导时,如果引导选项返回,控制将返回固件菜单;否则,将执行下一个引导选项,依此类推,直到一个返回或所有引导选项用尽(根据 UEFI 规范第 3.1.1 节)。鉴于上述 3 个问题,应用程序不会返回,因此固件会执行 UEFI shell,这是下一个选项。EFI_SUCCESSEFI_SUCCESSEFI_SUCCESS

请注意,上述引导选项处理的例外情况是当应用程序调用 .但是,这超出了本指南的范围。ExitBootServices()

从新的启动选项运行 — 安装驱动程序

在页面上,选择:Boot Maintenance ManagerDriver Options

Windows 中 UEFI 编程的搭便车指南

选择。Add Driver Option

Windows 中 UEFI 编程的搭便车指南

选择。Add Driver Option Using File

Windows 中 UEFI 编程的搭便车指南

选择第一个选项。

Windows 中 UEFI 编程的搭便车指南

选择。UefiDriver.efi

Windows 中 UEFI 编程的搭便车指南

输入任何描述;这将是新驱动程序选项的显示名称。该字段指定传递给 UEFI 可执行文件的参数。将其留空,因为示例驱动程序不使用它。Optional Data

该字段使驱动程序能够覆盖在执行固件启动管理器之前加载的任何驱动程序(根据 UEFI 规范第 3.1.3 节)。回想一下 and 命令的输出 - 除了示例驱动程序之外,所有条目都是固件内置的驱动程序,并在执行固件启动管理器之前加载。设备应仅由一个驱动程序管理,否则可能会出现兼容性问题。示例驱动程序不会替代任何现有驱动程序,因此启用该选项是不必要的,并且不会产生任何实际效果。保存并退出此页面。Load Option Reconnectdevtreedrivers

请注意,固件启动管理器是指负责在启动时自动执行启动和驱动程序选项的固件组件;不要将其与引导管理器 UI 混淆,后者供用户手动选择要运行的引导选项。另请注意,若要实现驱动程序替代,在处理最后一个驱动程序选项后,系统中的所有 UEFI 驱动程序都将断开连接并重新连接。

固件启动管理器在处理启动选项之前自动处理所有驱动程序选项,因此与启动选项不同,固件菜单不提供任何显式加载特定驱动程序选项的方法,也不需要 - 将加载所有已安装的驱动程序选项。

通过 EFI shell 手动安装

除了使用固件菜单外,还可以使用 EFI shell 中的命令来安装新的启动和驱动程序选项。有关详细信息,请参阅输出。bcfghelp bcfg

这在固件不提供任何添加新启动选项和/或驱动程序选项的功能的系统中很有用。这之所以有效,是因为)UEFI 规范第 3 节规定了实现这些选项的确切机制(NVRAM 变量,也称为 UEFI 变量),即所有符合 UEFI 的固件都必须以所述方式实现和处理这些选项。 在相同的机制下运行,因此即使固件表面上不支持此功能,它也能够修改选项。bcfg

事实上,可以直接修改 NVRAM 变量来安装/修改启动和驱动程序选项。但是,这超出了本指南的范围。

从现有引导选项运行

人们可能已经注意到,在引导管理器页面中,示例应用程序选项的描述显示了可执行文件的完整路径;但是,EFI shell 选项中缺少可执行文件的名称:Device Path

Windows 中 UEFI 编程的搭便车指南

列出该选项指向的分区的内容(EFI shell 为其分配卷标)显示唯一的 UEFI 可执行文件是 ,这听起来肯定不像 EFI shell:Device Pathfs0:EnrollDefaultKeys.efi

Windows 中 UEFI 编程的搭便车指南

实际情况是,固件启动管理器将枚举所有可移动媒体和固定媒体设备,为每个设备创建一个启动选项(前提是其文件系统为 FAT,详见 UEFI 规范第 13.3 节)。启动此类条目时,固件会将以下内容附加到设备路径: 其中定义了 PE32+ 映像格式体系结构。可能的值为:EFIBOOTBOOT{machine type short-name}.EFImachine type short-name

Windows 中 UEFI 编程的搭便车指南

在 Microsoft 可移植可执行文件和通用对象文件格式规范修订版 6.0 中定义的 COFF 文件头字段中指定。PE Executable Machine Typemachine

为了支持多个 CPU 架构,设备可以在子目录中存储多个可执行文件,每个可执行文件都是为不同的架构构建的。有关详细信息,请参阅 UEFI 规范第 3.1.2 和 3.5 节。EFIBOOT

因此,列出所有内部将显示一个内部。这是 EFI shell:.efifs0:bootx64.efiFS0:efiboot

Windows 中 UEFI 编程的搭便车指南

因此,可以对示例应用程序采用相同的方法。在 下创建子目录,将示例应用程序复制到其中,并将其重命名为 。要运行它,只需在启动管理器 UI 中选择现有选项即可。或者,对引导选项重新排序,以便 QEMU 首先引导它。EFIBOOTVisualUefisamplesx64ReleaseBOOTx64.efiUEFI Misc Device

此方法也是 UEFI 支持从可移动媒体启动的方式。例如,在创建 Windows 安装闪存驱动器时,安装程序可执行文件保存在 .要安装,请进入固件菜单以选择从闪存驱动器启动的选项。或者,如果闪存驱动器已配置为第一个引导选项,则系统将自动引导至安装程序。EFIBOOTBOOTx64.efi

EFI 外壳

shell 本质上是命令提示符/bash shell 的 UEFI 固件版本。它支持许多与 bash 相同的命令 — 使用该命令列出所有支持的命令。它使用户能够访问和/或修改系统上的文件,而无需启动到操作系统。UEFI 固件本身仅支持 FAT 文件系统,因此这些是 shell 可以访问的唯一文件系统。对其他文件系统的支持取决于是否安装和加载了相关的 UEFI 驱动程序。help

startup.nsh
在启动 shell 时,会出现以下提示:

Press ESC in 5 seconds to skip startup.nsh, any other key to continue.

shell 在每次运行时执行。通过将 shell 配置为第一个引导选项(QEMU 默认情况下已经如此),这样就可以在系统上自动运行脚本化操作,而无需引导到操作系统中。startup.nsh

默认情况下,脚本文件不存在,但可以在 EFI 系统分区 (ESP) 的根目录中创建自己的脚本文件。若要试用,请在 shell 中输入以下命令:

echo "@echo test startup script" > fs0:startup.nsh

这将在 中创建脚本文件,该文件是此处 ESP 的卷标。fs0:

使用命令退出 shell,然后再次运行:exit

Windows 中 UEFI 编程的搭便车指南

EFI 系统分区 (ESP)

这是由 UEFI 规范定义的特殊分区,可由 UEFI 固件访问。如果您想知道这是怎么回事,因为访问分区是一项基本操作,请知道这曾经只是操作系统的基本操作。BIOS 是 UEFI 取代的上一代固件,无法访问完整分区。它可以在磁盘上访问的唯一元素是 MBR(主引导记录)和 VBR(卷引导记录);这些也被 UEFI 取代。

ESP 通常是操作系统引导加载程序和/或引导管理器所在的位置。在 Windows 上,该分区无法直接访问,因为默认情况下未为其分配任何驱动器号,如以下所示:diskmgmt.msc

Windows 中 UEFI 编程的搭便车指南

要访问它,请打开 elevated 并使用命令为其分配驱动器号,其中是任何未使用的驱动器号。cmdmountvol X: /SX

要检查 Windows 启动管理器的位置,请执行以下操作:

X:>dir /b/s *.efi
X:EFIMicrosoftBootbootmgr.efi
X:EFIMicrosoftBootmemtest.efi
X:EFIMicrosoftBootbootmgfw.efi
X:EFIMicrosoftBootSecureBootRecovery.efi
X:EFIBootbootx64.efi

bootmgfw.efi是 Windows 启动管理器。另请注意该条目 - 这只是一个副本,用作回退应该丢失或损坏。EFIBootbootx64.efibootmgfw.efibootmgfw.efi

安全启动

到目前为止,所有 UEFI 可执行文件都已在禁用安全启动的情况下运行。安全启动是 UEFI 固件的一项安全功能,仅允许运行受信任的 UEFI 可执行文件。这可以保护系统免受恶意 UEFI 可执行文件(例如 bootkit)的侵害,并且应始终在生产环境中启用。信任被定义为可执行文件的签名存在于授权签名数据库中,而在禁止签名数据库中不存在。签名定义为以下项之一:

  • 可执行文件的 SHA-1 验证码哈希

  • SHA-256 可执行文件的验证码哈希

  • SHA-224 可执行文件的验证码哈希

  • SHA-384 可执行文件的验证码哈希

  • SHA-512 可执行文件的验证码哈希

  • 可执行文件的 SHA-256 验证码哈希的 RSA-2048 签名

  • 可执行文件的 SHA-1 验证码哈希的 RSA-2048 签名

  • 用于对可执行文件进行签名的 RSA-2048 密钥的模数(假定公钥指数为 0x10001)

  • 用于对可执行文件进行签名的密钥的 DER 编码的 X.509 证书

  • 其他更复杂的问题,例如涉及时间戳

有关如何计算验证码哈希的信息,请参阅 Windows Authenticode 可移植可执行签名格式的“计算 PE 映像哈希”部分。对于 X.509 证书,只要可执行文件的签名证书存在于 X.509 证书链的任何级别(根据 UEFI 规范第 8.2.6 节和第 32.6.3.3 节),就会找到匹配项。

因此,要使示例可执行文件在启用安全启动的情况下运行,需要将其中一个签名放入安全启动授权数据库中。使用证书是更灵活的选择,因为它允许运行任何使用相应私钥签名的可执行文件——这也允许重复修改和重建可执行文件,这在开发和测试期间很有用。但是,它不如使用哈希安全,因为存在私钥泄露的风险。

要安装签名,请进入固件菜单:

Windows 中 UEFI 编程的搭便车指南

选择。Device Manager

Windows 中 UEFI 编程的搭便车指南

选择。Secure Boot Configuration

Windows 中 UEFI 编程的搭便车指南

设置为 。此条目不可配置,仅用于显示当前状态。该选项本身已禁用(稍后解释),唯一可以更改的选项是 。将其更改为 和 这是唯一可用的其他选项:Current Secure Boot StateDisabledAttempt Secure BootSecure Boot ModeStandard ModeCustom Mode

Windows 中 UEFI 编程的搭便车指南

选择。Custom Secure Boot Options

Windows 中 UEFI 编程的搭便车指南

这五个选项中的每一个都对应一种类型的安全启动密钥。 是授权签名的数据库,也是禁止签名的数据库。请注意,虽然 UEFI 规范使用术语“密钥”,但存储在每种类型的“密钥”中的条目实际上是上述签名类型之一;我们将遵循规范,并使用“key”来指代一般的签名。如果我们查看每个条目:DBDBX

Windows 中 UEFI 编程的搭便车指南

我们看到了注册新密钥或删除现有密钥的选项。没有列出现有键的选项,但可以使用删除选项来执行此操作。这样做会显示所有键都是空的。这就是无法启用安全启动的原因 — 必须存在 PK(平台密钥)才能启用安全启动。我们可以自己创建一个,但回想一下我们之前看到过一个。EnrollDefaultKeys.efi

安装默认安全启动密钥

在 EFI shell 中运行以安装默认的安全启动密钥。这将覆盖所有现有密钥:EnrollDefaultKeys.efi

Windows 中 UEFI 编程的搭便车指南

当 PK 不存在时,安全启动处于设置模式。安装 PK 会自动将安全启动转换为用户模式(根据 UEFI 规范第 32.3 节,稍后会详细介绍)。

退出 shell 并再次查看:Secure Boot Configuration

Windows 中 UEFI 编程的搭便车指南

现在,安全启动已启用,选项已启用并设置。Attempt Secure Boot

再次切换到并列出密钥:Secure Boot ModeCustom

Windows 中 UEFI 编程的搭便车指南

  • PK:之前启用的选项现在被禁用。这是因为系统上只能有一个 PK。Enroll PK

  • KEK:有两个以 GUID 命名的条目。所有安全启动密钥都与指示密钥所有者的 GUID 相关联:SignatureOwner

  • d5c1df0b-1bac-4edf-ba48-08834009ca5a:此所有者未知

  • 77fa9abd-0359-4d32-bd60-28f4e78f784b:这是Microsoft(稍后解释)。

  • DB:有两个条目,都属于Microsoft(稍后解释)。

  • DBX:此处的键根据类型分组到列表中。目前只有 1 个列表,类型为 .请注意,所有类型的安全启动密钥都存储在类型化列表中;只是这里的固件决定只为 DBX 显示此信息。如果我们选择列表:SHA256

Windows 中 UEFI 编程的搭便车指南

我们看到 KEK 中的未知 GUID,哈希值只是一个空文件的哈希值。这很可能是作为示例提供的。在更新至日期的系统中,DBX 填充了已知易受攻击的 UEFI 可执行文件的条目。这些可执行文件的列表和用于更新 DBX 的二进制 blob 由此处的 UEFI 论坛维护。Microsoft 在 GitHub 存储库中维护此列表的镜像。E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855

  • DBT:这仍然是空的。

现在,如果我们尝试启动:

Windows 中 UEFI 编程的搭便车指南

这是意料之中的,因为 EFI shell 和我们的示例应用程序都没有签名,它们的签名也不存在于 DB 中。

创建 EFI 签名证书

若要对示例应用程序进行签名,我们首先会创建证书。我们将使用 Windows SDK 中提供的命令行工具。这些工具位于 中。子目录的名称将根据已安装的 SDK 版本而有所不同。%programfiles(x86)%Windows Kits10bin10.0.22621.0x64

首先,在命令提示符下使用以下命令创建私钥及其关联的证书:

MakeCert.exe -a sha256 -n "CN=SampleEfiSigner" -r -sv SampleEfiSigner.pvk SampleEfiSigner.cer

输出文件将采用 DER 编码的 X.509 格式,这是受支持的数据库签名类型之一。

选项说明:

  • -a签名的摘要算法(默认为 SHA1)

  • -n证书使用者 X509 名称

  • -r创建自签名证书

  • -sv主题的 PVK 文件,如果不存在,则创建

该命令会生成一个弹出提示,要求输入用于私钥文件的密码:

Windows 中 UEFI 编程的搭便车指南

设置任意密码,然后按 。OK

这将生成第二个提示,要求输入在上一步中设置的密码。这是针对自签名操作的:

Windows 中 UEFI 编程的搭便车指南

命令的输出应为:MakeCert

Succeeded

然后,将文件转换为,以便可以使用它来对 UEFI 可执行文件进行签名:.pvk.pfxsigntool

pvk2pfx.exe -pvk SampleEfiSigner.pvk -pi <password> -spc SampleEfiSigner.cer -pfx SampleEfiSigner.pfx

替换为私钥文件的密码。该命令不产生任何输出。<password>

选项说明:

  • -pvk输入 PVK 文件名

  • -piPVK 文件的密码

  • -spc输入 SPC 文件名

  • -pfx输出 PFX 文件名

对 UEFI 可执行文件进行签名

使用与早期工具位于同一目录中的工具:signtool

signtool.exe sign /fd sha256 /ac SampleEfiSigner.cer /f SampleEfiSigner.pfx /p <password> <efiToSign>

替换为私钥文件的密码。替换为目标的文件名。该命令应输出:<password><efiToSign>

Done Adding Additional Store
Successfully signed: <efiToSign>

选项说明:

  • /fd用于创建文件签名的文件摘要算法(默认为 SHA1)

  • /ac向签名块添加其他证书

  • /f签名证书的文件。如果文件不包含私钥,请使用“/csp”和“/kc”。

  • /pPFX 文件的密码

打开文件的属性窗口以验证它是否已签名。应存在一个新选项卡:Digital Signatures

Windows 中 UEFI 编程的搭便车指南

将证书安装到数据库中

搬进 ;我们稍后需要从固件菜单访问它。从固件菜单中,导航到并选择:SampleEfiSigner.cerVisualUefisamplesx64ReleaseDB OptionsEnroll Signature

Windows 中 UEFI 编程的搭便车指南

选择。Enroll Signature Using File

Windows 中 UEFI 编程的搭便车指南

选择第一个选项。

Windows 中 UEFI 编程的搭便车指南

选择。请注意,此处的固件仅支持 DER 编码的 X.509 证书和 UEFI 可执行文件本身。对于后者,它将计算可执行文件的 SHA-256 验证码哈希并将其保存为签名。SampleEfiSigner.cer

UI 将返回到页面。该字段对应于签名的 .输入一个随机值或将其留空以默认为全 0 的 GUID:Enroll SignatureSignature GUIDSignatureOwner

Windows 中 UEFI 编程的搭便车指南

签名现在显示在菜单下:Delete Signature

Windows 中 UEFI 编程的搭便车指南

如果选择了 UEFI 可执行文件本身而不是证书,则条目的描述将不是:SHA256_GUIDPKCS7_GUID

Windows 中 UEFI 编程的搭便车指南

现在尝试启动:

Windows 中 UEFI 编程的搭便车指南

安全启动安全性

除了使用固件 UI 注册签名外,还可以通过 UEFI 运行时服务函数以编程方式注册签名。这可以由 UEFI 可执行文件和 OS 可执行文件调用(在 Windows 中公开为 ),这引出了一个问题 - 这如何安全?启用安全启动后,仅允许运行受信任的 UEFI 可执行文件,因此也允许它们修改签名是合理的。但是,操作系统可以运行不受信任的代码,恶意软件就是最好的例子 - 它们可以修改数据库以将其 bootkit 列入白名单,或从 DBX 中删除易受攻击的可执行文件。Windows 确实仅限制特权用户(例如管理员)进行调用,但这仍然可以通过权限提升攻击来绕过。这就是PK和KEK的用武之地。SetVariable()SetFirmwareEnvironmentVariable()SetFirmwareEnvironmentVariable()

当安全启动处于设置模式(即没有 PK)时,用户可以通过固件 UI 修改任何安全启动密钥,也可以不受固件的任何限制。安全启动进入用户模式(即安装PK)后,用户可以通过固件UI继续修改安全启动密钥;但是,固件现在需要对要写入的数据进行签名。目标安全启动密钥确定需要哪个签名密钥:SetVariable()SetVariable()

  • DB、DBX:由PK或KEK对应的私钥签名

  • PK、KEK:PK对应的私钥签名

单一类型的密钥足以保护 DB 和 DBX,但对 PK 和 KEK 进行了区分,以划分安全启动密钥的不同所有权级别。PK 由计算机硬件的 OEM 拥有,每台计算机只有一个。对于惠普和戴尔:

Windows 中 UEFI 编程的搭便车指南

PK 的作用是控制 KEK 的安装。每台计算机可以有多个 KEK,每个 KEK 对应一个受信任的实体,该实体可以控制将签名安装到 DB 和 DBX 中。通常,这只是 OEM 和操作系统。对于惠普和戴尔:

Windows 中 UEFI 编程的搭便车指南

对于 Windows 系统,这将是 Microsoft 的 KEK:

  • Microsoft 公司 KEK CA 2011

  • Microsoft 公司 KEK 2K CA 2023

Windows 中 UEFI 编程的搭便车指南

CA 2023 旨在取代将于 2026 年到期的 CA 2011。

有关安全启动的更多详细信息,请参阅 UEFI 规范第 32 章和 Microsoft 有关安全启动密钥创建和管理的文档。

Microsoft 密钥作为默认值

对于网站证书,是否信任它们只是检查它们是否链接到来自 CA(证书颁发机构)的受信任根证书或中间证书的问题。这些通常预装在操作系统中,更新也由操作系统处理。

但是,如果想要分发与安全启动兼容的 UEFI 可执行文件,则没有 CA 可以获取签名证书,也没有 CA 可以对其可执行文件进行签名。

对于 Windows,其启动管理器(这是一个 UEFI 应用程序)能够在启用安全启动的情况下开箱即用,因为 Microsoft 与 OEM 合作,在制造过程中在所有零售 PC 和主板中安装 Microsoft 的 KEK 和 DB 密钥。不是每个人都有能力这样做,但值得庆幸的是,Microsoft 提供 UEFI 签名服务(类似于他们的 Windows 驱动程序签名服务)。签名后,UEFI 可执行文件将与 Windows 相同范围的硬件安全启动兼容。

这种安排意味着 Microsoft 是事实上的安全启动 CA,除了名称之外,这也是 Microsoft 的密钥被视为默认密钥的原因。除了前面提到的 KEK,Windows 系统的数据库中还安装了以下证书:

  • Microsoft Windows 生产 PCA 2011

  • Windows UEFI CA 2023

Windows 中 UEFI 编程的搭便车指南

Windows 启动管理器使用上述两个密钥之一进行签名。

  • Microsoft公司 UEFI CA 2011

  • Microsoft UEFI CA 2023

Windows 中 UEFI 编程的搭便车指南

所有第三方 UEFI 可执行文件都使用上述两个密钥之一进行签名。

回想一下,运行后在数据库中创建了两个条目 - 这些是 2011 年的证书。2023 年的证书旨在取代将于 2026 年到期的 2011 年证书。EnrollDefaultKeys.efi

除了直接签署 UEFI 可执行文件外,Microsoft 还可以选择:

  • 颁发链接到其 UEFI CA 证书的证书,并通过操作系统更新将其安装到 DB 中。

  • 使用他们的 KEK 对开发人员提交的数据库更新进行签名,以安装开发人员想要的任何签名。

但是,这些选项为不良行为者提供了更多途径,可以将引导工具包分发到所有 Windows 系统(在向 Microsoft 提交请求时冒充合法开发人员,或者窃取受信任开发人员的签名密钥)。Microsoft当然不想承担审查提交者的责任,并且考虑到启动模块在系统安全性和稳定性中的关键性质,确保它们符合Microsoft的利益:

  • 不是恶意的,也不提供任何绕过安全启动的方法(例如,执行未签名的代码)

  • 不会导致与 Microsoft 自己的启动模块的兼容性问题

这就解释了为什么Microsoft提供的唯一选项是将可执行文件提交给他们进行签名。

这种安排还意味着,如果在 Microsoft 签名的 UEFI 可执行文件中发现任何漏洞,将其列入黑名单的唯一方法是将其 Authenticode 哈希添加到 DBX — Microsoft 的 UEFI CA 证书不能添加到 DBX,因为这也会将所有其他不易受攻击的可执行文件列入黑名单。这就是 DBX 今天由于 Windows 启动管理器 (BlackLotus) 和 shim/GRUB2 (Boot Hole) 的漏洞而最终获得数百个条目的原因。

自定义密钥

为了获得更多的控制和安全性,可能希望删除不必要的默认密钥和/或安装自定义密钥。用户可能也不想,或者根本无法将其 UEFI 可执行文件提交给 Microsoft 进行签名:

Microsoft UEFI CA 仅对公开可用的产品进行签名,这些产品是所有 UEFI 安全启动支持的设备之间的互操作性所必需的。如果产品特定于特定 OEM 或组织,并且在外部不可用,则应使用私钥对其进行签名,并将证书添加到安全启动数据库。

所有这些都意味着在目标计算机上自定义安全启动密钥。但是,零售系统中默认的安全启动模式是用户模式(即安装了 PK),因此唯一的方法是亲自手动访问系统的固件。虽然对于有限的部署是可行的,但对于大规模部署(例如在企业中)来说是不切实际的——访问每个系统或将它们收集回来需要太多的时间和精力。仅当密钥更新使用相关密钥(即 PK 或 KEK)签名时,才能远程进行更改,但如前所述,出于安全考虑,拥有这些密钥的 OEM 和 Microsoft 都不会接受此类请求。

一种可能的替代方案是让 OEM 在交付系统之前自定义密钥或删除 PK;但是,这很可能会花费额外的费用,并且不会解决已经交付的系统。

最后,请注意,如果默认 PK 和/或 KEK 替换为自定义 PK 和/或 KEK,则 OEM/Microsoft 对 KEK 和/或 DB/DBX 的所有后续更新都将失败。因此,如果想要这些更新,则必须使用与自定义 PK/KEK 对应的私钥对其进行签名,然后才能部署到受影响的计算机。可以使用上述和 PowerShell 命令和 .有关如何执行此操作的详细信息超出了本指南的范围。signtool.exeFormat-SecureBootUEFISet-SecureBootUEFI

在实际系统上运行

用于在 QEMU 中运行 UEFI 可执行文件的所有方法也适用于实际的物理系统。唯一需要注意的区别是:

  1. 解决方案的输出目录不会作为卷装载到系统中。因此,要运行的可执行文件必须保存到固件可访问的卷中。这可以是 ESP、本地 FAT 卷或 FAT 可移动介质。

  2. 如果启用了 BitLocker,并配置为使用 TPM 作为密钥保护程序,则禁用安全启动将提示系统进入 BitLocker 恢复模式。根据配置为密封 BitLocker 密钥的 PCR 库,运行 UEFI 可执行文件(甚至是已签名的可执行文件)也可能触发 BitLocker 恢复模式。为什么会发生这种情况,这超出了本指南的范围。在更改固件设置和测试 UEFI 可执行文件之前,请确保 BitLocker 恢复密钥脱机可用并挂起 BitLocker。

  3. EFI 外壳的可用性取决于系统的品牌和型号。如果存在,则应可从固件菜单访问它。如果不存在,可以从以下官方 edk2 版本中获得:[edk2-stable201905、edk2-stable202002]。旧版本的 shell 作为文件夹中 edk2 存储库的一部分提交。该文件夹已于 2019 年 4 月 24 日删除,其内容已重新定位到发布页面。对于最新版本,可以自己构建或从第三方来源下载,例如 pbatard/UEFI-Shell。请注意,所有 edk2 版本都是未签名的,固件的内置 EFI shell 也可能是未签名的,因此必须禁用安全启动或将其签名安装到 DB 中才能运行它们。ShellBinPkg

  4. 固件 UI 和可用配置因系统的品牌和型号而异,因此更改设置(如禁用安全启动以及添加新的启动和驱动程序选项)的步骤会有所不同。

其他注意事项

  1. VisualUefi 存储库不再得到积极维护,并且已经严重过时——它同步到 的 edk2 提交是 2019 年的,而 openssl 是 2018 年的。必须手动将子模块更新为远程的最新提交,然后由于子模块的源代码树的更改而更新 Visual Studio 项目。

  2. VisualUefi 仅生成选定的 edk2 库,不包括所有 edk2 源代码。必须创建新的 Visual Studio 项目以包含其他库。

  3. 调试 UEFI 可执行文件可能具有挑战性,因为无法附加调试器以进行实时调试。日志记录到控制台可能会有所帮助,但这对于具有大量日志记录的大型程序来说是不切实际的。写入日志文件是另一种解决方案,但必须等待程序运行完成,然后才能检查日志文件。另一种解决方案是在虚拟机中运行 UEFI 可执行文件,并记录到连接到主机的管道。主机运行一个程序来读取管道的内容并将其打印到控制台,以便可以在运行时查看日志。这需要修改 VisualUefi 以构建新的 edk2 库,还需要修改 edk2 源代码。

原文始发于微信公众号(安全狗的自我修养):Windows 中 UEFI 编程的搭便车指南

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月17日15:20:49
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windows 中 UEFI 编程的搭便车指南https://cn-sec.com/archives/2855964.html

发表评论

匿名网友 填写信息