diff --git a/bluepill.sln b/bluepill.sln index c023079..6c9f5c9 100644 --- a/bluepill.sln +++ b/bluepill.sln @@ -23,9 +23,8 @@ Global {881CCCA6-80EB-486F-8923-4F4B7DD41F9A}.Release|x64.Build.0 = Release|x64 {881CCCA6-80EB-486F-8923-4F4B7DD41F9A}.Release|x64.Deploy.0 = Release|x64 {881CCCA6-80EB-486F-8923-4F4B7DD41F9A}.Release|x86.ActiveCfg = Release|x64 - {A3D028D7-1650-4036-9FB0-4B8CC16268FE}.Debug|x64.ActiveCfg = Debug|x64 - {A3D028D7-1650-4036-9FB0-4B8CC16268FE}.Debug|x64.Build.0 = Debug|x64 - {A3D028D7-1650-4036-9FB0-4B8CC16268FE}.Debug|x86.ActiveCfg = Debug|x64 + {A3D028D7-1650-4036-9FB0-4B8CC16268FE}.Debug|x64.ActiveCfg = Release|x64 + {A3D028D7-1650-4036-9FB0-4B8CC16268FE}.Debug|x86.ActiveCfg = Release|x64 {A3D028D7-1650-4036-9FB0-4B8CC16268FE}.Release|x64.ActiveCfg = Release|x64 {A3D028D7-1650-4036-9FB0-4B8CC16268FE}.Release|x64.Build.0 = Release|x64 {A3D028D7-1650-4036-9FB0-4B8CC16268FE}.Release|x86.ActiveCfg = Release|x64 diff --git a/demo/bluepill.cpp b/demo/bluepill.cpp new file mode 100644 index 0000000..1f9ffe3 --- /dev/null +++ b/demo/bluepill.cpp @@ -0,0 +1,78 @@ +#include "bluepill.h" + +namespace bluepill +{ + auto get_dirbase() -> u64 + { + vmcall_command_t command{}; + memset(&command, NULL, sizeof command); + + command.present = true; + command.option = vmcall_option::dirbase; + + hypercall(VMCALL_KEY, &command); + return command.dirbase; + } + + auto translate(void* dirbase, void* virt_addr) -> u64 + { + vmcall_command_t command{}; + memset(&command, NULL, sizeof command); + + command.present = true; + command.option = vmcall_option::translate; + command.translate.dirbase = reinterpret_cast(dirbase); + command.translate.virt_addr = reinterpret_cast(virt_addr); + + hypercall(VMCALL_KEY, &command); + return command.translate.phys_addr; + } + + auto read_phys(void* dest, void* phys_src, u64 size) -> bool + { + vmcall_command_t command{}; + memset(&command, NULL, sizeof command); + + command.present = true; + command.option = vmcall_option::read_phys; + command.read_phys.virt_dest = reinterpret_cast(dest); + command.read_phys.phys_src = reinterpret_cast(phys_src); + command.read_phys.dirbase_dest = get_dirbase(); + command.read_phys.size = size; + + hypercall(VMCALL_KEY, &command); + return command.result; + } + + auto write_phys(void* phys_dest, void* src, u64 size) -> bool + { + vmcall_command_t command{}; + memset(&command, NULL, sizeof command); + + command.present = true; + command.option = vmcall_option::write_phys; + command.write_phys.virt_src = reinterpret_cast(src); + command.write_phys.phys_dest = reinterpret_cast(phys_dest); + command.write_phys.dirbase_src = get_dirbase(); + command.write_phys.size = size; + + hypercall(VMCALL_KEY, &command); + return command.result; + } + + auto copy_virt(void* dirbase_src, void* virt_src, void* dirbase_dest, void* virt_dest, u64 size) -> bool + { + vmcall_command_t command{}; + memset(&command, NULL, sizeof command); + + command.present = true; + command.copy_virt.dirbase_dest = reinterpret_cast(dirbase_dest); + command.copy_virt.virt_dest = reinterpret_cast(virt_dest); + command.copy_virt.dirbase_src = reinterpret_cast(dirbase_src); + command.copy_virt.virt_src = reinterpret_cast(virt_src); + command.copy_virt.size = size; + + hypercall(VMCALL_KEY, &command); + return command.result; + } +} \ No newline at end of file diff --git a/demo/bluepill.h b/demo/bluepill.h new file mode 100644 index 0000000..bdb6ba9 --- /dev/null +++ b/demo/bluepill.h @@ -0,0 +1,88 @@ +#pragma once +#include +#include +#define VMCALL_KEY 0xC0FFEE + +using u8 = unsigned char; +using u16 = unsigned short; +using u32 = unsigned int; +using u64 = unsigned long long; +using u128 = __m128; + +using s8 = char; +using s16 = short; +using s32 = int; +using s64 = long long; + +namespace bluepill +{ + enum class vmcall_option + { + translate, + copy_virt, + write_phys, + read_phys, + dirbase + }; + + typedef struct _vmcall_command_t + { + bool present; + bool result; + vmcall_option option; + + union + { + struct + { + u64 dirbase; + u64 virt_addr; + u64 phys_addr; + } translate; + + struct + { + u64 virt_src; + u64 dirbase_src; + u64 virt_dest; + u64 dirbase_dest; + u64 size; + } copy_virt; + + struct + { + u64 virt_src; + u64 dirbase_src; + u64 phys_dest; + u64 size; + } write_phys; + + struct + { + u64 phys_src; + u64 dirbase_dest; + u64 virt_dest; + u64 size; + } read_phys; + + u64 dirbase; + }; + + } vmcall_command_t, * pvmcall_command_t; + + // vmcall into the hypervisor... + extern "C" u64 hypercall(u64 key, pvmcall_command_t command); + + // get vmexiting logical processors pml4... + auto get_dirbase() -> u64; + + // read/write physical memory... + auto read_phys(void* dest, void* phys_src, u64 size) -> bool; + auto write_phys(void* phys_dest, void* src, u64 size) -> bool; + + // translate virtual to physical... + auto translate(void* dirbase, void* virt_addr)->u64; + + // copy virtual memory between two address spaces... page protections are ignored... + auto copy_virt(void* dirbase_src, void* virt_src, void* dirbase_dest, void* virt_dest, u64 size) -> bool; +} \ No newline at end of file diff --git a/demo/demo.vcxproj b/demo/demo.vcxproj index 452255e..c10ecc8 100644 --- a/demo/demo.vcxproj +++ b/demo/demo.vcxproj @@ -1,10 +1,6 @@ - - Debug - x64 - Release x64 @@ -18,18 +14,12 @@ 10.0 - - Application - true - v142 - Unicode - Application false v142 true - Unicode + MultiByte @@ -37,39 +27,22 @@ - - - - - true - false - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - - Level3 true true true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true + stdcpp17 Console @@ -79,7 +52,9 @@ + + @@ -87,7 +62,10 @@ - + + + + diff --git a/demo/demo.vcxproj.filters b/demo/demo.vcxproj.filters index a921897..8dc8ed9 100644 --- a/demo/demo.vcxproj.filters +++ b/demo/demo.vcxproj.filters @@ -9,19 +9,37 @@ {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + {02abfc13-9b80-4455-8b2b-282417e04ab4} + Source Files + + Source Files + + + Source Files + - + Source Files - + - + + Header Files + + + Header Files\util + + + Header Files\util + + Header Files diff --git a/demo/hypercall.asm b/demo/hypercall.asm index 8d1bb96..5ce41bb 100644 --- a/demo/hypercall.asm +++ b/demo/hypercall.asm @@ -1,6 +1,6 @@ .code hypercall proc - cpuid + vmcall ret hypercall endp end \ No newline at end of file diff --git a/demo/hypercall.h b/demo/hypercall.h deleted file mode 100644 index ce3a6f7..0000000 --- a/demo/hypercall.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include -#include - -using u8 = unsigned char; -using u16 = unsigned short; -using u32 = unsigned int; -using u64 = unsigned long long; -using u128 = __m128; - -using s8 = char; -using s16 = short; -using s32 = int; -using s64 = long long; - -namespace bluepill -{ - constexpr auto key = 0xC0FFEE; - extern "C" u64 hypercall(u64 key); -} \ No newline at end of file diff --git a/demo/main.cpp b/demo/main.cpp index 3a8f75d..8d00fd4 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -1,9 +1,57 @@ -#include -#include -#include "hypercall.h" +#include "bluepill.h" +#include "vdm_ctx/vdm_ctx.hpp" -int main() +auto __cdecl main(int argc, char** argv) -> void { - std::printf("hypercall result: 0x%x\n", bluepill::hypercall(bluepill::key)); + vdm::read_phys_t _read_phys = + [&](void* addr, void* buffer, std::size_t size) -> bool + { + return bluepill::read_phys(buffer, addr, size); + }; + + vdm::write_phys_t _write_phys = + [&](void* addr, void* buffer, std::size_t size) -> bool + { + return bluepill::write_phys(addr, buffer, size); + }; + + const auto dirbase = + reinterpret_cast( + bluepill::get_dirbase()); + + std::printf("current dirbase -> 0x%p\n", dirbase); + std::getchar(); + + const auto nt_shutdown_phys = + bluepill::translate(dirbase, + util::get_kmodule_export("ntoskrnl.exe", + vdm::syscall_hook.second)); + + std::printf("NtShutdownSystem translated (phys) -> 0x%p\n", nt_shutdown_phys); + std::getchar(); + + vdm::vdm_ctx vdm(_read_phys, _write_phys); + const auto ntoskrnl_base = + reinterpret_cast( + util::get_kmodule_base("ntoskrnl.exe")); + + const auto ntoskrnl_memcpy = + util::get_kmodule_export("ntoskrnl.exe", "memcpy"); + + std::printf("[+] %s physical address -> 0x%p\n", vdm::syscall_hook.first, vdm::syscall_address.load()); + std::printf("[+] %s page offset -> 0x%x\n", vdm::syscall_hook.first, vdm::nt_page_offset); + + std::printf("[+] ntoskrnl base address -> 0x%p\n", ntoskrnl_base); + std::printf("[+] ntoskrnl memcpy address -> 0x%p\n", ntoskrnl_memcpy); + + short mz_bytes = 0; + vdm.syscall( + ntoskrnl_memcpy, + &mz_bytes, + ntoskrnl_base, + sizeof mz_bytes + ); + + std::printf("[+] kernel MZ -> 0x%x\n", mz_bytes); std::getchar(); } \ No newline at end of file diff --git a/demo/util/nt.hpp b/demo/util/nt.hpp new file mode 100644 index 0000000..0534c04 --- /dev/null +++ b/demo/util/nt.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include + +#pragma comment(lib, "ntdll.lib") +#define PAGE_4KB 0x1000 + +constexpr auto SystemModuleInformation = 11; +typedef struct _RTL_PROCESS_MODULE_INFORMATION +{ + HANDLE Section; + PVOID MappedBase; + PVOID ImageBase; + ULONG ImageSize; + ULONG Flags; + USHORT LoadOrderIndex; + USHORT InitOrderIndex; + USHORT LoadCount; + USHORT OffsetToFileName; + UCHAR FullPathName[256]; +} RTL_PROCESS_MODULE_INFORMATION, * PRTL_PROCESS_MODULE_INFORMATION; + +typedef struct _RTL_PROCESS_MODULES +{ + ULONG NumberOfModules; + RTL_PROCESS_MODULE_INFORMATION Modules[1]; +} RTL_PROCESS_MODULES, * PRTL_PROCESS_MODULES; + +typedef LARGE_INTEGER PHYSICAL_ADDRESS, * PPHYSICAL_ADDRESS; + +using PEPROCESS = PVOID; +using PsLookupProcessByProcessId = NTSTATUS(__fastcall*)( + HANDLE ProcessId, + PEPROCESS* Process +); \ No newline at end of file diff --git a/demo/util/util.hpp b/demo/util/util.hpp new file mode 100644 index 0000000..57fb96f --- /dev/null +++ b/demo/util/util.hpp @@ -0,0 +1,234 @@ +#pragma once +#include +#include + +#include +#include +#include +#include +#include +#include "nt.hpp" + +namespace util +{ + inline std::map pmem_ranges{}; + __forceinline auto is_valid(std::uintptr_t addr) -> bool + { + for (auto range : pmem_ranges) + if (addr >= range.first && addr <= range.first + range.second) + return true; + + return false; + } + +#pragma pack (push, 1) + struct PhysicalMemoryPage//CM_PARTIAL_RESOURCE_DESCRIPTOR + { + uint8_t type; + uint8_t shareDisposition; + uint16_t flags; + uint64_t pBegin; + uint32_t sizeButNotExactly; + uint32_t pad; + + static constexpr uint16_t cm_resource_memory_large_40{ 0x200 }; + static constexpr uint16_t cm_resource_memory_large_48{ 0x400 }; + static constexpr uint16_t cm_resource_memory_large_64{ 0x800 }; + + uint64_t size()const noexcept + { + if (flags & cm_resource_memory_large_40) + return uint64_t{ sizeButNotExactly } << 8; + else if (flags & cm_resource_memory_large_48) + return uint64_t{ sizeButNotExactly } << 16; + else if (flags & cm_resource_memory_large_64) + return uint64_t{ sizeButNotExactly } << 32; + else + return uint64_t{ sizeButNotExactly }; + } + }; + static_assert(sizeof(PhysicalMemoryPage) == 20); +#pragma pack (pop) + + inline const auto init_ranges = ([&]() -> bool + { + HKEY h_key; + DWORD type, size; + LPBYTE data; + RegOpenKeyEx(HKEY_LOCAL_MACHINE, "HARDWARE\\RESOURCEMAP\\System Resources\\Physical Memory", 0, KEY_READ, &h_key); + RegQueryValueEx(h_key, ".Translated", NULL, &type, NULL, &size); //get size + data = new BYTE[size]; + RegQueryValueEx(h_key, ".Translated", NULL, &type, data, &size); + DWORD count = *(DWORD*)(data + 16); + auto pmi = data + 24; + for (int dwIndex = 0; dwIndex < count; dwIndex++) + { +#if 0 + pmem_ranges.emplace(*(uint64_t*)(pmi + 0), *(uint64_t*)(pmi + 8)); +#else + const PhysicalMemoryPage& page{ *(PhysicalMemoryPage*)(pmi - 4) }; + pmem_ranges.emplace(page.pBegin, page.size()); +#endif + pmi += 20; + } + delete[] data; + RegCloseKey(h_key); + return true; + })(); + + __forceinline auto get_file_header(void* base_addr) -> PIMAGE_FILE_HEADER + { + PIMAGE_DOS_HEADER dos_headers = + reinterpret_cast(base_addr); + + PIMAGE_NT_HEADERS nt_headers = + reinterpret_cast( + reinterpret_cast(base_addr) + dos_headers->e_lfanew); + + return &nt_headers->FileHeader; + } + + __forceinline auto get_kmodule_base(const char* module_name) -> std::uintptr_t + { + void* buffer = nullptr; + DWORD buffer_size = NULL; + + auto status = NtQuerySystemInformation( + static_cast(SystemModuleInformation), + buffer, buffer_size, &buffer_size); + + while (status == STATUS_INFO_LENGTH_MISMATCH) + { + VirtualFree(buffer, NULL, MEM_RELEASE); + buffer = VirtualAlloc(nullptr, buffer_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + status = NtQuerySystemInformation( + static_cast(SystemModuleInformation), + buffer, buffer_size, &buffer_size); + } + + if (!NT_SUCCESS(status)) + { + VirtualFree(buffer, NULL, MEM_RELEASE); + return NULL; + } + + const auto modules = static_cast(buffer); + for (auto idx = 0u; idx < modules->NumberOfModules; ++idx) + { + const std::string current_module_name = std::string(reinterpret_cast(modules->Modules[idx].FullPathName) + modules->Modules[idx].OffsetToFileName); + if (!_stricmp(current_module_name.c_str(), module_name)) + { + const uint64_t result = reinterpret_cast(modules->Modules[idx].ImageBase); + VirtualFree(buffer, NULL, MEM_RELEASE); + return result; + } + } + + VirtualFree(buffer, NULL, MEM_RELEASE); + return NULL; + } + + __forceinline auto get_kmodule_export(const char* module_name, const char* export_name, bool rva = false) -> void* + { + void* buffer = nullptr; + DWORD buffer_size = NULL; + + NTSTATUS status = NtQuerySystemInformation( + static_cast(SystemModuleInformation), + buffer, + buffer_size, + &buffer_size + ); + + while (status == STATUS_INFO_LENGTH_MISMATCH) + { + VirtualFree(buffer, 0, MEM_RELEASE); + buffer = VirtualAlloc(nullptr, buffer_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + status = NtQuerySystemInformation( + static_cast(SystemModuleInformation), + buffer, + buffer_size, + &buffer_size + ); + } + + if (!NT_SUCCESS(status)) + { + VirtualFree(buffer, 0, MEM_RELEASE); + return nullptr; + } + + const auto modules = static_cast(buffer); + for (auto idx = 0u; idx < modules->NumberOfModules; ++idx) + { + // find module and then load library it + const std::string current_module_name = + std::string(reinterpret_cast( + modules->Modules[idx].FullPathName) + + modules->Modules[idx].OffsetToFileName + ); + + if (!_stricmp(current_module_name.c_str(), module_name)) + { + std::string full_path = reinterpret_cast(modules->Modules[idx].FullPathName); + full_path.replace(full_path.find("\\SystemRoot\\"), + sizeof("\\SystemRoot\\") - 1, std::string(getenv("SYSTEMROOT")).append("\\")); + + const auto module_base = + LoadLibraryEx( + full_path.c_str(), + NULL, + DONT_RESOLVE_DLL_REFERENCES + ); + + PIMAGE_DOS_HEADER p_idh; + PIMAGE_NT_HEADERS p_inh; + PIMAGE_EXPORT_DIRECTORY p_ied; + + PDWORD addr, name; + PWORD ordinal; + + p_idh = (PIMAGE_DOS_HEADER)module_base; + if (p_idh->e_magic != IMAGE_DOS_SIGNATURE) + return NULL; + + p_inh = (PIMAGE_NT_HEADERS)((LPBYTE)module_base + p_idh->e_lfanew); + if (p_inh->Signature != IMAGE_NT_SIGNATURE) + return NULL; + + if (p_inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0) + return NULL; + + p_ied = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)module_base + + p_inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + + addr = (PDWORD)((LPBYTE)module_base + p_ied->AddressOfFunctions); + name = (PDWORD)((LPBYTE)module_base + p_ied->AddressOfNames); + ordinal = (PWORD)((LPBYTE)module_base + p_ied->AddressOfNameOrdinals); + + // find exported function + for (auto i = 0; i < p_ied->AddressOfFunctions; i++) + { + if (!strcmp(export_name, (char*)module_base + name[i])) + { + if (!rva) + { + auto result = (void*)((std::uintptr_t)modules->Modules[idx].ImageBase + addr[ordinal[i]]); + VirtualFree(buffer, NULL, MEM_RELEASE); + return result; + } + else + { + auto result = (void*)addr[ordinal[i]]; + VirtualFree(buffer, NULL, MEM_RELEASE); + return result; + } + } + } + } + } + + VirtualFree(buffer, NULL, MEM_RELEASE); + return nullptr; + } +} \ No newline at end of file diff --git a/demo/vdm_ctx/vdm_ctx.cpp b/demo/vdm_ctx/vdm_ctx.cpp new file mode 100644 index 0000000..8d59289 --- /dev/null +++ b/demo/vdm_ctx/vdm_ctx.cpp @@ -0,0 +1,122 @@ +#include "vdm_ctx.hpp" + +namespace vdm +{ + vdm_ctx::vdm_ctx(read_phys_t& read_func, write_phys_t& write_func) + : + read_phys(read_func), + write_phys(write_func) + { + // already found the syscall's physical page... + if (vdm::syscall_address.load()) + return; + + vdm::ntoskrnl = reinterpret_cast( + LoadLibraryExA("ntoskrnl.exe", NULL, + DONT_RESOLVE_DLL_REFERENCES)); + + nt_rva = reinterpret_cast( + util::get_kmodule_export( + "ntoskrnl.exe", + syscall_hook.first, + true + )); + + vdm::nt_page_offset = nt_rva % PAGE_4KB; + // for each physical memory range, make a thread to search it + std::vector search_threads; + for (auto ranges : util::pmem_ranges) + search_threads.emplace_back(std::thread( + &vdm_ctx::locate_syscall, + this, + ranges.first, + ranges.second + )); + + for (std::thread& search_thread : search_threads) + search_thread.join(); + } + + void vdm_ctx::set_read(read_phys_t& read_func) + { + this->read_phys = read_func; + } + + void vdm_ctx::set_write(write_phys_t& write_func) + { + this->write_phys = write_func; + } + + void vdm_ctx::rkm(void* dst, void* src, std::size_t size) + { + static const auto ntoskrnl_memcpy = + util::get_kmodule_export("ntoskrnl.exe", "memcpy"); + + this->syscall( + ntoskrnl_memcpy, dst, src, size); + } + + void vdm_ctx::wkm(void* dst, void* src, std::size_t size) + { + static const auto ntoskrnl_memcpy = + util::get_kmodule_export("ntoskrnl.exe", "memcpy"); + + this->syscall( + ntoskrnl_memcpy, dst, src, size); + } + + void vdm_ctx::locate_syscall(std::uintptr_t address, std::uintptr_t length) const + { + const auto page_data = + reinterpret_cast( + VirtualAlloc( + nullptr, + PAGE_4KB, MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE + )); + + for (auto page = 0u; page < length; page += PAGE_4KB) + { + if (vdm::syscall_address.load()) + break; + + if (!read_phys(reinterpret_cast(address + page), page_data, PAGE_4KB)) + continue; + + // check the first 32 bytes of the syscall, if its the same, test that its the correct + // occurrence of these bytes (since dxgkrnl is loaded into physical memory at least 2 times now)... + if (!memcmp(page_data + nt_page_offset, ntoskrnl + nt_rva, 32)) + if (valid_syscall(reinterpret_cast(address + page + nt_page_offset))) + syscall_address.store( + reinterpret_cast( + address + page + nt_page_offset)); + } + VirtualFree(page_data, PAGE_4KB, MEM_DECOMMIT); + } + + bool vdm_ctx::valid_syscall(void* syscall_addr) const + { + static std::mutex syscall_mutex; + syscall_mutex.lock(); + + static const auto proc = + GetProcAddress( + LoadLibraryA(syscall_hook.second), + syscall_hook.first + ); + + // 0: 48 31 c0 xor rax, rax + // 3 : c3 ret + std::uint8_t shellcode[] = { 0x48, 0x31, 0xC0, 0xC3 }; + std::uint8_t orig_bytes[sizeof shellcode]; + + // save original bytes and install shellcode... + read_phys(syscall_addr, orig_bytes, sizeof orig_bytes); + write_phys(syscall_addr, shellcode, sizeof shellcode); + + auto result = reinterpret_cast(proc)(); + write_phys(syscall_addr, orig_bytes, sizeof orig_bytes); + syscall_mutex.unlock(); + return result == STATUS_SUCCESS; + } +} \ No newline at end of file diff --git a/demo/vdm_ctx/vdm_ctx.hpp b/demo/vdm_ctx/vdm_ctx.hpp new file mode 100644 index 0000000..a0f7961 --- /dev/null +++ b/demo/vdm_ctx/vdm_ctx.hpp @@ -0,0 +1,103 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include "../util/util.hpp" + +namespace vdm +{ + // change this to whatever you want :^) + constexpr std::pair syscall_hook = { "NtShutdownSystem", "ntdll.dll" }; + inline std::atomic is_page_found = false; + inline std::atomic syscall_address = nullptr; + inline std::uint16_t nt_page_offset; + inline std::uint32_t nt_rva; + inline std::uint8_t* ntoskrnl; + + using read_phys_t = std::function; + using write_phys_t = std::function; + + class vdm_ctx + { + public: + explicit vdm_ctx(read_phys_t& read_func, write_phys_t& write_func); + void set_read(read_phys_t& read_func); + void set_write(write_phys_t& write_func); + void rkm(void* dst, void* src, std::size_t size); + void wkm(void* dst, void* src, std::size_t size); + + template + __forceinline std::invoke_result_t syscall(void* addr, Ts ... args) const + { + static const auto proc = + GetProcAddress( + LoadLibraryA(syscall_hook.second), + syscall_hook.first + ); + + static std::mutex syscall_mutex; + syscall_mutex.lock(); + + // jmp [rip+0x0] + std::uint8_t jmp_code[] = + { + 0xff, 0x25, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + + std::uint8_t orig_bytes[sizeof jmp_code]; + *reinterpret_cast(jmp_code + 6) = addr; + read_phys(vdm::syscall_address.load(), orig_bytes, sizeof orig_bytes); + + // execute hook... + write_phys(vdm::syscall_address.load(), jmp_code, sizeof jmp_code); + auto result = reinterpret_cast(proc)(args ...); + write_phys(vdm::syscall_address.load(), orig_bytes, sizeof orig_bytes); + + syscall_mutex.unlock(); + return result; + } + + template + __forceinline auto rkm(std::uintptr_t addr) -> T + { + T buffer; + rkm((void*)&buffer, (void*)addr, sizeof T); + return buffer; + } + + template + __forceinline void wkm(std::uintptr_t addr, const T& value) + { + wkm((void*)addr, (void*)&value, sizeof T); + } + + __forceinline auto get_peprocess(std::uint32_t pid) -> PEPROCESS + { + static const auto ps_lookup_peproc = + util::get_kmodule_export( + "ntoskrnl.exe", + "PsLookupProcessByProcessId"); + + PEPROCESS peproc = nullptr; + this->syscall( + ps_lookup_peproc, + (HANDLE)pid, + &peproc + ); + return peproc; + } + private: + void locate_syscall(std::uintptr_t begin, std::uintptr_t end) const; + bool valid_syscall(void* syscall_addr) const; + + read_phys_t read_phys; + write_phys_t write_phys; + }; +} \ No newline at end of file diff --git a/drv_entry.cpp b/drv_entry.cpp index 7938fa4..3ded756 100644 --- a/drv_entry.cpp +++ b/drv_entry.cpp @@ -12,18 +12,18 @@ auto drv_entry(PDRIVER_OBJECT driver_object, PUNICODE_STRING registry_path) -> N cr3 cr3_value; cr3_value.flags = __readcr3(); - cr3_value.address_of_page_directory = + cr3_value.pml4_pfn = (MmGetPhysicalAddress(mm::pml4).QuadPart >> 12); memset(mm::pml4, NULL, sizeof mm::pml4); - mm::pml4[PML4_SELF_REF].pfn = cr3_value.address_of_page_directory; + mm::pml4[PML4_SELF_REF].pfn = cr3_value.pml4_pfn; mm::pml4[PML4_SELF_REF].present = true; mm::pml4[PML4_SELF_REF].rw = true; mm::pml4[PML4_SELF_REF].user_supervisor = false; PHYSICAL_ADDRESS current_pml4; current_pml4.QuadPart = - (cr3{ __readcr3() }.address_of_page_directory << 12); + (cr3{ __readcr3() }.pml4_pfn << 12); const auto kernel_pml4 = reinterpret_cast( @@ -33,7 +33,7 @@ auto drv_entry(PDRIVER_OBJECT driver_object, PUNICODE_STRING registry_path) -> N memcpy(&mm::pml4[256], &kernel_pml4[256], sizeof(mm::pml4e) * 256); // setup mapping ptes to be present, writeable, executable, and user supervisor false... - for (auto idx = 0u; idx < 254; ++idx) + for (auto idx = 0u; idx < 255; ++idx) { reinterpret_cast(mm::pml4)[idx].present = true; reinterpret_cast(mm::pml4)[idx].rw = true; diff --git a/exit_handler.cpp b/exit_handler.cpp index 5bcd115..eb2ea88 100644 --- a/exit_handler.cpp +++ b/exit_handler.cpp @@ -1,5 +1,27 @@ #include "vmxexit_handler.h" +auto get_command(u64 dirbase, u64 command_ptr) -> vmcall_command_t +{ + const auto virt_map = + mm::map_virt(dirbase, command_ptr); + + if (!virt_map) + return {}; + + return *reinterpret_cast(virt_map); +} + +auto set_command(u64 dirbase, u64 command_ptr, const vmcall_command_t& vmcall_command) -> void +{ + const auto virt_map = + mm::map_virt(dirbase, command_ptr); + + if (!virt_map) + return; + + *reinterpret_cast(virt_map) = vmcall_command; +} + auto exit_handler(hv::pguest_registers regs) -> void { u64 exit_reason; @@ -9,27 +31,12 @@ auto exit_handler(hv::pguest_registers regs) -> void { case VMX_EXIT_REASON_EXECUTE_CPUID: { - if (regs->rcx == 0xC0FFEE) - { - __try - { - *(u8*)0x0 = 0xDE; - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - regs->rax = 0xC0FFEE; - break; - } - } - else - { - int result[4]; - __cpuid(result, regs->rax); - regs->rax = result[0]; - regs->rbx = result[1]; - regs->rcx = result[2]; - regs->rdx = result[3]; - } + int result[4]; + __cpuid(result, regs->rax); + regs->rax = result[0]; + regs->rbx = result[1]; + regs->rcx = result[2]; + regs->rdx = result[3]; break; } case VMX_EXIT_REASON_EXECUTE_XSETBV: @@ -126,6 +133,97 @@ auto exit_handler(hv::pguest_registers regs) -> void __invd(); break; } + case VMX_EXIT_REASON_EXECUTE_VMCALL: + { + if (regs->rcx == VMCALL_KEY) + { + // test SEH... IST is currently boonk... + __try + { + *reinterpret_cast(0x0) = 0xDE; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + __debugbreak(); + } + + cr3 dirbase; + __vmx_vmread(VMCS_GUEST_CR3, &dirbase.flags); + + auto command = get_command( + dirbase.pml4_pfn << 12, regs->rdx); + + if (command.present) + { + switch (command.option) + { + case vmcall_option::copy_virt: + { + command.result = + mm::copy_virt( + command.copy_virt.dirbase_src, + command.copy_virt.virt_src, + command.copy_virt.dirbase_dest, + command.copy_virt.virt_dest, + command.copy_virt.size); + break; + } + case vmcall_option::translate: + { + command.translate.phys_addr = + mm::translate(mm::virt_addr_t{ + command.translate.virt_addr }, + command.translate.dirbase); + + // true if address is not null... + command.result = command.translate.phys_addr; + break; + } + case vmcall_option::read_phys: + { + command.result = + mm::read_phys( + command.read_phys.dirbase_dest, + command.read_phys.phys_src, + command.read_phys.virt_dest, + command.read_phys.size); + break; + } + case vmcall_option::write_phys: + { + command.result = + mm::write_phys( + command.write_phys.dirbase_src, + command.write_phys.phys_dest, + command.write_phys.virt_src, + command.write_phys.size); + break; + } + case vmcall_option::dirbase: + { + command.result = true; + command.dirbase = dirbase.pml4_pfn << 12; + break; + } + default: + // check to see why the option was invalid... + __debugbreak(); + break; + } + + set_command(dirbase.pml4_pfn << 12, regs->rdx, command); + } + } + else + { + vmentry_interrupt_information interrupt{}; + interrupt.flags = interruption_type::hardware_exception; + interrupt.vector = EXCEPTION_INVALID_OPCODE; + interrupt.valid = true; + __vmx_vmwrite(VMCS_CTRL_VMENTRY_INTERRUPTION_INFORMATION_FIELD, interrupt.flags); + } + break; + } case VMX_EXIT_REASON_EXECUTE_VMWRITE: case VMX_EXIT_REASON_EXECUTE_VMREAD: case VMX_EXIT_REASON_EXECUTE_VMPTRST: @@ -133,7 +231,6 @@ auto exit_handler(hv::pguest_registers regs) -> void case VMX_EXIT_REASON_EXECUTE_VMCLEAR: case VMX_EXIT_REASON_EXECUTE_VMXOFF: case VMX_EXIT_REASON_EXECUTE_VMXON: - case VMX_EXIT_REASON_EXECUTE_VMCALL: { vmentry_interrupt_information interrupt{}; interrupt.flags = interruption_type::hardware_exception; diff --git a/hv_types.hpp b/hv_types.hpp index 5b684ef..145f03e 100644 --- a/hv_types.hpp +++ b/hv_types.hpp @@ -29,6 +29,7 @@ extern "C" void _sgdt(void*); #define HOST_STACK_PAGES 6 #define HOST_STACK_SIZE PAGE_SIZE * HOST_STACK_PAGES +#define VMCALL_KEY 0xC0FFEE // Export Directory #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 diff --git a/ia32.hpp b/ia32.hpp index c3fd2c6..04f1484 100644 --- a/ia32.hpp +++ b/ia32.hpp @@ -294,7 +294,7 @@ typedef union * @see Vol3A[4.3(32-BIT PAGING)] * @see Vol3A[4.5(4-LEVEL PAGING)] */ - uint64_t address_of_page_directory : 36; + uint64_t pml4_pfn : 36; #define CR3_ADDRESS_OF_PAGE_DIRECTORY_BIT 12 #define CR3_ADDRESS_OF_PAGE_DIRECTORY_FLAG 0xFFFFFFFFF000 #define CR3_ADDRESS_OF_PAGE_DIRECTORY_MASK 0xFFFFFFFFF diff --git a/idt.cpp b/idt.cpp index 9bb0538..3fe5bd7 100644 --- a/idt.cpp +++ b/idt.cpp @@ -52,7 +52,7 @@ namespace idt result.segment_selector = readcs(); result.gate_type = SEGMENT_DESCRIPTOR_TYPE_INTERRUPT_GATE; result.present = true; - //result.ist_index = ist_index; + result.ist_index = ist_index; result.offset_high = idt_handler.offset_high; result.offset_middle = idt_handler.offset_middle; diff --git a/mm.cpp b/mm.cpp index 2e2b24c..ee70322 100644 --- a/mm.cpp +++ b/mm.cpp @@ -4,7 +4,7 @@ namespace mm { auto translate(virt_addr_t virt_addr) -> u64 { - virt_addr_t cursor{ vmxroot_pml4 }; + virt_addr_t cursor{ (u64) vmxroot_pml4 }; if (!reinterpret_cast(cursor.value)[virt_addr.pml4_index].present) return {}; @@ -82,7 +82,7 @@ namespace mm auto map_page(u64 phys_addr, map_type type) -> u64 { cpuid_eax_01 cpuid_value; - virt_addr_t result{ vmxroot_pml4 }; + virt_addr_t result{ (u64) vmxroot_pml4 }; __cpuid((int*)&cpuid_value, 1); result.pt_index = (cpuid_value @@ -93,16 +93,16 @@ namespace mm reinterpret_cast(vmxroot_pml4) [result.pt_index].pfn = phys_addr >> 12; - __invlpg(result.value); - result.offset_4kb = virt_addr_t{ (void*)phys_addr }.offset_4kb; - return reinterpret_cast(result.value); + __invlpg((void*)result.value); + result.offset_4kb = phys_addr_t{ phys_addr }.offset_4kb; + return result.value; } auto map_virt(u64 dirbase, u64 virt_addr, map_type map_type) -> u64 { const auto phys_addr = - translate(virt_addr_t{ (void*) - virt_addr }, dirbase, map_type); + translate(virt_addr_t{ virt_addr }, + dirbase, map_type); if (!phys_addr) return {}; @@ -110,15 +110,102 @@ namespace mm return map_page(phys_addr, map_type); } + auto read_phys(u64 dirbase, u64 guest_phys, u64 guest_virt, u64 size) -> bool + { + // handle reading over page boundaries + // of both src and dest... + while (size) + { + auto dest_current_size = PAGE_SIZE - + virt_addr_t{ guest_virt }.offset_4kb; + + if (size < dest_current_size) + dest_current_size = size; + + auto src_current_size = PAGE_SIZE - + phys_addr_t{ guest_phys }.offset_4kb; + + if (size < src_current_size) + src_current_size = size; + + auto current_size = + min(dest_current_size, src_current_size); + + const auto mapped_dest = + reinterpret_cast( + map_virt(dirbase, guest_virt, map_type::dest)); + + if (!mapped_dest) + return false; + + const auto mapped_src = + reinterpret_cast( + map_page(guest_phys, map_type::src)); + + if (!mapped_src) + return false; + + memcpy(mapped_dest, mapped_src, current_size); + guest_phys += current_size; + guest_virt += current_size; + size -= current_size; + } + return true; + } + + auto write_phys(u64 dirbase, u64 guest_phys, u64 guest_virt, u64 size) -> bool + { + // handle reading over page boundaries + // of both src and dest... + while (size) + { + auto dest_current_size = PAGE_SIZE - + virt_addr_t{ guest_virt }.offset_4kb; + + if (size < dest_current_size) + dest_current_size = size; + + auto src_current_size = PAGE_SIZE - + phys_addr_t{ guest_phys }.offset_4kb; + + if (size < src_current_size) + src_current_size = size; + + auto current_size = + min(dest_current_size, src_current_size); + + const auto mapped_src = + reinterpret_cast( + map_virt(dirbase, guest_virt, map_type::src)); + + if (!mapped_src) + return false; + + const auto mapped_dest = + reinterpret_cast( + map_page(guest_phys, map_type::dest)); + + if (!mapped_src) + return false; + + memcpy(mapped_dest, mapped_src, current_size); + guest_phys += current_size; + guest_virt += current_size; + size -= current_size; + } + + return true; + } + auto copy_virt(u64 dirbase_src, u64 virt_src, u64 dirbase_dest, u64 virt_dest, u64 size) -> bool { while (size) { - auto dest_size = PAGE_SIZE - virt_addr_t{ (void*)virt_dest }.offset_4kb; + auto dest_size = PAGE_SIZE - virt_addr_t{ virt_dest }.offset_4kb; if (size < dest_size) dest_size = size; - auto src_size = PAGE_SIZE - virt_addr_t{ (void*)virt_src }.offset_4kb; + auto src_size = PAGE_SIZE - virt_addr_t{ virt_src }.offset_4kb; if (size < src_size) src_size = size; diff --git a/mm.hpp b/mm.hpp index 069549c..d06c309 100644 --- a/mm.hpp +++ b/mm.hpp @@ -8,7 +8,7 @@ namespace mm { typedef union _virt_addr_t { - void* value; + u64 value; struct { u64 offset_4kb : 12; @@ -37,6 +37,7 @@ namespace mm }; } virt_addr_t, * pvirt_addr_t; + using phys_addr_t = virt_addr_t; typedef union _pml4e { @@ -138,12 +139,15 @@ namespace mm auto translate(virt_addr_t virt_addr, u64 pml4_phys, map_type type = map_type::src) -> u64; // map a page into vmxroot address space... - auto map_page(u64 phys_addr, map_type type) -> u64; + auto map_page(u64 phys_addr, map_type type = map_type::src) -> u64; // map a page (4kb) from another address into vmxroot... - auto map_virt(u64 dirbase, u64 virt_addr, map_type map_type)->u64; + auto map_virt(u64 dirbase, u64 virt_addr, map_type map_type = map_type::src)->u64; // copy virtual memory without changing cr3... this maps the physical memory into vmxroot // address space and copies the memory directly between the physical pages... the memory must be paged in... auto copy_virt(u64 dirbase_src, u64 virt_src, u64 dirbase_dest, u64 virt_dest, u64 size) -> bool; + + auto read_phys(u64 dirbase, u64 guest_phys, u64 guest_virt, u64 size) -> bool; + auto write_phys(u64 dirbase, u64 guest_phys, u64 guest_virt, u64 size) -> bool; } \ No newline at end of file diff --git a/vmxexit_handler.h b/vmxexit_handler.h index e5b5ae3..1cae8af 100644 --- a/vmxexit_handler.h +++ b/vmxexit_handler.h @@ -2,6 +2,64 @@ #include "hv_types.hpp" #include "debug.hpp" #include "invd.hpp" +#include "mm.hpp" + +enum class vmcall_option +{ + translate, + copy_virt, + write_phys, + read_phys, + dirbase +}; + +typedef struct _vmcall_command_t +{ + bool present; + bool result; + vmcall_option option; + + union + { + struct + { + u64 dirbase; + u64 virt_addr; + u64 phys_addr; + } translate; + + struct + { + u64 virt_src; + u64 dirbase_src; + u64 virt_dest; + u64 dirbase_dest; + u64 size; + } copy_virt; + + struct + { + u64 virt_src; + u64 dirbase_src; + u64 phys_dest; + u64 size; + } write_phys; + + struct + { + u64 phys_src; + u64 dirbase_dest; + u64 virt_dest; + u64 size; + } read_phys; + + u64 dirbase; + }; + +} vmcall_command_t, * pvmcall_command_t; extern "C" auto vmxexit_handler() -> void; -extern "C" auto exit_handler(hv::pguest_registers regs) -> void; \ No newline at end of file +extern "C" auto exit_handler(hv::pguest_registers regs) -> void; + +auto get_command(u64 dirbase, u64 command_ptr) -> vmcall_command_t; +auto set_command(u64 dirbase, u64 command_ptr, const vmcall_command_t& vmcall_command) -> void; \ No newline at end of file