aboutsummaryrefslogtreecommitdiff
path: root/EfiGuardDxe/PatchBootmgr.c
diff options
context:
space:
mode:
Diffstat (limited to 'EfiGuardDxe/PatchBootmgr.c')
-rw-r--r--EfiGuardDxe/PatchBootmgr.c372
1 files changed, 372 insertions, 0 deletions
diff --git a/EfiGuardDxe/PatchBootmgr.c b/EfiGuardDxe/PatchBootmgr.c
new file mode 100644
index 0000000..aa27135
--- /dev/null
+++ b/EfiGuardDxe/PatchBootmgr.c
@@ -0,0 +1,372 @@
+#include "EfiGuardDxe.h"
+
+#include <Library/BaseMemoryLib.h>
+
+VOID* /*t_ImgArchStartBootApplication_XX*/ gOriginalBootmgfwImgArchStartBootApplication = NULL;
+UINT8 gBootmgfwImgArchStartBootApplicationBackup[sizeof(gHookTemplate)] = { 0 };
+
+VOID* /*t_ImgArchStartBootApplication_XX*/ gOriginalBootmgrImgArchStartBootApplication = NULL;
+UINT8 gBootmgrImgArchStartBootApplicationBackup[sizeof(gHookTemplate)] = { 0 };
+
+
+//
+// Universal template bytes for a "faux call" inline hook
+//
+CONST UINT8 gHookTemplate[] =
+{
+#if defined(MDE_CPU_X64)
+ 0x48, 0xB8, // mov rax,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // <addr>
+#elif defined(MDE_CPU_IA32)
+ 0xB8, // mov eax,
+ 0x00, 0x00, 0x00, 0x00, // <addr>
+#endif
+ 0x50, // push [e|r]ax
+ 0xC3 // ret
+};
+
+
+// Signature for [bootmgfw|bootmgr]!ImgArch[Efi]StartBootApplication
+STATIC CONST UINT8 SigImgArchStartBootApplication[] = {
+ 0x41, 0xB8, 0x09, 0x00, 0x00, 0xD0 // mov r8d, 0D0000009h
+};
+
+
+//
+// Shared function called by [bootmgfw|bootmgr]!ImgArch[Efi]StartBootApplication hooks to patch either winload.efi or bootmgr.efi
+//
+STATIC
+EFI_STATUS
+EFIAPI
+HookedBootManagerImgArchStartBootApplication(
+ IN PBL_APPLICATION_ENTRY AppEntry,
+ IN VOID* ImageBase,
+ IN UINT32 ImageSize,
+ IN UINT32 BootOption,
+ OUT PBL_RETURN_ARGUMENTS ReturnArguments,
+ IN VOID* /*t_ImgArchStartBootApplication_XX*/ OriginalFunction,
+ IN CONST UINT8* OriginalFunctionBytes
+ )
+{
+ // Restore the original function bytes that we replaced with our hook
+ CopyMem(OriginalFunction, OriginalFunctionBytes, sizeof(gHookTemplate));
+
+ // Clear the screen and paint it, paint it bl... green
+ CONST INT32 OriginalAttribute = SetConsoleTextColour(EFI_GREEN, TRUE);
+
+ // Get the PE headers
+ CONST PEFI_IMAGE_NT_HEADERS NtHeaders = RtlpImageNtHeaderEx(ImageBase, ImageSize);
+ INPUT_FILETYPE FileType = Unknown;
+ if (NtHeaders == NULL)
+ {
+ Print(L"\r\nHookedBootmanagerImgArchStartBootApplication: PE image at 0x%p with size 0x%lx is invalid!\r\nPress any key to continue anyway, or press ESC to reboot.\r\n",
+ ImageBase, ImageSize);
+ if (!WaitForKey())
+ {
+ gRT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
+ }
+ goto CallOriginal;
+ }
+
+ // Determine if we're starting winload.efi, bootmgr.efi (when booting a WIM), or something else
+ FileType = GetInputFileType((UINT8*)ImageBase, (UINTN)ImageSize);
+ if (FileType != WinloadEfi && FileType != BootmgrEfi)
+ {
+ // Nothing for us to do
+ DEBUG((DEBUG_INFO, "HookedBootmanagerImgArchStartBootApplication: booting application of type %S; not winload.efi or bootmgr.efi. No further patches will be applied.\r\n",
+ FileTypeToString(FileType)));
+ goto CallOriginal;
+ }
+
+ // Print info
+ Print(L"[ %S!ImgArchStartBootApplication ]\r\n", (OriginalFunctionBytes == gBootmgrImgArchStartBootApplicationBackup ? L"bootmgr" : L"bootmgfw"));
+ Print(L"ImageBase: 0x%p\r\n", ImageBase);
+ Print(L"ImageSize: %lx\r\n", ImageSize);
+ Print(L"File type: %S\r\n", FileTypeToString(FileType));
+ Print(L"EntryPoint: 0x%p\r\n", ((UINT8*)ImageBase + HEADER_FIELD(NtHeaders, AddressOfEntryPoint)));
+ Print(L"AppEntry:\r\n");
+ Print(L" Signature: %a\r\n", AppEntry->Signature);
+ Print(L" Flags: %lx\r\n", AppEntry->Flags);
+ Print(L" GUID: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x\r\n",
+ AppEntry->Guid.Data1, AppEntry->Guid.Data2, AppEntry->Guid.Data3,
+ AppEntry->Guid.Data4[0], AppEntry->Guid.Data4[1], AppEntry->Guid.Data4[2], AppEntry->Guid.Data4[3],
+ AppEntry->Guid.Data4[4], AppEntry->Guid.Data4[5], AppEntry->Guid.Data4[6], AppEntry->Guid.Data4[7]);
+#ifdef EFI_DEBUG
+ // Stuff likely no one cares about
+ Print(L" Unknown: %lx %lx %lx %lx\r\n", AppEntry->Unknown[0], AppEntry->Unknown[1], AppEntry->Unknown[2], AppEntry->Unknown[3]);
+ Print(L" BcdData:\r\n");
+ Print(L" Type: %lx\r\n", AppEntry->BcdData.Type);
+ Print(L" DataOffset: %lx\r\n", AppEntry->BcdData.DataOffset);
+ Print(L" DataSize: %lx\r\n", AppEntry->BcdData.DataSize);
+ Print(L" ListOffset: %lx\r\n", AppEntry->BcdData.ListOffset);
+ Print(L" NextEntryOffset: %lx\r\n", AppEntry->BcdData.NextEntryOffset);
+ Print(L" Empty: %lx\r\n", AppEntry->BcdData.Empty);
+#endif
+
+ if (FileType == WinloadEfi)
+ {
+ // Patch winload.efi
+ PatchWinload(ImageBase,
+ NtHeaders);
+ }
+ else if (FileType == BootmgrEfi)
+ {
+ // Call PatchBootManager a second time; this time to patch bootmgr.efi
+ PatchBootManager(FileType,
+ ImageBase,
+ ImageSize);
+ }
+
+CallOriginal:
+ if (FileType == WinloadEfi || FileType == BootmgrEfi)
+ {
+ // Clear screen
+ gST->ConOut->EnableCursor(gST->ConOut, FALSE);
+ SetConsoleTextColour((UINTN)((OriginalAttribute >> 4) & 0x7), TRUE);
+ }
+
+ // Call the original function to transfer execution to the boot application entry point; normally winload.efi!OslMain or bootmgr.efi!BmMain.
+ // If FileType != WinloadEfi && FileType != BootmgrEfi, no further patches will be applied because this is some other application being started.
+ CONST BOOLEAN VistaOrSevenBootManager = BootOption == MAX_UINT32;
+ return VistaOrSevenBootManager
+ ? ((t_ImgArchStartBootApplication_Vista)OriginalFunction)(AppEntry, ImageBase, ImageSize, ReturnArguments)
+ : ((t_ImgArchStartBootApplication_Eight)OriginalFunction)(AppEntry, ImageBase, ImageSize, BootOption, ReturnArguments);
+}
+
+//
+// bootmgfw!ImgArchEfiStartBootApplication hook to patch either winload.efi or bootmgr.efi, Windows Vista/7 version.
+// This has to be a separate function from the bootmgr hook because their backup and return addresses will differ
+//
+STATIC
+EFI_STATUS
+EFIAPI
+HookedBootmgfwImgArchEfiStartBootApplication_Vista(
+ IN PBL_APPLICATION_ENTRY AppEntry,
+ IN VOID* ImageBase,
+ IN UINT32 ImageSize,
+ OUT PBL_RETURN_ARGUMENTS ReturnArguments
+ )
+{
+ return HookedBootManagerImgArchStartBootApplication(AppEntry,
+ ImageBase,
+ ImageSize,
+ MAX_UINT32,
+ ReturnArguments,
+ gOriginalBootmgfwImgArchStartBootApplication,
+ gBootmgfwImgArchStartBootApplicationBackup);
+}
+
+//
+// bootmgfw!ImgArch[Efi]StartBootApplication hook to patch either winload.efi or bootmgr.efi, Windows >= 8 version.
+// This has to be a separate function from the bootmgr hook because their backup and return addresses will differ
+//
+STATIC
+EFI_STATUS
+EFIAPI
+HookedBootmgfwImgArchStartBootApplication_Eight(
+ IN PBL_APPLICATION_ENTRY AppEntry,
+ IN VOID* ImageBase,
+ IN UINT32 ImageSize,
+ IN UINT32 BootOption,
+ OUT PBL_RETURN_ARGUMENTS ReturnArguments
+ )
+{
+ return HookedBootManagerImgArchStartBootApplication(AppEntry,
+ ImageBase,
+ ImageSize,
+ BootOption,
+ ReturnArguments,
+ gOriginalBootmgfwImgArchStartBootApplication,
+ gBootmgfwImgArchStartBootApplicationBackup);
+}
+
+//
+// bootmgr!ImgArchEfiStartBootApplication hook to patch winload.efi, Windows Vista/7 version.
+// This has to be a separate function from the bootmgfw hook because their backup and return addresses will differ
+//
+STATIC
+EFI_STATUS
+EFIAPI
+HookedBootmgrImgArchEfiStartBootApplication_Vista(
+ IN PBL_APPLICATION_ENTRY AppEntry,
+ IN VOID* ImageBase,
+ IN UINT32 ImageSize,
+ OUT PBL_RETURN_ARGUMENTS ReturnArguments
+ )
+{
+ return HookedBootManagerImgArchStartBootApplication(AppEntry,
+ ImageBase,
+ ImageSize,
+ MAX_UINT32,
+ ReturnArguments,
+ gOriginalBootmgrImgArchStartBootApplication,
+ gBootmgrImgArchStartBootApplicationBackup);
+}
+
+//
+// bootmgr!ImgArch[Efi]StartBootApplication hook to patch winload.efi, Windows >= 8 version.
+// This has to be a separate function from the bootmgfw hook because their backup and return addresses will differ
+//
+STATIC
+EFI_STATUS
+EFIAPI
+HookedBootmgrImgArchStartBootApplication_Eight(
+ IN PBL_APPLICATION_ENTRY AppEntry,
+ IN VOID* ImageBase,
+ IN UINT32 ImageSize,
+ IN UINT32 BootOption,
+ OUT PBL_RETURN_ARGUMENTS ReturnArguments
+ )
+{
+ return HookedBootManagerImgArchStartBootApplication(AppEntry,
+ ImageBase,
+ ImageSize,
+ BootOption,
+ ReturnArguments,
+ gOriginalBootmgrImgArchStartBootApplication,
+ gBootmgrImgArchStartBootApplicationBackup);
+}
+
+//
+// Patches the Windows Boot Manager (either bootmgfw.efi or bootmgr.efi; normally the former unless booting a WIM file)
+//
+EFI_STATUS
+EFIAPI
+PatchBootManager(
+ IN INPUT_FILETYPE FileType,
+ IN VOID* ImageBase,
+ IN UINTN ImageSize
+ )
+{
+ if (gBootmgfwHandle == NULL)
+ return EFI_NOT_STARTED;
+
+ ASSERT(FileType == BootmgfwEfi || FileType == BootmgrEfi);
+
+ // Get PE headers
+ CONST BOOLEAN PatchingBootmgrEfi = FileType == BootmgrEfi;
+ CONST CHAR16* ShortFileName = PatchingBootmgrEfi ? L"bootmgr" : L"bootmgfw";
+ CONST PEFI_IMAGE_NT_HEADERS NtHeaders = RtlpImageNtHeaderEx(ImageBase, ImageSize);
+ EFI_STATUS Status;
+ if (NtHeaders == NULL)
+ {
+ Status = EFI_LOAD_ERROR;
+ Print(L"\r\nPatchBootManager: %S.efi PE image at 0x%p with size 0x%llx is invalid!\r\nPress any key to continue anyway, or press ESC to reboot.\r\n",
+ ShortFileName, ImageBase, ImageSize);
+ if (!WaitForKey())
+ {
+ gRT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
+ }
+ goto Exit;
+ }
+
+ // Print file and version info
+ UINT16 MajorVersion = 0, MinorVersion = 0, BuildNumber = 0, Revision = 0;
+ Status = GetPeFileVersionInfo(ImageBase, &MajorVersion, &MinorVersion, &BuildNumber, &Revision, NULL);
+ if (EFI_ERROR(Status))
+ Print(L"\r\nPatchBootManager: WARNING: failed to obtain %S.efi version info. Status: %llx\r\n", ShortFileName, Status);
+ else
+ {
+ Print(L"\r\nPatching %S.efi v%u.%u.%u.%u...\r\n", ShortFileName, MajorVersion, MinorVersion, BuildNumber, Revision);
+
+ // Check if this is a supported boot manager version. All patches should work on all versions since Vista SP1,
+ // except for the ImgpFilterValidationFailure patch because this function only exists on Windows 7 and higher.
+ if (BuildNumber < 6001)
+ {
+ Print(L"\r\nPatchBootManager: ERROR: Unsupported %S.efi image version.\r\n"
+ L"The minimum supported boot manager version is Windows Vista SP1.\r\n"
+ L"It is recommended to use the Windows 10 boot manager even when running an older OS.\r\n", ShortFileName);
+ Status = EFI_UNSUPPORTED;
+ goto Exit;
+ }
+ }
+
+ // Find [bootmgfw|bootmgr]!ImgArch[Efi]StartBootApplication
+ CONST CHAR16* FunctionName = BuildNumber >= 17134 ? L"ImgArchStartBootApplication" : L"ImgArchEfiStartBootApplication";
+ CONST PEFI_IMAGE_SECTION_HEADER CodeSection = IMAGE_FIRST_SECTION(NtHeaders);
+ UINT8* Found = NULL;
+ Status = FindPattern(SigImgArchStartBootApplication,
+ 0xCC,
+ sizeof(SigImgArchStartBootApplication),
+ (UINT8*)ImageBase + CodeSection->VirtualAddress,
+ CodeSection->SizeOfRawData,
+ (VOID**)&Found);
+ if (EFI_ERROR(Status))
+ {
+ Print(L"\r\nPatchBootManager: failed to find %S!%S signature. Status: %llx\r\n", ShortFileName, FunctionName, Status);
+ goto Exit;
+ }
+
+ // Found signature; backtrack to function start
+ // Note: pOriginalAddress is a pointer to a (function) pointer, because the original address depends on the type of boot manager we are patching.
+ VOID **pOriginalAddress = PatchingBootmgrEfi ? &gOriginalBootmgrImgArchStartBootApplication : &gOriginalBootmgfwImgArchStartBootApplication;
+ *pOriginalAddress = (VOID*)BacktrackToFunctionStart(Found, MAX((UINT8*)ImageBase + CodeSection->VirtualAddress, Found - 1024));
+ CONST VOID* OriginalAddress = *pOriginalAddress;
+ if (OriginalAddress == NULL)
+ {
+ Print(L"\r\nPatchBootManager: failed to find %S!%S function start [signature at 0x%p].\r\n", ShortFileName, FunctionName, (VOID*)Found);
+ Status = EFI_NOT_FOUND;
+ goto Exit;
+ }
+
+ // Found
+ VOID* HookAddress;
+ if (BuildNumber < 9200)
+ HookAddress = PatchingBootmgrEfi ? (VOID*)&HookedBootmgrImgArchEfiStartBootApplication_Vista : (VOID*)&HookedBootmgfwImgArchEfiStartBootApplication_Vista;
+ else
+ HookAddress = PatchingBootmgrEfi ? (VOID*)&HookedBootmgrImgArchStartBootApplication_Eight : (VOID*)&HookedBootmgfwImgArchStartBootApplication_Eight;
+ UINT8* BackupAddress = PatchingBootmgrEfi ? gBootmgrImgArchStartBootApplicationBackup : gBootmgfwImgArchStartBootApplicationBackup;
+ Print(L"\r\nFound %S!%S at 0x%p.\r\n", ShortFileName, FunctionName, (VOID*)OriginalAddress);
+ Print(L"Hooked%S%S at 0x%p.\r\n", (PatchingBootmgrEfi ? L"Bootmgr" : L"Bootmgfw"), FunctionName, HookAddress);
+
+ CONST EFI_TPL Tpl = gBS->RaiseTPL(TPL_HIGH_LEVEL); // Note: implies cli
+
+ // Backup original function prologue
+ CopyMem(BackupAddress, (VOID*)OriginalAddress, sizeof(gHookTemplate));
+
+ // Place faux call (push addr, ret) at the start of the function to transfer execution to our hook
+ CopyMem((VOID*)OriginalAddress, (VOID*)gHookTemplate, sizeof(gHookTemplate));
+ *(UINTN*)((UINT8*)OriginalAddress + 2) = (UINTN)HookAddress;
+
+ gBS->RestoreTPL(Tpl);
+
+ // Patch ImgpValidateImageHash to allow custom boot loaders. This is completely
+ // optional (unless booting a custom winload.efi), and failures are ignored
+ PatchImgpValidateImageHash(FileType,
+ (UINT8*)ImageBase,
+ NtHeaders);
+
+ if (BuildNumber >= 7600)
+ {
+ // Patch ImgpFilterValidationFailure so it doesn't silently
+ // rat out every violation to a TPM or SI log. Also optional
+ PatchImgpFilterValidationFailure(FileType,
+ (UINT8*)ImageBase,
+ NtHeaders);
+ }
+
+Exit:
+ if (EFI_ERROR(Status))
+ {
+ // Patch failed. Prompt user to ask what they want to do
+ Print(L"\r\nPress any key to continue anyway, or press ESC to reboot.\r\n");
+ if (!WaitForKey())
+ {
+ gRT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
+ }
+ }
+ else
+ {
+ Print(L"Successfully patched %S!%S.\r\n", ShortFileName, FunctionName);
+ RtlSleep(2000);
+
+ if (gDriverConfig.WaitForKeyPress)
+ {
+ Print(L"\r\nPress any key to continue.\r\n");
+ WaitForKey();
+ }
+ }
+
+ // Return success, because even if the patch failed, the user chose not to reboot above
+ return EFI_SUCCESS;
+}