Step to UEFI (147)先于Windows启动的Application

前面的一些实验涉及到修改 ACPI Table 之类的,都是要先进入 Shell 运行之后,再退出进入 Windows 中,如果能够自动调用Windows的引导程序,就不需要这么多复杂的步骤了。
结合前面的知识,可以使用gBS中的LoadImage 和StartImage加载和运行EFI 程序。难点在于这两个函数都需要被加载文件的DevicePath。近日恰好看到了【参考1】UEFI-Bootkit 项目,其中下面这个函数实现了我们需要的功能,对我们来说可以借用之。
//
// Try to find a file by browsing each device
//
EFI_STATUS LocateFile( IN CHAR16* ImagePath, OUT EFI_DEVICE_PATH** DevicePath )

这个函数的使用方法:
1. 用LocateHandleBuffer 找到所有带有SimpleFileSystemProtocol的设备(因为Windows只能从 FAT 格式的分区上加载引导的 EFI 文件)
2. 针对每一个上面找到的 Handle,使用 OpenVolume 打开上面的 EFI_FILE_PROTOCOL
3. 尝试打开EFI_FILE_PROTOCOL上面的文件(就是 Windows引导文件,路径和名称都是已知的),失败就是不存在,比如,找到U盘上去的;成功表示找到了,就可以获得需要的 DevicePath了
根据上面的编写自己的 Application 代码如下:

#include <Uefi.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h> //global gST gBS gImageHandle

#include <Protocol/LoadedImage.h>      //EFI_LOADED_IMAGE_PROTOCOL
#include <Protocol/DevicePath.h>       //EFI_DEVICE_PATH_PROTOCOL

#include <Library/DevicePathLib.h>     //link
#include <Protocol/SimpleFileSystem.h>

EFI_GUID        gEfiSimpleFileSystemProtocolGuid ={ 0x964E5B22, 0x6459, 0x11D2, 
                        { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};

// 
// Try to find a file by browsing each device
// 
EFI_STATUS LocateFile( IN CHAR16* ImagePath, OUT EFI_DEVICE_PATH** DevicePath )
{
        EFI_FILE_IO_INTERFACE *ioDevice;
        EFI_FILE_HANDLE handleRoots, bootFile;
        EFI_HANDLE* handleArray;
        UINTN nbHandles, i;
        EFI_STATUS efistatus;
        
        *DevicePath = (EFI_DEVICE_PATH *)NULL;
        //
        //Get all the Handles which have Simple File System Protocol 
        //
        efistatus = gBS->LocateHandleBuffer( ByProtocol, &gEfiSimpleFileSystemProtocolGuid, NULL, &nbHandles, &handleArray );
        if (EFI_ERROR( efistatus ))
                return efistatus;
        
        Print( L"\r\nNumber of UEFI Filesystem Devices: %d\r\n", nbHandles );
        
        for (i = 0; i < nbHandles; i++)
        {
                efistatus = gBS->HandleProtocol( handleArray[i], &gEfiSimpleFileSystemProtocolGuid, &ioDevice );
                if (efistatus != EFI_SUCCESS)
                        continue;
        
                efistatus = ioDevice->OpenVolume( ioDevice, &handleRoots );
                if (EFI_ERROR( efistatus ))
                        continue;
                //
                //Try to open the specific path on the device
                //
                efistatus = handleRoots->Open( handleRoots, &bootFile, ImagePath, EFI_FILE_MODE_READ, EFI_FILE_READ_ONLY );
                if (!EFI_ERROR( efistatus ))
                {
                        handleRoots->Close( bootFile );
                        *DevicePath = FileDevicePath( handleArray[i], ImagePath );
                        Print( L"\r\nFound file at \'%s\'\r\n", ConvertDevicePathToText( *DevicePath, TRUE, TRUE ) );
                        break;
                }
        }
        
        return efistatus;
}
        
EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable)
{
        // Windows Boot Manager x64 image path
        static CHAR16    *gRuntimeDriverImagePath = L"\\EFI\\Microsoft\\Boot\\win.efi";
        EFI_DEVICE_PATH* RuntimeDriverDevicePath = NULL;
        EFI_HANDLE       RuntimeDriverHandle = NULL;        
        EFI_STATUS       efiStatus;
        
        //
        // Clear screen and make pretty
        //
        gST->ConOut->ClearScreen( gST->ConOut );
        gST->ConOut->SetAttribute( gST->ConOut, EFI_GREEN | EFI_BACKGROUND_LIGHTGRAY );
        
        //
        // Locate the runtime driver
        //
        efiStatus = LocateFile( gRuntimeDriverImagePath, &RuntimeDriverDevicePath );
        if (EFI_ERROR( efiStatus )) {
                Print(L"Can't find %s\n",gRuntimeDriverImagePath);
                goto Exit;
        }

        Print(L"Found %s\n",gRuntimeDriverImagePath);
        Print(L"Boot to it in 5 seconds \n");
        gBS->Stall(5000000UL);
        
        //
        // Load Runtime Driver into memory
        //
        efiStatus = gBS->LoadImage(TRUE, ImageHandle, RuntimeDriverDevicePath,NULL,0,&RuntimeDriverHandle);
        if (EFI_ERROR( efiStatus )) {
                Print(L"Loading %s failed\n",gRuntimeDriverImagePath);
                goto Exit;
        }
        
        //
        // Transfer executon to the Runtime Driver
        //
        efiStatus = gBS->StartImage( RuntimeDriverHandle, (UINTN *)NULL,(CHAR16**)NULL);
        if (EFI_ERROR( efiStatus )) {
                Print(L"Starting %s failed\n",gRuntimeDriverImagePath);
                goto Exit;
        }     
Exit:
        gBS->Stall(15000000UL);
  return EFI_SUCCESS;
}

 

对应的 INF 文件

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = bf
  FILE_GUID                      = b931b80a-9c19-41e1-aa42-474da3f76013
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain

[Sources]
  bf.c

[Packages]
  MdePkg/MdePkg.dec
  
[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

 

在实体机上测试,运行结果如下,随后就开始启动到 Windows中了:

fbw

编写这样的程序特别需要注意地方:
1. 不要在代码中使用 Shell 相关的函数,因为BIOS直接加载你的 EFI Application, 没有其他的人供给你 Shell 提供服务;
2. 可以使用BIOS中直接 Boot From File 功能测试你的代码,这是验证的最简单方法;
3. 将原装的 Windows引导程序改名为 Win.efi,代码会尝试启动这个文件;
4. 确保BIOS中的 Secure Boot 是 Disabled;
5. 能找到或者无法找到启动文件都会停留一段时间再继续。
上面的代码写好之后,就可以去替换 Windows的引导程序了。根据观察正常启动之后,Windows 会创建一个Boot选项,这个选项会加载 fsX:\EFI\Microsoft\Boot\ 下面 , 有时候是 Bootmgr.efi 有时候是Bootmgrw.efi这两个文件(这个可以在 Boot 相关的 Variable 中看到,其中写的是Bootmgr.efi,但是不知道为什么,有时候会用Bootmgrw.efi)。对于我们来说简单起见都替换掉即可。

完整的代码和编译后的X64 EFI Application下载

BeforeWinloader

最后多说一句: 前几天给家里的台式机重装系统,结果中了MBR引导区病毒(Win7系统,GHOST安装的),难以想象时隔将近二十年,仍然活跃着这样的病毒。万幸,现在有了GPT分区,不再需要从MBR进行引导(MBR跑的代码完全没有文件显而易见)再加上现在的 Secure Boot 功能, 相信这样的问题会越来越少吧。

参考:
1. https://github.com/dude719/UEFI-Bootkit

发表评论

电子邮件地址不会被公开。 必填项已用*标注