前面的一些实验涉及到修改 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中了:
编写这样的程序特别需要注意地方:
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下载
最后多说一句: 前几天给家里的台式机重装系统,结果中了MBR引导区病毒(Win7系统,GHOST安装的),难以想象时隔将近二十年,仍然活跃着这样的病毒。万幸,现在有了GPT分区,不再需要从MBR进行引导(MBR跑的代码完全没有文件显而易见)再加上现在的 Secure Boot 功能, 相信这样的问题会越来越少吧。
参考:
1. https://github.com/dude719/UEFI-Bootkit