2022-04-24 21:23:54 +02:00

532 lines
14 KiB
C++

#include "std_include.hpp"
#include "ept.hpp"
#include "assembly.hpp"
#include "finally.hpp"
#include "logging.hpp"
#include "memory.hpp"
#include "vmx.hpp"
#define MTRR_PAGE_SIZE 4096
#define MTRR_PAGE_MASK (~(MTRR_PAGE_SIZE-1))
#define ADDRMASK_EPT_PML1_OFFSET(_VAR_) ((_VAR_) & 0xFFFULL)
#define ADDRMASK_EPT_PML1_INDEX(_VAR_) (((_VAR_) & 0x1FF000ULL) >> 12)
#define ADDRMASK_EPT_PML2_INDEX(_VAR_) (((_VAR_) & 0x3FE00000ULL) >> 21)
#define ADDRMASK_EPT_PML3_INDEX(_VAR_) (((_VAR_) & 0x7FC0000000ULL) >> 30)
#define ADDRMASK_EPT_PML4_INDEX(_VAR_) (((_VAR_) & 0xFF8000000000ULL) >> 39)
namespace vmx
{
namespace
{
struct mtrr_range
{
uint32_t enabled;
uint32_t type;
uint64_t physical_address_min;
uint64_t physical_address_max;
};
using mtrr_list = mtrr_range[16];
void initialize_mtrr(mtrr_list& mtrr_data)
{
ia32_mtrr_capabilities_register mtrr_capabilities{};
mtrr_capabilities.flags = __readmsr(IA32_MTRR_CAPABILITIES);
for (auto i = 0u; i < mtrr_capabilities.variable_range_count; i++)
{
ia32_mtrr_physbase_register mtrr_base{};
ia32_mtrr_physmask_register mtrr_mask{};
mtrr_base.flags = __readmsr(IA32_MTRR_PHYSBASE0 + i * 2);
mtrr_mask.flags = __readmsr(IA32_MTRR_PHYSMASK0 + i * 2);
mtrr_data[i].type = static_cast<uint32_t>(mtrr_base.type);
mtrr_data[i].enabled = static_cast<uint32_t>(mtrr_mask.valid);
if (mtrr_data[i].enabled != FALSE)
{
mtrr_data[i].physical_address_min = mtrr_base.page_frame_number *
MTRR_PAGE_SIZE;
unsigned long bit{};
_BitScanForward64(&bit, mtrr_mask.page_frame_number * MTRR_PAGE_SIZE);
mtrr_data[i].physical_address_max = mtrr_data[i].
physical_address_min +
(1ULL << bit) - 1;
}
}
}
uint32_t mtrr_adjust_effective_memory_type(const mtrr_list& mtrr_data, const uint64_t large_page_address,
uint32_t candidate_memory_type)
{
for (const auto& mtrr_entry : mtrr_data)
{
if (mtrr_entry.enabled && large_page_address + (2_mb - 1) >= mtrr_entry.physical_address_min &&
large_page_address <= mtrr_entry.physical_address_max)
{
candidate_memory_type = mtrr_entry.type;
}
}
return candidate_memory_type;
}
}
ept::ept() = default;
ept::~ept()
{
auto* split = this->ept_splits;
while (split)
{
auto* current_split = split;
split = split->next_split;
memory::free_aligned_object(current_split);
}
auto* hook = this->ept_hooks;
while (hook)
{
auto* current_hook = hook;
hook = hook->next_hook;
memory::free_aligned_object(current_hook);
}
}
void ept::install_page_hook(void* destination, const void* source, const size_t length,
ept_translation_hint* translation_hint)
{
auto* hook = this->get_or_create_ept_hook(destination, translation_hint);
const auto page_offset = ADDRMASK_EPT_PML1_OFFSET(reinterpret_cast<uint64_t>(destination));
memcpy(hook->fake_page + page_offset, source, length);
}
void ept::install_hook(const void* destination, const void* source, const size_t length,
ept_translation_hint* translation_hint)
{
auto current_destination = reinterpret_cast<uint64_t>(destination);
auto current_source = reinterpret_cast<uint64_t>(source);
auto current_length = length;
while (current_length != 0)
{
const auto aligned_destination = PAGE_ALIGN(current_destination);
const auto page_offset = ADDRMASK_EPT_PML1_OFFSET(current_destination);
const auto page_remaining = PAGE_SIZE - page_offset;
const auto data_to_write = min(page_remaining, current_length);
ept_translation_hint* relevant_hint = nullptr;
ept_translation_hint* current_hint = translation_hint;
while (current_hint)
{
if (current_hint->virtual_base_address == aligned_destination)
{
relevant_hint = current_hint;
break;
}
current_hint = current_hint->next_hint;
}
this->install_page_hook(reinterpret_cast<void*>(current_destination),
reinterpret_cast<const void*>(current_source), data_to_write, relevant_hint);
current_length -= data_to_write;
current_destination += data_to_write;
current_source += data_to_write;
}
}
void ept::disable_all_hooks() const
{
auto* hook = this->ept_hooks;
while (hook)
{
hook->target_page->flags = hook->original_entry.flags;
hook = hook->next_hook;
}
}
void ept::handle_violation(guest_context& guest_context) const
{
vmx_exit_qualification_ept_violation violation_qualification{};
violation_qualification.flags = guest_context.exit_qualification;
if (!violation_qualification.caused_by_translation)
{
guest_context.exit_vm = true;
}
const auto physical_base_address = reinterpret_cast<uint64_t>(PAGE_ALIGN(guest_context.guest_physical_address));
auto* hook = this->find_ept_hook(physical_base_address);
if (!hook)
{
return;
}
if (!violation_qualification.ept_executable && violation_qualification.execute_access)
{
hook->target_page->flags = hook->execute_entry.flags;
guest_context.increment_rip = false;
}
if (violation_qualification.ept_executable && (violation_qualification.read_access || violation_qualification.
write_access))
{
hook->target_page->flags = hook->readwrite_entry.flags;
guest_context.increment_rip = false;
}
}
void ept::handle_misconfiguration(guest_context& guest_context) const
{
guest_context.increment_rip = false;
guest_context.exit_vm = true;
}
void ept::initialize()
{
mtrr_list mtrr_data{};
initialize_mtrr(mtrr_data);
this->epml4[0].read_access = 1;
this->epml4[0].write_access = 1;
this->epml4[0].execute_access = 1;
this->epml4[0].page_frame_number = memory::get_physical_address(&this->epdpt) /
PAGE_SIZE;
// --------------------------
epdpte temp_epdpte;
temp_epdpte.flags = 0;
temp_epdpte.read_access = 1;
temp_epdpte.write_access = 1;
temp_epdpte.execute_access = 1;
__stosq(reinterpret_cast<uint64_t*>(&this->epdpt[0]), temp_epdpte.flags, EPT_PDPTE_ENTRY_COUNT);
for (auto i = 0; i < EPT_PDPTE_ENTRY_COUNT; i++)
{
this->epdpt[i].page_frame_number = memory::get_physical_address(&this->epde[i][0]) / PAGE_SIZE;
}
// --------------------------
epde_2mb temp_epde{};
temp_epde.flags = 0;
temp_epde.read_access = 1;
temp_epde.write_access = 1;
temp_epde.execute_access = 1;
temp_epde.large_page = 1;
__stosq(reinterpret_cast<uint64_t*>(this->epde), temp_epde.flags, EPT_PDPTE_ENTRY_COUNT * EPT_PDE_ENTRY_COUNT);
for (auto i = 0; i < EPT_PDPTE_ENTRY_COUNT; i++)
{
for (auto j = 0; j < EPT_PDE_ENTRY_COUNT; j++)
{
this->epde[i][j].page_frame_number = (i * 512) + j;
this->epde[i][j].memory_type = mtrr_adjust_effective_memory_type(
mtrr_data, this->epde[i][j].page_frame_number * 2_mb, MEMORY_TYPE_WRITE_BACK);
}
}
}
ept_pointer ept::get_ept_pointer() const
{
const auto ept_pml4_physical_address = memory::get_physical_address(const_cast<pml4*>(&this->epml4[0]));
ept_pointer vmx_eptp{};
vmx_eptp.flags = 0;
vmx_eptp.page_walk_length = 3;
vmx_eptp.memory_type = MEMORY_TYPE_WRITE_BACK;
vmx_eptp.page_frame_number = ept_pml4_physical_address / PAGE_SIZE;
return vmx_eptp;
}
void ept::invalidate() const
{
const auto ept_pointer = this->get_ept_pointer();
invept_descriptor descriptor{};
descriptor.ept_pointer = ept_pointer.flags;
descriptor.reserved = 0;
__invept(1, &descriptor);
}
pml2* ept::get_pml2_entry(const uint64_t physical_address)
{
const auto directory = ADDRMASK_EPT_PML2_INDEX(physical_address);
const auto directory_pointer = ADDRMASK_EPT_PML3_INDEX(physical_address);
const auto pml4_entry = ADDRMASK_EPT_PML4_INDEX(physical_address);
if (pml4_entry > 0)
{
return nullptr;
}
return &this->epde[directory_pointer][directory];
}
pml1* ept::get_pml1_entry(const uint64_t physical_address)
{
auto* pml2_entry = this->get_pml2_entry(physical_address);
if (!pml2_entry || pml2_entry->large_page)
{
return nullptr;
}
const auto* pml2 = reinterpret_cast<pml2_ptr*>(pml2_entry);
auto* pml1 = this->find_pml1_table(pml2->page_frame_number * PAGE_SIZE);
if (!pml1)
{
pml1 = static_cast<epte*>(memory::get_virtual_address(pml2->page_frame_number * PAGE_SIZE));
}
if (!pml1)
{
return nullptr;
}
return &pml1[ADDRMASK_EPT_PML1_INDEX(physical_address)];
}
pml1* ept::find_pml1_table(const uint64_t physical_address) const
{
auto* split = this->ept_splits;
while (split)
{
if (memory::get_physical_address(&split->pml1[0]) == physical_address)
{
return split->pml1;
}
split = split->next_split;
}
return nullptr;
}
ept_split* ept::allocate_ept_split()
{
auto* split = memory::allocate_aligned_object<ept_split>();
if (!split)
{
throw std::runtime_error("Failed to allocate ept split object");
}
split->next_split = this->ept_splits;
this->ept_splits = split;
return split;
}
ept_hook* ept::allocate_ept_hook()
{
auto* hook = memory::allocate_aligned_object<ept_hook>();
if (!hook)
{
throw std::runtime_error("Failed to allocate ept hook object");
}
hook->next_hook = this->ept_hooks;
this->ept_hooks = hook;
return hook;
}
ept_hook* ept::find_ept_hook(const uint64_t physical_address) const
{
auto* hook = this->ept_hooks;
while (hook)
{
if (hook->physical_base_address == physical_address)
{
return hook;
}
hook = hook->next_hook;
}
return nullptr;
}
ept_hook* ept::get_or_create_ept_hook(void* destination, ept_translation_hint* translation_hint)
{
const auto virtual_target = PAGE_ALIGN(destination);
uint64_t physical_address = 0;
if (translation_hint)
{
physical_address = translation_hint->physical_base_address + ADDRMASK_EPT_PML1_OFFSET(
reinterpret_cast<uint64_t>(destination));
}
else
{
physical_address = memory::get_physical_address(virtual_target);
}
if (!physical_address)
{
throw std::runtime_error("No physical address for destination");
}
const auto physical_base_address = reinterpret_cast<uint64_t>(PAGE_ALIGN(physical_address));
auto* hook = this->find_ept_hook(physical_base_address);
if (hook)
{
if (hook->target_page->flags == hook->original_entry.flags)
{
const auto* data_source = translation_hint ? &translation_hint->page[0] : virtual_target;
memcpy(&hook->fake_page[0], data_source, PAGE_SIZE);
hook->target_page->flags = hook->readwrite_entry.flags;
}
return hook;
}
hook = this->allocate_ept_hook();
if (!hook)
{
throw std::runtime_error("Failed to allocate hook");
}
this->split_large_page(physical_address);
const auto* data_source = translation_hint ? &translation_hint->page[0] : virtual_target;
memcpy(&hook->fake_page[0], data_source, PAGE_SIZE);
hook->physical_base_address = physical_base_address;
hook->target_page = this->get_pml1_entry(physical_address);
if (!hook->target_page)
{
throw std::runtime_error("Failed to get PML1 entry for target address");
}
hook->original_entry = *hook->target_page;
hook->readwrite_entry = hook->original_entry;
hook->readwrite_entry.read_access = 1;
hook->readwrite_entry.write_access = 1;
hook->readwrite_entry.execute_access = 0;
hook->execute_entry.flags = 0;
hook->execute_entry.read_access = 0;
hook->execute_entry.write_access = 0;
hook->execute_entry.execute_access = 1;
hook->execute_entry.page_frame_number = memory::get_physical_address(&hook->fake_page) / PAGE_SIZE;
hook->target_page->flags = hook->readwrite_entry.flags;
return hook;
}
void ept::split_large_page(const uint64_t physical_address)
{
auto* target_entry = this->get_pml2_entry(physical_address);
if (!target_entry)
{
throw std::runtime_error("Invalid physical address");
}
if (!target_entry->large_page)
{
return;
}
auto* split = this->allocate_ept_split();
epte pml1_template{};
pml1_template.flags = 0;
pml1_template.read_access = 1;
pml1_template.write_access = 1;
pml1_template.execute_access = 1;
pml1_template.memory_type = target_entry->memory_type;
pml1_template.ignore_pat = target_entry->ignore_pat;
pml1_template.suppress_ve = target_entry->suppress_ve;
__stosq(reinterpret_cast<uint64_t*>(&split->pml1[0]), pml1_template.flags, EPT_PTE_ENTRY_COUNT);
for (auto i = 0; i < EPT_PTE_ENTRY_COUNT; ++i)
{
split->pml1[i].page_frame_number = ((target_entry->page_frame_number * 2_mb) / PAGE_SIZE) + i;
}
pml2_ptr new_pointer{};
new_pointer.flags = 0;
new_pointer.read_access = 1;
new_pointer.write_access = 1;
new_pointer.execute_access = 1;
new_pointer.page_frame_number = memory::get_physical_address(&split->pml1[0]) / PAGE_SIZE;
target_entry->flags = new_pointer.flags;
}
ept_translation_hint* ept::generate_translation_hints(const void* destination, const size_t length)
{
auto current_destination = reinterpret_cast<uint64_t>(destination);
auto current_length = length;
ept_translation_hint* current_hints = nullptr;
auto destructor = utils::finally([&current_hints]()
{
ept::free_translation_hints(current_hints);
});
while (current_length != 0)
{
const auto aligned_destination = PAGE_ALIGN(current_destination);
const auto page_offset = ADDRMASK_EPT_PML1_OFFSET(current_destination);
const auto page_remaining = PAGE_SIZE - page_offset;
const auto data_to_write = min(page_remaining, current_length);
auto* new_hint = memory::allocate_non_paged_object<ept_translation_hint>();
if (!new_hint)
{
throw std::runtime_error("Failed to allocate hint");
}
new_hint->next_hint = current_hints;
current_hints = new_hint;
current_hints->virtual_base_address = aligned_destination;
current_hints->physical_base_address = memory::get_physical_address(aligned_destination);
if (!current_hints->physical_base_address)
{
throw std::runtime_error("Failed to resolve physical address");
}
memcpy(&current_hints->page[0], aligned_destination, PAGE_SIZE);
current_length -= data_to_write;
current_destination += data_to_write;
}
destructor.cancel();
return current_hints;
}
void ept::free_translation_hints(ept_translation_hint* hints)
{
auto* hint = hints;
while (hint)
{
auto* current_hint = hint;
hint = hint->next_hint;
memory::free_non_paged_object(current_hint);
}
}
}