用 UEFI 下的 Windows Boot Manager 来加载诸如 GRUB 之类的启动器

2019-02-03 12:03:08 +08:00
 imbushuo

此贴有感而发。

背景知识

曾经在 Legacy BIOS 时代,你可以很容易地从 Windows Boot Manager 切换到其他引导器(比如 ntldr,比如 GRUB ),因为有现实的兼容需求存在。只需要添加一个实模式启动项即可。

然而在 UEFI 时代,Windows Boot Manager 并没有提供这个功能。你可能尝试过将 grub.efi 直接添加为 Windows Boot Manager 的启动项目,但是发现这样并没有什么卵用 —— 尽管 winload.efi, bootmgr.efi 之类的程序都是以 .efi 为扩展名。关了 Secure Boot 也是如此。

那么这是为什么呢

因为实际上 Windows 那些玩意并不是标准的 EFI 程序!观察 MSVC Linker 的选项,你就会注意到一些神奇的东西:

今天的主角就是 BOOT_APPLICATION。上述提及的 winload.efi 之类的程序就是这个类型的。它和 EFI_APPLICATION 有着很大的区别。

入口点的区别

这是一个常见的 EFI 程序的入口点:

EFI_STATUS
EFIAPI
InitializeUserInterface(
	IN EFI_HANDLE        ImageHandle,
	IN EFI_SYSTEM_TABLE  *SystemTable
)

而这是一个 Boot Application 的入口点:

// Boot Manager Application Entrypoint
// Bootstraps environment and transfer control to EFI Application entry point.
NTSTATUS BlApplicationEntry(
	_In_ PBOOT_APPLICATION_PARAMETER_BLOCK BootAppParameters,
	_In_ PBL_LIBRARY_PARAMETERS LibraryParameters
)

入口点都不一样,怎么谈恋爱,当然加载不起来(。

环境上的区别

Windows Boot Manager 是一个 UEFI Application。从固件进入后,它会创建一套自己的环境(与固件相独立),配置自己的 MMU 映射和自己的异常向量,为加载 Windows Kernel 作准备。所以 前人的尝试 只能运行 EFI 的一部分功能,而且好像只在 VMware 的固件上工作。对此感兴趣的可以看一下 bootmgfw.efi 的 BlInitializeLibrary 函数的内容。

创建完自己的环境后,它会把相关信息放到一个结构体里供其他 Boot Application 作参考,这玩意大概长这样:

typedef struct _BOOT_APPLICATION_PARAMETER_BLOCK
{
	/* This header tells the library what image we're dealing with */
	unsigned long Signature[2];
	unsigned long Version;
	unsigned long Size;
	unsigned long ImageType;
	unsigned long MemoryTranslationType;

	/* Where is the image located */
	unsigned __int64 ImageBase;
	unsigned long ImageSize;

	/* Offset to BL_MEMORY_DATA */
	unsigned long MemoryDataOffset;

	/* Offset to BL_APPLICATION_ENTRY */
	unsigned long AppEntryOffset;

	/* Offset to BL_DEVICE_DESCRPIPTOR */
	unsigned long BootDeviceOffset;

	/* Offset to BL_FIRMWARE_PARAMETERS */
	unsigned long FirmwareParametersOffset;

	/* Offset to BL_RETURN_ARGUMENTS */
	unsigned long ReturnArgumentsOffset;
} BOOT_APPLICATION_PARAMETER_BLOCK, *PBOOT_APPLICATION_PARAMETER_BLOCK;

里面有原始固件环境信息结构体的指针(随体系结构而变)。最近相关内容好像进公开的 Windows SDK/DDK 里了,感兴趣的可以找一下。也可以看一下这个很刺激的演讲,Alex Ionescu 写的。

回到 UEFI 环境里

以下内容为在 ARMv7 上的情况。其他平台请自己分析,不过大同小异。直接贴代码。

// 先去拿一个固件描述符。之后你要把这玩意传进下面的函数里
FirmwareDescriptor = (PBL_FIRMWARE_DESCRIPTOR) (ParamPointer + BootAppParameters->FirmwareParametersOffset);

...

// Switch to "real" mode and never look back.
// And we do not need boot application context in this application.
void SwitchToRealModeContext(PBL_FIRMWARE_DESCRIPTOR FirmwareDescriptor)
{
	// 切换到 Firmware Mode (代码里称之为 Real Mode, anyway ARM 上没有 Real Mode)
        // 从固件环境描述符里获得目前的中断状态
	unsigned int InterruptState = FirmwareDescriptor->InterruptState;

	// 关掉中断
	DisableInterrupt();

	// 切换 MMU 状态,以下请参考 ARMv7 手册
	unsigned long Value = FirmwareDescriptor->MmState.HardwarePageDirectory | 
		FirmwareDescriptor->MmState.TTB_Config;

	ArmMoveToProcessor(Value, CP15_TTBR0);
	ArmInstructionSynchronizationBarrier();

	ArmMoveToProcessor(0, CP15_TLBIALL);
	ArmInvalidateBTAC();
	ArmDataSynchronizationBarrier();
	ArmInstructionSynchronizationBarrier();

	// 切换好了,下面切换异常状态。参考 ARMv7 手册
	ArmMoveToProcessor(FirmwareDescriptor->ExceptionState.IdSvcRW, CP15_TPIDRPRW);
	ArmDataSynchronizationBarrier();

	ArmMoveToProcessor(FirmwareDescriptor->ExceptionState.Control, CP15_SCTLR);
	ArmInvalidateBTAC();
	ArmDataSynchronizationBarrier();
	ArmInstructionSynchronizationBarrier();

	ArmMoveToProcessor(FirmwareDescriptor->ExceptionState.Vbar, CP15_VBAR);
	ArmInstructionSynchronizationBarrier();

	// 如果固件描述符之前报告开了中断,那么重新开中断
	if (InterruptState) ArmEnableInterrupt();
}

在完成这些步骤后你就可以把相关信息传送到标准的 EFI 程序入口点了:

SomeRandomEfiApplicationEntry(
	FirmwareDescriptor->ImageHandle, 
	FirmwareDescriptor->SystemTable
);

然后就可以做任何事情了。抛砖引玉,你可以自己分析一下在 x86 和 amd64 上这个事情上怎么实现的。

bootmgfw.efi 和 bootmgr.efi 的区别?

前者是 EFI Application,后者是 Boot Application。但是前者除了会设置环境外,和后者没有什么本质上的区别。

附录

Windows Phone 上的其他 ELF chainload (包括跑一些喜闻乐见的东西)就是用这个东西做的。因为那个漏洞的本质是 Windows Boot Manager 感觉没有打开 Secure Boot,但是固件依然会验证从固件本身加载的程序,所以需要一个 chainloader 来取得控制权。代码可以来这儿看。在 Windows Phone 上需要为这个启动项打开 NoIntegrityChecks 开关。

程序退出时不需要切换回 Boot Manager 状态,bootmgfw 会负责善后。退出后重新回到启动选项界面。

2059 次点击
所在节点    分享创造
24 条回复
rickliu2000
2019-02-03 12:07:52 +08:00
竟然在 v 站活捉 ben
hjc4869
2019-02-03 12:25:57 +08:00
竟然在 v 站活捉 ben
TechCiel
2019-02-03 12:34:41 +08:00
竟然在 v 站活捉 ben
Trumeet
2019-02-03 13:03:53 +08:00
竟然在 v 站活捉 ben
locoz
2019-02-03 13:20:25 +08:00
竟然在 v 站活捉 ben
azhangbing
2019-02-03 13:32:38 +08:00
竟然在 v 站活捉 ben
edsheeran
2019-02-03 15:44:56 +08:00
竟然在 v 站活捉 ben
orancho
2019-02-03 16:25:52 +08:00
竟然在 v 站活捉 ben
codechaser
2019-02-03 17:14:20 +08:00
这个门槛有点高,不会弄😂
DesignerSkyline
2019-02-03 17:35:27 +08:00
竟然在 v 站活捉 ben
daya
2019-02-03 17:43:04 +08:00
竟然在 v 站活捉 ben
processzzp
2019-02-03 18:16:51 +08:00
竟然在 v 站活捉 ben
AEANWspPmj3FUhDc
2019-02-03 18:24:13 +08:00
小声的问一句,ben 是谁呀?
orzfly
2019-02-03 18:46:54 +08:00
@ivlioioilvi 楼主就是 ben (
orzfly
2019-02-03 18:49:21 +08:00
啊呀,ben 生日快乐 🎂
EricwcirE
2019-02-03 19:06:24 +08:00
竟然在 v 站活捉 ben
dyxang
2019-02-03 22:24:05 +08:00
@orzfly 啥是 ben ?
orzfly
2019-02-03 23:20:43 +08:00
imbushuo
2019-02-03 23:41:48 +08:00
@orzfly 谢谢~
Dreista
2019-02-04 01:28:44 +08:00
竟然在 v 站活捉 ben

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/532802

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX