diff --git a/binaries/bluepill.sys b/binaries/bluepill.sys new file mode 100644 index 0000000..d5e06ba Binary files /dev/null and b/binaries/bluepill.sys differ diff --git a/binaries/demo.exe b/binaries/demo.exe new file mode 100644 index 0000000..1377376 Binary files /dev/null and b/binaries/demo.exe differ diff --git a/demo/bluepill.cpp b/demo/bluepill.cpp index 1f9ffe3..852102d 100644 --- a/demo/bluepill.cpp +++ b/demo/bluepill.cpp @@ -10,69 +10,122 @@ namespace bluepill command.present = true; command.option = vmcall_option::dirbase; - hypercall(VMCALL_KEY, &command); + // can throw invalid opcode if hypervisor is not loaded... + __try + { + hypercall(VMCALL_KEY, &command); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return {}; + } return command.dirbase; } - auto translate(void* dirbase, void* virt_addr) -> u64 + auto translate(u64 dirbase, void* virt_addr) -> u64 { + if (!dirbase || !virt_addr) + return false; + 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.dirbase = dirbase; command.translate.virt_addr = reinterpret_cast(virt_addr); - hypercall(VMCALL_KEY, &command); + // can throw invalid opcode if hypervisor is not loaded... + __try + { + hypercall(VMCALL_KEY, &command); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return {}; + } return command.translate.phys_addr; } - auto read_phys(void* dest, void* phys_src, u64 size) -> bool + auto read_phys(u64 phys_src, void* virt_dest, u64 size) -> bool { + if (!phys_src || !virt_dest || !size) + return false; + 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.virt_dest = reinterpret_cast(virt_dest); + command.read_phys.phys_src = phys_src; command.read_phys.dirbase_dest = get_dirbase(); command.read_phys.size = size; - hypercall(VMCALL_KEY, &command); + // can throw invalid opcode if hypervisor is not loaded... + __try + { + hypercall(VMCALL_KEY, &command); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return false; + } return command.result; } - auto write_phys(void* phys_dest, void* src, u64 size) -> bool + auto write_phys(u64 phys_dest, void* src, u64 size) -> bool { + if (!phys_dest || !src || !size) + return false; + 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.phys_dest = phys_dest; command.write_phys.dirbase_src = get_dirbase(); command.write_phys.size = size; - hypercall(VMCALL_KEY, &command); + // can throw invalid opcode if hypervisor is not loaded... + __try + { + hypercall(VMCALL_KEY, &command); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return false; + } return command.result; } - auto copy_virt(void* dirbase_src, void* virt_src, void* dirbase_dest, void* virt_dest, u64 size) -> bool + auto copy_virt(u64 dirbase_src, void* virt_src, u64 dirbase_dest, void* virt_dest, u64 size) -> bool { + if (!dirbase_src || !virt_src || !dirbase_dest || !virt_dest) + return false; + vmcall_command_t command{}; memset(&command, NULL, sizeof command); command.present = true; - command.copy_virt.dirbase_dest = reinterpret_cast(dirbase_dest); + command.option = vmcall_option::copy_virt; + command.copy_virt.dirbase_dest = dirbase_dest; command.copy_virt.virt_dest = reinterpret_cast(virt_dest); - command.copy_virt.dirbase_src = reinterpret_cast(dirbase_src); + command.copy_virt.dirbase_src = dirbase_src; command.copy_virt.virt_src = reinterpret_cast(virt_src); command.copy_virt.size = size; - hypercall(VMCALL_KEY, &command); + // can throw invalid opcode if hypervisor is not loaded... + __try + { + hypercall(VMCALL_KEY, &command); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return false; + } return command.result; } } \ No newline at end of file diff --git a/demo/bluepill.h b/demo/bluepill.h index bdb6ba9..e268c67 100644 --- a/demo/bluepill.h +++ b/demo/bluepill.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include "vdm_ctx/vdm_ctx.hpp" #define VMCALL_KEY 0xC0FFEE using u8 = unsigned char; @@ -73,16 +74,38 @@ namespace bluepill // vmcall into the hypervisor... extern "C" u64 hypercall(u64 key, pvmcall_command_t command); - // get vmexiting logical processors pml4... + // get vmexiting logical processors pml4 physical address... 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; + auto read_phys(u64 phys_src, void* virt_dest, u64 size) -> bool; + auto write_phys(u64 phys_dest, void* src, u64 size) -> bool; // translate virtual to physical... - auto translate(void* dirbase, void* virt_addr)->u64; + auto translate(u64 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; + // copy virtual memory between two address spaces... page protections are ignored... + // + // WARNING: + // COW (copy on write) will not be triggered, if you write to ntdll.dll, + // kernel32.dll, kernelbase.dll, or any other globally mapped DLL's all processes + // will see the patch unless that process has already triggered COW on the page you are changing... + auto copy_virt(u64 dirbase_src, void* virt_src, u64 dirbase_dest, void* virt_dest, u64 size) -> bool; + + template + inline auto rpm(u64 dirbase, u64 addr) -> T + { + T result{}; + + copy_virt(dirbase, (void*)addr, + get_dirbase(), (void*)&result, sizeof T); + + return result; + } + + template + inline auto wpm(u64 dirbase, u64 addr, const T& data) -> bool + { + return copy_virt(get_dirbase(), (void*)&data, + dirbase, (void*)addr, sizeof T); + } } \ No newline at end of file diff --git a/demo/main.cpp b/demo/main.cpp index 8d00fd4..000e7b7 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -6,29 +6,16 @@ auto __cdecl main(int argc, char** argv) -> void vdm::read_phys_t _read_phys = [&](void* addr, void* buffer, std::size_t size) -> bool { - return bluepill::read_phys(buffer, addr, size); + return bluepill::read_phys( + reinterpret_cast(addr), buffer, size); }; vdm::write_phys_t _write_phys = [&](void* addr, void* buffer, std::size_t size) -> bool { - return bluepill::write_phys(addr, buffer, size); + return bluepill::write_phys( + reinterpret_cast(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 = @@ -40,7 +27,6 @@ auto __cdecl main(int argc, char** argv) -> void 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); @@ -54,4 +40,15 @@ auto __cdecl main(int argc, char** argv) -> void std::printf("[+] kernel MZ -> 0x%x\n", mz_bytes); std::getchar(); + + const auto explorer_pid = util::get_pid("explorer.exe"); + const auto explorer_dirbase = vdm.get_dirbase(explorer_pid); + const auto explorer_base = vdm.get_base_address(explorer_pid); + + std::printf("explorer.exe pid -> %d\n", explorer_pid); + std::printf("explorer.exe dirbase -> 0x%p\n", explorer_dirbase); + std::printf("explorer.exe base address -> 0x%p\n", explorer_base); + std::printf("explorer.exe MZ -> 0x%x\n", bluepill::rpm(explorer_dirbase, explorer_base)); + bluepill::wpm(explorer_dirbase, explorer_base, 0xDE); + std::getchar(); } \ No newline at end of file diff --git a/demo/util/nt.hpp b/demo/util/nt.hpp index 0534c04..22f3b2e 100644 --- a/demo/util/nt.hpp +++ b/demo/util/nt.hpp @@ -32,4 +32,18 @@ using PEPROCESS = PVOID; using PsLookupProcessByProcessId = NTSTATUS(__fastcall*)( HANDLE ProcessId, PEPROCESS* Process -); \ No newline at end of file +); + +typedef union +{ + uint64_t flags; + struct + { + uint64_t reserved1 : 3; + uint64_t page_level_write_through : 1; + uint64_t page_level_cache_disable : 1; + uint64_t reserved2 : 7; + uint64_t pml4_pfn : 36; + uint64_t reserved3 : 16; + }; +} cr3; \ No newline at end of file diff --git a/demo/util/util.hpp b/demo/util/util.hpp index 57fb96f..f2bb049 100644 --- a/demo/util/util.hpp +++ b/demo/util/util.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include @@ -88,6 +89,35 @@ namespace util return &nt_headers->FileHeader; } + __forceinline auto get_pid(const char* proc_name) -> std::uint32_t + { + PROCESSENTRY32 proc_info; + proc_info.dwSize = sizeof(proc_info); + + HANDLE proc_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + if (proc_snapshot == INVALID_HANDLE_VALUE) + return NULL; + + Process32First(proc_snapshot, &proc_info); + if (!strcmp(proc_info.szExeFile, proc_name)) + { + CloseHandle(proc_snapshot); + return proc_info.th32ProcessID; + } + + while (Process32Next(proc_snapshot, &proc_info)) + { + if (!strcmp(proc_info.szExeFile, proc_name)) + { + CloseHandle(proc_snapshot); + return proc_info.th32ProcessID; + } + } + + CloseHandle(proc_snapshot); + return NULL; + } + __forceinline auto get_kmodule_base(const char* module_name) -> std::uintptr_t { void* buffer = nullptr; diff --git a/demo/vdm_ctx/vdm_ctx.cpp b/demo/vdm_ctx/vdm_ctx.cpp index 8d59289..1963284 100644 --- a/demo/vdm_ctx/vdm_ctx.cpp +++ b/demo/vdm_ctx/vdm_ctx.cpp @@ -37,14 +37,42 @@ namespace vdm search_thread.join(); } - void vdm_ctx::set_read(read_phys_t& read_func) + auto vdm_ctx::get_peprocess(std::uint32_t pid) -> PEPROCESS { - this->read_phys = read_func; + 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; } - void vdm_ctx::set_write(write_phys_t& write_func) + auto vdm_ctx::get_dirbase(std::uint32_t pid) -> std::uintptr_t { - this->write_phys = write_func; + const auto peproc = + reinterpret_cast( + get_peprocess(pid)); + + if (!peproc) + return {}; + + return rkm(peproc + 0x28).pml4_pfn << 12; + } + + auto vdm_ctx::get_base_address(std::uint32_t pid) -> std::uintptr_t + { + static const auto ps_get_base_addr = + util::get_kmodule_export( + "ntoskrnl.exe", "PsGetProcessSectionBaseAddress"); + + return syscall( + ps_get_base_addr, get_peprocess(pid)); } void vdm_ctx::rkm(void* dst, void* src, std::size_t size) @@ -52,7 +80,7 @@ namespace vdm static const auto ntoskrnl_memcpy = util::get_kmodule_export("ntoskrnl.exe", "memcpy"); - this->syscall( + syscall( ntoskrnl_memcpy, dst, src, size); } @@ -61,7 +89,7 @@ namespace vdm static const auto ntoskrnl_memcpy = util::get_kmodule_export("ntoskrnl.exe", "memcpy"); - this->syscall( + syscall( ntoskrnl_memcpy, dst, src, size); } @@ -75,6 +103,10 @@ namespace vdm PAGE_READWRITE )); + // you must write to the VirtualAlloc + // page in order for the PTE to be created... + memset(page_data, NULL, PAGE_4KB); + for (auto page = 0u; page < length; page += PAGE_4KB) { if (vdm::syscall_address.load()) @@ -91,6 +123,7 @@ namespace vdm reinterpret_cast( address + page + nt_page_offset)); } + VirtualFree(page_data, PAGE_4KB, MEM_DECOMMIT); } diff --git a/demo/vdm_ctx/vdm_ctx.hpp b/demo/vdm_ctx/vdm_ctx.hpp index a0f7961..3d31f5a 100644 --- a/demo/vdm_ctx/vdm_ctx.hpp +++ b/demo/vdm_ctx/vdm_ctx.hpp @@ -25,13 +25,15 @@ namespace vdm { 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); + auto get_peprocess(std::uint32_t pid) -> PEPROCESS; + auto get_dirbase(std::uint32_t pid) -> std::uintptr_t; + auto get_base_address(std::uint32_t pid) -> std::uintptr_t; + template - __forceinline std::invoke_result_t syscall(void* addr, Ts ... args) const + std::invoke_result_t syscall(void* addr, Ts ... args) const { static const auto proc = GetProcAddress( @@ -65,7 +67,7 @@ namespace vdm } template - __forceinline auto rkm(std::uintptr_t addr) -> T + auto rkm(std::uintptr_t addr) -> T { T buffer; rkm((void*)&buffer, (void*)addr, sizeof T); @@ -73,31 +75,15 @@ namespace vdm } template - __forceinline void wkm(std::uintptr_t addr, const T& value) + 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; - } + read_phys_t read_phys; + write_phys_t write_phys; 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/exit_handler.cpp b/exit_handler.cpp index eb2ea88..fbb7f5d 100644 --- a/exit_handler.cpp +++ b/exit_handler.cpp @@ -137,16 +137,6 @@ auto exit_handler(hv::pguest_registers regs) -> void { 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); diff --git a/idt.cpp b/idt.cpp index 3fe5bd7..3a90c56 100644 --- a/idt.cpp +++ b/idt.cpp @@ -52,7 +52,6 @@ namespace idt result.segment_selector = readcs(); result.gate_type = SEGMENT_DESCRIPTOR_TYPE_INTERRUPT_GATE; result.present = true; - 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 ee70322..c91dfd3 100644 --- a/mm.cpp +++ b/mm.cpp @@ -145,7 +145,15 @@ namespace mm if (!mapped_src) return false; - memcpy(mapped_dest, mapped_src, current_size); + __try + { + memcpy(mapped_dest, mapped_src, current_size); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return false; + } + guest_phys += current_size; guest_virt += current_size; size -= current_size; @@ -188,7 +196,15 @@ namespace mm if (!mapped_src) return false; - memcpy(mapped_dest, mapped_src, current_size); + __try + { + memcpy(mapped_dest, mapped_src, current_size); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return false; + } + guest_phys += current_size; guest_virt += current_size; size -= current_size; @@ -225,12 +241,21 @@ namespace mm // copy directly between the two pages... auto current_size = min(dest_size, src_size); - memcpy(mapped_dest, mapped_src, current_size); + + __try + { + memcpy(mapped_dest, mapped_src, current_size); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return false; + } virt_src += current_size; virt_dest += current_size; size -= current_size; } + return true; } } \ No newline at end of file diff --git a/screenshots/demo.png b/screenshots/demo.png new file mode 100644 index 0000000..f4f2eb5 Binary files /dev/null and b/screenshots/demo.png differ diff --git a/screenshots/firstvmexit.png b/screenshots/firstvmexit.png new file mode 100644 index 0000000..a9d818c Binary files /dev/null and b/screenshots/firstvmexit.png differ