mirror of
https://github.com/Mattiwatti/EfiGuard
synced 2026-08-17 06:26:03 -04:00
This function is highly PE-specific, and pe.c is therefore a more suitable TU. Also rename it to FindFunctionStart, so as to not imply that any state (e.g. the virtual runtime address used for decoding, ..) is modified, nor that we necessarily need to be backtracking when calling it. This function can also be used more generally to verify whether an address is part of any function at all according to the PE exception directory. Misc: minor const correctness fixes
377 lines
14 KiB
C
377 lines
14 KiB
C
#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
|
|
};
|
|
#if defined(MDE_CPU_X64)
|
|
CONST UINTN gHookTemplateAddressOffset = 2;
|
|
#elif defined(MDE_CPU_IA32)
|
|
CONST UINTN gHookTemplateAddressOffset = 1;
|
|
#endif
|
|
|
|
|
|
// 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
|
|
CopyWpMem(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(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 CONST 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*)FindFunctionStart(ImageBase, NtHeaders, Found);
|
|
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
|
|
CopyWpMem((VOID*)OriginalAddress, gHookTemplate, sizeof(gHookTemplate));
|
|
CopyWpMem((UINT8*)OriginalAddress + gHookTemplateAddressOffset, (UINTN*)&HookAddress, sizeof(UINTN));
|
|
|
|
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,
|
|
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,
|
|
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;
|
|
}
|