diff options
Diffstat (limited to 'EfiGuardDxe/PatchBootmgr.c')
-rw-r--r-- | EfiGuardDxe/PatchBootmgr.c | 372 |
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; +} |