diff --git a/src/common/platform/status.hpp b/src/common/platform/status.hpp index 7918fcf0..720f700c 100644 --- a/src/common/platform/status.hpp +++ b/src/common/platform/status.hpp @@ -10,6 +10,7 @@ using NTSTATUS = std::uint32_t; #define STATUS_TIMEOUT ((NTSTATUS)0x00000102L) #define STATUS_PENDING ((NTSTATUS)0x00000103L) +#define STATUS_GUARD_PAGE_VIOLATION ((NTSTATUS)0x80000001L) #define STATUS_BREAKPOINT ((NTSTATUS)0x80000003L) #define STATUS_SINGLE_STEP ((NTSTATUS)0x80000004L) diff --git a/src/emulator/memory_region.hpp b/src/emulator/memory_region.hpp index cdf25ce9..6cec89c9 100644 --- a/src/emulator/memory_region.hpp +++ b/src/emulator/memory_region.hpp @@ -2,14 +2,15 @@ #include "memory_permission.hpp" #include +template struct basic_memory_region { uint64_t start{}; size_t length{}; // uint64_t? - memory_permission permissions{}; + PermissionType permissions{}; }; -struct memory_region : basic_memory_region +struct memory_region : basic_memory_region<> { bool committed{}; }; diff --git a/src/samples/test-sample/test.cpp b/src/samples/test-sample/test.cpp index 6b5cdba6..fa2d844a 100644 --- a/src/samples/test-sample/test.cpp +++ b/src/samples/test-sample/test.cpp @@ -647,9 +647,106 @@ namespace return res; } + INT32 test_guard_page_seh_filter(LPVOID address, DWORD code, struct _EXCEPTION_POINTERS* ep) + { + // We are only looking for guard page exceptions. + if (code != STATUS_GUARD_PAGE_VIOLATION) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + // The number of defined elements in the ExceptionInformation array for + // a guard page violation should be 2. + if (ep->ExceptionRecord->NumberParameters != 2) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + // The ExceptionInformation array specifies additional arguments that + // describe the exception. + auto* exception_information = ep->ExceptionRecord->ExceptionInformation; + + // If this value is zero, the thread attempted to read the inaccessible + // data. If this value is 1, the thread attempted to write to an + // inaccessible address. + if (exception_information[0] != 1) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + // The second array element specifies the virtual address of the + // inaccessible data. + if (exception_information[1] != (ULONG_PTR)address) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + return EXCEPTION_EXECUTE_HANDLER; + } + + bool test_guard_page_exception() + { + SYSTEM_INFO sys_info; + GetSystemInfo(&sys_info); + + // Allocate a guarded memory region with the length of the system page + // size. + auto* addr = static_cast( + VirtualAlloc(nullptr, sys_info.dwPageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE | PAGE_GUARD)); + if (addr == nullptr) + { + puts("Failed to allocate guard page"); + return false; + } + + bool success = false; + + // We want to access some arbitrary offset into the guarded page, to + // ensure that ExceptionInformation correctly contains the virtual + // address of the inaccessible data, not the base address of the region. + constexpr size_t offset = 10; + + // Trigger a guard page violation + __try + { + addr[offset] = 255; + } + // If the filter function returns EXCEPTION_CONTINUE_SEARCH, the + // exception contains all of the correct information. + __except (test_guard_page_seh_filter(addr + offset, GetExceptionCode(), GetExceptionInformation())) + { + success = true; + } + + // The page guard should be lifted, so no exception should be raised. + __try + { + // The previous write should not have went through, this is probably + // superflous. + if (addr[offset] == 255) + { + success = false; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + puts("Failed to read from page after guard exception!"); + success = false; + } + + // Free the allocated memory + if (!VirtualFree(addr, 0, MEM_RELEASE)) + { + puts("Failed to free allocated region"); + success = false; + } + + return success; + } + bool test_native_exceptions() { - return test_access_violation_exception() && test_illegal_instruction_exception(); + return test_access_violation_exception() && test_illegal_instruction_exception() && test_guard_page_exception(); } #endif diff --git a/src/windows-emulator/exception_dispatch.cpp b/src/windows-emulator/exception_dispatch.cpp index e4626cf2..09e9d93c 100644 --- a/src/windows-emulator/exception_dispatch.cpp +++ b/src/windows-emulator/exception_dispatch.cpp @@ -182,6 +182,16 @@ void dispatch_access_violation(x86_64_emulator& emu, const process_context& proc }); } +void dispatch_guard_page_violation(x86_64_emulator& emu, const process_context& proc, const uint64_t address, + const memory_operation operation) +{ + dispatch_exception(emu, proc, STATUS_GUARD_PAGE_VIOLATION, + { + map_violation_operation_to_parameter(operation), + address, + }); +} + void dispatch_illegal_instruction_violation(x86_64_emulator& emu, const process_context& proc) { dispatch_exception(emu, proc, STATUS_ILLEGAL_INSTRUCTION, {}); diff --git a/src/windows-emulator/exception_dispatch.hpp b/src/windows-emulator/exception_dispatch.hpp index 4a717c3d..46850435 100644 --- a/src/windows-emulator/exception_dispatch.hpp +++ b/src/windows-emulator/exception_dispatch.hpp @@ -19,6 +19,8 @@ void dispatch_exception(x86_64_emulator& emu, const process_context& proc, const void dispatch_access_violation(x86_64_emulator& emu, const process_context& proc, uint64_t address, memory_operation operation); +void dispatch_guard_page_violation(x86_64_emulator& emu, const process_context& proc, uint64_t address, + memory_operation operation); void dispatch_illegal_instruction_violation(x86_64_emulator& emu, const process_context& proc); void dispatch_integer_division_by_zero(x86_64_emulator& emu, const process_context& proc); void dispatch_single_step(x86_64_emulator& emu, const process_context& proc); diff --git a/src/windows-emulator/memory_manager.cpp b/src/windows-emulator/memory_manager.cpp index 82c9cb42..7f61df2e 100644 --- a/src/windows-emulator/memory_manager.cpp +++ b/src/windows-emulator/memory_manager.cpp @@ -3,6 +3,7 @@ #include "memory_region.hpp" #include "address_utils.hpp" +#include "memory_permission_ext.hpp" #include #include @@ -73,7 +74,7 @@ namespace utils static void deserialize(buffer_deserializer& buffer, memory_manager::committed_region& region) { region.length = static_cast(buffer.read()); - region.permissions = buffer.read(); + region.permissions = buffer.read(); } static void serialize(buffer_serializer& buffer, const memory_manager::reserved_region& region) @@ -189,8 +190,8 @@ void memory_manager::deserialize_memory_state(utils::buffer_deserializer& buffer } } -bool memory_manager::protect_memory(const uint64_t address, const size_t size, const memory_permission permissions, - memory_permission* old_permissions) +bool memory_manager::protect_memory(const uint64_t address, const size_t size, const nt_memory_permission permissions, + nt_memory_permission* old_permissions) { const auto entry = this->find_reserved_region(address); if (entry == this->reserved_regions_.end()) @@ -268,7 +269,7 @@ bool memory_manager::allocate_mmio(const uint64_t address, const size_t size, mm return true; } -bool memory_manager::allocate_memory(const uint64_t address, const size_t size, const memory_permission permissions, +bool memory_manager::allocate_memory(const uint64_t address, const size_t size, const nt_memory_permission permissions, const bool reserve_only) { if (this->overlaps_reserved_region(address, size)) @@ -286,8 +287,9 @@ bool memory_manager::allocate_memory(const uint64_t address, const size_t size, if (!reserve_only) { - this->map_memory(address, size, permissions); - entry->second.committed_regions[address] = committed_region{size, memory_permission::read_write}; + this->map_memory(address, size, permissions.is_guarded() ? memory_permission::none : permissions.common); + entry->second.committed_regions[address] = + committed_region{size, nt_memory_permission{memory_permission::read_write, permissions.extended}}; } this->update_layout_version(); @@ -295,7 +297,7 @@ bool memory_manager::allocate_memory(const uint64_t address, const size_t size, return true; } -bool memory_manager::commit_memory(const uint64_t address, const size_t size, const memory_permission permissions) +bool memory_manager::commit_memory(const uint64_t address, const size_t size, const nt_memory_permission permissions) { const auto entry = this->find_reserved_region(address); if (entry == this->reserved_regions_.end()) @@ -473,7 +475,7 @@ void memory_manager::unmap_all_memory() this->reserved_regions_.clear(); } -uint64_t memory_manager::allocate_memory(const size_t size, const memory_permission permissions, +uint64_t memory_manager::allocate_memory(const size_t size, const nt_memory_permission permissions, const bool reserve_only) { const auto allocation_base = this->find_free_allocation_base(size); @@ -519,8 +521,8 @@ region_info memory_manager::get_region_info(const uint64_t address) region_info result{}; result.start = MIN_ALLOCATION_ADDRESS; result.length = static_cast(MAX_ALLOCATION_ADDRESS - result.start); - result.permissions = memory_permission::none; - result.initial_permissions = memory_permission::none; + result.permissions = nt_memory_permission(); + result.initial_permissions = nt_memory_permission(); result.allocation_base = {}; result.allocation_length = result.length; result.is_committed = false; diff --git a/src/windows-emulator/memory_manager.hpp b/src/windows-emulator/memory_manager.hpp index b490ee23..92b398df 100644 --- a/src/windows-emulator/memory_manager.hpp +++ b/src/windows-emulator/memory_manager.hpp @@ -3,6 +3,7 @@ #include #include +#include "memory_permission_ext.hpp" #include "memory_region.hpp" #include "serialization.hpp" @@ -12,13 +13,15 @@ constexpr auto ALLOCATION_GRANULARITY = 0x0000000000010000ULL; constexpr auto MIN_ALLOCATION_ADDRESS = 0x0000000000010000ULL; constexpr auto MAX_ALLOCATION_ADDRESS = 0x00007ffffffeffffULL; -struct region_info : basic_memory_region +// This maps to the `basic_memory_region` struct defined in +// emulator\memory_region.hpp +struct region_info : basic_memory_region { uint64_t allocation_base{}; size_t allocation_length{}; bool is_reserved{}; bool is_committed{}; - memory_permission initial_permissions{}; + nt_memory_permission initial_permissions{}; }; using mmio_read_callback = std::function; @@ -41,7 +44,7 @@ class memory_manager : public memory_interface struct committed_region { size_t length{}; - memory_permission permissions{}; + nt_memory_permission permissions{}; }; using committed_region_map = std::map; @@ -60,20 +63,20 @@ class memory_manager : public memory_interface bool try_read_memory(uint64_t address, void* data, size_t size) const final; void write_memory(uint64_t address, const void* data, size_t size) final; - bool protect_memory(uint64_t address, size_t size, memory_permission permissions, - memory_permission* old_permissions = nullptr); + bool protect_memory(uint64_t address, size_t size, nt_memory_permission permissions, + nt_memory_permission* old_permissions = nullptr); bool allocate_mmio(uint64_t address, size_t size, mmio_read_callback read_cb, mmio_write_callback write_cb); - bool allocate_memory(uint64_t address, size_t size, memory_permission permissions, bool reserve_only = false); + bool allocate_memory(uint64_t address, size_t size, nt_memory_permission permissions, bool reserve_only = false); - bool commit_memory(uint64_t address, size_t size, memory_permission permissions); + bool commit_memory(uint64_t address, size_t size, nt_memory_permission permissions); bool decommit_memory(uint64_t address, size_t size); bool release_memory(uint64_t address, size_t size); void unmap_all_memory(); - uint64_t allocate_memory(size_t size, memory_permission permissions, bool reserve_only = false); + uint64_t allocate_memory(size_t size, nt_memory_permission permissions, bool reserve_only = false); uint64_t find_free_allocation_base(size_t size, uint64_t start = 0) const; diff --git a/src/windows-emulator/memory_permission_ext.hpp b/src/windows-emulator/memory_permission_ext.hpp new file mode 100644 index 00000000..1ea95426 --- /dev/null +++ b/src/windows-emulator/memory_permission_ext.hpp @@ -0,0 +1,180 @@ +#pragma once +#include "memory_permission.hpp" + +enum class memory_permission_ext : uint8_t +{ + none = 0, + guard = 1 << 0, +}; + +/***************************************************************************** + * + ****************************************************************************/ + +constexpr memory_permission_ext operator&(const memory_permission_ext x, const memory_permission_ext y) +{ + return static_cast(static_cast(x) & static_cast(y)); +} + +constexpr memory_permission_ext operator|(const memory_permission_ext x, const memory_permission_ext y) +{ + return static_cast(static_cast(x) | static_cast(y)); +} + +constexpr memory_permission_ext operator^(const memory_permission_ext x, const memory_permission_ext y) +{ + return static_cast(static_cast(x) ^ static_cast(y)); +} + +constexpr memory_permission_ext operator~(memory_permission_ext x) +{ + return static_cast(~static_cast(x)); +} + +inline memory_permission_ext& operator&=(memory_permission_ext& x, const memory_permission_ext y) +{ + x = x & y; + return x; +} + +inline memory_permission_ext& operator|=(memory_permission_ext& x, const memory_permission_ext y) +{ + x = x | y; + return x; +} + +inline memory_permission_ext& operator^=(memory_permission_ext& x, const memory_permission_ext y) +{ + x = x ^ y; + return x; +} + +/***************************************************************************** + * + ****************************************************************************/ + +struct nt_memory_permission +{ + memory_permission common; + memory_permission_ext extended; + + constexpr nt_memory_permission() + : common(memory_permission::none), + extended(memory_permission_ext::none) + { + } + constexpr nt_memory_permission(memory_permission common) + : common(common), + extended(memory_permission_ext::none) + { + } + constexpr nt_memory_permission(memory_permission common, memory_permission_ext ext) + : common(common), + extended(ext) + { + } + + // Implicit coercions + operator memory_permission() const + { + return common; + } + operator memory_permission_ext() const + { + return extended; + } + + // This just does memberwise equality on each of the members in declaration order + bool operator==(nt_memory_permission const&) const = default; + + nt_memory_permission& operator=(memory_permission const& y) + { + this->common = y; + this->extended = memory_permission_ext::none; + return *this; + } + + constexpr bool is_guarded() const + { + return (this->extended & memory_permission_ext::guard) == memory_permission_ext::guard; + } +}; + +/***************************************************************************** + * + ****************************************************************************/ + +constexpr nt_memory_permission operator&(const nt_memory_permission x, const memory_permission y) +{ + return nt_memory_permission{x.common & y, x.extended}; +} + +constexpr nt_memory_permission operator&(const nt_memory_permission x, const memory_permission_ext y) +{ + return nt_memory_permission{x.common, x.extended & y}; +} + +constexpr nt_memory_permission operator|(const nt_memory_permission x, const memory_permission y) +{ + return nt_memory_permission{x.common | y, x.extended}; +} + +constexpr nt_memory_permission operator|(const nt_memory_permission x, const memory_permission_ext y) +{ + return nt_memory_permission{x.common, x.extended | y}; +} + +constexpr nt_memory_permission operator^(const nt_memory_permission x, const memory_permission y) +{ + return nt_memory_permission{x.common ^ y, x.extended}; +} + +constexpr nt_memory_permission operator^(const nt_memory_permission x, const memory_permission_ext y) +{ + return nt_memory_permission{x.common, x.extended ^ y}; +} + +inline nt_memory_permission& operator&=(nt_memory_permission& x, const memory_permission y) +{ + x = x & y; + return x; +} + +inline nt_memory_permission& operator&=(nt_memory_permission& x, const memory_permission_ext y) +{ + x = x & y; + return x; +} + +inline nt_memory_permission& operator|=(nt_memory_permission& x, const memory_permission y) +{ + x.common | y; + return x; +} + +inline nt_memory_permission& operator|=(nt_memory_permission& x, const nt_memory_permission y) +{ + x.extended | y; + return x; +} + +inline nt_memory_permission& operator^=(nt_memory_permission& x, const memory_permission y) +{ + x.common ^ y; + return x; +} + +inline nt_memory_permission& operator^=(nt_memory_permission& x, const nt_memory_permission y) +{ + x.extended ^ y; + return x; +} + +/***************************************************************************** + * + ****************************************************************************/ + +inline bool is_guarded(const memory_permission_ext permission) +{ + return (permission & memory_permission_ext::guard) != memory_permission_ext::none; +} diff --git a/src/windows-emulator/memory_utils.hpp b/src/windows-emulator/memory_utils.hpp index 70b1a1f8..e50b0098 100644 --- a/src/windows-emulator/memory_utils.hpp +++ b/src/windows-emulator/memory_utils.hpp @@ -2,6 +2,7 @@ #include #include #include +#include "memory_permission_ext.hpp" inline std::string get_permission_string(const memory_permission permission) { @@ -19,28 +20,43 @@ inline std::string get_permission_string(const memory_permission permission) return res; } -inline memory_permission map_nt_to_emulator_protection(uint32_t nt_protection) +inline nt_memory_permission map_nt_to_emulator_protection(uint32_t nt_protection) { - nt_protection &= ~static_cast(PAGE_GUARD); // TODO: Implement that + memory_permission_ext ext = memory_permission_ext::none; + // TODO: Check for invalid combinations + if (nt_protection & PAGE_GUARD) + { + // Unset the guard flag so the following switch statement will still work + nt_protection &= ~static_cast(PAGE_GUARD); + ext = memory_permission_ext::guard; + } + memory_permission common = memory_permission::none; switch (nt_protection) { case PAGE_NOACCESS: - return memory_permission::none; + common = memory_permission::none; + break; case PAGE_READONLY: - return memory_permission::read; + common = memory_permission::read; + break; case PAGE_READWRITE: case PAGE_WRITECOPY: - return memory_permission::read | memory_permission::write; + common = memory_permission::read | memory_permission::write; + break; case PAGE_EXECUTE: case PAGE_EXECUTE_READ: - return memory_permission::read | memory_permission::exec; + common = memory_permission::read | memory_permission::exec; + break; case PAGE_EXECUTE_READWRITE: - return memory_permission::all; + common = memory_permission::all; + break; case PAGE_EXECUTE_WRITECOPY: default: throw std::runtime_error("Failed to map protection"); } + + return nt_memory_permission{common, ext}; } inline uint32_t map_emulator_to_nt_protection(const memory_permission permission) diff --git a/src/windows-emulator/module/mapped_module.hpp b/src/windows-emulator/module/mapped_module.hpp index dfe2b268..2019243f 100644 --- a/src/windows-emulator/module/mapped_module.hpp +++ b/src/windows-emulator/module/mapped_module.hpp @@ -15,7 +15,7 @@ using address_name_mapping = std::map; struct mapped_section { std::string name{}; - basic_memory_region region{}; + basic_memory_region<> region{}; }; struct mapped_module diff --git a/src/windows-emulator/module/module_manager.cpp b/src/windows-emulator/module/module_manager.cpp index c8549218..bb57fa77 100644 --- a/src/windows-emulator/module/module_manager.cpp +++ b/src/windows-emulator/module/module_manager.cpp @@ -23,14 +23,14 @@ namespace utils buffer.read(sym.address); } - static void serialize(buffer_serializer& buffer, const basic_memory_region& region) + static void serialize(buffer_serializer& buffer, const basic_memory_region<>& region) { buffer.write(region.start); buffer.write(region.length); buffer.write(region.permissions); } - static void deserialize(buffer_deserializer& buffer, basic_memory_region& region) + static void deserialize(buffer_deserializer& buffer, basic_memory_region<>& region) { buffer.read(region.start); region.length = static_cast(buffer.read()); diff --git a/src/windows-emulator/syscalls/memory.cpp b/src/windows-emulator/syscalls/memory.cpp index efd5e2f2..0f08c057 100644 --- a/src/windows-emulator/syscalls/memory.cpp +++ b/src/windows-emulator/syscalls/memory.cpp @@ -148,7 +148,7 @@ namespace syscalls c.win_emu.callbacks.on_memory_protect(aligned_start, aligned_length, requested_protection); - memory_permission old_protection_value{}; + nt_memory_permission old_protection_value{}; try { diff --git a/src/windows-emulator/windows_emulator.cpp b/src/windows-emulator/windows_emulator.cpp index b384c576..da3ddd09 100644 --- a/src/windows-emulator/windows_emulator.cpp +++ b/src/windows-emulator/windows_emulator.cpp @@ -11,6 +11,7 @@ #include "apiset/apiset.hpp" #include "network/static_socket_factory.hpp" +#include "memory_permission_ext.hpp" constexpr auto MAX_INSTRUCTIONS_PER_TIME_SLICE = 0x20000; @@ -501,8 +502,20 @@ void windows_emulator::setup_hooks() this->emu().hook_memory_violation([&](const uint64_t address, const size_t size, const memory_operation operation, const memory_violation_type type) { - this->callbacks.on_memory_violate(address, size, operation, type); - dispatch_access_violation(this->emu(), this->process, address, operation); + auto region = this->memory.get_region_info(address); + if (region.permissions.is_guarded()) + { + // Unset the GUARD_PAGE flag and dispatch a STATUS_GUARD_PAGE_VIOLATION + this->memory.protect_memory(region.allocation_base, region.length, + region.permissions & ~memory_permission_ext::guard); + dispatch_guard_page_violation(this->emu(), this->process, address, operation); + } + else + { + this->callbacks.on_memory_violate(address, size, operation, type); + dispatch_access_violation(this->emu(), this->process, address, operation); + } + return memory_violation_continuation::resume; });