diff options
Diffstat (limited to 'EfiGuardDxe')
-rw-r--r-- | EfiGuardDxe/EfiGuardDxe.c | 107 | ||||
-rw-r--r-- | EfiGuardDxe/EfiGuardDxe.h | 5 | ||||
-rw-r--r-- | EfiGuardDxe/EfiGuardDxe.inf | 5 | ||||
-rw-r--r-- | EfiGuardDxe/EfiGuardDxe.vcxproj | 12 | ||||
-rw-r--r-- | EfiGuardDxe/EfiGuardDxe.vcxproj.filters | 13 | ||||
-rw-r--r-- | EfiGuardDxe/PatchNtoskrnl.c | 85 | ||||
-rw-r--r-- | EfiGuardDxe/PatchWinload.c | 91 | ||||
-rw-r--r-- | EfiGuardDxe/VisualUefi.c | 2 | ||||
-rw-r--r-- | EfiGuardDxe/X64/Cet.asm | 37 | ||||
-rw-r--r-- | EfiGuardDxe/X64/Cet.nasm | 36 | ||||
-rw-r--r-- | EfiGuardDxe/util.c | 112 | ||||
-rw-r--r-- | EfiGuardDxe/util.h | 50 |
12 files changed, 444 insertions, 111 deletions
diff --git a/EfiGuardDxe/EfiGuardDxe.c b/EfiGuardDxe/EfiGuardDxe.c index 170a433..74dbf37 100644 --- a/EfiGuardDxe/EfiGuardDxe.c +++ b/EfiGuardDxe/EfiGuardDxe.c @@ -54,6 +54,7 @@ EFI_HANDLE gBootmgfwHandle = NULL; // // EFI runtime globals // +EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL* gTextInputEx = NULL; EFI_EVENT gEfiExitBootServicesEvent = NULL; BOOLEAN gEfiAtRuntime = FALSE; EFI_EVENT gEfiVirtualNotifyEvent = NULL; @@ -155,8 +156,7 @@ HookedLoadImage( // We only have a filename to go on at this point. We will determine the final 'is this bootmgfw.efi?' status after the image has been loaded CONST BOOLEAN MaybeBootmgfw = ImagePath != NULL - ? (StrStr(ImagePath, L"bootmgfw.efi") != NULL || StrStr(ImagePath, L"BOOTMGFW.EFI") != NULL || - StrStr(ImagePath, L"bootx64.efi") != NULL || StrStr(ImagePath, L"BOOTX64.EFI") != NULL) + ? StriStr(ImagePath, L"bootmgfw.efi") != NULL || StriStr(ImagePath, L"Bootmgfw_ms.vc") != NULL || StriStr(ImagePath, L"bootx64.efi") != NULL : FALSE; CONST BOOLEAN IsBoot = (MaybeBootmgfw || (BootPolicy == TRUE && SourceBuffer == NULL)); @@ -220,11 +220,6 @@ HookedLoadImage( LoadedImage->ImageBase, LoadedImage->ImageSize); } - else - { - // A non-Windows OS is being booted. Unload ourselves - EfiGuardUnload(gImageHandle); - } } } @@ -248,7 +243,12 @@ HookedSetVariable( ) { // We should not be hooking the runtime table after ExitBootServices() unless this is the selected DSE bypass method - ASSERT(!gEfiAtRuntime || gDriverConfig.DseBypassMethod == DSE_DISABLE_SETVARIABLE_HOOK); + ASSERT(!gEfiAtRuntime || (gDriverConfig.DseBypassMethod == DSE_DISABLE_SETVARIABLE_HOOK && gBootmgfwHandle != NULL)); + + if (StrCmp(VariableName, L"SecureBoot") == 0) + { + return EFI_WRITE_PROTECTED; + } // Do we have a match for the variable name and vendor GUID? if (gEfiAtRuntime && gEfiGoneVirtual && @@ -274,52 +274,52 @@ HookedSetVariable( BackdoorData->Size > 0 && (UINTN)BackdoorData->KernelAddress >= (UINTN)MM_SYSTEM_RANGE_START) { - if (BackdoorData->IsMemCopy && BackdoorData->u.UserBuffer != NULL) - { - if (BackdoorData->IsReadOperation) // Copy kernel buffer to user address - CopyMem(BackdoorData->u.UserBuffer, BackdoorData->KernelAddress, BackdoorData->Size); - else // Copy user buffer to kernel address - CopyMem(BackdoorData->KernelAddress, BackdoorData->u.UserBuffer, BackdoorData->Size); - } - else + // For scalars, copy user value to kernel memory and put the old value in BackdoorData->u.XXX + switch (BackdoorData->Size) { - // Copy user scalar to kernel memory, and put the old value in BackdoorData->u.XXX - switch (BackdoorData->Size) + case 1: { - case 1: - { - CONST UINT8 NewByte = (UINT8)BackdoorData->u.s.Byte; - BackdoorData->u.s.Byte = *(UINT8*)BackdoorData->KernelAddress; - if (!BackdoorData->IsReadOperation) - *(UINT8*)BackdoorData->KernelAddress = NewByte; - break; - } - case 2: - { - CONST UINT16 NewWord = (UINT16)BackdoorData->u.s.Word; - BackdoorData->u.s.Word = *(UINT16*)BackdoorData->KernelAddress; - if (!BackdoorData->IsReadOperation) - *(UINT16*)BackdoorData->KernelAddress = NewWord; - break; - } - case 4: - { - CONST UINT32 NewDword = (UINT32)BackdoorData->u.s.Dword; - BackdoorData->u.s.Dword = *(UINT32*)BackdoorData->KernelAddress; - if (!BackdoorData->IsReadOperation) - *(UINT32*)BackdoorData->KernelAddress = NewDword; - break; - } - case 8: + CONST UINT8 NewByte = (UINT8)BackdoorData->u.s.Byte; + BackdoorData->u.s.Byte = *(UINT8*)BackdoorData->KernelAddress; + if (!BackdoorData->ReadOnly) + CopyWpMem(BackdoorData->KernelAddress, &NewByte, sizeof(NewByte)); + break; + } + case 2: + { + CONST UINT16 NewWord = (UINT16)BackdoorData->u.s.Word; + BackdoorData->u.s.Word = *(UINT16*)BackdoorData->KernelAddress; + if (!BackdoorData->ReadOnly) + CopyWpMem(BackdoorData->KernelAddress, &NewWord, sizeof(NewWord)); + break; + } + case 4: + { + CONST UINT32 NewDword = (UINT32)BackdoorData->u.s.Dword; + BackdoorData->u.s.Dword = *(UINT32*)BackdoorData->KernelAddress; + if (!BackdoorData->ReadOnly) + CopyWpMem(BackdoorData->KernelAddress, &NewDword, sizeof(NewDword)); + break; + } + case 8: + { + CONST UINT64 NewQword = BackdoorData->u.Qword; + BackdoorData->u.Qword = *(UINT64*)BackdoorData->KernelAddress; + if (!BackdoorData->ReadOnly) + CopyWpMem(BackdoorData->KernelAddress, &NewQword, sizeof(NewQword)); + break; + } + default: + { + // Arbitrary size memcpy + if (BackdoorData->u.UserBuffer != NULL) { - CONST UINT64 NewQword = BackdoorData->u.Qword; - BackdoorData->u.Qword = *(UINT64*)BackdoorData->KernelAddress; - if (!BackdoorData->IsReadOperation) - *(UINT64*)BackdoorData->KernelAddress = NewQword; - break; + if (BackdoorData->ReadOnly) + CopyWpMem(BackdoorData->u.UserBuffer, BackdoorData->KernelAddress, BackdoorData->Size); + else + CopyWpMem(BackdoorData->KernelAddress, BackdoorData->u.UserBuffer, BackdoorData->Size); } - default: - break; // Invalid size; do nothing + break; } } @@ -402,7 +402,7 @@ ExitBootServicesEvent( // If the DSE bypass method is *not* DSE_DISABLE_SETVARIABLE_HOOK, perform some cleanup now. In principle this should allow // linking with /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER, because our driver image may be freed after this callback returns. // Using DSE_DISABLE_SETVARIABLE_HOOK requires linking with /SUBSYSTEM:EFI_RUNTIME_DRIVER, because the image must not be freed. - if (gDriverConfig.DseBypassMethod != DSE_DISABLE_SETVARIABLE_HOOK) + if (gDriverConfig.DseBypassMethod != DSE_DISABLE_SETVARIABLE_HOOK || gBootmgfwHandle == NULL) { // Uninstall our installed driver protocols gBS->UninstallMultipleProtocolInterfaces(gImageHandle, @@ -572,6 +572,11 @@ EfiGuardInitialize( } // + // Query the console input handle for the Simple Text Input Ex protocol + // + gBS->HandleProtocol(gST->ConsoleInHandle, &gEfiSimpleTextInputExProtocolGuid, (VOID **)&gTextInputEx); + + // // Install EfiGuard driver protocol // Status = gBS->InstallProtocolInterface(&gImageHandle, diff --git a/EfiGuardDxe/EfiGuardDxe.h b/EfiGuardDxe/EfiGuardDxe.h index 5d87513..e1e1d53 100644 --- a/EfiGuardDxe/EfiGuardDxe.h +++ b/EfiGuardDxe/EfiGuardDxe.h @@ -36,6 +36,11 @@ extern EFIGUARD_CONFIGURATION_DATA gDriverConfig; extern EFI_HANDLE gBootmgfwHandle; // +// Simple Text Input Ex protocol pointer. May be NULL +// +extern EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL* gTextInputEx; + +// // TRUE if ExitBootServices() has been called // extern BOOLEAN gEfiAtRuntime; diff --git a/EfiGuardDxe/EfiGuardDxe.inf b/EfiGuardDxe/EfiGuardDxe.inf index 67df756..438fff8 100644 --- a/EfiGuardDxe/EfiGuardDxe.inf +++ b/EfiGuardDxe/EfiGuardDxe.inf @@ -26,6 +26,9 @@ Zydis/src/Utils.c Zydis/src/Zydis.c +[Sources.X64] + X64/Cet.nasm + [Packages] MdePkg/MdePkg.dec EfiGuardPkg/EfiGuardPkg.dec @@ -50,6 +53,8 @@ gEfiDevicePathUtilitiesProtocolGuid ## CONSUMES gEfiLoadedImageProtocolGuid ## CONSUMES gEfiShellProtocolGuid ## SOMETIMES_CONSUMES + gEfiSimpleTextInProtocolGuid ## SOMETIMES_CONSUMES + gEfiSimpleTextInputExProtocolGuid ## SOMETIMES_CONSUMES [Guids] gEfiGlobalVariableGuid ## SOMETIMES_PRODUCES diff --git a/EfiGuardDxe/EfiGuardDxe.vcxproj b/EfiGuardDxe/EfiGuardDxe.vcxproj index 0b95d46..19db278 100644 --- a/EfiGuardDxe/EfiGuardDxe.vcxproj +++ b/EfiGuardDxe/EfiGuardDxe.vcxproj @@ -19,10 +19,16 @@ <WholeProgramOptimization>true</WholeProgramOptimization> <CharacterSet>Unicode</CharacterSet> <SpectreMitigation>false</SpectreMitigation> + <VcpkgEnabled>false</VcpkgEnabled> + <EnableStdModules>false</EnableStdModules> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <Import Project="$(SolutionDir)\EfiGuard.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="PropertySheets"> + <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" /> + <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" /> + </ImportGroup> <ItemDefinitionGroup> <ClCompile> <PreprocessorDefinitions>ZYAN_NO_LIBC;ZYCORE_STATIC_BUILD;ZYDIS_STATIC_BUILD;ZYDIS_DISABLE_ENCODER;ZYDIS_DISABLE_FORMATTER;ZYDIS_DISABLE_AVX512;ZYDIS_DISABLE_KNC;%(PreprocessorDefinitions)</PreprocessorDefinitions> @@ -73,6 +79,12 @@ <ClCompile Include="Zydis\src\Zydis.c" /> </ItemGroup> <ItemGroup> + <MASM Include="X64\Cet.asm" /> + </ItemGroup> + <ItemGroup> + <None Include="X64\Cet.nasm" /> + </ItemGroup> + <ItemGroup> <ClInclude Include="..\Include\Protocol\EfiGuard.h" /> <ClInclude Include="arc.h" /> <ClInclude Include="EfiGuardDxe.h" /> diff --git a/EfiGuardDxe/EfiGuardDxe.vcxproj.filters b/EfiGuardDxe/EfiGuardDxe.vcxproj.filters index ef7eb16..e2bcab6 100644 --- a/EfiGuardDxe/EfiGuardDxe.vcxproj.filters +++ b/EfiGuardDxe/EfiGuardDxe.vcxproj.filters @@ -21,6 +21,9 @@ <Filter Include="Header Files\Zydis\Internal"> <UniqueIdentifier>{09843B9B-51DC-4418-9585-2ED4BD3F1643}</UniqueIdentifier> </Filter> + <Filter Include="Source Files\X64"> + <UniqueIdentifier>{4cd0c7c8-71ff-4d6d-bb2d-a9e65cc3d7ce}</UniqueIdentifier> + </Filter> <Filter Include="Header Files\Protocol"> <UniqueIdentifier>{aa6da080-fea5-447e-8722-35a98038eb4e}</UniqueIdentifier> </Filter> @@ -97,6 +100,16 @@ </ClCompile> </ItemGroup> <ItemGroup> + <MASM Include="X64\Cet.asm"> + <Filter>Source Files\X64</Filter> + </MASM> + </ItemGroup> + <ItemGroup> + <None Include="X64\Cet.nasm"> + <Filter>Source Files\X64</Filter> + </None> + </ItemGroup> + <ItemGroup> <ClInclude Include="EfiGuardDxe.h"> <Filter>Header Files</Filter> </ClInclude> diff --git a/EfiGuardDxe/PatchNtoskrnl.c b/EfiGuardDxe/PatchNtoskrnl.c index a852dcb..2e2d20e 100644 --- a/EfiGuardDxe/PatchNtoskrnl.c +++ b/EfiGuardDxe/PatchNtoskrnl.c @@ -35,7 +35,7 @@ STATIC CONST UINT8 SigKeInitAmd64SpecificState[] = { // This function is present since Windows 8.1 and is responsible for executing all functions in the KiVerifyXcptRoutines array. // One of these functions, KiVerifyXcpt15, will indirectly initialize a PatchGuard context from its exception handler. STATIC CONST UINT8 SigKiVerifyScopesExecute[] = { - 0x48, 0x83, 0xCC, 0xCC, 0x00, // and [REG+XX], 0 + 0x83, 0xCC, 0xCC, 0x00, // and d/qword ptr [REG+XX], 0 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE // mov rax, 0FEFFFFFFFFFFFFFFh }; @@ -57,10 +57,11 @@ STATIC CONST UINT8 SigKiMcaDeferredRecoveryService[] = { // If int 20h is issued from kernel mode, the PatchGuard verification routine KiSwInterruptDispatch is called. STATIC CONST UINT8 SigKiSwInterrupt[] = { 0xFB, // sti - 0x48, 0x8D, 0xCC, 0xCC, // lea rcx, XX + 0x48, 0x8D, 0xCC, 0xCC, // lea REG, [REG-XX] 0xE8, 0xCC, 0xCC, 0xCC, 0xCC, // call KiSwInterruptDispatch 0xFA // cli }; +STATIC CONST UINTN SigKiSwInterruptCallOffset = 5, SigKiSwInterruptCliOffset = 10; #endif #endif @@ -69,7 +70,7 @@ STATIC CONST UINT8 SigKiSwInterrupt[] = { // This signature is only for the Windows 10 RS3+ version. I could add more signatures but this is a pretty superficial patch anyway. STATIC CONST UINT8 SigSeCodeIntegrityQueryInformation[] = { 0x48, 0x83, 0xEC, // sub rsp, XX - 0xCC, 0x48, 0x83, 0x3D, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, // cmp cs:qword_14035E638, 0 + 0xCC, 0x48, 0x83, 0x3D, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, // cmp ds:qword_xxxx, 0 0x4D, 0x8B, 0xC8, // mov r9, r8 0x4C, 0x8B, 0xD1, // mov r10, rcx 0x74, 0xCC // jz XX @@ -295,7 +296,7 @@ DisablePatchGuard( #ifndef EAC_COMPAT_MODE // Search for callers of KiMcaDeferredRecoveryService (only exists on Windows >= 8.1) UINT8* KiMcaDeferredRecoveryServiceCallers[2]; - ZeroMem(KiMcaDeferredRecoveryServiceCallers, sizeof(KiMcaDeferredRecoveryServiceCallers)); + ZeroMem((VOID*)KiMcaDeferredRecoveryServiceCallers, sizeof(KiMcaDeferredRecoveryServiceCallers)); if (BuildNumber >= 9600) { StartRva = TextSection->VirtualAddress; @@ -367,11 +368,20 @@ DisablePatchGuard( } } - // Search for KiSwInterrupt (only exists on Windows >= 10) - UINT8* KiSwInterruptPatternAddress = NULL; + // We need KiSwInterruptDispatch to call ExAllocatePool2 for our preferred method to work, because we rely on it to + // return null for zero pool tags. Windows 10 20H1 does export ExAllocatePool2, but without using it where we need it. + CONST BOOLEAN FindGlobalPgContext = BuildNumber >= 20348 && GetProcedureAddress((UINTN)ImageBase, NtHeaders, "ExAllocatePool2") != NULL; + + // Search for KiSwInterrupt[Dispatch] and optionally its global PatchGuard context (named g_PgContext here). Both of these only exist on Windows >= 10 + UINT8* KiSwInterruptPatternAddress = NULL, *gPgContext = NULL; if (BuildNumber >= 10240) { + StartRva = TextSection->VirtualAddress; + SizeOfRawData = TextSection->SizeOfRawData; + StartVa = ImageBase + StartRva; + PRINT_KERNEL_PATCH_MSG(L"== Searching for nt!KiSwInterrupt pattern in .text ==\r\n"); + UINT8* KiSwInterruptDispatchAddress = NULL; CONST EFI_STATUS FindKiSwInterruptStatus = FindPattern(SigKiSwInterrupt, 0xCC, sizeof(SigKiSwInterrupt), @@ -380,14 +390,55 @@ DisablePatchGuard( (VOID**)&KiSwInterruptPatternAddress); if (EFI_ERROR(FindKiSwInterruptStatus)) { - // This is not a fatal error as the system can still boot without patching KiSwInterrupt. + // This is not a fatal error as the system can still boot without patching g_PgContext or KiSwInterrupt. // However note that in this case, any attempt to issue int 20h from kernel mode later will result in a bugcheck. PRINT_KERNEL_PATCH_MSG(L" Failed to find KiSwInterrupt. Skipping patch.\r\n"); } else { + ASSERT(SigKiSwInterrupt[SigKiSwInterruptCallOffset] == 0xE8 && SigKiSwInterrupt[SigKiSwInterruptCliOffset] == 0xFA); + CONST INT32 Relative = *(INT32*)(KiSwInterruptPatternAddress + SigKiSwInterruptCallOffset + 1); + KiSwInterruptDispatchAddress = KiSwInterruptPatternAddress + SigKiSwInterruptCliOffset + Relative; + PRINT_KERNEL_PATCH_MSG(L" Found KiSwInterrupt pattern at 0x%llX.\r\n", (UINTN)KiSwInterruptPatternAddress); } + + if (KiSwInterruptDispatchAddress != NULL && FindGlobalPgContext) + { + // Start decode loop + Context.Length = 128; + Context.Offset = 0; + while ((Context.InstructionAddress = (ZyanU64)(KiSwInterruptDispatchAddress + 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 REG, ds:g_PgContext' + if (Context.Instruction.operand_count == 2 && + Context.Instruction.mnemonic == ZYDIS_MNEMONIC_MOV && + (Context.Instruction.attributes & ZYDIS_ATTRIB_ACCEPTS_SEGMENT) != 0 && + Context.Operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && + Context.Operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY && Context.Operands[1].mem.base == ZYDIS_REGISTER_RIP && + (Context.Operands[1].mem.segment == ZYDIS_REGISTER_CS || Context.Operands[1].mem.segment == ZYDIS_REGISTER_DS)) + { + if (ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&Context.Instruction, &Context.Operands[1], Context.InstructionAddress, (ZyanU64*)&gPgContext))) + { + PRINT_KERNEL_PATCH_MSG(L" Found g_PgContext at 0x%llX.\r\n", (UINTN)gPgContext); + break; + } + } + + Context.Offset += Context.Instruction.length; + } + } } #endif @@ -406,8 +457,15 @@ DisablePatchGuard( CopyWpMem(KiMcaDeferredRecoveryServiceCallers[0], &No, sizeof(No)); CopyWpMem(KiMcaDeferredRecoveryServiceCallers[1], &No, sizeof(No)); } - if (KiSwInterruptPatternAddress != NULL) + if (gPgContext != NULL) + { + CONST UINT64 NewPgContextAddress = (UINT64)ImageBase + InitSection->VirtualAddress; // Address in discardable section + CopyWpMem(gPgContext, &NewPgContextAddress, sizeof(NewPgContextAddress)); + } + else if (KiSwInterruptPatternAddress != NULL) + { SetWpMem(KiSwInterruptPatternAddress, sizeof(SigKiSwInterrupt), 0x90); // 11 x nop + } #endif // Print info @@ -432,7 +490,12 @@ DisablePatchGuard( (UINT32)(KiMcaDeferredRecoveryServiceCallers[0] - ImageBase), (UINT32)(KiMcaDeferredRecoveryServiceCallers[1] - ImageBase)); } - if (KiSwInterruptPatternAddress != NULL) + if (gPgContext != NULL) + { + PRINT_KERNEL_PATCH_MSG(L" Patched g_PgContext [RVA: 0x%X].\r\n", + (UINT32)(gPgContext - ImageBase)); + } + else if (KiSwInterruptPatternAddress != NULL) { PRINT_KERNEL_PATCH_MSG(L" Patched KiSwInterrupt [RVA: 0x%X].\r\n", (UINT32)(KiSwInterruptPatternAddress - ImageBase)); @@ -821,9 +884,7 @@ PatchNtoskrnl( Section++; } - ASSERT(InitSection != NULL); - ASSERT(TextSection != NULL); - ASSERT(PageSection != NULL); + ASSERT(InitSection != NULL && TextSection != NULL && PageSection != NULL); #ifndef DO_NOT_DISABLE_PATCHGUARD // Patch INIT and .text sections to disable PatchGuard diff --git a/EfiGuardDxe/PatchWinload.c b/EfiGuardDxe/PatchWinload.c index 197ca75..0fbda68 100644 --- a/EfiGuardDxe/PatchWinload.c +++ b/EfiGuardDxe/PatchWinload.c @@ -8,12 +8,11 @@ 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 +// 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[] = { - 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 + 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... @@ -405,7 +404,7 @@ FindOslFwpKernelSetupPhase1( IN PEFI_IMAGE_NT_HEADERS NtHeaders, IN PEFI_IMAGE_SECTION_HEADER CodeSection, IN PEFI_IMAGE_SECTION_HEADER PatternSection, - IN BOOLEAN TryPatternMatch, + IN UINT16 BuildNumber, OUT UINT8** OslFwpKernelSetupPhase1Address ) { @@ -415,9 +414,9 @@ FindOslFwpKernelSetupPhase1( CONST UINT32 CodeSizeOfRawData = CodeSection->SizeOfRawData; CONST UINT8* PatternStartVa = ImageBase + PatternSection->VirtualAddress; - if (TryPatternMatch) + if (BuildNumber >= 17134) { - // On Windows 10, try simple pattern matching first since it will most likely work + // 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, @@ -437,14 +436,69 @@ FindOslFwpKernelSetupPhase1( } } + // 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 = BacktrackToFunctionStart(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. - 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); + 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; @@ -468,16 +522,6 @@ FindOslFwpKernelSetupPhase1( Print(L"\r\n== Disassembling .text to find EfipGetRsdt ==\r\n"); UINT8* LeaEfiAcpiTableGuidAddress = 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; @@ -534,7 +578,6 @@ FindOslFwpKernelSetupPhase1( } 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 @@ -673,7 +716,7 @@ PatchWinload( NtHeaders, CodeSection, PatternSection, - BuildNumber >= 10240, + BuildNumber, (UINT8**)&gOriginalOslFwpKernelSetupPhase1); if (EFI_ERROR(Status)) { diff --git a/EfiGuardDxe/VisualUefi.c b/EfiGuardDxe/VisualUefi.c index e1f76db..9795faf 100644 --- a/EfiGuardDxe/VisualUefi.c +++ b/EfiGuardDxe/VisualUefi.c @@ -5,6 +5,7 @@ #include <Uefi.h> #include <Protocol/DriverSupportedEfiVersion.h> +#include <Protocol/SimpleTextInEx.h> #include <Protocol/EfiGuard.h> #include <Guid/Acpi.h> #include <Library/DebugLib.h> @@ -31,6 +32,7 @@ EFI_GUID gEfiGuardDriverProtocolGuid = EFI_EFIGUARD_DRIVER_PROTOCOL_GUID; // GUIDs // EFI_GUID gEfiDriverSupportedEfiVersionProtocolGuid = EFI_DRIVER_SUPPORTED_EFI_VERSION_PROTOCOL_GUID; +EFI_GUID gEfiSimpleTextInputExProtocolGuid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID; EFI_GUID gEfiAcpi20TableGuid = EFI_ACPI_20_TABLE_GUID; diff --git a/EfiGuardDxe/X64/Cet.asm b/EfiGuardDxe/X64/Cet.asm new file mode 100644 index 0000000..74433c2 --- /dev/null +++ b/EfiGuardDxe/X64/Cet.asm @@ -0,0 +1,37 @@ +MSR_S_CET EQU 6A2h +MSR_S_CET_SH_STK_EN EQU 1 +CR4_CET EQU (1 SHL 23) +N_CR4_CET EQU 23 + +.code + +align 16 +AsmDisableCet PROC + mov ecx, MSR_S_CET + rdmsr + test al, MSR_S_CET_SH_STK_EN + jz @F ; if z, shadow stack not enabled + + ; Pop pushed data for 'call' + mov rax, 1 + incsspq rax + +@@: + mov rax, cr4 + btr eax, N_CR4_CET ; clear CR4_CET + mov cr4, rax + ret +AsmDisableCet ENDP + +align 16 +AsmEnableCet PROC + mov rax, cr4 + bts eax, N_CR4_CET ; set CR4_CET + mov cr4, rax + + ; Use jmp to skip check for 'ret' + pop rax + jmp rax +AsmEnableCet ENDP + +end diff --git a/EfiGuardDxe/X64/Cet.nasm b/EfiGuardDxe/X64/Cet.nasm new file mode 100644 index 0000000..b93ca16 --- /dev/null +++ b/EfiGuardDxe/X64/Cet.nasm @@ -0,0 +1,36 @@ +%define MSR_S_CET 0x6A2 +%define MSR_S_CET_SH_STK_EN 0x1 +%define CR4_CET (1 << 23) +%define N_CR4_CET 23 + +DEFAULT REL +SECTION .text + +align 16 +global ASM_PFX(AsmDisableCet) +ASM_PFX(AsmDisableCet): + mov ecx, MSR_S_CET + rdmsr + test al, MSR_S_CET_SH_STK_EN + jz .SsDone ; if z, shadow stack not enabled + + ; Pop pushed data for 'call' + mov rax, 1 + incsspq rax + +.SsDone: + mov rax, cr4 + btr eax, N_CR4_CET ; clear CR4_CET + mov cr4, rax + ret + +align 16 +global ASM_PFX(AsmEnableCet) +ASM_PFX(AsmEnableCet): + mov rax, cr4 + bts eax, N_CR4_CET ; set CR4_CET + mov cr4, rax + + ; Use jmp to skip check for 'ret' + pop rax + jmp rax diff --git a/EfiGuardDxe/util.c b/EfiGuardDxe/util.c index 17686ef..78424e5 100644 --- a/EfiGuardDxe/util.c +++ b/EfiGuardDxe/util.c @@ -119,6 +119,40 @@ PrintKernelPatchInfo( } } +VOID +EFIAPI +DisableWriteProtect( + OUT BOOLEAN *WpEnabled, + OUT BOOLEAN *CetEnabled + ) +{ + CONST UINTN Cr0 = AsmReadCr0(); + *WpEnabled = (Cr0 & CR0_WP) != 0; + *CetEnabled = (AsmReadCr4() & CR4_CET) != 0; + + if (*WpEnabled) + { + if (*CetEnabled) + AsmDisableCet(); + AsmWriteCr0(Cr0 & ~CR0_WP); + } +} + +VOID +EFIAPI +EnableWriteProtect( + IN BOOLEAN WpEnabled, + IN BOOLEAN CetEnabled + ) +{ + if (WpEnabled) + { + AsmWriteCr0(AsmReadCr0() | CR0_WP); + if (CetEnabled) + AsmEnableCet(); + } +} + VOID* EFIAPI CopyWpMem( @@ -127,16 +161,12 @@ CopyWpMem( IN UINTN Length ) { - CONST UINTN Cr0 = AsmReadCr0(); - CONST BOOLEAN WpSet = (Cr0 & CR0_WP) != 0; - if (WpSet) - AsmWriteCr0(Cr0 & ~CR0_WP); + BOOLEAN WpEnabled, CetEnabled; + DisableWriteProtect(&WpEnabled, &CetEnabled); VOID* Result = CopyMem(Destination, Source, Length); - if (WpSet) - AsmWriteCr0(Cr0); - + EnableWriteProtect(WpEnabled, CetEnabled); return Result; } @@ -148,16 +178,12 @@ SetWpMem( IN UINT8 Value ) { - CONST UINTN Cr0 = AsmReadCr0(); - CONST BOOLEAN WpSet = (Cr0 & CR0_WP) != 0; - if (WpSet) - AsmWriteCr0(Cr0 & ~CR0_WP); + BOOLEAN WpEnabled, CetEnabled; + DisableWriteProtect(&WpEnabled, &CetEnabled); VOID* Result = SetMem(Destination, Length, Value); - if (WpSet) - AsmWriteCr0(Cr0); - + EnableWriteProtect(WpEnabled, CetEnabled); return Result; } @@ -199,6 +225,43 @@ StrniCmp( return UpperFirstChar - UpperSecondChar; } +CONST CHAR16* +EFIAPI +StriStr( + IN CONST CHAR16 *String1, + IN CONST CHAR16 *String2 + ) +{ + if (*String2 == L'\0') + return String1; + + while (*String1 != L'\0') + { + CONST CHAR16* FirstMatch = String1; + CONST CHAR16* String2Ptr = String2; + CHAR16 String1Char = CharToUpper(*String1); + CHAR16 String2Char = CharToUpper(*String2Ptr); + + while (String1Char == String2Char && String1Char != L'\0') + { + String1++; + String2Ptr++; + + String1Char = CharToUpper(*String1); + String2Char = CharToUpper(*String2Ptr); + } + + if (String2Char == L'\0') + return FirstMatch; + + if (String1Char == L'\0') + return NULL; + + String1 = FirstMatch + 1; + } + return NULL; +} + BOOLEAN EFIAPI WaitForKey( @@ -206,27 +269,28 @@ WaitForKey( ) { // Hack: because we call this at TPL_NOTIFY in ExitBootServices, we cannot use WaitForEvent() - // in that scenario because it requires TPL == TPL_APPLICATION. So check the TPL + // in that scenario because it requires TPL <= TPL_APPLICATION. So check the TPL CONST EFI_TPL Tpl = EfiGetCurrentTpl(); - EFI_INPUT_KEY Key = { 0, 0 }; + EFI_KEY_DATA KeyData = { 0 }; EFI_STATUS Status = EFI_NOT_READY; while (Status == EFI_NOT_READY) { - // Can we call WaitForEvent()? UINTN Index = 0; - if (Tpl == TPL_APPLICATION) - gBS->WaitForEvent(1, &gST->ConIn->WaitForKey, &Index); // Yep + if (Tpl <= TPL_APPLICATION) + gBS->WaitForEvent(1, (VOID**)(gTextInputEx != NULL ? gTextInputEx->WaitForKeyEx : gST->ConIn->WaitForKey), &Index); else - RtlStall(1); // Nope; burn CPU. // TODO: find a way to parallelize this to achieve GeForce FX 5800 temperatures + RtlStall(1); // WaitForEvent() unavailable, burn CPU // At TPL_APPLICATION, we will always get EFI_SUCCESS (barring hardware failures). At higher TPLs we may also get EFI_NOT_READY - Status = gST->ConIn->ReadKeyStroke(gST->ConIn, &Key); + Status = gTextInputEx != NULL + ? gTextInputEx->ReadKeyStrokeEx(gTextInputEx, &KeyData) + : gST->ConIn->ReadKeyStroke(gST->ConIn, &KeyData.Key); } ASSERT_EFI_ERROR(Status); - return (BOOLEAN)(Key.ScanCode != SCAN_ESC); + return KeyData.Key.ScanCode != SCAN_ESC; } INT32 @@ -475,8 +539,8 @@ BacktrackToFunctionStart( if (High >= Low) { - // If the function entry specifies indirection, get the address of the master function entry - if ((FunctionEntry->u.UnwindData & RUNTIME_FUNCTION_INDIRECT) != 0) + // If the function entry specifies indirection, get the address of its master function entry + while ((FunctionEntry->u.UnwindData & RUNTIME_FUNCTION_INDIRECT) != 0) { FunctionEntry = (PIMAGE_RUNTIME_FUNCTION_ENTRY)(FunctionEntry->u.UnwindData + ImageBase - 1); } diff --git a/EfiGuardDxe/util.h b/EfiGuardDxe/util.h index 00294df..bbdf4d1 100644 --- a/EfiGuardDxe/util.h +++ b/EfiGuardDxe/util.h @@ -8,6 +8,7 @@ #define CR0_WP ((UINTN)0x00010000) // CR0.WP #define CR0_PG ((UINTN)0x80000000) // CR0.PG +#define CR4_CET ((UINTN)0x00800000) // CR4.CET #define CR4_LA57 ((UINTN)0x00001000) // CR4.LA57 #define MSR_EFER ((UINTN)0xC0000080) // Extended Function Enable Register #define EFER_LMA ((UINTN)0x00000400) // Long Mode Active @@ -66,6 +67,45 @@ PrintKernelPatchInfo( ); // +// Disables CET. +// +VOID +EFIAPI +AsmDisableCet( + VOID + ); + +// +// Enables CET. +// +VOID +EFIAPI +AsmEnableCet( + VOID + ); + +// +// Disables write protection if it is currently enabled. +// Returns the current CET and WP states for use when calling EnableWriteProtect(). +// +VOID +EFIAPI +DisableWriteProtect( + OUT BOOLEAN *WpEnabled, + OUT BOOLEAN *CetEnabled + ); + +// +// Enables write protection if it was previously enabled. +// +VOID +EFIAPI +EnableWriteProtect( + IN BOOLEAN WpEnabled, + IN BOOLEAN CetEnabled + ); + +// // Wrapper for CopyMem() that disables write protection prior to copying if needed. // VOID* @@ -108,6 +148,16 @@ StrniCmp( ); // +// Case-insensitive string search. +// +CONST CHAR16* +EFIAPI +StriStr( + IN CONST CHAR16 *String1, + IN CONST CHAR16 *String2 + ); + +// // Waits for a key to be pressed before continuing execution. // Returns FALSE if ESC was pressed to abort, TRUE otherwise. // |