V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
imbushuo
V2EX  ›  分享创造

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

  •  
  •   imbushuo ·
    imbushuo · 2019-02-03 12:03:08 +08:00 · 2022 次点击
    这是一个创建于 2122 天前的主题,其中的信息可能已经有所发展或是发生改变。

    此贴有感而发。

    背景知识

    曾经在 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 的选项,你就会注意到一些神奇的东西:

    Screen Shot 2019-02-02 at 10.32.44 PM.png

    今天的主角就是 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 会负责善后。退出后重新回到启动选项界面。

    24 条回复    2019-02-04 23:48:02 +08:00
    rickliu2000
        1
    rickliu2000  
       2019-02-03 12:07:52 +08:00
    竟然在 v 站活捉 ben
    hjc4869
        2
    hjc4869  
       2019-02-03 12:25:57 +08:00 via iPhone
    竟然在 v 站活捉 ben
    TechCiel
        3
    TechCiel  
       2019-02-03 12:34:41 +08:00
    竟然在 v 站活捉 ben
    Trumeet
        4
    Trumeet  
       2019-02-03 13:03:53 +08:00 via Android
    竟然在 v 站活捉 ben
    locoz
        5
    locoz  
       2019-02-03 13:20:25 +08:00 via Android
    竟然在 v 站活捉 ben
    azhangbing
        6
    azhangbing  
       2019-02-03 13:32:38 +08:00 via iPhone
    竟然在 v 站活捉 ben
    edsheeran
        7
    edsheeran  
       2019-02-03 15:44:56 +08:00 via iPhone
    竟然在 v 站活捉 ben
    orancho
        8
    orancho  
       2019-02-03 16:25:52 +08:00 via iPhone
    竟然在 v 站活捉 ben
    codechaser
        9
    codechaser  
       2019-02-03 17:14:20 +08:00 via Android
    这个门槛有点高,不会弄😂
    DesignerSkyline
        10
    DesignerSkyline  
       2019-02-03 17:35:27 +08:00
    竟然在 v 站活捉 ben
    daya
        11
    daya  
       2019-02-03 17:43:04 +08:00 via iPhone
    竟然在 v 站活捉 ben
    processzzp
        12
    processzzp  
       2019-02-03 18:16:51 +08:00 via iPhone
    竟然在 v 站活捉 ben
    AEANWspPmj3FUhDc
        13
    AEANWspPmj3FUhDc  
       2019-02-03 18:24:13 +08:00
    小声的问一句,ben 是谁呀?
    orzfly
        14
    orzfly  
       2019-02-03 18:46:54 +08:00
    @ivlioioilvi 楼主就是 ben (
    orzfly
        15
    orzfly  
       2019-02-03 18:49:21 +08:00   ❤️ 1
    啊呀,ben 生日快乐 🎂
    EricwcirE
        16
    EricwcirE  
       2019-02-03 19:06:24 +08:00 via iPhone
    竟然在 v 站活捉 ben
    dyxang
        17
    dyxang  
       2019-02-03 22:24:05 +08:00 via Android
    @orzfly 啥是 ben ?
    orzfly
        18
    orzfly  
       2019-02-03 23:20:43 +08:00
    imbushuo
        19
    imbushuo  
    OP
       2019-02-03 23:41:48 +08:00   ❤️ 1
    @orzfly 谢谢~
    Dreista
        20
    Dreista  
       2019-02-04 01:28:44 +08:00
    竟然在 v 站活捉 ben
    Mingcr
        21
    Mingcr  
       2019-02-04 02:05:32 +08:00
    竟然在 v 站活捉 ben
    vB4h3r2AS7wOYkY0
        22
    vB4h3r2AS7wOYkY0  
       2019-02-04 02:19:45 +08:00
    @Livid 楼上所有队形

    回楼主... 虽然用 Windows Boot Manager 引导也很不错...
    不过我感觉还是关掉 Secure Boot 用 systemd-boot / GRUB 引导 Windows Boot Manager 比较方便...
    毕竟这些可以随意改配置文件配置...
    jakes
        23
    jakes  
       2019-02-04 11:50:11 +08:00 via iPhone
    V 站真的需要一些像这样的干货
    mario85
        24
    mario85  
       2019-02-04 23:48:02 +08:00 via iPhone
    后排仰望大神
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3564 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 10:51 · PVG 18:51 · LAX 02:51 · JFK 05:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.