mirror of
https://github.com/Idov31/NovaHypervisor
synced 2026-06-06 15:54:26 +00:00
1049 lines
35 KiB
C++
1049 lines
35 KiB
C++
#include "pch.h"
|
|
#include "Ept.h"
|
|
#include "EventInjection.h"
|
|
|
|
Ept::Ept() {
|
|
eptPointer = { 0 };
|
|
numberOfEnabledMemoryRanges = 0;
|
|
eptPageTable = NULL;
|
|
defaultMemoryType = 0xFF;
|
|
executeOnlySupport = true;
|
|
memset(memoryRanges, 0, sizeof(memoryRanges));
|
|
hookedPages = AllocateVirtualMemory<PLIST_ENTRY>(sizeof(LIST_ENTRY), false);
|
|
|
|
if (!hookedPages) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Failed to allocate memory for hooked pages list");
|
|
ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
InitializeListHead(hookedPages);
|
|
hookedPagesLock = Spinlock();
|
|
|
|
if (!CheckFeatures()) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "EPT features are not supported by the processor");
|
|
FreeVirtualMemory(hookedPages);
|
|
ExRaiseStatus(STATUS_NOT_SUPPORTED);
|
|
}
|
|
BuildMtrrMap();
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "MTRR memory map built successfully");
|
|
|
|
if (!LogicalProcessorInitialize()) {
|
|
FreeVirtualMemory(hookedPages);
|
|
ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
}
|
|
|
|
Ept::~Ept() {
|
|
UnhookAllPages();
|
|
FreeVirtualMemory(hookedPages);
|
|
|
|
if (this->eptPageTable) {
|
|
MmFreeContiguousMemory(this->eptPageTable);
|
|
this->eptPageTable = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* GetEptPointerFlags is responsible for returning the flags member from the EPT pointer.
|
|
*
|
|
* Parameters:
|
|
* There are no parameters.
|
|
*
|
|
* Returns:
|
|
* @flags [ULONG64] -- EPT pointer flags.
|
|
*/
|
|
ULONG64 Ept::GetEptPointerFlags() const {
|
|
return eptPointer.Flags;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* CheckFeatures is responsible for checking that all the critical EPT features are supported by the processor.
|
|
*
|
|
* Parameters:
|
|
* There are no parameters.
|
|
*
|
|
* Returns:
|
|
* @supported [bool] -- True if all the critical EPT features are supported, otherwise false.
|
|
*/
|
|
bool Ept::CheckFeatures() {
|
|
IA32_VMX_EPT_VPID_CAP_REGISTER vpidRegister = { 0 };
|
|
IA32_MTRR_DEF_TYPE_REGISTER mtrrDefType = { 0 };
|
|
|
|
vpidRegister.Flags = __readmsr(MSR_IA32_VMX_EPT_VPID_CAP);
|
|
mtrrDefType.Flags = __readmsr(MSR_IA32_MTRR_DEF_TYPE);
|
|
|
|
if (!vpidRegister.PageWalkLength4 || !vpidRegister.MemoryTypeWriteBack || !vpidRegister.Pde2MbPages ||
|
|
!mtrrDefType.MtrrEnable) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "EPT is not supported by the processor.");
|
|
return false;
|
|
}
|
|
|
|
if (!vpidRegister.AdvancedVmexitEptViolationsInformation)
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "The processor doesn't report advanced vmexit information for EPT violations.");
|
|
|
|
if (!vpidRegister.ExecuteOnlyPages) {
|
|
executeOnlySupport = false;
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "The processor doesn't support execute-only pages.");
|
|
}
|
|
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "All important EPT features are present.");
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* BuildMtrrMap is responsible for building the MTRR map.
|
|
*
|
|
* Parameters:
|
|
* There are no parameters.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
void Ept::BuildMtrrMap() {
|
|
IA32_MTRR_CAPABILITIES_REGISTER mtrrCapabilities = { 0 };
|
|
IA32_MTRR_PHYSBASE_REGISTER currentPhyiscalBase = { 0 };
|
|
IA32_MTRR_PHYSMASK_REGISTER currentPhysicalMask = { 0 };
|
|
IA32_MTRR_FIXED_RANGE_TYPE k64Types = { 0 };
|
|
IA32_MTRR_FIXED_RANGE_TYPE k16Types = { 0 };
|
|
IA32_MTRR_FIXED_RANGE_TYPE k4Types = { 0 };
|
|
PMTRR_RANGE_DESCRIPTOR descriptor = nullptr;
|
|
ULONG numberOfBitsInMask = 0;
|
|
IA32_MTRR_DEF_TYPE_REGISTER mtrrDefType = { 0 };
|
|
mtrrDefType.Flags = __readmsr(MSR_IA32_MTRR_DEF_TYPE);
|
|
|
|
if (!mtrrDefType.MtrrEnable) {
|
|
this->defaultMemoryType = MEMORY_TYPE_UNCACHEABLE;
|
|
return;
|
|
}
|
|
this->defaultMemoryType = static_cast<UCHAR>(mtrrDefType.DefaultMemoryType);
|
|
mtrrCapabilities.Flags = __readmsr(MSR_IA32_MTRR_CAPABILITIES);
|
|
|
|
if (mtrrCapabilities.FixedRangeSupported && mtrrDefType.FixedRangeMtrrEnable) {
|
|
k64Types.All = __readmsr(IA32_MTRR_FIX64K_00000);
|
|
|
|
for (unsigned int i = 0; i < 8; i++) {
|
|
descriptor = &this->memoryRanges[this->numberOfEnabledMemoryRanges++];
|
|
descriptor->MemoryType = k64Types.s.Types[i];
|
|
descriptor->PhysicalBaseAddress = IA32_MTRR_FIX64K_BASE + (IA32_MTRR_FIX64K_SIZE * i);
|
|
descriptor->PhysicalEndAddress = IA32_MTRR_FIX64K_BASE + (IA32_MTRR_FIX64K_SIZE * i) + (IA32_MTRR_FIX64K_SIZE - 1);
|
|
descriptor->FixedRange = true;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < 2; i++) {
|
|
k16Types.All = __readmsr(IA32_MTRR_FIX16K_80000 + i);
|
|
|
|
for (unsigned int j = 0; j < 8; j++) {
|
|
descriptor = &this->memoryRanges[this->numberOfEnabledMemoryRanges++];
|
|
descriptor->MemoryType = k16Types.s.Types[j];
|
|
descriptor->PhysicalBaseAddress = (IA32_MTRR_FIX16K_BASE + (i * IA32_MTRR_FIX16K_SIZE * 8)) + (IA32_MTRR_FIX16K_SIZE * j);
|
|
descriptor->PhysicalEndAddress = (IA32_MTRR_FIX16K_BASE + (i * IA32_MTRR_FIX16K_SIZE * 8)) + (IA32_MTRR_FIX16K_SIZE * j) +
|
|
(IA32_MTRR_FIX16K_SIZE - 1);
|
|
descriptor->FixedRange = true;
|
|
}
|
|
}
|
|
|
|
for (unsigned int i = 0; i < 8; i++) {
|
|
k4Types.All = __readmsr(IA32_MTRR_FIX4K_C0000 + i);
|
|
|
|
for (unsigned int j = 0; j < 8; j++) {
|
|
descriptor = &this->memoryRanges[this->numberOfEnabledMemoryRanges++];
|
|
descriptor->MemoryType = k4Types.s.Types[j];
|
|
descriptor->PhysicalBaseAddress = (IA32_MTRR_FIX4K_BASE + (i * IA32_MTRR_FIX4K_SIZE * 8)) + (IA32_MTRR_FIX4K_SIZE * j);
|
|
descriptor->PhysicalEndAddress = (IA32_MTRR_FIX4K_BASE + (i * IA32_MTRR_FIX4K_SIZE * 8)) + (IA32_MTRR_FIX4K_SIZE * j) +
|
|
(IA32_MTRR_FIX4K_SIZE - 1);
|
|
descriptor->FixedRange = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (UINT64 currentRegister = 0; currentRegister < mtrrCapabilities.VariableRangeCount; currentRegister++) {
|
|
currentPhyiscalBase.Flags = __readmsr(MSR_IA32_MTRR_PHYSBASE0 + (currentRegister * 2));
|
|
currentPhysicalMask.Flags = __readmsr(MSR_IA32_MTRR_PHYSMASK0 + (currentRegister * 2));
|
|
|
|
if (currentPhysicalMask.Valid) {
|
|
descriptor = &this->memoryRanges[this->numberOfEnabledMemoryRanges++];
|
|
descriptor->PhysicalBaseAddress = currentPhyiscalBase.PageFrameNumber * PAGE_SIZE;
|
|
_BitScanForward64(&numberOfBitsInMask, currentPhysicalMask.PageFrameNumber * PAGE_SIZE);
|
|
descriptor->PhysicalEndAddress = descriptor->PhysicalBaseAddress + (1ULL << numberOfBitsInMask) - 1;
|
|
descriptor->MemoryType = (UCHAR)currentPhyiscalBase.Type;
|
|
descriptor->FixedRange = false;
|
|
|
|
if (descriptor->MemoryType == MEMORY_TYPE_WRITE_BACK)
|
|
this->numberOfEnabledMemoryRanges--;
|
|
NovaHypervisorLog(TRACE_FLAG_DEBUG, "MTRR Range: Base=0x%llx End=0x%llx Type=0x%x", descriptor->PhysicalBaseAddress, descriptor->PhysicalEndAddress,
|
|
descriptor->MemoryType);
|
|
}
|
|
}
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "Default memory type: 0x%x", this->defaultMemoryType);
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* GetPml1Entry is responsible for getting a PML1 entry.
|
|
*
|
|
* Parameters:
|
|
* @physicalAddress [_In_ SIZE_T] -- The physical address to search.
|
|
*
|
|
* Returns:
|
|
* @pml1 [PEPT_PML1_ENTRY] -- The PML1 entry.
|
|
*/
|
|
PEPT_PML1_ENTRY Ept::GetPml1Entry(_In_ SIZE_T physicalAddress) {
|
|
SIZE_T directory = ADDRMASK_EPT_PML2_INDEX(physicalAddress);
|
|
SIZE_T directoryPointer = ADDRMASK_EPT_PML3_INDEX(physicalAddress);
|
|
SIZE_T pml4Entry = ADDRMASK_EPT_PML4_INDEX(physicalAddress);
|
|
|
|
if (pml4Entry > 0)
|
|
return NULL;
|
|
PEPT_PML2_ENTRY pml2 = &eptPageTable->PML2[directoryPointer][directory];
|
|
|
|
if (pml2->LargePage)
|
|
return NULL;
|
|
PEPT_PML1_ENTRY pml1 = reinterpret_cast<PEPT_PML1_ENTRY>(GetVirtualAddress(reinterpret_cast<PEPT_PML2_POINTER>(pml2)->PageFrameNumber * PAGE_SIZE));
|
|
|
|
if (!pml1)
|
|
return NULL;
|
|
pml1 = &pml1[ADDRMASK_EPT_PML1_INDEX(physicalAddress)];
|
|
return pml1;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* GetPml2Entry is responsible for getting a PML2 entry.
|
|
*
|
|
* Parameters:
|
|
* @physicalAddress [_In_ SIZE_T] -- The physical address to search.
|
|
*
|
|
* Returns:
|
|
* @pml2 [PEPT_PML2_ENTRY] -- The PML2 entry.
|
|
*/
|
|
PEPT_PML2_ENTRY Ept::GetPml2Entry(_In_ SIZE_T physicalAddress) {
|
|
SIZE_T directory = ADDRMASK_EPT_PML2_INDEX(physicalAddress);
|
|
SIZE_T directoryPointer = ADDRMASK_EPT_PML3_INDEX(physicalAddress);
|
|
SIZE_T pml4Entry = ADDRMASK_EPT_PML4_INDEX(physicalAddress);
|
|
|
|
// Addresses above 512GB are invalid because it is greater than physical address bus width
|
|
if (pml4Entry > 0)
|
|
return NULL;
|
|
return &eptPageTable->PML2[directoryPointer][directory];
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* SplitLargePage is responsible for splitting a large page into smaller pages.
|
|
*
|
|
* Parameters:
|
|
* @buffer [_Inout_ PVOID] -- The buffer to store the split.
|
|
* @physicalAddress [_In_ SIZE_T] -- The physical address to split.
|
|
*
|
|
* Returns:
|
|
* @status [bool] -- True if splitted else false.
|
|
*/
|
|
bool Ept::SplitLargePage(_Inout_ PVOID buffer, _In_ SIZE_T physicalAddress) {
|
|
|
|
EPT_PML1_ENTRY pml1Template = { 0 };
|
|
EPT_PML2_POINTER newSplitPtr = { 0 };
|
|
|
|
if (!buffer) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Buffer is not allocated");
|
|
return false;
|
|
}
|
|
|
|
PEPT_PML2_ENTRY pml2Entry = GetPml2Entry(physicalAddress);
|
|
|
|
if (!pml2Entry) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "An invalid physical address passed 0x%llx", physicalAddress);
|
|
return false;
|
|
}
|
|
|
|
// If this large page is not marked a large page, that means it's already splitted.
|
|
if (!pml2Entry->LargePage) {
|
|
poolManager->Free(buffer, SPLIT_2MB_PAGING_TO_4KB_PAGE);
|
|
return true;
|
|
}
|
|
PVMM_EPT_DYNAMIC_SPLIT newSplit = static_cast<PVMM_EPT_DYNAMIC_SPLIT>(buffer);
|
|
|
|
if (!newSplit) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Failed to allocate dynamic split memory");
|
|
return false;
|
|
}
|
|
RtlSecureZeroMemory(newSplit, sizeof(VMM_EPT_DYNAMIC_SPLIT));
|
|
newSplit->Entry = pml2Entry;
|
|
|
|
// Make a template for RWX and copy it to all PML1 entries.
|
|
pml1Template.Flags = 0;
|
|
pml1Template.ReadAccess = 1;
|
|
pml1Template.WriteAccess = 1;
|
|
pml1Template.ExecuteAccess = 1;
|
|
pml1Template.MemoryType = pml2Entry->MemoryType;
|
|
pml1Template.IgnorePat = pml2Entry->IgnorePat;
|
|
pml1Template.SuppressVe = pml2Entry->SuppressVe;
|
|
|
|
__stosq(reinterpret_cast<SIZE_T*>(&newSplit->PML1[0]), pml1Template.Flags, VMM_EPT_PML1E_COUNT);
|
|
|
|
// Set the PFNs for identity mapping by converting the 2MB PFN to 4KB + offset to the frame.
|
|
for (SIZE_T entryIndex = 0; entryIndex < VMM_EPT_PML1E_COUNT; entryIndex++) {
|
|
newSplit->PML1[entryIndex].PageFrameNumber = ((pml2Entry->PageFrameNumber * SIZE_2_MB) / PAGE_SIZE) + entryIndex;
|
|
newSplit->PML1[entryIndex].MemoryType = GetMemoryType(newSplit->PML1[entryIndex].PageFrameNumber, false);
|
|
}
|
|
|
|
// Set a new pointer that points to the new split instead of the 2MB page.
|
|
newSplitPtr.Flags = 0;
|
|
newSplitPtr.WriteAccess = 1;
|
|
newSplitPtr.ReadAccess = 1;
|
|
newSplitPtr.ExecuteAccess = 1;
|
|
newSplitPtr.PageFrameNumber = GetPhysicalAddress(reinterpret_cast<UINT64>(&newSplit->PML1[0])) / PAGE_SIZE;
|
|
RtlCopyMemory(pml2Entry, &newSplitPtr, sizeof(newSplitPtr));
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* SetupPML2Entry is responsible for setting the correct memory type for a PML2 entry.
|
|
*
|
|
* Parameters:
|
|
* @newEntry [_Inout_ PEPT_PML2_ENTRY] -- The new PML2 entry.
|
|
* @pageFrameNumber [_In_ SIZE_T] -- The page frame number.
|
|
*
|
|
* Returns:
|
|
* @status [bool] -- True if the entry is set up successfully, otherwise false.
|
|
*/
|
|
bool Ept::SetupPML2Entry(_Inout_ PEPT_PML2_ENTRY newEntry, _In_ SIZE_T pageFrameNumber) {
|
|
newEntry->PageFrameNumber = pageFrameNumber;
|
|
SIZE_T addressOfPage = pageFrameNumber * SIZE_2_MB;
|
|
|
|
if (IsValidForLargePage(pageFrameNumber)) {
|
|
newEntry->MemoryType = GetMemoryType(pageFrameNumber, true);
|
|
return true;
|
|
}
|
|
// First entry MUST be uncacheable, otherwise it will cause a page fault.
|
|
if (pageFrameNumber == 0) {
|
|
newEntry->MemoryType = MEMORY_TYPE_UNCACHEABLE;
|
|
return true;
|
|
}
|
|
PVOID buffer = poolManager->Allocate(SPLIT_2MB_PAGING_TO_4KB_PAGE);
|
|
|
|
if (buffer)
|
|
return SplitLargePage(buffer, pageFrameNumber * SIZE_2_MB);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* AllocateAndCreateIdentityPageTable is responsible for creating a new EPT page table.
|
|
*
|
|
* Parameters:
|
|
* There are no parameters.
|
|
*
|
|
* Returns:
|
|
* @pageTable [PVMM_EPT_PAGE_TABLE] -- The allocated page table.
|
|
*/
|
|
PVMM_EPT_PAGE_TABLE Ept::AllocateAndCreateIdentityPageTable() {
|
|
PHYSICAL_ADDRESS maxPhysicalAddress = { 0 };
|
|
EPT_PML3_POINTER pml3Template = { 0 };
|
|
EPT_PML2_ENTRY pml2Template = { 0 };
|
|
maxPhysicalAddress.QuadPart = MAXULONG64;
|
|
|
|
PVMM_EPT_PAGE_TABLE pageTable = static_cast<PVMM_EPT_PAGE_TABLE>(MmAllocateContiguousMemory(sizeof(VMM_EPT_PAGE_TABLE), maxPhysicalAddress));
|
|
|
|
if (!pageTable) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Failed to allocate memory for EPT page table");
|
|
return NULL;
|
|
}
|
|
RtlSecureZeroMemory(pageTable, sizeof(VMM_EPT_PAGE_TABLE));
|
|
|
|
// Mark the first 512GB PML4 entry as present to manage up to 512GB of discrete paging structures.
|
|
pageTable->PML4[0].PageFrameNumber = GetPhysicalAddress(reinterpret_cast<UINT64>(&pageTable->PML3[0])) / PAGE_SIZE;
|
|
pageTable->PML4[0].ReadAccess = 1;
|
|
pageTable->PML4[0].WriteAccess = 1;
|
|
pageTable->PML4[0].ExecuteAccess = 1;
|
|
|
|
// Copy RWX template to all PML3 entries.
|
|
pml3Template.Flags = 0;
|
|
pml3Template.ReadAccess = 1;
|
|
pml3Template.WriteAccess = 1;
|
|
pml3Template.ExecuteAccess = 1;
|
|
__stosq(reinterpret_cast<SIZE_T*>(&pageTable->PML3[0]), pml3Template.Flags, VMM_EPT_PML3E_COUNT);
|
|
|
|
// For each of the 512 PML3 entries
|
|
for (SIZE_T entryIndex = 0; entryIndex < VMM_EPT_PML3E_COUNT; entryIndex++)
|
|
pageTable->PML3[entryIndex].PageFrameNumber = GetPhysicalAddress(reinterpret_cast<UINT64>(&pageTable->PML2[entryIndex][0])) / PAGE_SIZE;
|
|
|
|
// Copy RWX template to all PML2 entries and mark them as present.
|
|
pml2Template.Flags = 0;
|
|
pml2Template.WriteAccess = 1;
|
|
pml2Template.ReadAccess = 1;
|
|
pml2Template.ExecuteAccess = 1;
|
|
pml2Template.LargePage = 1;
|
|
__stosq(reinterpret_cast<SIZE_T*>(&pageTable->PML2[0]), pml2Template.Flags, VMM_EPT_PML3E_COUNT * VMM_EPT_PML2E_COUNT);
|
|
|
|
for (SIZE_T entryGroupIndex = 0; entryGroupIndex < VMM_EPT_PML3E_COUNT; entryGroupIndex++) {
|
|
for (SIZE_T entryIndex = 0; entryIndex < VMM_EPT_PML2E_COUNT; entryIndex++) {
|
|
if (!SetupPML2Entry(&pageTable->PML2[entryGroupIndex][entryIndex], (entryGroupIndex * VMM_EPT_PML2E_COUNT) + entryIndex)) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Failed to setup PML2 entry for page table");
|
|
MmFreeContiguousMemory(pageTable);
|
|
pageTable = NULL;
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
return pageTable;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* LogicalProcessorInitialize is responsible for initializing the EPT.
|
|
*
|
|
* Parameters:
|
|
* There are no parameters.
|
|
*
|
|
* Returns:
|
|
* @status [bool] -- True if the EPT is initialized, otherwise false.
|
|
*/
|
|
bool Ept::LogicalProcessorInitialize() {
|
|
EPTP eptp = { 0 };
|
|
PVMM_EPT_PAGE_TABLE pageTable = AllocateAndCreateIdentityPageTable();
|
|
|
|
if (!pageTable) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Unable to allocate memory for EPT");
|
|
return false;
|
|
}
|
|
this->eptPageTable = pageTable;
|
|
|
|
eptp.Flags = 0;
|
|
eptp.MemoryType = MEMORY_TYPE_WRITE_BACK;
|
|
eptp.EnableAccessAndDirtyFlags = FALSE;
|
|
|
|
// Must be 3, indicating an EPT page-walk length of 4.
|
|
eptp.PageWalkLength = 3;
|
|
eptp.PageFrameNumber = GetPhysicalAddress(reinterpret_cast<UINT64>(&pageTable->PML4)) / PAGE_SIZE;
|
|
this->eptPointer = eptp;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* HandlePageHookExit is responsible for handling a page hook vmexit.
|
|
*
|
|
* Parameters:
|
|
* @violationQualification [_In_ VMX_EXIT_QUALIFICATION_EPT_VIOLATION] -- The EPT violation qualification.
|
|
* @guestPhysicalAddr [_In_ UINT64] -- The guest physical address.
|
|
* @guestLinearAddress [_In_ ULONG64] -- The guest linear address being accessed.
|
|
* @guestRip [_In_ ULONG64] -- The guest instruction pointer that caused the access.
|
|
*
|
|
* Returns:
|
|
* @status [bool] -- True if the page hook exit is handled, otherwise false.
|
|
*/
|
|
_Use_decl_annotations_
|
|
bool Ept::HandlePageHookExit(_In_ VMX_EXIT_QUALIFICATION_EPT_VIOLATION violationQualification,
|
|
_In_ UINT64 guestPhysicalAddr,
|
|
_In_ ULONG64 guestLinearAddress,
|
|
_In_ ULONG64 guestRip) {
|
|
PEPT_HOOKED_PAGE_DETAIL hookedEntry = NULL;
|
|
bool handled = false;
|
|
bool restoreHookAfterInstruction = false;
|
|
UINT64 alignedPhysicalAddress = reinterpret_cast<UINT64>(PAGE_ALIGN(guestPhysicalAddr));
|
|
|
|
if (!alignedPhysicalAddress) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Target address could not be mapped to physical memory");
|
|
return handled;
|
|
}
|
|
this->hookedPagesLock.Lock();
|
|
PLIST_ENTRY currentEntry = this->hookedPages;
|
|
|
|
while (currentEntry->Flink != this->hookedPages) {
|
|
currentEntry = currentEntry->Flink;
|
|
hookedEntry = CONTAINING_RECORD(currentEntry, EPT_HOOKED_PAGE_DETAIL, Entry);
|
|
|
|
if (hookedEntry->PhysicalBaseAddress == alignedPhysicalAddress) {
|
|
ULONG currentProcessor = KeGetCurrentProcessorNumber();
|
|
handled = HandleHookedPage(hookedEntry, violationQualification, guestLinearAddress, guestRip, &restoreHookAfterInstruction);
|
|
|
|
if (restoreHookAfterInstruction) {
|
|
GuestState[currentProcessor].HookedPage = hookedEntry;
|
|
VmxHelper::SetMonitorTrapFlag(true);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
this->hookedPagesLock.Unlock();
|
|
return handled;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* HandleHookedPage is responsible to handle a hooked page.
|
|
*
|
|
* Parameters:
|
|
* @hookedEntryDetails [_Inout_ EPT_HOOKED_PAGE_DETAIL] -- The hooked page details.
|
|
* @violationQualification [_In_ VMX_EXIT_QUALIFICATION_EPT_VIOLATION] -- The violation qualification.
|
|
* @guestLinearAddress [_In_ ULONG64] -- The guest virtual address that caused the hook.
|
|
* @guestRip [_In_ ULONG64] -- The guest instruction pointer that caused the access.
|
|
*
|
|
* Returns:
|
|
* @status [bool] -- True if the page is handled, otherwise false.
|
|
*/
|
|
bool Ept::HandleHookedPage(_Inout_ EPT_HOOKED_PAGE_DETAIL* hookedEntryDetails,
|
|
_In_ VMX_EXIT_QUALIFICATION_EPT_VIOLATION violationQualification,
|
|
_In_ ULONG64 guestLinearAddress,
|
|
_In_ ULONG64 guestRip,
|
|
_Out_ bool* restoreHookAfterInstruction) {
|
|
bool operationAllowed = false;
|
|
bool handled = false;
|
|
PEPT_PML1_ENTRY pml1Entry = hookedEntryDetails->EntryAddress;
|
|
bool fromKernelImage = IsAccessFromKernelImage(guestRip);
|
|
ULONG32 pageFaultErrorCode = 1;
|
|
|
|
*restoreHookAfterInstruction = false;
|
|
|
|
if (violationQualification.WriteAccess)
|
|
pageFaultErrorCode |= 1 << 1;
|
|
if (violationQualification.UserModeLinearAddress)
|
|
pageFaultErrorCode |= 1 << 2;
|
|
if (violationQualification.ExecuteAccess)
|
|
pageFaultErrorCode |= 1 << 4;
|
|
|
|
if (!violationQualification.EptExecutable && violationQualification.ExecuteAccess) {
|
|
if (fromKernelImage) {
|
|
pml1Entry->ExecuteAccess = 1;
|
|
operationAllowed = true;
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "Allowed execute access to protected address 0x%llx from RIP 0x%llx", guestLinearAddress, guestRip);
|
|
}
|
|
else
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "Blocked execute access to protected address 0x%llx from RIP 0x%llx", guestLinearAddress, guestRip);
|
|
handled = true;
|
|
}
|
|
else if (!violationQualification.EptWriteable && violationQualification.WriteAccess) {
|
|
if (fromKernelImage) {
|
|
pml1Entry->WriteAccess = 1;
|
|
operationAllowed = true;
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "Allowed write access to protected address 0x%llx from RIP 0x%llx", guestLinearAddress, guestRip);
|
|
}
|
|
else
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "Blocked write access to protected address 0x%llx from RIP 0x%llx", guestLinearAddress, guestRip);
|
|
handled = true;
|
|
}
|
|
else if (!violationQualification.EptReadable && violationQualification.ReadAccess) {
|
|
if (fromKernelImage) {
|
|
pml1Entry->ReadAccess = 1;
|
|
operationAllowed = true;
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "Allowed read access to protected address 0x%llx from RIP 0x%llx", guestLinearAddress, guestRip);
|
|
}
|
|
else
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "Blocked read access to protected address 0x%llx from RIP 0x%llx", guestLinearAddress, guestRip);
|
|
handled = true;
|
|
}
|
|
|
|
if (operationAllowed) {
|
|
SetPML1AndInvalidateTLB(hookedEntryDetails->EntryAddress, hookedEntryDetails->OriginalEntry, SINGLE_CONTEXT);
|
|
*restoreHookAfterInstruction = true;
|
|
}
|
|
else if (handled) {
|
|
EventHandler::InjectPageFault(guestLinearAddress, pageFaultErrorCode);
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* HandleEptViolation is responsible for handling ept violations.
|
|
*
|
|
* Parameters:
|
|
* @exitQualification [_In_ ULONG64] -- The exit qualification.
|
|
* @guestPhysicalAddr [_In_ ULONG64] -- The guest physical address.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
void Ept::HandleEptViolation(_In_ ULONG64 exitQualification, _In_ ULONG64 guestPhysicalAddr) {
|
|
VMX_EXIT_QUALIFICATION_EPT_VIOLATION violationQualification = { 0 };
|
|
violationQualification.Flags = exitQualification;
|
|
SIZE_T guestLinearAddress = 0;
|
|
SIZE_T guestRip = 0;
|
|
|
|
__vmx_vmread(GUEST_LINEAR_ADDRESS, &guestLinearAddress);
|
|
__vmx_vmread(GUEST_RIP, &guestRip);
|
|
|
|
if (HandlePageHookExit(violationQualification, guestPhysicalAddr, guestLinearAddress, guestRip))
|
|
return;
|
|
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Unexpected EPT violation at 0x%llx", guestPhysicalAddr);
|
|
DbgBreakPoint();
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* HandleMisconfiguration is responsible for handling EPT misconfigurations.
|
|
*
|
|
* Parameters:
|
|
* @guestAddress [_In_ UINT64] -- The guest address.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
void Ept::HandleMisconfiguration(_In_ UINT64 guestAddress) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "EPT Misconfiguration!");
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "A field in the EPT paging structure was invalid, faulting guest address: 0x%llx", guestAddress);
|
|
DbgBreakPoint();
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* HandleMonitorTrapFlag is responsible for handling MTF event.
|
|
*
|
|
* Parameters:
|
|
* @hookedEntry [_Inout_ PEPT_HOOKED_PAGE_DETAIL] -- The hooked page detail.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
void Ept::HandleMonitorTrapFlag(_Inout_ PEPT_HOOKED_PAGE_DETAIL hookedEntry) {
|
|
SetPML1AndInvalidateTLB(hookedEntry->EntryAddress, hookedEntry->ChangedEntry, SINGLE_CONTEXT);
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "Restored hooked page 0x%llx", hookedEntry->VirtualAddress);
|
|
}
|
|
|
|
bool Ept::IsAccessFromKernelImage(_In_ UINT64 guestRip) const {
|
|
const UINT64 kernelBase = KernelBaseInfo.KernelBaseAddress;
|
|
const UINT64 kernelSize = KernelBaseInfo.KernelSize;
|
|
|
|
if (!kernelBase || !kernelSize)
|
|
return false;
|
|
|
|
if (kernelSize > 0x10000000 || kernelBase + kernelSize < kernelBase)
|
|
return false;
|
|
|
|
return guestRip >= kernelBase && guestRip < kernelBase + kernelSize;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* RootModePageHook is responsible for hooking a page in VMX root mode.
|
|
*
|
|
* Parameters:
|
|
* @targetFunc [_In_ PVOID] -- The target function to hook.
|
|
* @permissions [_In_ UINT8] -- The permissions for the page.
|
|
*
|
|
* Returns:
|
|
* @status [bool] -- True if the page is hooked, otherwise false.
|
|
*/
|
|
bool Ept::RootModePageHook(_In_ PVOID targetFunc, _In_ UINT8 permissions) {
|
|
EPT_PML1_ENTRY changedEntry = { 0 };
|
|
ULONG currentProcessorIndex = KeGetCurrentProcessorIndex();
|
|
|
|
if (GuestState[currentProcessorIndex].IsOnVmxRoot && !GuestState[currentProcessorIndex].IsLaunched)
|
|
return false;
|
|
|
|
if (permissions & EPT_PAGE_WRITE && !(permissions & EPT_PAGE_READ)) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Invalid permissions to perform a hook");
|
|
return false;
|
|
}
|
|
|
|
PVOID virtualFuncAddress = PAGE_ALIGN(targetFunc);
|
|
|
|
if (IsHookExists(reinterpret_cast<UINT64>(virtualFuncAddress))) {
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "Hook already exists for the target function: 0x%llx", reinterpret_cast<UINT64>(targetFunc));
|
|
return true;
|
|
}
|
|
|
|
SIZE_T physicalFuncAddress = GetPhysicalAddress(reinterpret_cast<UINT64>(virtualFuncAddress));
|
|
|
|
if (!physicalFuncAddress) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Target address could not be mapped to physical memory");
|
|
return false;
|
|
}
|
|
PVOID targetBuffer = poolManager->Allocate(SPLIT_2MB_PAGING_TO_4KB_PAGE);
|
|
|
|
if (!targetBuffer) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Failed to allocate memory for the target buffer");
|
|
return false;
|
|
}
|
|
|
|
if (!SplitLargePage(targetBuffer, physicalFuncAddress)) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Could not split page for the address: 0x%llx", physicalFuncAddress);
|
|
poolManager->Free(targetBuffer, SPLIT_2MB_PAGING_TO_4KB_PAGE);
|
|
return false;
|
|
}
|
|
PEPT_PML1_ENTRY pml1Entry = GetPml1Entry(physicalFuncAddress);
|
|
|
|
if (!pml1Entry) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Failed to get PML1 entry of the target address: 0x%llx", physicalFuncAddress);
|
|
poolManager->Free(targetBuffer, SPLIT_2MB_PAGING_TO_4KB_PAGE);
|
|
return false;
|
|
}
|
|
changedEntry.Flags = pml1Entry->Flags;
|
|
changedEntry.ReadAccess = permissions & EPT_PAGE_READ;
|
|
changedEntry.WriteAccess = permissions & EPT_PAGE_WRITE;
|
|
changedEntry.ExecuteAccess = permissions & EPT_PAGE_EXECUTE;
|
|
|
|
PEPT_HOOKED_PAGE_DETAIL hookedEntry = static_cast<PEPT_HOOKED_PAGE_DETAIL>(poolManager->Allocate(EPT_HOOK_PAGE));
|
|
|
|
if (!hookedEntry) {
|
|
NovaHypervisorLog(TRACE_FLAG_ERROR, "Failed to allocate memory for the hooked entry");
|
|
poolManager->Free(targetBuffer, SPLIT_2MB_PAGING_TO_4KB_PAGE);
|
|
return false;
|
|
}
|
|
hookedEntry->IsExecutionHook = false;
|
|
hookedEntry->VirtualAddress = reinterpret_cast<UINT64>(virtualFuncAddress);
|
|
hookedEntry->PhysicalAddress = physicalFuncAddress;
|
|
hookedEntry->PhysicalBaseAddress = reinterpret_cast<UINT64>(PAGE_ALIGN(physicalFuncAddress));
|
|
hookedEntry->EntryAddress = pml1Entry;
|
|
hookedEntry->OriginalEntry = *pml1Entry;
|
|
hookedEntry->ChangedEntry = changedEntry;
|
|
this->hookedPagesLock.Lock();
|
|
InsertHeadList(this->hookedPages, &(hookedEntry->Entry));
|
|
this->hookedPagesLock.Unlock();
|
|
|
|
// Invalidate the entry in the TLB caches so it will not conflict with the actual paging structure.
|
|
if (GuestState[currentProcessorIndex].IsLaunched)
|
|
SetPML1AndInvalidateTLB(pml1Entry, changedEntry, SINGLE_CONTEXT);
|
|
else
|
|
pml1Entry->Flags = changedEntry.Flags;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* PageHook is responsible for hooking a page.
|
|
*
|
|
* Parameters:
|
|
* @targetFunc [_In_ PVOID] -- The target function to hook.
|
|
* @permissions [_In_ UINT8] -- The permissions for the page.
|
|
*
|
|
* Returns:
|
|
* @status [bool] -- True if the page is hooked, otherwise false.
|
|
*/
|
|
bool Ept::PageHook(_In_ PVOID targetFunc, _In_ UINT8 permissions) {
|
|
ULONG currentProcessor = KeGetCurrentProcessorIndex();
|
|
|
|
if (GuestState[currentProcessor].IsLaunched) {
|
|
if (NT_SUCCESS(AsmVmxVmcall(VMCALL_EXEC_HOOK_PAGE, reinterpret_cast<UINT64>(targetFunc), permissions, NULL))) {
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "Hook applied from vmx root mode");
|
|
KeIpiGenericCall(reinterpret_cast<PKIPI_BROADCAST_WORKER>(VmxHelper::InvalidateEptByVmcall), this->eptPointer.Flags);
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
if (RootModePageHook(targetFunc, permissions)) {
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "Hook applied (vm not launched yet)");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
NovaHypervisorLog(TRACE_FLAG_INFO, "Hook not applied");
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* GetMemoryType is responsible for getting a memory type for PFN.
|
|
*
|
|
* Parameters:
|
|
* @pfn [_In_ ULONG64] -- The PFN.
|
|
* @isLargePage [_In_ bool] -- True if the page is a large page, otherwise false.
|
|
*
|
|
* Returns:
|
|
* @targetMemoryType [UCHAR] -- The memory type.
|
|
*/
|
|
UCHAR Ept::GetMemoryType(_In_ ULONG64 pfn, _In_ bool isLargePage) {
|
|
UCHAR targetMemoryType = 0xFF;
|
|
MTRR_RANGE_DESCRIPTOR* currentMemoryRange;
|
|
ULONG64 addressOfPage = isLargePage ? pfn * SIZE_2_MB : pfn * PAGE_SIZE;
|
|
|
|
for (UINT32 currentMtrrRange = 0; currentMtrrRange < this->numberOfEnabledMemoryRanges; currentMtrrRange++) {
|
|
currentMemoryRange = &this->memoryRanges[currentMtrrRange];
|
|
|
|
if (addressOfPage >= currentMemoryRange->PhysicalBaseAddress &&
|
|
addressOfPage < currentMemoryRange->PhysicalEndAddress) {
|
|
if (currentMemoryRange->FixedRange) {
|
|
targetMemoryType = currentMemoryRange->MemoryType;
|
|
break;
|
|
}
|
|
|
|
if (targetMemoryType == MEMORY_TYPE_UNCACHEABLE) {
|
|
targetMemoryType = currentMemoryRange->MemoryType;
|
|
break;
|
|
}
|
|
|
|
if (targetMemoryType == MEMORY_TYPE_WRITE_THROUGH || currentMemoryRange->MemoryType == MEMORY_TYPE_WRITE_THROUGH) {
|
|
if (targetMemoryType == MEMORY_TYPE_WRITE_BACK) {
|
|
targetMemoryType = MEMORY_TYPE_WRITE_THROUGH;
|
|
continue;
|
|
}
|
|
}
|
|
targetMemoryType = currentMemoryRange->MemoryType;
|
|
}
|
|
}
|
|
|
|
if (targetMemoryType == 0xFF)
|
|
targetMemoryType = this->defaultMemoryType;
|
|
|
|
return targetMemoryType;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* GetPml1OrPml2Entry is responsible for getting PML1 or PML2 entry.
|
|
*
|
|
* Parameters:
|
|
* @physicalAddress [_In_ SIZE_T] -- The physical address to search.
|
|
* @isLargePage [_Inout_ bool] -- True if the page is a large page, otherwise false.
|
|
*
|
|
* Returns:
|
|
* @entry [PVOID] -- The PML1 or PML2 entry.
|
|
*/
|
|
PVOID Ept::GetPml1OrPml2Entry(_In_ SIZE_T physicalAddress, _Inout_ bool* isLargePage) {
|
|
SIZE_T directory = ADDRMASK_EPT_PML2_INDEX(physicalAddress);
|
|
SIZE_T directoryPointer = ADDRMASK_EPT_PML3_INDEX(physicalAddress);
|
|
SIZE_T pml4Entry = ADDRMASK_EPT_PML4_INDEX(physicalAddress);
|
|
|
|
if (pml4Entry > 0)
|
|
return NULL;
|
|
PEPT_PML2_ENTRY pml2 = &eptPageTable->PML2[directoryPointer][directory];
|
|
|
|
if (pml2->LargePage) {
|
|
*isLargePage = true;
|
|
return pml2;
|
|
}
|
|
PEPT_PML2_POINTER pml2Pointer = reinterpret_cast<PEPT_PML2_POINTER>(pml2);
|
|
PEPT_PML1_ENTRY pml1 = reinterpret_cast<PEPT_PML1_ENTRY>(GetVirtualAddress(pml2Pointer->PageFrameNumber * PAGE_SIZE));
|
|
|
|
if (!pml1)
|
|
return NULL;
|
|
pml1 = &pml1[ADDRMASK_EPT_PML1_INDEX(physicalAddress)];
|
|
|
|
*isLargePage = false;
|
|
return pml1;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* IsValidForLargePage checks if the page is valid for a large page.
|
|
*
|
|
* Parameters:
|
|
* @pfn [_In_ ULONG64] -- The page frame number.
|
|
*
|
|
* Returns:
|
|
* @status [bool] -- True if the page is valid for a large page, otherwise false.
|
|
*/
|
|
bool Ept::IsValidForLargePage(_In_ ULONG64 pfn) {
|
|
ULONG64 startPageAddress = pfn * SIZE_2_MB;
|
|
ULONG64 endPageAddress = startPageAddress + SIZE_2_MB - 1;
|
|
MTRR_RANGE_DESCRIPTOR* currentMemoryRange = nullptr;
|
|
|
|
for (UINT32 memoryPageIndex = 0; memoryPageIndex < this->numberOfEnabledMemoryRanges; memoryPageIndex++) {
|
|
currentMemoryRange = &this->memoryRanges[memoryPageIndex];
|
|
|
|
if ((startPageAddress <= currentMemoryRange->PhysicalEndAddress && endPageAddress > currentMemoryRange->PhysicalEndAddress) ||
|
|
(startPageAddress < currentMemoryRange->PhysicalBaseAddress && endPageAddress >= currentMemoryRange->PhysicalBaseAddress))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* SetPML1AndInvalidateTLB is responsible for setting the PML1 entry and invalidating the TLB.
|
|
*
|
|
* Parameters:
|
|
* @pml1Entry [_Inout_ PEPT_PML1_ENTRY] -- The PML1 entry to set.
|
|
* @pml1Value [_In_ EPT_PML1_ENTRY] -- The PML1 value to set.
|
|
* @invalidationType [_In_ INVEPT_TYPE] -- The invalidation type.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
_Use_decl_annotations_
|
|
void Ept::SetPML1AndInvalidateTLB(_Inout_ PEPT_PML1_ENTRY pml1Entry, _In_ EPT_PML1_ENTRY pml1Value, _In_ INVEPT_TYPE invalidationType) {
|
|
pml1Entry->Flags = pml1Value.Flags;
|
|
|
|
switch (invalidationType) {
|
|
case SINGLE_CONTEXT:
|
|
VmxHelper::InvalidateEpt(this->eptPointer.Flags);
|
|
break;
|
|
case ALL_CONTEXTS:
|
|
VmxHelper::InvalidateEpt();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* PageUnhook is responsible to dispatch a vmcall to remove a hooked page.
|
|
*
|
|
* Parameters:
|
|
* @guestVirtualAddress [_In_ UINT64] -- The guest virtual address.
|
|
*
|
|
* Returns:
|
|
* @status [bool] -- True if the page is unhooked, otherwise false.
|
|
*/
|
|
bool Ept::PageUnhook(_In_ UINT64 guestVirtualAddress) {
|
|
if (GuestState[KeGetCurrentProcessorNumber()].IsOnVmxRoot)
|
|
return false;
|
|
UINT64 alignedGuestVirtualAddress = reinterpret_cast<UINT64>(PAGE_ALIGN(guestVirtualAddress));
|
|
PEPT_HOOKED_PAGE_DETAIL hookedEntry = GetHookedPage(alignedGuestVirtualAddress);
|
|
|
|
if (hookedEntry) {
|
|
KeGenericCallDpc(UnhookSinglePage, reinterpret_cast<PVOID>(hookedEntry->VirtualAddress));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* PageUnhookVmcall is responsible to invalidate the TLB for a specific page.
|
|
*
|
|
* Parameters:
|
|
* @guestPhysicalAddress [_In_ UINT64] -- The guest virtual address.
|
|
*
|
|
* Returns:
|
|
* @status [bool] -- True if the page is unhooked, otherwise false.
|
|
*/
|
|
bool Ept::PageUnhookVmcall(_In_ UINT64 guestVirtualAddress) {
|
|
if (!GuestState[KeGetCurrentProcessorNumber()].IsOnVmxRoot)
|
|
return false;
|
|
UINT64 alignedGuestVirtualAddress = reinterpret_cast<UINT64>(PAGE_ALIGN(guestVirtualAddress));
|
|
PEPT_HOOKED_PAGE_DETAIL hookedEntry = nullptr;
|
|
|
|
this->hookedPagesLock.Lock();
|
|
PLIST_ENTRY entry = this->hookedPages;
|
|
|
|
while (this->hookedPages != entry->Flink) {
|
|
entry = entry->Flink;
|
|
hookedEntry = CONTAINING_RECORD(entry, EPT_HOOKED_PAGE_DETAIL, Entry);
|
|
|
|
if (hookedEntry->VirtualAddress == alignedGuestVirtualAddress) {
|
|
SetPML1AndInvalidateTLB(hookedEntry->EntryAddress, hookedEntry->OriginalEntry, SINGLE_CONTEXT);
|
|
RemoveEntryList(&hookedEntry->Entry);
|
|
InitializeListHead(&hookedEntry->Entry);
|
|
this->hookedPagesLock.Unlock();
|
|
poolManager->Free(hookedEntry, EPT_HOOK_PAGE);
|
|
return true;
|
|
}
|
|
}
|
|
this->hookedPagesLock.Unlock();
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* PageUnhook is responsible to unhook all pages.
|
|
*
|
|
* Parameters:
|
|
* There are no parameters.
|
|
*
|
|
* Returns:
|
|
* @status [bool] -- True if unhooked all pages, else false.
|
|
*/
|
|
bool Ept::UnhookAllPagesVmcall() {
|
|
PEPT_HOOKED_PAGE_DETAIL hookedEntry = nullptr;
|
|
|
|
if (!GuestState[KeGetCurrentProcessorNumber()].IsOnVmxRoot)
|
|
return false;
|
|
this->hookedPagesLock.Lock();
|
|
PLIST_ENTRY entry = this->hookedPages->Flink;
|
|
|
|
while (entry != this->hookedPages) {
|
|
PLIST_ENTRY nextEntry = entry->Flink;
|
|
hookedEntry = CONTAINING_RECORD(entry, EPT_HOOKED_PAGE_DETAIL, Entry);
|
|
SetPML1AndInvalidateTLB(hookedEntry->EntryAddress, hookedEntry->OriginalEntry, SINGLE_CONTEXT);
|
|
RemoveEntryList(&hookedEntry->Entry);
|
|
InitializeListHead(&hookedEntry->Entry);
|
|
poolManager->Free(hookedEntry, EPT_HOOK_PAGE);
|
|
entry = nextEntry;
|
|
}
|
|
this->hookedPagesLock.Unlock();
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* PageUnhook is responsible to dispatch a vmcall to unhook all pages.
|
|
*
|
|
* Parameters:
|
|
* There are no parameters.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
void Ept::UnhookAllPages() {
|
|
if (GuestState[KeGetCurrentProcessorNumber()].IsOnVmxRoot)
|
|
return;
|
|
|
|
if (GuestState[KeGetCurrentProcessorNumber()].IsLaunched)
|
|
KeGenericCallDpc(UnhookAllPagesDpc, NULL);
|
|
else
|
|
ReleaseAllHookedPageRecords();
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
*
|
|
* IsHookExists checks if a hook exists for the given guest virtual address.
|
|
*
|
|
* Parameters:
|
|
* @guestVirtualAddress [_In_ UINT64] -- The guest virtual address to check.
|
|
*
|
|
* Returns:
|
|
* @status [bool] -- True if a hook exists, otherwise false.
|
|
*/
|
|
bool Ept::IsHookExists(_In_ UINT64 guestVirtualAddress) {
|
|
return GetHookedPage(reinterpret_cast<UINT64>(PAGE_ALIGN(guestVirtualAddress))) != NULL;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* GetHookedPage retrieves the hooked page details for a given guest virtual address.
|
|
*
|
|
* Parameters:
|
|
* @guestVirtualAddress [_In_ UINT64] -- The guest virtual address to retrieve the hooked page for.
|
|
*
|
|
* Returns:
|
|
* @hookedPage [PEPT_HOOKED_PAGE_DETAIL] -- The details of the hooked page, or NULL if not found.
|
|
*/
|
|
PEPT_HOOKED_PAGE_DETAIL Ept::GetHookedPage(_In_ UINT64 guestVirtualAddress) {
|
|
PEPT_HOOKED_PAGE_DETAIL hookedEntry = nullptr;
|
|
UINT64 alignedGuestVirtualAddress = reinterpret_cast<UINT64>(PAGE_ALIGN(guestVirtualAddress));
|
|
|
|
this->hookedPagesLock.Lock();
|
|
PLIST_ENTRY entry = this->hookedPages;
|
|
|
|
if (IsListEmpty(this->hookedPages)) {
|
|
this->hookedPagesLock.Unlock();
|
|
return NULL;
|
|
}
|
|
|
|
while (this->hookedPages != entry->Flink) {
|
|
entry = entry->Flink;
|
|
hookedEntry = CONTAINING_RECORD(entry, EPT_HOOKED_PAGE_DETAIL, Entry);
|
|
|
|
if (hookedEntry->VirtualAddress == alignedGuestVirtualAddress) {
|
|
this->hookedPagesLock.Unlock();
|
|
return hookedEntry;
|
|
}
|
|
}
|
|
this->hookedPagesLock.Unlock();
|
|
return NULL;
|
|
}
|
|
|
|
void Ept::ReleaseAllHookedPageRecords() {
|
|
PEPT_HOOKED_PAGE_DETAIL hookedEntry = nullptr;
|
|
this->hookedPagesLock.Lock();
|
|
PLIST_ENTRY entry = this->hookedPages->Flink;
|
|
|
|
while (entry != this->hookedPages) {
|
|
PLIST_ENTRY nextEntry = entry->Flink;
|
|
hookedEntry = CONTAINING_RECORD(entry, EPT_HOOKED_PAGE_DETAIL, Entry);
|
|
RemoveEntryList(entry);
|
|
InitializeListHead(&hookedEntry->Entry);
|
|
poolManager->Free(hookedEntry, EPT_HOOK_PAGE);
|
|
entry = nextEntry;
|
|
}
|
|
this->hookedPagesLock.Unlock();
|
|
}
|