Skip to content

Commit

Permalink
Merge pull request #3 from thesecretclub/patchguard-fix
Browse files Browse the repository at this point in the history
Improve the PatchGuard bypass
  • Loading branch information
mrexodia authored Sep 2, 2022
2 parents 3c8db31 + 010bfc0 commit c0d5f6e
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 127 deletions.
135 changes: 10 additions & 125 deletions SandboxBootkit/EfiEntry.cpp
Original file line number Diff line number Diff line change
@@ -1,103 +1,5 @@
#include "Efi.hpp"

static void DisablePatchGuard(void* ImageBase, uint64_t ImageSize)
{
/*
nt!KeInitAmd64SpecificState
INIT:0000000140A4F601 8B C2 mov eax, edx
INIT:0000000140A4F603 99 cdq
INIT:0000000140A4F604 41 F7 F8 idiv r8d
INIT:0000000140A4F607 89 44 24 30 mov [rsp+28h+arg_0], eax
INIT:0000000140A4F60B EB 00 jmp short $+2
*/

auto KeInitAmd64SpecificStateJmp = FIND_PATTERN(ImageBase, ImageSize, "\x8B\xC2\x99\x41\xF7\xF8");

if (KeInitAmd64SpecificStateJmp != nullptr)
{
// Prevent the mov from modifying the return address
memset(RVA<void*>(KeInitAmd64SpecificStateJmp, 6), 0x90, 4); // nop x4
}
else
{
Die();
}

/*
nt!KiSwInterrupt
.text:00000001403FD24E FB sti
.text:00000001403FD24F 48 8D 4D 80 lea rcx, [rbp+0E8h+var_168]
.text:00000001403FD253 E8 E8 C2 FD FF call KiSwInterruptDispatch
.text:00000001403FD258 FA cli
*/

auto KiSwInterruptDispatchCall = FIND_PATTERN(ImageBase, ImageSize, "\xFB\x48\x8D\xCC\xCC\xE8\xCC\xCC\xCC\xCC\xFA");

if (KiSwInterruptDispatchCall != nullptr)
{
// Prevent KiSwInterruptDispatch from being executed
memset(KiSwInterruptDispatchCall, 0x90, 11); // nop x11
}
else
{
Die();
}

// NOTE: EfiGuard has some additional patches, but they do not seem necessary
// https://github.com/Mattiwatti/EfiGuard/blob/25bb182026d24944713e36f129a93d08397de913/EfiGuardDxe/PatchNtoskrnl.c#L30-L47
}

static void DisableDSE(void* ImageBase, uint64_t ImageSize)
{
/*
nt!SepInitializeCodeIntegrity
PAGE:0000000140799EBB 4C 8D 05 DE 39 48 00 lea r8, SeCiCallbacks
PAGE:0000000140799EC2 8B CF mov ecx, edi
PAGE:0000000140799EC4 48 FF 15 95 71 99 FF call cs:__imp_CiInitialize
*/

auto CiInitializeCall = FIND_PATTERN(ImageBase, ImageSize, "\x4C\x8D\x05\xCC\xCC\xCC\xCC\x8B\xCF");

if (CiInitializeCall != nullptr)
{
// Change CodeIntegrityOptions to zero for CiInitialize call
*RVA<uint16_t*>(CiInitializeCall, 7) = 0xC931; // xor ecx, ecx
}
else
{
Die();
}

/*
nt!SeValidateImageData
PAGE:00000001406EBD15 loc_1406EBD15:
PAGE:00000001406EBD15 48 83 C4 48 add rsp, 48h
PAGE:00000001406EBD19 C3 retn
PAGE:00000001406EBD1A CC db 0CCh
PAGE:00000001406EBD1B loc_1406EBD1B:
PAGE:00000001406EBD1B B8 28 04 00 C0 mov eax, 0C0000428h
PAGE:00000001406EBD20 EB F3 jmp short loc_1406EBD15
PAGE:00000001406EBD20 SeValidateImageData endp
*/

auto SeValidateImageDataRet = FIND_PATTERN(ImageBase, ImageSize, "\x48\x83\xC4\x48\xC3\xCC\xB8\x28\x04\x00\xC0");

if (SeValidateImageDataRet != nullptr)
{
// Ensure SeValidateImageData returns a success status
*RVA<uint32_t*>(SeValidateImageDataRet, 7) = 0; // mov eax, 0
}
else
{
Die();
}
}

static void HookNtoskrnl(void* ImageBase, uint64_t ImageSize)
{
DisablePatchGuard(ImageBase, ImageSize);
DisableDSE(ImageBase, ImageSize);
}
#include "PatchNtoskrnl.hpp"

static bool IsNtoskrnl(const wchar_t* ImageName)
{
Expand Down Expand Up @@ -127,10 +29,10 @@ static EFI_STATUS BlImgLoadPEImageExHook(void* a1, void* a2, wchar_t* LoadFile,

DetourCreate(BlImgLoadPEImageEx, BlImgLoadPEImageExHook, BlImgLoadPEImageExOriginal);

// Check if loaded file is ntoskrnl and hook it
// Check if loaded file is ntoskrnl and patch it
if (!EFI_ERROR(Status) && IsNtoskrnl(LoadFile))
{
HookNtoskrnl(*ImageBase, *ImageSize);
PatchNtoskrnl(*ImageBase, *ImageSize);
}

return Status;
Expand Down Expand Up @@ -184,39 +86,24 @@ static void PatchSelfIntegrity(void* ImageBase, uint64_t ImageSize)
.text:000000001002AE76 33 FF xor edi, edi
.text:000000001002AE78 48 83 65 C8 00 and qword ptr [rbp+Device.Type], 0
.text:000000001002AE7D 48 83 65 48 00 and [rbp+arg_10], 0
We try to find this first:
We try to find this:
.text:000000001002AE82 83 4D 38 FF or [rbp+arg_0], 0FFFFFFFFh
.text:000000001002AE86 83 4D 40 FF or [rbp+a1], 0FFFFFFFFh
*/

auto VerifySelfIntegrityMid = FIND_PATTERN(ImageBase, ImageSize, "\x83\x4D\xCC\xFF\x83\x4D\xCC\xFF");
if (VerifySelfIntegrityMid != nullptr)
{
// Find the function start (NOTE: would be cleaner to use the RUNTIME_FUNCTION in the exception directory)
// mov [rsp+8], ecx
constexpr auto WalkBack = 0x30;
auto BmFwVerifySelfIntegrity = FIND_PATTERN(VerifySelfIntegrityMid - WalkBack, WalkBack, "\x89\x4C\x24\x08");
if (BmFwVerifySelfIntegrity != nullptr)
{
memcpy(BmFwVerifySelfIntegrity, "\x33\xC0\xC3", 3); // xor eax, eax; ret
}
else
{
Die();
}
}
else
{
Die();
}
ASSERT(VerifySelfIntegrityMid != nullptr);

auto BmFwVerifySelfIntegrity = FindFunctionStart(ImageBase, VerifySelfIntegrityMid);
ASSERT(BmFwVerifySelfIntegrity != nullptr);

PatchReturn0(BmFwVerifySelfIntegrity);
}

static EFI_STATUS LoadBootManager()
{
// Query bootmgfw from the filesystem
EFI_DEVICE_PATH* BootmgfwPath = nullptr;
auto Status = EfiQueryDevicePath(L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi", &BootmgfwPath);

if (EFI_ERROR(Status))
{
return Status;
Expand All @@ -226,7 +113,6 @@ static EFI_STATUS LoadBootManager()
EFI_HANDLE BootmgfwHandle = nullptr;
Status = gBS->LoadImage(TRUE, gImageHandle, BootmgfwPath, nullptr, 0, &BootmgfwHandle);
gBS->FreePool(BootmgfwPath);

if (EFI_ERROR(Status))
{
return Status;
Expand All @@ -237,7 +123,6 @@ static EFI_STATUS LoadBootManager()

// Start the boot manager
Status = gBS->StartImage(BootmgfwHandle, nullptr, nullptr);

if (EFI_ERROR(Status))
{
gBS->UnloadImage(BootmgfwHandle);
Expand Down
80 changes: 79 additions & 1 deletion SandboxBootkit/EfiUtils.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <algorithm>

#include "Efi.hpp"

EFI_IMAGE_NT_HEADERS64* GetNtHeaders(void* ImageBase)
Expand Down Expand Up @@ -92,6 +94,7 @@ void* GetExport(void* ImageBase, const char* FunctionName, const char* ModuleNam

bool FixRelocations(void* ImageBase, uint64_t ImageBaseDelta)
{
// Check if relocations are already applied to the image
if (ImageBaseDelta == 0)
{
return true;
Expand Down Expand Up @@ -147,6 +150,81 @@ bool FixRelocations(void* ImageBase, uint64_t ImageBaseDelta)
return true;
}

struct RUNTIME_FUNCTION
{
uint32_t BeginAddress;
uint32_t EndAddress;
uint32_t UnwindInfo;
};

#define RUNTIME_FUNCTION_INDIRECT 0x1

uint8_t* FindFunctionStart(void* ImageBase, void* Address)
{
auto NtHeaders = GetNtHeaders(ImageBase);
if (NtHeaders == nullptr)
{
return nullptr;
}

auto ExceptionDirectory = &NtHeaders->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_EXCEPTION];
if (ExceptionDirectory->VirtualAddress == 0 || ExceptionDirectory->Size == 0)
{
return nullptr;
}

// Do a binary search to find the RUNTIME_FUNCTION
auto Rva = (uint32_t)((uint8_t*)Address - (uint8_t*)ImageBase);
auto Begin = RVA<RUNTIME_FUNCTION*>(ImageBase, ExceptionDirectory->VirtualAddress);
auto End = Begin + ExceptionDirectory->Size / sizeof(RUNTIME_FUNCTION);
auto FoundEntry = std::lower_bound(Begin, End, Rva, [](const RUNTIME_FUNCTION& Entry, uint32_t Rva)
{
return Entry.EndAddress < Rva;
});

// Make sure the found entry is in-range
// See: https://en.cppreference.com/w/cpp/algorithm/lower_bound
if (FoundEntry == End || Rva < FoundEntry->BeginAddress)
{
return nullptr;
}

// Resolve indirect function entries back to the owning entry
// See: https://github.com/dotnet/runtime/blob/d5e3a5c2ca46691d65c81d520cb95f13f7a94652/src/coreclr/vm/codeman.cpp#L4403-L4416
// As a sidenote, this seems to be why functions addresses have to be aligned?
if ((FoundEntry->UnwindInfo & RUNTIME_FUNCTION_INDIRECT) != 0)
{
auto OwningEntryRva = FoundEntry->UnwindInfo - RUNTIME_FUNCTION_INDIRECT;
FoundEntry = RVA<RUNTIME_FUNCTION*>(ImageBase, OwningEntryRva);
}

return RVA<uint8_t*>(ImageBase, FoundEntry->BeginAddress);
}

EFI_IMAGE_SECTION_HEADER* FindSection(void* ImageBase, const char* SectionName)
{
auto NtHeaders = GetNtHeaders(ImageBase);
if (NtHeaders == nullptr)
{
return nullptr;
}

auto NumberOfSections = NtHeaders->FileHeader.NumberOfSections;
auto Sections = RVA<EFI_IMAGE_SECTION_HEADER*>(&NtHeaders->OptionalHeader, NtHeaders->FileHeader.SizeOfOptionalHeader);
for (int i = 0; i < NumberOfSections; i++)
{
auto Section = &Sections[i];
char Name[9] = {};
memcpy(Name, Section->Name, 8);
if (strcmp(Name, SectionName) == 0)
{
return Section;
}
}

return nullptr;
}

bool ComparePattern(uint8_t* Base, uint8_t* Pattern, size_t PatternLen)
{
for (; PatternLen; ++Base, ++Pattern, PatternLen--)
Expand Down Expand Up @@ -177,7 +255,7 @@ uint8_t* FindPattern(uint8_t* Base, size_t Size, uint8_t* Pattern, size_t Patter
return nullptr;
}

void Die()
void __declspec(noreturn) Die()
{
// At least one of these should kill the VM
__fastfail(1);
Expand Down
10 changes: 9 additions & 1 deletion SandboxBootkit/EfiUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,16 @@ EFI_IMAGE_NT_HEADERS64* GetNtHeaders(void* ImageBase);
void* FindImageBase(uint64_t Address, size_t MaxSize = (1 * 1024 * 1024));
void* GetExport(void* ImageBase, const char* FunctionName, const char* ModuleName = nullptr);
bool FixRelocations(void* ImageBase, uint64_t ImageBaseDelta);
uint8_t* FindFunctionStart(void* ImageBase, void* Address);
EFI_IMAGE_SECTION_HEADER* FindSection(void* ImageBase, const char* SectionName);
bool ComparePattern(uint8_t* Base, uint8_t* Pattern, size_t PatternLen);
uint8_t* FindPattern(uint8_t* Base, size_t Size, uint8_t* Pattern, size_t PatternLen);
void Die();
void __declspec(noreturn) Die();

#define ASSERT(Condition) \
if (!(Condition)) \
{ \
Die(); \
}

#define FIND_PATTERN(Base, Size, Pattern) FindPattern((uint8_t*)Base, Size, (uint8_t*)Pattern, ARRAY_SIZE(Pattern) - 1);
Loading

0 comments on commit c0d5f6e

Please sign in to comment.