aboutsummaryrefslogtreecommitdiff
path: root/EfiGuardDxe
diff options
context:
space:
mode:
Diffstat (limited to 'EfiGuardDxe')
-rw-r--r--EfiGuardDxe/EfiGuardDxe.c107
-rw-r--r--EfiGuardDxe/EfiGuardDxe.h5
-rw-r--r--EfiGuardDxe/EfiGuardDxe.inf5
-rw-r--r--EfiGuardDxe/EfiGuardDxe.vcxproj12
-rw-r--r--EfiGuardDxe/EfiGuardDxe.vcxproj.filters13
-rw-r--r--EfiGuardDxe/PatchNtoskrnl.c85
-rw-r--r--EfiGuardDxe/PatchWinload.c91
-rw-r--r--EfiGuardDxe/VisualUefi.c2
-rw-r--r--EfiGuardDxe/X64/Cet.asm37
-rw-r--r--EfiGuardDxe/X64/Cet.nasm36
-rw-r--r--EfiGuardDxe/util.c112
-rw-r--r--EfiGuardDxe/util.h50
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.
//