Skip to content

Commit c0d5f6e

Browse files
authored
Merge pull request #3 from thesecretclub/patchguard-fix
Improve the PatchGuard bypass
2 parents 3c8db31 + 010bfc0 commit c0d5f6e

File tree

7 files changed

+275
-127
lines changed

7 files changed

+275
-127
lines changed

SandboxBootkit/EfiEntry.cpp

Lines changed: 10 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,5 @@
11
#include "Efi.hpp"
2-
3-
static void DisablePatchGuard(void* ImageBase, uint64_t ImageSize)
4-
{
5-
/*
6-
nt!KeInitAmd64SpecificState
7-
INIT:0000000140A4F601 8B C2 mov eax, edx
8-
INIT:0000000140A4F603 99 cdq
9-
INIT:0000000140A4F604 41 F7 F8 idiv r8d
10-
INIT:0000000140A4F607 89 44 24 30 mov [rsp+28h+arg_0], eax
11-
INIT:0000000140A4F60B EB 00 jmp short $+2
12-
*/
13-
14-
auto KeInitAmd64SpecificStateJmp = FIND_PATTERN(ImageBase, ImageSize, "\x8B\xC2\x99\x41\xF7\xF8");
15-
16-
if (KeInitAmd64SpecificStateJmp != nullptr)
17-
{
18-
// Prevent the mov from modifying the return address
19-
memset(RVA<void*>(KeInitAmd64SpecificStateJmp, 6), 0x90, 4); // nop x4
20-
}
21-
else
22-
{
23-
Die();
24-
}
25-
26-
/*
27-
nt!KiSwInterrupt
28-
.text:00000001403FD24E FB sti
29-
.text:00000001403FD24F 48 8D 4D 80 lea rcx, [rbp+0E8h+var_168]
30-
.text:00000001403FD253 E8 E8 C2 FD FF call KiSwInterruptDispatch
31-
.text:00000001403FD258 FA cli
32-
*/
33-
34-
auto KiSwInterruptDispatchCall = FIND_PATTERN(ImageBase, ImageSize, "\xFB\x48\x8D\xCC\xCC\xE8\xCC\xCC\xCC\xCC\xFA");
35-
36-
if (KiSwInterruptDispatchCall != nullptr)
37-
{
38-
// Prevent KiSwInterruptDispatch from being executed
39-
memset(KiSwInterruptDispatchCall, 0x90, 11); // nop x11
40-
}
41-
else
42-
{
43-
Die();
44-
}
45-
46-
// NOTE: EfiGuard has some additional patches, but they do not seem necessary
47-
// https://github.com/Mattiwatti/EfiGuard/blob/25bb182026d24944713e36f129a93d08397de913/EfiGuardDxe/PatchNtoskrnl.c#L30-L47
48-
}
49-
50-
static void DisableDSE(void* ImageBase, uint64_t ImageSize)
51-
{
52-
/*
53-
nt!SepInitializeCodeIntegrity
54-
PAGE:0000000140799EBB 4C 8D 05 DE 39 48 00 lea r8, SeCiCallbacks
55-
PAGE:0000000140799EC2 8B CF mov ecx, edi
56-
PAGE:0000000140799EC4 48 FF 15 95 71 99 FF call cs:__imp_CiInitialize
57-
*/
58-
59-
auto CiInitializeCall = FIND_PATTERN(ImageBase, ImageSize, "\x4C\x8D\x05\xCC\xCC\xCC\xCC\x8B\xCF");
60-
61-
if (CiInitializeCall != nullptr)
62-
{
63-
// Change CodeIntegrityOptions to zero for CiInitialize call
64-
*RVA<uint16_t*>(CiInitializeCall, 7) = 0xC931; // xor ecx, ecx
65-
}
66-
else
67-
{
68-
Die();
69-
}
70-
71-
/*
72-
nt!SeValidateImageData
73-
PAGE:00000001406EBD15 loc_1406EBD15:
74-
PAGE:00000001406EBD15 48 83 C4 48 add rsp, 48h
75-
PAGE:00000001406EBD19 C3 retn
76-
PAGE:00000001406EBD1A CC db 0CCh
77-
PAGE:00000001406EBD1B loc_1406EBD1B:
78-
PAGE:00000001406EBD1B B8 28 04 00 C0 mov eax, 0C0000428h
79-
PAGE:00000001406EBD20 EB F3 jmp short loc_1406EBD15
80-
PAGE:00000001406EBD20 SeValidateImageData endp
81-
*/
82-
83-
auto SeValidateImageDataRet = FIND_PATTERN(ImageBase, ImageSize, "\x48\x83\xC4\x48\xC3\xCC\xB8\x28\x04\x00\xC0");
84-
85-
if (SeValidateImageDataRet != nullptr)
86-
{
87-
// Ensure SeValidateImageData returns a success status
88-
*RVA<uint32_t*>(SeValidateImageDataRet, 7) = 0; // mov eax, 0
89-
}
90-
else
91-
{
92-
Die();
93-
}
94-
}
95-
96-
static void HookNtoskrnl(void* ImageBase, uint64_t ImageSize)
97-
{
98-
DisablePatchGuard(ImageBase, ImageSize);
99-
DisableDSE(ImageBase, ImageSize);
100-
}
2+
#include "PatchNtoskrnl.hpp"
1013

1024
static bool IsNtoskrnl(const wchar_t* ImageName)
1035
{
@@ -127,10 +29,10 @@ static EFI_STATUS BlImgLoadPEImageExHook(void* a1, void* a2, wchar_t* LoadFile,
12729

12830
DetourCreate(BlImgLoadPEImageEx, BlImgLoadPEImageExHook, BlImgLoadPEImageExOriginal);
12931

130-
// Check if loaded file is ntoskrnl and hook it
32+
// Check if loaded file is ntoskrnl and patch it
13133
if (!EFI_ERROR(Status) && IsNtoskrnl(LoadFile))
13234
{
133-
HookNtoskrnl(*ImageBase, *ImageSize);
35+
PatchNtoskrnl(*ImageBase, *ImageSize);
13436
}
13537

13638
return Status;
@@ -184,39 +86,24 @@ static void PatchSelfIntegrity(void* ImageBase, uint64_t ImageSize)
18486
.text:000000001002AE76 33 FF xor edi, edi
18587
.text:000000001002AE78 48 83 65 C8 00 and qword ptr [rbp+Device.Type], 0
18688
.text:000000001002AE7D 48 83 65 48 00 and [rbp+arg_10], 0
187-
We try to find this first:
89+
We try to find this:
18890
.text:000000001002AE82 83 4D 38 FF or [rbp+arg_0], 0FFFFFFFFh
18991
.text:000000001002AE86 83 4D 40 FF or [rbp+a1], 0FFFFFFFFh
19092
*/
191-
19293
auto VerifySelfIntegrityMid = FIND_PATTERN(ImageBase, ImageSize, "\x83\x4D\xCC\xFF\x83\x4D\xCC\xFF");
193-
if (VerifySelfIntegrityMid != nullptr)
194-
{
195-
// Find the function start (NOTE: would be cleaner to use the RUNTIME_FUNCTION in the exception directory)
196-
// mov [rsp+8], ecx
197-
constexpr auto WalkBack = 0x30;
198-
auto BmFwVerifySelfIntegrity = FIND_PATTERN(VerifySelfIntegrityMid - WalkBack, WalkBack, "\x89\x4C\x24\x08");
199-
if (BmFwVerifySelfIntegrity != nullptr)
200-
{
201-
memcpy(BmFwVerifySelfIntegrity, "\x33\xC0\xC3", 3); // xor eax, eax; ret
202-
}
203-
else
204-
{
205-
Die();
206-
}
207-
}
208-
else
209-
{
210-
Die();
211-
}
94+
ASSERT(VerifySelfIntegrityMid != nullptr);
95+
96+
auto BmFwVerifySelfIntegrity = FindFunctionStart(ImageBase, VerifySelfIntegrityMid);
97+
ASSERT(BmFwVerifySelfIntegrity != nullptr);
98+
99+
PatchReturn0(BmFwVerifySelfIntegrity);
212100
}
213101

214102
static EFI_STATUS LoadBootManager()
215103
{
216104
// Query bootmgfw from the filesystem
217105
EFI_DEVICE_PATH* BootmgfwPath = nullptr;
218106
auto Status = EfiQueryDevicePath(L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi", &BootmgfwPath);
219-
220107
if (EFI_ERROR(Status))
221108
{
222109
return Status;
@@ -226,7 +113,6 @@ static EFI_STATUS LoadBootManager()
226113
EFI_HANDLE BootmgfwHandle = nullptr;
227114
Status = gBS->LoadImage(TRUE, gImageHandle, BootmgfwPath, nullptr, 0, &BootmgfwHandle);
228115
gBS->FreePool(BootmgfwPath);
229-
230116
if (EFI_ERROR(Status))
231117
{
232118
return Status;
@@ -237,7 +123,6 @@ static EFI_STATUS LoadBootManager()
237123

238124
// Start the boot manager
239125
Status = gBS->StartImage(BootmgfwHandle, nullptr, nullptr);
240-
241126
if (EFI_ERROR(Status))
242127
{
243128
gBS->UnloadImage(BootmgfwHandle);

SandboxBootkit/EfiUtils.cpp

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <algorithm>
2+
13
#include "Efi.hpp"
24

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

9395
bool FixRelocations(void* ImageBase, uint64_t ImageBaseDelta)
9496
{
97+
// Check if relocations are already applied to the image
9598
if (ImageBaseDelta == 0)
9699
{
97100
return true;
@@ -147,6 +150,81 @@ bool FixRelocations(void* ImageBase, uint64_t ImageBaseDelta)
147150
return true;
148151
}
149152

153+
struct RUNTIME_FUNCTION
154+
{
155+
uint32_t BeginAddress;
156+
uint32_t EndAddress;
157+
uint32_t UnwindInfo;
158+
};
159+
160+
#define RUNTIME_FUNCTION_INDIRECT 0x1
161+
162+
uint8_t* FindFunctionStart(void* ImageBase, void* Address)
163+
{
164+
auto NtHeaders = GetNtHeaders(ImageBase);
165+
if (NtHeaders == nullptr)
166+
{
167+
return nullptr;
168+
}
169+
170+
auto ExceptionDirectory = &NtHeaders->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_EXCEPTION];
171+
if (ExceptionDirectory->VirtualAddress == 0 || ExceptionDirectory->Size == 0)
172+
{
173+
return nullptr;
174+
}
175+
176+
// Do a binary search to find the RUNTIME_FUNCTION
177+
auto Rva = (uint32_t)((uint8_t*)Address - (uint8_t*)ImageBase);
178+
auto Begin = RVA<RUNTIME_FUNCTION*>(ImageBase, ExceptionDirectory->VirtualAddress);
179+
auto End = Begin + ExceptionDirectory->Size / sizeof(RUNTIME_FUNCTION);
180+
auto FoundEntry = std::lower_bound(Begin, End, Rva, [](const RUNTIME_FUNCTION& Entry, uint32_t Rva)
181+
{
182+
return Entry.EndAddress < Rva;
183+
});
184+
185+
// Make sure the found entry is in-range
186+
// See: https://en.cppreference.com/w/cpp/algorithm/lower_bound
187+
if (FoundEntry == End || Rva < FoundEntry->BeginAddress)
188+
{
189+
return nullptr;
190+
}
191+
192+
// Resolve indirect function entries back to the owning entry
193+
// See: https://github.com/dotnet/runtime/blob/d5e3a5c2ca46691d65c81d520cb95f13f7a94652/src/coreclr/vm/codeman.cpp#L4403-L4416
194+
// As a sidenote, this seems to be why functions addresses have to be aligned?
195+
if ((FoundEntry->UnwindInfo & RUNTIME_FUNCTION_INDIRECT) != 0)
196+
{
197+
auto OwningEntryRva = FoundEntry->UnwindInfo - RUNTIME_FUNCTION_INDIRECT;
198+
FoundEntry = RVA<RUNTIME_FUNCTION*>(ImageBase, OwningEntryRva);
199+
}
200+
201+
return RVA<uint8_t*>(ImageBase, FoundEntry->BeginAddress);
202+
}
203+
204+
EFI_IMAGE_SECTION_HEADER* FindSection(void* ImageBase, const char* SectionName)
205+
{
206+
auto NtHeaders = GetNtHeaders(ImageBase);
207+
if (NtHeaders == nullptr)
208+
{
209+
return nullptr;
210+
}
211+
212+
auto NumberOfSections = NtHeaders->FileHeader.NumberOfSections;
213+
auto Sections = RVA<EFI_IMAGE_SECTION_HEADER*>(&NtHeaders->OptionalHeader, NtHeaders->FileHeader.SizeOfOptionalHeader);
214+
for (int i = 0; i < NumberOfSections; i++)
215+
{
216+
auto Section = &Sections[i];
217+
char Name[9] = {};
218+
memcpy(Name, Section->Name, 8);
219+
if (strcmp(Name, SectionName) == 0)
220+
{
221+
return Section;
222+
}
223+
}
224+
225+
return nullptr;
226+
}
227+
150228
bool ComparePattern(uint8_t* Base, uint8_t* Pattern, size_t PatternLen)
151229
{
152230
for (; PatternLen; ++Base, ++Pattern, PatternLen--)
@@ -177,7 +255,7 @@ uint8_t* FindPattern(uint8_t* Base, size_t Size, uint8_t* Pattern, size_t Patter
177255
return nullptr;
178256
}
179257

180-
void Die()
258+
void __declspec(noreturn) Die()
181259
{
182260
// At least one of these should kill the VM
183261
__fastfail(1);

SandboxBootkit/EfiUtils.hpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,16 @@ EFI_IMAGE_NT_HEADERS64* GetNtHeaders(void* ImageBase);
4848
void* FindImageBase(uint64_t Address, size_t MaxSize = (1 * 1024 * 1024));
4949
void* GetExport(void* ImageBase, const char* FunctionName, const char* ModuleName = nullptr);
5050
bool FixRelocations(void* ImageBase, uint64_t ImageBaseDelta);
51+
uint8_t* FindFunctionStart(void* ImageBase, void* Address);
52+
EFI_IMAGE_SECTION_HEADER* FindSection(void* ImageBase, const char* SectionName);
5153
bool ComparePattern(uint8_t* Base, uint8_t* Pattern, size_t PatternLen);
5254
uint8_t* FindPattern(uint8_t* Base, size_t Size, uint8_t* Pattern, size_t PatternLen);
53-
void Die();
55+
void __declspec(noreturn) Die();
56+
57+
#define ASSERT(Condition) \
58+
if (!(Condition)) \
59+
{ \
60+
Die(); \
61+
}
5462

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

0 commit comments

Comments
 (0)