我在学习关于引导程序时,恰好遇到了UEFI这个术语。我了解一些关于UEFI的知识。但是,使用UEFI启动系统是在哪种模式(Real,Protected,Long)下进行的?如果普通的启动加载程序无法与UEFI配合工作,那么在处理UEFI时有什么替代的启动加载程序呢?除了汇编之外,我是否需要其他编程语言来创建一个启动加载程序?
我在学习关于引导程序时,恰好遇到了UEFI这个术语。我了解一些关于UEFI的知识。但是,使用UEFI启动系统是在哪种模式(Real,Protected,Long)下进行的?如果普通的启动加载程序无法与UEFI配合工作,那么在处理UEFI时有什么替代的启动加载程序呢?除了汇编之外,我是否需要其他编程语言来创建一个启动加载程序?
UEFI固件在64位平台上以64位长模式运行,在32位平台上以平面模式运行。与BIOS不同,UEFI具有自己的架构,独立于CPU,并拥有自己的设备驱动程序。UEFI可以挂载分区并读取特定的文件系统。
当一台装有UEFI的x86计算机启动时,接口会搜索系统存储器中标记为特定全球唯一标识符(GUID)的分区,该分区被标记为EFI系统分区(ESP)。顺便说一句,Windows不会挂载此分区,您无法在操作系统中看到它。但是有一个技巧,您只需更改VBR中的分区类型(使用HexWorkshop)为常规FAT32代码,就可以将其挂载到操作系统中。
此分区包含为EFI架构编译的应用程序。通常情况下,您无需处理汇编语言即可编写UEFI应用程序/加载程序,它只是普通的C代码。默认情况下,它位于“EFI / BOOT / BOOTX64.EFI”。当选择引导加载程序时,手动或自动,UEFI将其读入内存并将引导过程的控制权交给它。
UEFI之前的阶段
当OEM收到PCH时,ME仍处于“制造模式”,运行固件的特殊部分,将“OEM公钥哈希”和“Boot Guard配置文件”策略值从其flash ROM部分复制到可编程熔丝(FPF)中,使它们成为永久且不可更改的值。然后,它设置一个保险丝,指示它已退出制造模式,因此这部分固件将不会再次运行。可以使用英特尔闪存映像工具(FITC)调整这些值,但除非您有一种方法强制ME进入制造模式,否则忽略闪存映像中的值。
Bootguard配置文件如下:
启用保护BIOS环境:如果设置了这个选项,那么ACM可能会将IBB段复制到CPU缓存中,以便它在缓存作为RAM(CAR)模式下运行,并禁用所有DMA以防止设备能够修改它。这张图片展示了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;
在我的系统中,有一个 FIT 指针位于 FFFFFFC0 到 FFD90100。
FIT表指向微码更新、ACM、BootGuard启动策略清单(其中包含IBBS)和BootGuard密钥清单等。
我的FIT中没有任何0x7条目。记录类型7仅用于传统的Intel® 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
__KEYM__
标识)中读取BootGuard密钥清单到L3中,并对其中存储的RSA公钥进行哈希。如果它与OEM公钥哈希不匹配,或者如果密钥清单上的OEM公钥签名不正确,或者存储的KmSvn不正确,则ACM根据Bootguard Profile位采取行动。如果匹配,则在FIT中查找Bootguard Policy(由__ACBP__
标识),并将其复制到L3中。然后,ACM计算策略中RSA公钥的哈希值,并将其与密钥清单中存储的SHA256哈希进行比较。如果无法匹配,或者策略上的RSA签名不匹配,则ACM根据Profile设置再次采取行动。fptw64 -d -me -bios dump.bin
将转储闪存(通常只能转储BIOS区域而无法转储ME区域)。现在可以在UEFITool NE Alpha 58中分析该转储,并且FIT在正文中显示ACM的EntryPoint。因此,如果右键单击并将其提取出来,在IDA中作为32位打开,EntryPoint将位于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]
到 IBBS 基址 + 入口点地址 (0xFEE90000
,位于第一个 IBB 中) 在 bootguard 策略清单中,该清单似乎在 ME 区域中,并且可能包含代码以将 CPU 切换回非真实模式并跳转到第四个 IBB 段的传统复位向量。 GETSEC
只能在保护模式下执行,因此显然 CPU 在启动 ACM 时处于保护模式,因此必须在进入之前由微码启用。 传统复位向量位于 0xFFFFFFF0,对于我的系统来说,它是相对跳转到 FFFFFFF5 - 3BD = FFFFFC38,这是 SEC 核心入口点。
SEC
SEC核心是从 FFFFCA14 到 FFFFCA17 的原始部分,从 FFFFCA18 到 FFFFFFBB 是PE32映像,从 FFFFFFBC 到 FFFFFFBF 是原始部分,从 FFFFFFC0 到 FFFFFFFF 是原始部分(其中包含复位向量)。
在SEC核心中由复位向量跳转到的PE32映像的入口点包含:
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
.
.
.
以下是这个问题的优秀答案:
其他现代的64位计算机有新的EFI固件。它们根本不从磁盘的第0扇区加载引导程序。它们通过EFI启动管理器加载和运行EFI引导加载程序进行引导。这些程序在保护模式下运行。这就是EFI引导过程。
一般情况下,EFI固件会在处理器复位后的几条指令内切换到保护模式。切换到保护模式早在EFI固件初始化的“SEC阶段”中就完成了。从技术上讲,32位及以上的x86处理器甚至没有真正进入实模式,而是进入了俗称的虚拟模式。(CS寄存器的初始段描述符并不描述传统的实模式映射,这使得它“虚拟”了。)
因此,当原生引导到EFI引导加载程序时(即在不使用兼容性支持模块的情况下),可以说这些EFI系统根本没有进入真正的实模式,因为它们直接从虚拟模式切换到保护模式,并从那时起一直处于保护模式。
此外,Wikipedia页面Unified Extensible Firmware Interface也是一个有用的资源。现在让我们来看看UEFI系统上的引导过程。即使您不理解本文的细节,也请理解:它完全不同。与BIOS引导方式完全不同。您不能将任何关于BIOS引导方式的理解应用于本机UEFI引导。您不能对专为BIOS引导世界设计的系统进行微小的调整,并将其应用于本机UEFI引导。您需要了解这是一个完全不同的世界。
fdisk -l
命令轻松找到ESP(EFI系统分区)。在我的计算机上,它是/dev/nvme0n1p1
。然后像往常一样挂载它:sudo mount /dev/nvme0n1p1 ~/esp
。接下来,您可以检查~/esp
中的内容。 - smwikipedia