UEFI 是如何工作的?
Posted
技术标签:
【中文标题】UEFI 是如何工作的?【英文标题】:How does UEFI work? 【发布时间】:2015-11-20 07:10:37 【问题描述】:我在研究引导加载程序时正好遇到了 UEFI 这个词。我可以理解一些关于 UEFI 的事情。但是,带有 UEFI 的系统在什么模式下(Real、Protected、Long)启动?如果正常的引导加载程序无法与 UEFI 一起工作,那么在处理 UEFI 时引导加载程序的替代方案是什么?除了汇编之外,我还需要任何其他编程来创建它吗?
【问题讨论】:
【参考方案1】:在下文中,一些句子是我见过的最佳资源的复制、合并和措辞改进,然后我通过使用我自己的硬件案例研究来改进和纠正它们的未知数/错误。
引导保护
英特尔 Boot Guard 是英特尔在第四代英特尔酷睿 (Haswell) 中引入的一项技术,用于验证引导过程。这是通过在制造过程中将 Bios 签名的公钥刷入现场可编程熔断器 (FPF) 来实现的,FPF 是英特尔 ME 内的一次性可编程存储器(在 PCH 中);通过这种方式,它拥有 BIOS 的公钥,并且可以在每次后续引导期间验证正确的签名。一旦制造商启用,英特尔 Boot Guard 将无法再禁用。
据英特尔称,Boot Guard 有两种不同的模式。典型的 PC OEM 将其配置为 “验证启动” 模式。 PC 制造商将其公钥融合到硬件本身中。如果 UEFI 固件未经 OEM 签名(即由 OEM 创建),计算机将停止并拒绝启动。这就是您无法修改 UEFI 固件的原因。还有第二个选项:“Measured Boot” 模式,其中硬件使用英特尔 TXT 来安全存储有关引导过程的信息(在受信任的平台模块 (TPM) 中)或英特尔平台信任技术 (PTT) ) 在 SMX 的帮助下。然后,操作系统可以检查这些信息,如果有问题,就会向用户显示错误。
安全启动
启用并完全配置后,安全启动可帮助计算机抵御恶意软件的攻击和感染。 Secure Boot 通过验证其数字签名来检测对引导加载程序、关键操作系统文件和未经授权的选项 ROM 的篡改。检测在攻击或感染系统之前被阻止运行。 UEFI 安全启动假定 OEM 平台固件是可信计算库 (TCB)(即它已使用 BootGuard 技术进行初始化并隐式信任它)。
验证启动过程
预 UEFI 阶段
当 OEM 收到 PCH 时,ME 仍处于“制造模式”并运行固件的特殊部分,该部分将从其部分复制“OEM 公钥哈希”和“引导保护配置文件配置”策略值将闪存 ROM 插入现场可编程熔断器 (FPF),使其永久不变。然后它会设置一个保险丝以指示它已退出制造模式,因此这部分固件将不会再次运行。可以使用英特尔闪存映像工具 (FITC) 在闪存映像中调整这些值,但除非您有办法强制 ME 进入制造模式,否则闪存映像中的值将被忽略。
Bootguard 配置文件如下:
Protect BIOS Environment Enabled:如果设置,这可能意味着 ACM 会将 IBB 段复制到 CPU 缓存中,以便它在缓存作为 RAM (CAR) 模式下运行,并禁用所有 DMA 以防止设备可以修改。
一旦电源可用,ME CPU 就会从其片上引导 ROM 启动,检查一些带和保险丝以确定其配置,然后通常从 ME 区域复制闪存分区表 (FPT) SPI 闪存(通常位于 BIOS 区域下方)到其片上 SRAM。它通过使用 SPI flash 最低地址的 flash 描述符来定位 ME 区域。
引导 ROM 使用 FPT 定位 FTPR 分区,并将其从 SPI 闪存复制到片上 SRAM。然后,它检查存储在分区清单中的密钥的 SHA-1 哈希值是否与其片内 ROM 中的哈希值匹配,并验证分区清单其余部分上的 RSA 签名。分区表包含分区中每个模块的哈希值,允许在将模块复制到片上 SRAM 执行后对其进行验证。
当 ME 启动 x86 CPU 时,可能在 bup.met
中。 BIST 和 BSP MP 初始化算法发生。 CS:FFF0(其中 CS 为 0xF000,段描述符缓存包含基址 0xFFFF0000)处的传统复位向量不再是 x86 CPU 在复位时执行的第一条指令。相反,片上微码在 0xFFFFFFC0 处获取 FIT 指针(使用 0xF000:FFC0 和虚幻模式段描述符破解 - 因为this 是寄存器的初始状态),它指向 SPI 闪存 BIOS 中某处的 FIT 表地区。
This image 显示 16 字节的 FIT 条目,它们的模式距离末尾 3 个字节。
#pragma pack (1)
typedef struct
UINT64 Address;
UINT8 Size[3];
UINT8 Rsvd;
UINT16 Version;
UINT8 Type:7;
UINT8 C_V:1;
UINT8 Checksum;
FIRMWARE_INTERFACE_TABLE_ENTRY;
在我的系统上,在 FFFFFFC0 处有一个指向 FFD90100 的 FIT 指针
FIT 表指向微码更新、ACM、BootGuard 引导策略清单(包含 IBBS)和 BootGuard 密钥清单等。
我的 FIT 不包含任何 0x7 entries。记录类型 7 仅由旧版英特尔® TXT FIT 引导使用,如果不使用后者,则不需要。
BootGuard 引导策略 (IBBM) 包含:
Intel BootGuard Boot Policy Manifest found at base FD3C00h
Tag: ACBP Version: 10h HeaderVersion: 01h
PMBPMVersion: 10h PBSVN: 00h ACMSVN: 02h NEMDataStack: 0010h
Initial Boot Block Element found at base FD3C28h
Tag: IBBS Version: 10h Unknown: 0Fh
Flags: 00000000h IbbMchBar: FED10000h VtdBar: 00000000h
PmrlBase: FED90000h PmrlLimit: 00000000h EntryPoint: 00100000h
Post IBB Hash:
0000000000000000000000000000000000000000000000000000000000000000
IBB Digest:
B9CCC06B77AEACC51768981D07CBE9E43D34DB6795752C4B998312241B26F874
IBB Segments:
Flags: 0000h Address: FFE10000h Size: 001C3C00h
Flags: 0000h Address: FFFD4C00h Size: 00000080h
Flags: 0000h Address: FFFD5C80h Size: 0000A380h
Flags: 0000h Address: FFFE8000h Size: 00018000h
Boot Policy Signature Element found at base FD3CDDh
Tag: PMSG Version: 10h
Boot Policy RSA Public Key (Exponent: 10001h):
....
Boot Policy RSA Public Key Hash:
....
Boot Policy RSA Signature:
....
密钥清单包含:
Intel BootGuard Key Manifest (KEYM) found at base FD4C80h
Tag: KEYM Version: 10h KmVersion: 10h KmSvn: 00h KmId: 0Fh
Key Manifest RSA Public Key Hash:
...
Boot Policy RSA Public Key Hash:
...
Key Manifest RSA Public Key (Exponent: 10001h):
...
Key Manifest RSA Signature:
... //it does actually contain a signature, I just removed these for space
然后由微码设置缓存为 RAM (CAR)(又名 AC-RAM,无填充模式和无驱逐模式)。然后在 FIT 中搜索与 CPU ID 匹配的微码更新。当前的微码将它们从闪存线性复制到 L3 缓存中,并使用片上对称 AES 密钥解密,然后使用(片上?)RSA 密钥进行验证。这些微码更新很可能还包含 ACM 的密钥散列。然后通过写入 UCODE MSR 来应用微码更新,CPU 会通过正常的内存访问将其读出。 FIT 绝对必须包含一个微码补丁,它对微码 SRAM 进行补丁以补充现有的微码 ROM。 [1]
接下来,微码返回 FIT 以查找 Startup ACM(又名 BIOS 或 Bootguard ACM)并将其复制到 L3 中(看起来多个超线程正在复制 4KB 块?--根据该来源) . ACM 包含一个 RSA 公钥;微码将其与芯片上的密钥或存储在微码更新中的密钥进行比较,如果不匹配则停止 CPU。微码然后检查 ACM 上的签名,如果不匹配则再次停止。
Startup ACM 完全用完 L3。 ACM 通过 MSR 从 ME 接收 OEM 公钥哈希 (the Key Hash that verifies the Key Manifest) 和 Bootguard Profile。
ACM 将 BootGuard 密钥清单从 SPI 闪存(由 FIT 指向并由 __KEYM__
标识)读取到 L3 并散列存储在其中的 RSA 公钥。如果它与 OEM 公钥哈希不匹配,或者如果密钥清单上的 OEM 公钥签名或者如果存储的 KmSvn 不正确,ACM 将根据 Bootguard 配置文件位采取措施。如果匹配,它会在 FIT(并由 __ACBP__
标识)中找到 Bootguard 策略并将其复制到 L3。然后,ACM 计算策略中 RSA 公钥的哈希值,并将其与存储在密钥清单中的 SHA256 哈希值进行比较。如果匹配失败,或者策略上的 RSA 签名不匹配,则 ACM 会再次根据配置文件设置采取措施。
ACM 使用现已验证的 Bootguard 策略结构将初始引导块 (IBB) 段读入 L3,并在复制它们时对其进行散列处理。如果此计算的哈希与策略中的“IBB 摘要”不匹配,ACM 将根据配置文件设置采取措施。第 4 个 IBB 段包含重置向量,因此它可以防止被修改。目前尚不清楚它是否让 CPU 处于 CAR 模式,或者是否只是将它们缓存在 L3 中但没有禁用驱逐,即没有启用 CAR 模式。然而,SEC 需要(禁用)并设置新的 CAR 模式。
使用 RwEverything 转储物理内存,或为您的 ME 版本下载 CSME 系统工具,然后执行 fptw64 -d -me -bios dump.bin
将转储闪存(通常您只能转储 BIOS 区域而不是 ME 区域) .现在可以在 UEFITool NE Alpha 58 中分析转储,并且 FIT 在正文中显示 ACM 的入口点。因此,如果您右键单击,将其解压缩并在 IDA 中以 32 位格式打开,入口点将位于 3BB1h + 18h = 3BC9h
3BC9 mov ax, ds
3BCC mov ss, ax
3BCF mov es, ax
3BD2 mov fs, ax
3BD5 mov gs, ax
3BD8 mov esp, ebp
3BDA add esp, 1000h
3BE0 mov eax, ebp
3BE2 add eax, 4C8h
3BE7 lidt fword ptr [eax]
3BEA push ebp
3BEB call sub_392A
3BF0 mov ebx, eax
3BF2 mov edx, 0
3BF7 mov eax, 3
3BFC getsec
启动 ACM 始终是最终 ACM,它 GETSEC[EXITAC]
s 到引导保护策略清单中的 IBBS 基地址 + 入口点地址(0xFEE90000
,位于第一个 IBB 中),它似乎在 ME 中区域,并且可能包含将 CPU 切换回虚幻模式并跳转到第 4 个 IBB 段中的旧重置向量的代码。 GETSEC
只能在保护模式下执行,所以很明显CPU在启动ACM中处于保护模式,所以进入前必须通过微码开启。旧的重置向量位于 0xFFFFFFF0,在我的系统上是相对跳转到 FFFFFFF5 - 3BD = FFFFFC38,这是 SEC 核心入口点。
SEC
SEC 核心是来自 FFFFCA14 - FFFFCA17 的原始部分,来自 FFFFCA18 - FFFFFFBB 的 PE32 图像,来自 FFFFFFBC - FFFFFFBF 的原始部分,以及来自 FFFFFFC0-FFFFFFFF 的原始部分(包含重置向量)。
复位向量跳转到的SEC Core中PE32 Image的入口点包含:
0x00: DB E3 fninit
0x02: 0F 6E C0 movd mm0, eax //move BIST value to mm0
0x05: 0F 31 rdtsc
0x07: 0F 6E EA movd mm5, edx
0x0a: 0F 6E F0 movd mm6, eax //save tsc
0x0d: 66 33 C0 xor eax, eax //clear eax
0x10: 8E C0 mov es, ax
0x12: 8C C8 mov ax, cs
0x14: 8E D8 mov ds, ax
0x16: B8 00 F0 mov ax, 0xf000
0x19: 8E C0 mov es, ax
0x1b: 67 26 A0 F0 FF 00 00 mov al, byte ptr es:[0xfff0]
0x22: 3C EA cmp al, 0xea
0x24: 74 0E je 0x34 //if ea is at ffff0h then jump to the 0xf000e05b check
0x26: BA F9 0C mov dx, 0xcf9
0x29: EC in al, dx //read port 0xcf9
0x2a: 3C 04 cmp al, 4
0x2c: 75 25 jne 0x53
0x2e: BA F9 0C mov dx, 0xcf9 //perform warm reset since if CPU only reset is issued not all MSRs are restored to their defaults
0x31: B0 06 mov al, 6
0x33: EE out dx, al
0x34: 67 66 26 A1 F1 FF 00 00 mov eax, dword ptr es:[0xfff1]
0x3c: 66 3D 5B E0 00 F0 cmp eax, 0xf000e05b
0x42: 75 0F jne 0x53 //if it isn't, move to notwarmstart
0x44: B9 1B 00 mov cx, 0x1b //if it is equal, read bsp bit from apic_base msr
0x47: 0F 32 rdmsr
0x49: F6 C4 01 test ah, 1
0x4c: 74 41 je 0x8f //if the and operation with 00000001b produces a zero result i.e. it's an AP then jump to cli, hlt
0x4e: EA F0 FF 00 F0 ljmp 0xf000:0xfff0 //if it's the BSP, exit unreal mode by far jumping to 0xffff0 which reloads the segment descriptor cache with a 0 base
notwarmstart:
0x53: B0 01 mov al, 1
0x55: E6 80 out 0x80, al //send 1 as a debug POST code
0x57: 66 BE 68 FF FF FF mov esi, 0xffffff68
0x5d: 66 2E 0F 01 14 lgdt cs:[si] //loads 32&16 GDT pointer (not 16&6, due to 66 prefix) at 16bit address fff68 in si into GDTR (base:ffffff28 limit:003f); will be accessing alias and not shadow ROM
//enter 16 bit protected mode//
0x62: 0F 20 C0 mov eax, cr0
0x65: 66 83 C8 03 or eax, 3 //Set PE bit (bit #0) & MP bit (bit #1)
0x69: 0F 22 C0 mov cr0, eax //Activate protected mode
0x6c: 0F 20 E0 mov eax, cr4
0x6f: 66 0D 00 06 00 00 or eax, 0x600 //Set OSFXSR bit (bit #9) & OSXMMEXCPT bit (bit #10)
0x75: 0F 22 E0 mov cr4, eax
//set up selectors for 32 bit protected mode entry
0x78: B8 18 00 mov ax, 0x18 //segment descriptor at 0x18 in GDT is (raw): 00cf93000000ffff
0x7b: 8E D8 mov ds, ax
0x7d: 8E C0 mov es, ax
0x7f: 8E E0 mov fs, ax
0x81: 8E E8 mov gs, ax
0x83: 8E D0 mov ss, ax
0x85: 66 BE 6E FF FF FF mov esi, 0xffffff6e
0x8b: 66 2E FF 2C ljmp cs:[si] //transition to flat 32 bit protected mode and jump to address at 0x0:0xffffff6e aka. 0xffffff6e which is fffffcd8. CS contains 0 remember (it's the base that is 0xffff) so it will load the first entry. This address is also in the SEC Core PE32 Image
0x8f: FA cli
0x90: F4 hlt
.
.
.
【讨论】:
根据source,FPF 驻留在 PCH 中,而不是 CPU。另一个source 表示:“2013 年及之后选定的英特尔平台附带的较新的安全和管理引擎支持称为现场可编程熔断器的功能”。由于 Intel ME 位于 PCH 中,这似乎也证实了 FPF 位于那里。 @wmjdgla 它们在 PCH 中(如果 PCH 像在某些较新的笔记本电脑架构上一样滚入 CPU 中,则位于 CPU 中)。有一天我会得到这个答案。我走神了【参考方案2】:如果您想进一步了解 UEFI 的工作原理,强烈推荐 Adam Williamson 的帖子UEFI boot: how does that actually work, then?。
他回答了你的问题并且很好阅读:
现在让我们看看如何在 UEFI 系统上进行引导。即使你 不要抓这个帖子的细节,抓这个:完全是 不同的。与 BIOS 启动方式完全不同 作品。您不能将您对 BIOS 引导的任何理解应用于 本机 UEFI 引导。你不能对系统做一点调整 专为 BIOS 启动世界而设计,并将其应用于本机 UEFI 开机。你需要明白这是一个完全不同的 世界。
***页面Unified Extensible Firmware Interface 也是一个有用的资源。
【讨论】:
【参考方案3】:当您问“具有 UEFI 的系统在什么模式下(Real,Protected,Long)启动?”时,您是什么意思?处理器开始以类似于过去 80386 的模式的模式执行。但你真的在乎吗。当您的操作系统加载程序代码获得控制权时,您真的不关心处理器的模式吗?而且您关心为您的操作系统加载程序代码提供了哪些服务。
环境在 UEFI 规范中定义。 Latest Versions of the UEFI Specifications
至于你使用哪种语言,汇编是很好的开始。稍等片刻之后,使用 C 或其他高级语言可能会更容易。
其他背景:
这里有很多术语我们在正确使用时并不总是小心翼翼。
处理器退出复位时执行的代码是系统固件,它对系统中的各种硬件进行大量初始化。
在 UEFI 论坛存在之前的 x86 PC 系统上,系统固件称为 BIOS。那时的 BIOS 会执行其所有的初始化代码,然后从软盘或硬盘上加载一些代码并跳转到代码中。 BIOS 还提供了硬件和操作系统之间的一些接口,以帮助将操作系统与硬件差异隔离开来。然而,没有什么是标准化的。唯一的标准是使用 BIOS 接口的操作系统和应用程序级软件。
如果操作系统和应用程序正常运行,则认为 BIOS 是正确的。但是你只能通过没有失败来证明正确性。因此,新的操作系统或应用程序可以在一个正确的系统上运行,但在另一个正确的系统上会失败。
今天,我们尝试为这些接口提供一些实际的标准化。它们由 UEFI 论坛定义。今天我可以证明我的系统是正确的,符合 UEFI 规范。
当人们说 UEFI 之类的东西时,他们通常指的是在操作系统开始执行之前安装在系统上的实际系统固件。但是我们中的很多人仍然在这些术语周围折腾,就像我们泼了字母汤一样。
引导加载程序实际上是操作系统拥有的代码,由系统固件加载到内存中,系统固件将硬件控制权交给引导加载程序。可以说系统固件的结尾是 UEFI Boot Loader。或者你可以说 BDS 使用系统策略来查找操作系统。而且你仍然可以找到不同意这些话的人。
【讨论】:
UEFI 引导加载程序这个词不是一个好词。引导加载程序,例如GRUB,不是 UEFI 的一部分。它在 UEFI 之后运行。有为 UEFI 编写的引导加载程序 superuser.com/questions/1112090/what-is-a-uefi-bootloader【参考方案4】:UEFI 固件在 64 位平台上以 64 位长模式运行,在 32 位平台上以平面模式运行;与 BIOS 不同,UEFI 具有自己的体系结构,独立于 CPU 和自己的设备驱动程序。 UEFI 可以挂载分区并读取某些文件系统。
当 x86 计算机配备 UEFI 时,该界面会在系统存储中搜索标有特定全局唯一标识符 (GUID) 的分区,该标识符将其标记为 EFI 系统分区 (ESP)。顺便说一句,Windows 不会挂载此分区,您无法在操作系统中看到它。但是有一个技巧,你只需将 VBR 中的分区类型(使用 HexWorkshop)更改为常规的 FAT32 代码,它就会被挂载到操作系统中。
此分区包含为 EFI 架构编译的应用程序。一般来说,您不必处理汇编程序来编写 UEFI 应用程序/加载器,它只是一个常规的 C 代码。默认情况下,它位于“EFI/BOOT/BOOTX64.EFI”。当手动或自动选择引导加载程序时,UEFI 会将其读入内存并将引导过程的控制权交给它。
【讨论】:
我听说“CPU 以 16 位模式启动。UEFI 运行在 32 位或 64 位。”那么在这种情况下,在 UEFI 之前运行的是什么? @barlop bootloader ,通常它是非常原始的软件,甚至可能不使用 32 位内存地址 据我了解,UEFI 从 16 位 github.com/tianocore/edk2/blob/master/UefiCpuPkg/ResetVector/… 开始,在这里它从 16 位实模式过渡到 32 位保护模式 github.com/tianocore/edk2/blob/master/IntelFsp2Pkg/FspSecCore/… 当我问“什么在 UEFI 之前运行?”你说的是“引导加载程序”。但是引导加载程序不在 UEFI 之前! Grub 是一个引导加载程序,它在 UEFI 之后运行。 (也许引导加载程序在某种程度上是一个模棱两可的术语) @barlop:上电后执行的第一条指令是从硬连接到 CPU 的物理地址(复位向量)中获取的。此时 CPU 处于“虚幻”模式,CS 段基址在低 1MiB 之外,但在其他方面与 16 位实模式相同。系统设计人员确保该地址映射到闪存 ROM。该代码最终将在从磁盘读取任何内容之前切换到 64 位模式。但在此之前,它必须配置 DRAM 控制器和类似的东西!它通常会在某些时候使用缓存作为 RAM(无填充)模式...【参考方案5】:这里有一个good answer 来回答这个问题:
其他现代 64 位机器具有新的 EFI 固件。这些根本不会从磁盘的扇区#0 加载引导程序。它们通过 EFI 引导管理器加载和运行 EFI 引导加载程序应用程序进行引导。此类程序以保护模式运行。这是 EFI 引导过程。
EFI 固件通常会在退出处理器复位的几条指令内切换到保护模式。切换到保护模式是在 EFI 固件初始化的所谓“SEC 阶段”早期完成的。从技术上讲,32 位和更高版本的 x86 处理器甚至不能以实模式启动,而是以俗称的虚模式启动。 (CS 寄存器的初始段描述符没有描述传统的实模式映射,这使得它变得“不真实”。)
因此,可以说这些 EFI 系统在本机引导到 EFI 引导加载程序时(即当它们不使用兼容性支持模块时)根本不会正确进入实模式,因为它们直接从虚模式切换到保护模式并从那时起保持在保护模式。
【讨论】:
UEFI 没有保护模式! UEFI 在 64 位长模式下工作!某些平台可能使用 32 位平面模式。这个术语首先是错误的,这就是为什么它让很多人感到困惑。在过去,所有不是 x86 模式的东西都被称为保护模式。但这并不完全正确。 @Alex 我确实链接了你 UEFI 从 16 位模式开始并移动到 32 位模式的位置。您可能似乎将其称为某种引导加载程序,但事实是它以 16 位模式启动的位置是 UEFI 的一部分 @barlop:我想说从 16 位(非)实模式开始是 x86 早期启动的一部分。在固件准备好从磁盘加载某些内容并为其加载的代码提供标准化配置和启动 API 之前,它不是 EFI / UEFI(或旧版 BIOS)。在此之前,固件必须配置这个特定系统的内存控制器,以及类似的东西,而那部分是完全私有的,不受任何标准的约束。 16 位不是 UEFI 的一部分,所以是的,UEFI 固件必须先退出该模式,然后才能跳转到从磁盘加载的任何代码,并提供该 ABI。以上是关于UEFI 是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章
hpz620在pcie固态硬盘安装win7 uefi引导,找不到pcie固态硬盘,请问如何加载驱动