mirror of
https://github.com/Mattiwatti/EfiGuard
synced 2026-08-17 06:26:03 -04:00
832 lines
30 KiB
C
832 lines
30 KiB
C
#include "EfiGuardDxe.h"
|
|
|
|
#include <Guid/Acpi.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
|
|
t_OslFwpKernelSetupPhase1 gOriginalOslFwpKernelSetupPhase1 = NULL;
|
|
UINT8 gOslFwpKernelSetupPhase1Backup[sizeof(gHookTemplate)] = { 0 };
|
|
|
|
|
|
// Signature for winload!OslFwpKernelSetupPhase1+XX, where the value of XX needs to be determined by backtracking.
|
|
// Windows 10 RS4 and later only. On older OSes, and on Windows 10 as fallback, OslFwpKernelSetupPhase1 is found via xrefs to EfipGetRsdt
|
|
STATIC CONST UINT8 SigOslFwpKernelSetupPhase1[] = {
|
|
0x89, 0xCC, 0x24, 0x01, 0x00, 0x00, // mov [REG+124h], r32
|
|
0xE8, 0xCC, 0xCC, 0xCC, 0xCC, // call BlBdStop
|
|
0xCC, 0x8B, 0xCC // mov r32, r/m32
|
|
};
|
|
|
|
STATIC UNICODE_STRING ImgpFilterValidationFailureMessage = RTL_CONSTANT_STRING(L"*** Windows is unable to verify the signature of"); // newline, etc etc...
|
|
|
|
// Signature for winload!BlStatusPrint. This is only needed if winload.efi does not export it (RS4 and earlier)
|
|
// Windows 10 only. I could find a universal signature for this, but I rarely need the debugger output anymore...
|
|
STATIC CONST UINT8 SigBlStatusPrint[] = {
|
|
0x48, 0x8B, 0xC4, // mov rax, rsp
|
|
0x48, 0x89, 0x48, 0x08, // mov [rax+8], rcx
|
|
0x48, 0x89, 0x50, 0x10, // mov [rax+10h], rdx
|
|
0x4C, 0x89, 0x40, 0x18, // mov [rax+18h], r8
|
|
0x4C, 0x89, 0x48, 0x20, // mov [rax+20h], r9
|
|
0x53, // push rbx
|
|
0x48, 0x83, 0xEC, 0x40, // sub rsp, 40h
|
|
0xE8, 0xCC, 0xCC, 0xCC, 0xCC, // call BlBdDebuggerEnabled
|
|
0x84, 0xC0, // test al, al
|
|
0x74, 0xCC // jz XX
|
|
};
|
|
|
|
// EFI vendor GUID used by Microsoft
|
|
STATIC CONST EFI_GUID MicrosoftVendorGuid = {
|
|
0x77fa9abd, 0x0359, 0x4d32, { 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b }
|
|
};
|
|
|
|
// EFI variable used to set VBS enablement. Set by SecConfig.efi when disabling VBS/IUM,
|
|
// read (and then deleted) by winload.efi during boot
|
|
STATIC CONST CHAR16 VbsPolicyDisabledVariableName[] = L"VbsPolicyDisabled";
|
|
|
|
|
|
NTSTATUS
|
|
EFIAPI
|
|
BlStatusPrintNoop(
|
|
IN CONST CHAR16 *Format,
|
|
...
|
|
)
|
|
{
|
|
return (NTSTATUS)0xC00000BBL; // STATUS_NOT_SUPPORTED
|
|
}
|
|
|
|
t_BlStatusPrint gBlStatusPrint = BlStatusPrintNoop;
|
|
|
|
//
|
|
// Gets a loaded module entry from the boot loader's LoadOrderList
|
|
//
|
|
STATIC
|
|
PKLDR_DATA_TABLE_ENTRY
|
|
EFIAPI
|
|
GetBootLoadedModule(
|
|
IN CONST LIST_ENTRY* LoadOrderListHead,
|
|
IN CONST CHAR16* ModuleName
|
|
)
|
|
{
|
|
if (ModuleName == NULL || LoadOrderListHead == NULL)
|
|
return NULL;
|
|
|
|
for (LIST_ENTRY* ListEntry = LoadOrderListHead->ForwardLink; ListEntry != LoadOrderListHead; ListEntry = ListEntry->ForwardLink)
|
|
{
|
|
// This is fairly heavy abuse of CR(), but legal C because (only) the first field of a struct is guaranteed to be at offset 0 (C99 6.7.2.1, point 13)
|
|
CONST PBLDR_DATA_TABLE_ENTRY Entry = (PBLDR_DATA_TABLE_ENTRY)BASE_CR(ListEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
|
|
if (Entry != NULL && StrniCmp(Entry->KldrEntry.BaseDllName.Buffer, ModuleName, (Entry->KldrEntry.BaseDllName.Length / sizeof(CHAR16))) == 0)
|
|
return &Entry->KldrEntry;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Disables VBS for this boot
|
|
//
|
|
STATIC
|
|
EFI_STATUS
|
|
EFIAPI
|
|
DisableVbs(
|
|
VOID
|
|
)
|
|
{
|
|
CONST BOOLEAN Disabled = TRUE;
|
|
UINT32 Attributes;
|
|
UINTN Size = 0;
|
|
|
|
// Clear VbsPolicyDisabled variable if needed
|
|
EFI_STATUS Status = gRT->GetVariable((CHAR16*)VbsPolicyDisabledVariableName,
|
|
(EFI_GUID*)&MicrosoftVendorGuid,
|
|
&Attributes,
|
|
&Size,
|
|
NULL);
|
|
if (Status != EFI_NOT_FOUND &&
|
|
(Attributes != (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS) || Size != sizeof(Disabled)))
|
|
{
|
|
gRT->SetVariable((CHAR16*)VbsPolicyDisabledVariableName,
|
|
(EFI_GUID*)&MicrosoftVendorGuid,
|
|
0,
|
|
0,
|
|
NULL);
|
|
}
|
|
|
|
// Write the new value
|
|
Status = gRT->SetVariable((CHAR16*)VbsPolicyDisabledVariableName,
|
|
(EFI_GUID*)&MicrosoftVendorGuid,
|
|
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
|
|
sizeof(Disabled),
|
|
(BOOLEAN*)&Disabled);
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// winload.efi!OslFwpKernelSetupPhase1 hook to patch ntoskrnl.exe
|
|
//
|
|
EFI_STATUS
|
|
EFIAPI
|
|
HookedOslFwpKernelSetupPhase1(
|
|
IN PLOADER_PARAMETER_BLOCK LoaderBlock
|
|
)
|
|
{
|
|
// Restore the original function bytes that we replaced with our hook
|
|
CopyWpMem((VOID*)gOriginalOslFwpKernelSetupPhase1, gOslFwpKernelSetupPhase1Backup, sizeof(gHookTemplate));
|
|
|
|
UINT8* LoadOrderListHeadAddress = (UINT8*)&LoaderBlock->LoadOrderListHead;
|
|
if (gKernelPatchInfo.WinloadBuildNumber < 7600)
|
|
{
|
|
// We are booting Vista or some other fossil, which means that our LOADER_PARAMETER_BLOCK declaration in no way matches what is
|
|
// actually being passed by the loader. Notably, the first four UINT32 fields are absent, so fix up the list entry pointer.
|
|
LoadOrderListHeadAddress -= FIELD_OFFSET(LOADER_PARAMETER_BLOCK, LoadOrderListHead);
|
|
}
|
|
|
|
// Get the kernel entry from the loader block's LoadOrderList
|
|
CONST PKLDR_DATA_TABLE_ENTRY KernelEntry = GetBootLoadedModule((LIST_ENTRY*)LoadOrderListHeadAddress, L"ntoskrnl.exe");
|
|
if (KernelEntry == NULL)
|
|
{
|
|
gKernelPatchInfo.Status = EFI_LOAD_ERROR;
|
|
PRINT_KERNEL_PATCH_MSG(L"[HookedOslFwpKernelSetupPhase1] Failed to find ntoskrnl.exe in LoadOrderList!\r\n");
|
|
goto CallOriginal;
|
|
}
|
|
|
|
VOID* KernelBase = KernelEntry->DllBase;
|
|
CONST UINT32 KernelSize = KernelEntry->SizeOfImage;
|
|
CONST PEFI_IMAGE_NT_HEADERS NtHeaders = KernelBase != NULL && KernelSize > 0
|
|
? RtlpImageNtHeaderEx(KernelBase, (UINTN)KernelSize)
|
|
: NULL;
|
|
if (KernelBase == NULL || KernelSize == 0)
|
|
{
|
|
gKernelPatchInfo.Status = EFI_NOT_FOUND;
|
|
PRINT_KERNEL_PATCH_MSG(L"[HookedOslFwpKernelSetupPhase1] Kernel image at 0x%p with size 0x%lx is invalid!\r\n", KernelBase, KernelSize);
|
|
goto CallOriginal;
|
|
}
|
|
|
|
// Patch the kernel
|
|
gKernelPatchInfo.KernelBase = KernelBase;
|
|
gKernelPatchInfo.Status = PatchNtoskrnl(KernelBase,
|
|
NtHeaders);
|
|
|
|
CallOriginal:
|
|
// No error handling here (not a lot of options). This is done in the ExitBootServices() callback which reads the patch status
|
|
|
|
// Call the original function to transfer execution back to winload!OslFwpKernelSetupPhase1
|
|
return gOriginalOslFwpKernelSetupPhase1(LoaderBlock);
|
|
}
|
|
|
|
//
|
|
// Patches ImgpValidateImageHash in bootmgfw.efi, bootmgr.efi, and winload.[efi|exe] to allow loading modified kernels and boot loaders.
|
|
// Failures are ignored because this patch is not needed for the bootkit to work
|
|
//
|
|
EFI_STATUS
|
|
EFIAPI
|
|
PatchImgpValidateImageHash(
|
|
IN INPUT_FILETYPE FileType,
|
|
IN CONST UINT8* ImageBase,
|
|
IN PEFI_IMAGE_NT_HEADERS NtHeaders
|
|
)
|
|
{
|
|
// This works on pretty much anything really
|
|
ASSERT(FileType == WinloadExe || FileType == BootmgfwEfi || FileType == BootmgrEfi || FileType == WinloadEfi);
|
|
CONST CHAR16* ShortName = FileType == BootmgfwEfi ? L"bootmgfw" : (FileType == BootmgrEfi ? L"bootmgr" : L"winload");
|
|
|
|
CONST PEFI_IMAGE_SECTION_HEADER CodeSection = IMAGE_FIRST_SECTION(NtHeaders);
|
|
|
|
CONST UINT32 CodeSizeOfRawData = CodeSection->SizeOfRawData;
|
|
CONST UINT8* CodeStartVa = ImageBase + CodeSection->VirtualAddress;
|
|
|
|
Print(L"== Disassembling .text to find %S!ImgpValidateImageHash ==\r\n", ShortName);
|
|
UINT8* AndMinusFortyOneAddress = NULL;
|
|
|
|
// Initialize Zydis
|
|
ZYDIS_CONTEXT Context;
|
|
ZyanStatus Status = ZydisInit(NtHeaders, &Context);
|
|
if (!ZYAN_SUCCESS(Status))
|
|
{
|
|
Print(L"Failed to initialize disassembler engine.\r\n");
|
|
return EFI_LOAD_ERROR;
|
|
}
|
|
|
|
Context.Length = CodeSizeOfRawData;
|
|
Context.Offset = 0;
|
|
|
|
// Start decode loop
|
|
while ((Context.InstructionAddress = (ZyanU64)(CodeStartVa + Context.Offset),
|
|
Status = ZydisDecoderDecodeFull(&Context.Decoder,
|
|
(VOID*)Context.InstructionAddress,
|
|
Context.Length - Context.Offset,
|
|
&Context.Instruction,
|
|
Context.Operands)) != ZYDIS_STATUS_NO_MORE_DATA)
|
|
{
|
|
if (!ZYAN_SUCCESS(Status))
|
|
{
|
|
Context.Offset++;
|
|
continue;
|
|
}
|
|
|
|
// Check if this is 'and REG32, 0FFFFFFD7h'
|
|
if (Context.Instruction.operand_count == 3 &&
|
|
(Context.Instruction.length == 3 || Context.Instruction.length == 4) &&
|
|
Context.Instruction.mnemonic == ZYDIS_MNEMONIC_AND &&
|
|
Context.Operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
|
Context.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
|
|
Context.Operands[1].imm.is_signed == ZYAN_TRUE &&
|
|
Context.Operands[1].imm.value.s == (ZyanI64)((ZyanI32)0xFFFFFFD7) && // Sign extend to 64 bits
|
|
FindFunctionStart(ImageBase, NtHeaders, (UINT8*)Context.InstructionAddress) != NULL)
|
|
{
|
|
AndMinusFortyOneAddress = (UINT8*)Context.InstructionAddress;
|
|
break;
|
|
}
|
|
|
|
Context.Offset += Context.Instruction.length;
|
|
}
|
|
|
|
// Backtrack to function start
|
|
UINT8* ImgpValidateImageHash = FindFunctionStart(ImageBase, NtHeaders, AndMinusFortyOneAddress);
|
|
if (ImgpValidateImageHash == NULL)
|
|
{
|
|
Print(L" Failed to find %S!ImgpValidateImageHash%S.\r\n",
|
|
ShortName, (AndMinusFortyOneAddress == NULL ? L" 'and xxx, 0FFFFFFD7h' instruction" : L""));
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
// Find select occurrences of 'return STATUS_IMAGE_CHECKSUM_MISMATCH' and avoid them.
|
|
Context.Length = CodeSizeOfRawData;
|
|
Context.Offset = 0;
|
|
|
|
// Start decode loop
|
|
while ((Context.InstructionAddress = (ZyanU64)(CodeStartVa + Context.Offset),
|
|
Status = ZydisDecoderDecodeFull(&Context.Decoder,
|
|
(VOID*)Context.InstructionAddress,
|
|
Context.Length - Context.Offset,
|
|
&Context.Instruction,
|
|
Context.Operands)) != ZYDIS_STATUS_NO_MORE_DATA)
|
|
{
|
|
if (!ZYAN_SUCCESS(Status))
|
|
{
|
|
Context.Offset++;
|
|
continue;
|
|
}
|
|
|
|
// Check if this is 'mov REG32, C0000221h'
|
|
if (Context.Instruction.operand_count == 2 &&
|
|
(Context.Instruction.length == 5 || Context.Instruction.length == 6) &&
|
|
Context.Instruction.mnemonic == ZYDIS_MNEMONIC_MOV &&
|
|
Context.Operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
|
Context.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
|
|
Context.Operands[1].imm.is_signed == ZYAN_TRUE &&
|
|
Context.Operands[1].imm.value.s == (ZyanI64)((ZyanI32)0xC0000221) &&
|
|
FindFunctionStart(ImageBase, NtHeaders, (UINT8*)Context.InstructionAddress) != NULL)
|
|
{
|
|
UINT8* CheckJccAddress = (UINT8*)(Context.InstructionAddress - 2);
|
|
if ((*CheckJccAddress & 0x70) == 0x70) // Jcc rel8?
|
|
{
|
|
SetWpMem(CheckJccAddress, 1, 0xEB); // Change to jmp rel8
|
|
}
|
|
else
|
|
{
|
|
CheckJccAddress = (UINT8*)(Context.InstructionAddress - 6);
|
|
if (CheckJccAddress[0] == 0x0F && (CheckJccAddress[1] & 0x80) == 0x80) // Jcc rel32?
|
|
{
|
|
CONST UINT16 NopPlusJmpOpcode = 0xE990;
|
|
CopyWpMem(CheckJccAddress, &NopPlusJmpOpcode, sizeof(NopPlusJmpOpcode)); // Change to nop, jmp rel32
|
|
}
|
|
}
|
|
}
|
|
|
|
Context.Offset += Context.Instruction.length;
|
|
}
|
|
|
|
// Apply the patch
|
|
CONST UINT32 Ok = 0xC3C033; // xor eax, eax, ret
|
|
CopyWpMem(ImgpValidateImageHash, &Ok, sizeof(Ok));
|
|
|
|
// Print info
|
|
Print(L" Patched %S!ImgpValidateImageHash [RVA: 0x%X].\r\n",
|
|
ShortName, (UINT32)(ImgpValidateImageHash - ImageBase));
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Patches ImgpFilterValidationFailure in bootmgfw.efi, bootmgr.efi, and winload.[efi|exe]
|
|
// Failures are ignored because this patch is not needed for the bootkit to work
|
|
//
|
|
EFI_STATUS
|
|
EFIAPI
|
|
PatchImgpFilterValidationFailure(
|
|
IN INPUT_FILETYPE FileType,
|
|
IN CONST UINT8* ImageBase,
|
|
IN PEFI_IMAGE_NT_HEADERS NtHeaders
|
|
)
|
|
{
|
|
// This works on pretty much anything really
|
|
ASSERT(FileType == WinloadExe || FileType == BootmgfwEfi || FileType == BootmgrEfi || FileType == WinloadEfi);
|
|
CONST CHAR16* ShortName = FileType == BootmgfwEfi ? L"bootmgfw" : (FileType == BootmgrEfi ? L"bootmgr" : L"winload");
|
|
|
|
// Find .text and/or .rdata sections
|
|
PEFI_IMAGE_SECTION_HEADER PatternSection = NULL, CodeSection = NULL;
|
|
PEFI_IMAGE_SECTION_HEADER Section = IMAGE_FIRST_SECTION(NtHeaders);
|
|
for (UINT16 i = 0; i < NtHeaders->FileHeader.NumberOfSections; ++i)
|
|
{
|
|
if (CompareMem(Section->Name, ".text", sizeof(".text") - 1) == 0)
|
|
CodeSection = Section;
|
|
if (((FileType == BootmgfwEfi || FileType == BootmgrEfi) &&
|
|
CompareMem(Section->Name, ".text", sizeof(".text") - 1) == 0) // [bootmgfw|bootmgr].efi (usually) has no .rdata section, and starting at .text is always fine
|
|
||
|
|
((FileType == WinloadExe || FileType == WinloadEfi) &&
|
|
CompareMem(Section->Name, ".rdata", sizeof(".rdata") - 1) == 0)) // For winload.[exe|efi] the string is in .rdata
|
|
PatternSection = Section;
|
|
Section++;
|
|
}
|
|
|
|
ASSERT(PatternSection != NULL);
|
|
ASSERT(CodeSection != NULL);
|
|
|
|
CONST UINT32 PatternStartRva = PatternSection->VirtualAddress;
|
|
CONST UINT32 PatternSizeOfRawData = PatternSection->SizeOfRawData;
|
|
CONST UINT8* PatternStartVa = ImageBase + PatternStartRva;
|
|
|
|
CHAR8 SectionName[EFI_IMAGE_SIZEOF_SHORT_NAME + 1];
|
|
CopyMem(SectionName, PatternSection->Name, EFI_IMAGE_SIZEOF_SHORT_NAME);
|
|
SectionName[EFI_IMAGE_SIZEOF_SHORT_NAME] = '\0';
|
|
Print(L"\r\n== Searching for load failure string in %a [RVA: 0x%X - 0x%X] ==\r\n",
|
|
SectionName, PatternStartRva, PatternStartRva + PatternSizeOfRawData);
|
|
|
|
// Search for the black screen of death string "Windows is unable to verify the integrity of the file [...]"
|
|
UINT8* IntegrityFailureStringAddress = NULL;
|
|
for (UINT8* Address = (UINT8*)PatternStartVa;
|
|
Address < ImageBase + NtHeaders->OptionalHeader.SizeOfImage - ImgpFilterValidationFailureMessage.MaximumLength;
|
|
++Address)
|
|
{
|
|
if (CompareMem(Address, ImgpFilterValidationFailureMessage.Buffer, ImgpFilterValidationFailureMessage.Length) == 0)
|
|
{
|
|
IntegrityFailureStringAddress = Address;
|
|
Print(L" Found load failure string at 0x%llx.\r\n", (UINTN)IntegrityFailureStringAddress);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (IntegrityFailureStringAddress == NULL)
|
|
{
|
|
Print(L" Failed to find load failure string.\r\n");
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
CONST UINT32 CodeStartRva = CodeSection->VirtualAddress;
|
|
CONST UINT32 CodeSizeOfRawData = CodeSection->SizeOfRawData;
|
|
CONST UINT8* CodeStartVa = ImageBase + CodeStartRva;
|
|
|
|
ZeroMem(SectionName, sizeof(SectionName));
|
|
CopyMem(SectionName, CodeSection->Name, EFI_IMAGE_SIZEOF_SHORT_NAME);
|
|
Print(L"== Disassembling %a to find %S!ImgpFilterValidationFailure ==\r\n", SectionName, ShortName);
|
|
UINT8* LeaIntegrityFailureAddress = NULL;
|
|
|
|
// Initialize Zydis
|
|
ZYDIS_CONTEXT Context;
|
|
ZyanStatus Status = ZydisInit(NtHeaders, &Context);
|
|
if (!ZYAN_SUCCESS(Status))
|
|
{
|
|
Print(L"Failed to initialize disassembler engine.\r\n");
|
|
return EFI_LOAD_ERROR;
|
|
}
|
|
|
|
Context.Length = CodeSizeOfRawData;
|
|
Context.Offset = 0;
|
|
|
|
// Start decode loop
|
|
while ((Context.InstructionAddress = (ZyanU64)(CodeStartVa + Context.Offset),
|
|
Status = ZydisDecoderDecodeFull(&Context.Decoder,
|
|
(VOID*)Context.InstructionAddress,
|
|
Context.Length - Context.Offset,
|
|
&Context.Instruction,
|
|
Context.Operands)) != ZYDIS_STATUS_NO_MORE_DATA)
|
|
{
|
|
if (!ZYAN_SUCCESS(Status))
|
|
{
|
|
Context.Offset++;
|
|
continue;
|
|
}
|
|
|
|
// Check if this is "lea REG, ds:[rip + offset_to_bsod_string]"
|
|
if (Context.Instruction.operand_count == 2 && Context.Instruction.mnemonic == ZYDIS_MNEMONIC_LEA &&
|
|
Context.Operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY &&
|
|
Context.Operands[1].mem.base == ZYDIS_REGISTER_RIP)
|
|
{
|
|
ZyanU64 OperandAddress = 0;
|
|
if (ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&Context.Instruction, &Context.Operands[1], Context.InstructionAddress, &OperandAddress)) &&
|
|
OperandAddress == (UINTN)IntegrityFailureStringAddress &&
|
|
FindFunctionStart(ImageBase, NtHeaders, (UINT8*)Context.InstructionAddress) != NULL)
|
|
{
|
|
LeaIntegrityFailureAddress = (UINT8*)Context.InstructionAddress;
|
|
Print(L" Found load instruction for load failure string at 0x%llx.\r\n", (UINTN)LeaIntegrityFailureAddress);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Context.Offset += Context.Instruction.length;
|
|
}
|
|
|
|
// Backtrack to function start
|
|
UINT8* ImgpFilterValidationFailure = FindFunctionStart(ImageBase, NtHeaders, LeaIntegrityFailureAddress);
|
|
if (ImgpFilterValidationFailure == NULL)
|
|
{
|
|
Print(L" Failed to find %S!ImgpFilterValidationFailure%S.\r\n",
|
|
ShortName, (LeaIntegrityFailureAddress == NULL ? L" load failure string load instruction" : L""));
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
// Apply the patch
|
|
CONST UINT32 Ok = 0xC3C033; // xor eax, eax, ret
|
|
CopyWpMem(ImgpFilterValidationFailure, &Ok, sizeof(Ok));
|
|
|
|
// Print info
|
|
Print(L" Patched %S!ImgpFilterValidationFailure [RVA: 0x%X].\r\n\r\n",
|
|
ShortName, (UINT32)(ImgpFilterValidationFailure - ImageBase));
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Finds OslFwpKernelSetupPhase1 in winload.efi
|
|
//
|
|
EFI_STATUS
|
|
EFIAPI
|
|
FindOslFwpKernelSetupPhase1(
|
|
IN CONST UINT8* ImageBase,
|
|
IN PEFI_IMAGE_NT_HEADERS NtHeaders,
|
|
IN PEFI_IMAGE_SECTION_HEADER CodeSection,
|
|
IN PEFI_IMAGE_SECTION_HEADER PatternSection,
|
|
IN UINT16 BuildNumber,
|
|
OUT UINT8** OslFwpKernelSetupPhase1Address
|
|
)
|
|
{
|
|
*OslFwpKernelSetupPhase1Address = NULL;
|
|
|
|
CONST UINT8* CodeStartVa = ImageBase + CodeSection->VirtualAddress;
|
|
CONST UINT32 CodeSizeOfRawData = CodeSection->SizeOfRawData;
|
|
CONST UINT8* PatternStartVa = ImageBase + PatternSection->VirtualAddress;
|
|
|
|
if (BuildNumber >= 17134)
|
|
{
|
|
// On Windows 10 RS4 and later, try simple pattern matching first since it will most likely work
|
|
UINT8* Found = NULL;
|
|
CONST EFI_STATUS Status = FindPattern(SigOslFwpKernelSetupPhase1,
|
|
0xCC,
|
|
sizeof(SigOslFwpKernelSetupPhase1),
|
|
(VOID*)CodeStartVa,
|
|
CodeSizeOfRawData,
|
|
(VOID**)&Found);
|
|
if (!EFI_ERROR(Status))
|
|
{
|
|
// Found signature; backtrack to function start
|
|
*OslFwpKernelSetupPhase1Address = FindFunctionStart(ImageBase, NtHeaders, Found);
|
|
if (*OslFwpKernelSetupPhase1Address != NULL)
|
|
{
|
|
Print(L"\r\nFound OslFwpKernelSetupPhase1 at 0x%llX.\r\n", (UINTN)(*OslFwpKernelSetupPhase1Address));
|
|
return EFI_SUCCESS; // Found; early out
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize Zydis
|
|
Print(L"\r\n== Disassembling .text to find OslFwpKernelSetupPhase1 ==\r\n");
|
|
ZYDIS_CONTEXT Context;
|
|
ZyanStatus Status = ZydisInit(NtHeaders, &Context);
|
|
if (!ZYAN_SUCCESS(Status))
|
|
{
|
|
Print(L"Failed to initialize disassembler engine.\r\n");
|
|
return EFI_LOAD_ERROR;
|
|
}
|
|
|
|
CONST VOID* BlBdStop = GetProcedureAddress((UINTN)ImageBase, NtHeaders, "BlBdStop");
|
|
if (BuildNumber >= 17134 && BlBdStop != NULL)
|
|
{
|
|
Context.Length = CodeSizeOfRawData;
|
|
Context.Offset = 6;
|
|
|
|
// Start decode loop
|
|
while ((Context.InstructionAddress = (ZyanU64)(CodeStartVa + Context.Offset),
|
|
Status = ZydisDecoderDecodeFull(&Context.Decoder,
|
|
(VOID*)Context.InstructionAddress,
|
|
Context.Length - Context.Offset,
|
|
&Context.Instruction,
|
|
Context.Operands)) != ZYDIS_STATUS_NO_MORE_DATA)
|
|
{
|
|
if (!ZYAN_SUCCESS(Status))
|
|
{
|
|
Context.Offset++;
|
|
continue;
|
|
}
|
|
|
|
// Check if this is 'call BlBdStop'
|
|
if (Context.Instruction.operand_count == 4 &&
|
|
Context.Operands[0].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && Context.Operands[0].imm.is_relative == ZYAN_TRUE &&
|
|
Context.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL)
|
|
{
|
|
ZyanU64 OperandAddress = 0;
|
|
if (ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&Context.Instruction, &Context.Operands[0], Context.InstructionAddress, &OperandAddress)) &&
|
|
OperandAddress == (UINTN)BlBdStop)
|
|
{
|
|
// Check if the preceding instruction is 'mov [REG+124h], r32'
|
|
CONST UINT8* CallBlBdStopAddress = (UINT8*)Context.InstructionAddress;
|
|
if ((CallBlBdStopAddress[-6] == 0x89 || CallBlBdStopAddress[-6] == 0x8B) &&
|
|
*(UINT32*)(&CallBlBdStopAddress[-4]) == 0x124 &&
|
|
(*OslFwpKernelSetupPhase1Address = FindFunctionStart(ImageBase, NtHeaders, CallBlBdStopAddress)) != NULL)
|
|
{
|
|
Print(L" Found OslFwpKernelSetupPhase1 at 0x%llX.\r\n\r\n", (UINTN)(*OslFwpKernelSetupPhase1Address));
|
|
return EFI_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
Context.Offset += Context.Instruction.length;
|
|
}
|
|
}
|
|
|
|
// On RS4 and later, the previous method really should have worked
|
|
ASSERT(BuildNumber < 17134);
|
|
|
|
// On older versions, use some convoluted but robust logic to find OslFwpKernelSetupPhase1 by matching xrefs to EfipGetRsdt.
|
|
// This of course implies finding EfipGetRsdt first. After that, find all calls to this function, and for each, calculate
|
|
// the distance from the start of the function to the call. OslFwpKernelSetupPhase1 is reliably (Vista through 10)
|
|
// the function that has the smallest value for this distance, i.e. the call happens very early in the function.
|
|
Print(L"\r\n== Searching for EfipGetRsdt pattern in .text ==\r\n");
|
|
|
|
// Search for EFI ACPI 2.0 table GUID: { 8868e871-e4f1-11d3-bc22-0080c73c8881 }
|
|
UINT8* PatternAddress = NULL;
|
|
for (UINT8* Address = (UINT8*)PatternStartVa;
|
|
Address < ImageBase + NtHeaders->OptionalHeader.SizeOfImage - sizeof(gEfiAcpi20TableGuid);
|
|
++Address)
|
|
{
|
|
if (CompareGuid((CONST GUID*)Address, &gEfiAcpi20TableGuid))
|
|
{
|
|
PatternAddress = Address;
|
|
Print(L" Found EFI ACPI 2.0 GUID at 0x%llX.\r\n", (UINTN)PatternAddress);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (PatternAddress == NULL)
|
|
{
|
|
Print(L" Failed to find EFI ACPI 2.0 GUID.\r\n");
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
Print(L"\r\n== Disassembling .text to find EfipGetRsdt ==\r\n");
|
|
UINT8* LeaEfiAcpiTableGuidAddress = NULL;
|
|
Context.Length = CodeSizeOfRawData;
|
|
Context.Offset = 0;
|
|
|
|
// Start decode loop
|
|
while ((Context.InstructionAddress = (ZyanU64)(CodeStartVa + Context.Offset),
|
|
Status = ZydisDecoderDecodeFull(&Context.Decoder,
|
|
(VOID*)Context.InstructionAddress,
|
|
Context.Length - Context.Offset,
|
|
&Context.Instruction,
|
|
Context.Operands)) != ZYDIS_STATUS_NO_MORE_DATA)
|
|
{
|
|
if (!ZYAN_SUCCESS(Status))
|
|
{
|
|
Context.Offset++;
|
|
continue;
|
|
}
|
|
|
|
// Check if this is "lea rcx, ds:[rip + offset_to_acpi20_guid]"
|
|
if (Context.Instruction.operand_count == 2 && Context.Instruction.mnemonic == ZYDIS_MNEMONIC_LEA &&
|
|
Context.Operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
|
Context.Operands[0].reg.value == ZYDIS_REGISTER_RCX &&
|
|
Context.Operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY &&
|
|
Context.Operands[1].mem.base == ZYDIS_REGISTER_RIP)
|
|
{
|
|
ZyanU64 OperandAddress = 0;
|
|
if (ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&Context.Instruction, &Context.Operands[1], Context.InstructionAddress, &OperandAddress)) &&
|
|
OperandAddress == (UINTN)PatternAddress &&
|
|
FindFunctionStart(ImageBase, NtHeaders, (UINT8*)Context.InstructionAddress) != NULL)
|
|
{
|
|
// Check for false positives (BlFwGetSystemTable)
|
|
CONST UINT8* Check = (UINT8*)(CodeStartVa + Context.Offset - 4); // 4 = length of 'lea rdx, [r11+18h]' which precedes this instruction in EfipGetRsdt
|
|
if (Check[0] == 0x49 && Check[1] == 0x8D && Check[2] == 0x53) // If no match, this is not EfipGetRsdt
|
|
{
|
|
LeaEfiAcpiTableGuidAddress = (UINT8*)Context.InstructionAddress;
|
|
Print(L" Found load instruction for EFI ACPI 2.0 GUID at 0x%llX.\r\n", (UINTN)LeaEfiAcpiTableGuidAddress);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Context.Offset += Context.Instruction.length;
|
|
}
|
|
|
|
if (LeaEfiAcpiTableGuidAddress == NULL)
|
|
{
|
|
Print(L" Failed to find load instruction for EFI ACPI 2.0 GUID.\r\n");
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
CONST UINT8* EfipGetRsdt = FindFunctionStart(ImageBase, NtHeaders, LeaEfiAcpiTableGuidAddress);
|
|
if (EfipGetRsdt == NULL)
|
|
{
|
|
Print(L" Failed to find EfipGetRsdt.\r\n");
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
Print(L" Found EfipGetRsdt at 0x%llX.\r\n", (UINTN)EfipGetRsdt);
|
|
UINT8* CallEfipGetRsdtAddress = NULL;
|
|
|
|
// Start decode loop
|
|
Context.Offset = 0;
|
|
UINTN ShortestDistanceToCall = MAX_UINTN;
|
|
while ((Context.InstructionAddress = (ZyanU64)(CodeStartVa + Context.Offset),
|
|
Status = ZydisDecoderDecodeFull(&Context.Decoder,
|
|
(VOID*)Context.InstructionAddress,
|
|
Context.Length - Context.Offset,
|
|
&Context.Instruction,
|
|
Context.Operands)) != ZYDIS_STATUS_NO_MORE_DATA)
|
|
{
|
|
if (!ZYAN_SUCCESS(Status))
|
|
{
|
|
Context.Offset++;
|
|
continue;
|
|
}
|
|
|
|
// Check if this is 'call IMM'
|
|
if (Context.Instruction.operand_count == 4 &&
|
|
Context.Operands[0].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && Context.Operands[0].imm.is_relative == ZYAN_TRUE &&
|
|
Context.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL)
|
|
{
|
|
// Check if this is 'call EfipGetRsdt'
|
|
ZyanU64 OperandAddress = 0;
|
|
if (ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&Context.Instruction, &Context.Operands[0], Context.InstructionAddress, &OperandAddress)) &&
|
|
OperandAddress == (UINTN)EfipGetRsdt &&
|
|
FindFunctionStart(ImageBase, NtHeaders, (UINT8*)Context.InstructionAddress) != NULL)
|
|
{
|
|
// Calculate the distance from the start of the function to the instruction. OslFwpKernelSetupPhase1 will always have the shortest distance
|
|
CONST UINTN StartOfFunction = (UINTN)FindFunctionStart(ImageBase, NtHeaders, (UINT8*)Context.InstructionAddress);
|
|
CONST UINTN Distance = Context.InstructionAddress - StartOfFunction;
|
|
if (Distance < ShortestDistanceToCall)
|
|
{
|
|
CallEfipGetRsdtAddress = (UINT8*)Context.InstructionAddress;
|
|
ShortestDistanceToCall = Distance;
|
|
}
|
|
}
|
|
}
|
|
|
|
Context.Offset += Context.Instruction.length;
|
|
}
|
|
|
|
if (CallEfipGetRsdtAddress == NULL)
|
|
{
|
|
Print(L" Failed to find a single 'call EfipGetRsdt' instruction.\r\n");
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
// Found
|
|
*OslFwpKernelSetupPhase1Address = CallEfipGetRsdtAddress - ShortestDistanceToCall;
|
|
Print(L" Found OslFwpKernelSetupPhase1 at 0x%llX.\r\n\r\n", (UINTN)(*OslFwpKernelSetupPhase1Address));
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Patches winload.efi
|
|
//
|
|
EFI_STATUS
|
|
EFIAPI
|
|
PatchWinload(
|
|
IN CONST VOID* ImageBase,
|
|
IN PEFI_IMAGE_NT_HEADERS NtHeaders
|
|
)
|
|
{
|
|
// Print file and version info
|
|
UINT16 MajorVersion = 0, MinorVersion = 0, BuildNumber = 0, Revision = 0;
|
|
EFI_STATUS Status = GetPeFileVersionInfo(ImageBase, &MajorVersion, &MinorVersion, &BuildNumber, &Revision, NULL);
|
|
if (EFI_ERROR(Status))
|
|
Print(L"\r\nPatchWinload: WARNING: failed to obtain winload.efi version info. Status: %llx\r\n", Status);
|
|
else
|
|
{
|
|
Print(L"\r\nPatching winload.efi v%u.%u.%u.%u...\r\n", MajorVersion, MinorVersion, BuildNumber, Revision);
|
|
|
|
// Some... adjustments... need to be made later on in the case of pre-Windows 7 loader blocks, so store the build number
|
|
gKernelPatchInfo.WinloadBuildNumber = BuildNumber;
|
|
|
|
// Check if this is a supported winload 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\nPatchWinload: ERROR: Unsupported winload.efi image version.\r\n");
|
|
Status = EFI_UNSUPPORTED;
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Find the .text and .rdata sections
|
|
PEFI_IMAGE_SECTION_HEADER CodeSection = NULL, PatternSection = NULL;
|
|
PEFI_IMAGE_SECTION_HEADER Section = IMAGE_FIRST_SECTION(NtHeaders);
|
|
for (UINT16 i = 0; i < NtHeaders->FileHeader.NumberOfSections; ++i)
|
|
{
|
|
CHAR8 SectionName[EFI_IMAGE_SIZEOF_SHORT_NAME + 1];
|
|
CopyMem(SectionName, Section->Name, EFI_IMAGE_SIZEOF_SHORT_NAME);
|
|
SectionName[EFI_IMAGE_SIZEOF_SHORT_NAME] = '\0';
|
|
|
|
if (AsciiStrCmp(SectionName, ".text") == 0)
|
|
CodeSection = Section;
|
|
else if (AsciiStrCmp(SectionName, ".rdata") == 0)
|
|
PatternSection = Section;
|
|
|
|
Section++;
|
|
}
|
|
|
|
ASSERT(CodeSection != NULL);
|
|
ASSERT(PatternSection != NULL);
|
|
|
|
if (BuildNumber >= 10240)
|
|
{
|
|
// (Optional) find winload!BlStatusPrint
|
|
gBlStatusPrint = (t_BlStatusPrint)GetProcedureAddress((UINTN)ImageBase, NtHeaders, "BlStatusPrint");
|
|
if (gBlStatusPrint == NULL)
|
|
{
|
|
// Not exported (RS4 and earlier) - try to find by signature
|
|
FindPattern(SigBlStatusPrint,
|
|
0xCC,
|
|
sizeof(SigBlStatusPrint),
|
|
(UINT8*)ImageBase + CodeSection->VirtualAddress,
|
|
CodeSection->SizeOfRawData,
|
|
(VOID**)&gBlStatusPrint);
|
|
if (gBlStatusPrint == NULL)
|
|
{
|
|
gBlStatusPrint = BlStatusPrintNoop;
|
|
Print(L"\r\nWARNING: winload!BlStatusPrint not found. No boot debugger output will be available.\r\n");
|
|
}
|
|
}
|
|
|
|
// Disable VBS for the duration of this boot
|
|
Status = DisableVbs();
|
|
if (EFI_ERROR(Status))
|
|
Print(L"\r\nWARNING: failed to set EFI runtime variable \"%ls\" in order to disable VBS.\r\n", VbsPolicyDisabledVariableName);
|
|
}
|
|
|
|
// Find winload!OslFwpKernelSetupPhase1
|
|
Status = FindOslFwpKernelSetupPhase1(ImageBase,
|
|
NtHeaders,
|
|
CodeSection,
|
|
PatternSection,
|
|
BuildNumber,
|
|
(UINT8**)&gOriginalOslFwpKernelSetupPhase1);
|
|
if (EFI_ERROR(Status))
|
|
{
|
|
Print(L"\r\nPatchWinload: failed to find OslFwpKernelSetupPhase1. Status: %llx\r\n", Status);
|
|
goto Exit;
|
|
}
|
|
|
|
CONST UINTN HookedOslFwpKernelSetupPhase1Address = (UINTN)&HookedOslFwpKernelSetupPhase1;
|
|
Print(L"HookedOslFwpKernelSetupPhase1 at 0x%p.\r\n", (VOID*)HookedOslFwpKernelSetupPhase1Address);
|
|
|
|
CONST EFI_TPL Tpl = gBS->RaiseTPL(TPL_HIGH_LEVEL); // Note: implies cli
|
|
|
|
// Backup original function prologue
|
|
CopyMem(gOslFwpKernelSetupPhase1Backup, (VOID*)gOriginalOslFwpKernelSetupPhase1, sizeof(gHookTemplate));
|
|
|
|
// Place faux call (push addr, ret) at the start of the function to transfer execution to our hook
|
|
CopyWpMem((VOID*)gOriginalOslFwpKernelSetupPhase1, gHookTemplate, sizeof(gHookTemplate));
|
|
CopyWpMem((UINT8*)gOriginalOslFwpKernelSetupPhase1 + gHookTemplateAddressOffset,
|
|
(UINTN*)&HookedOslFwpKernelSetupPhase1Address, sizeof(HookedOslFwpKernelSetupPhase1Address));
|
|
|
|
gBS->RestoreTPL(Tpl);
|
|
|
|
// Patch ImgpValidateImageHash to allow custom boot loaders. This is completely
|
|
// optional (unless booting a custom ntoskrnl.exe), and failures are ignored
|
|
PatchImgpValidateImageHash(WinloadEfi,
|
|
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(WinloadEfi,
|
|
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 winload!OslFwpKernelSetupPhase1.\r\n");
|
|
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;
|
|
}
|