From de491ade0ea9991a78d9d4113f1ea6ae09b51900 Mon Sep 17 00:00:00 2001 From: Igor Pissolati Date: Fri, 2 Jan 2026 16:21:43 -0300 Subject: [PATCH] Add support for user_object/user_handle_table --- src/common/platform/platform.hpp | 1 + src/common/platform/primitives.hpp | 8 + src/common/platform/user.hpp | 102 ++++++ src/samples/test-sample/test.cpp | 36 ++ src/windows-emulator/handles.hpp | 1 + src/windows-emulator/ports/api_port.cpp | 17 +- src/windows-emulator/process_context.cpp | 22 ++ src/windows-emulator/process_context.hpp | 5 +- src/windows-emulator/syscalls.cpp | 20 +- src/windows-emulator/user_handle_table.hpp | 389 +++++++++++++++++++++ src/windows-emulator/windows_objects.hpp | 40 ++- 11 files changed, 628 insertions(+), 13 deletions(-) create mode 100644 src/common/platform/user.hpp create mode 100644 src/windows-emulator/user_handle_table.hpp diff --git a/src/common/platform/platform.hpp b/src/common/platform/platform.hpp index da729274..39507f41 100644 --- a/src/common/platform/platform.hpp +++ b/src/common/platform/platform.hpp @@ -17,6 +17,7 @@ #include "unicode.hpp" #include "status.hpp" #include "process.hpp" +#include "user.hpp" #include "kernel_mapped.hpp" #include "memory.hpp" #include "file_management.hpp" diff --git a/src/common/platform/primitives.hpp b/src/common/platform/primitives.hpp index 80eacb39..4fc111b4 100644 --- a/src/common/platform/primitives.hpp +++ b/src/common/platform/primitives.hpp @@ -50,6 +50,14 @@ typedef union _LARGE_INTEGER using BYTE = std::uint8_t; #define CHAR BYTE + +typedef struct _RECT +{ + LONG left; + LONG top; + LONG right; + LONG bottom; +} RECT; #endif using WORD = std::uint16_t; diff --git a/src/common/platform/user.hpp b/src/common/platform/user.hpp new file mode 100644 index 00000000..bf027f72 --- /dev/null +++ b/src/common/platform/user.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include + +// NOLINTBEGIN(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + +struct USER_SERVERINFO +{ + DWORD dwSRVIFlags; + uint64_t cHandleEntries; + uint8_t unknown[0x1000]; +}; + +struct USER_DISPINFO +{ + DWORD dwMonitorCount; + EMULATOR_CAST(uint64_t, USER_MONITOR*) pPrimaryMonitor; + uint8_t unknown[0xFF]; +}; + +struct USER_HANDLEENTRY +{ + uint64_t pHead; + uint64_t pOwner; + uint64_t unknown; + uint8_t bType; + uint8_t bFlags; + uint16_t wUniq; +}; +static_assert(sizeof(USER_HANDLEENTRY) == 0x20); + +struct USER_SHAREDINFO +{ + uint64_t psi; + uint64_t aheList; + uint32_t HeEntrySize; + uint64_t pDispInfo; + uint8_t unknown[0xFF]; +}; + +struct USER_THROBJHEAD +{ + struct + { + uint64_t h; + uint32_t cLockObj; + } h; + uint64_t pti; +}; + +struct USER_THRDESKHEAD +{ + USER_THROBJHEAD h; + uint64_t rpdesk; + uint64_t pSelf; +}; + +enum USER_HANDLETYPE : uint8_t +{ + TYPE_FREE = 0, + TYPE_WINDOW = 1, + TYPE_MENU = 2, + TYPE_CURSOR = 3, + TYPE_SETWINDOWPOS = 4, + TYPE_HOOK = 5, + TYPE_CLIPDATA = 6, + TYPE_CALLPROC = 7, + TYPE_ACCELTABLE = 8, + TYPE_DDEACCESS = 9, + TYPE_DDECONV = 10, + TYPE_DDEXACT = 11, + TYPE_MONITOR = 12, + TYPE_KBDLAYOUT = 13, + TYPE_KBDFILE = 14, + TYPE_WINEVENTHOOK = 15, + TYPE_TIMER = 16, + TYPE_INPUTCONTEXT = 17, + TYPE_HIDDATA = 18, + TYPE_DEVICEINFO = 19, + TYPE_TOUCHINPUTINFO = 20, + TYPE_GESTUREINFOOBJ = 21, + TYPE_CTYPES = 22, + TYPE_GENERIC = 255 +}; + +struct USER_MONITOR +{ + EMULATOR_CAST(uint64_t, HMONITOR) hmon; + uint8_t unknown1[0x14]; + RECT monitorRect; + RECT workRect; + uint16_t monitorDpi; + uint16_t nativeDpi; + uint8_t unknown2[0xFF]; +}; + +struct USER_WINDOW +{ + uint8_t unknown[0xFF]; +}; + +// NOLINTEND(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) diff --git a/src/samples/test-sample/test.cpp b/src/samples/test-sample/test.cpp index ef7bbdd9..5472cc93 100644 --- a/src/samples/test-sample/test.cpp +++ b/src/samples/test-sample/test.cpp @@ -509,6 +509,41 @@ namespace return true; } + bool test_monitor_info() + { + const POINT pt = {0, 0}; + const auto hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY); + if (!hMonitor) + { + return false; + } + + MONITORINFOEXA mi; + mi.cbSize = sizeof(mi); + + if (!GetMonitorInfoA(hMonitor, &mi)) + { + return false; + } + + if (std::string_view(mi.szDevice) != R"(\\.\DISPLAY1)") + { + return false; + } + + if (mi.rcMonitor.left != 0 || mi.rcMonitor.top != 0 || mi.rcMonitor.right != 1920 || mi.rcMonitor.bottom != 1080) + { + return false; + } + + if (!(mi.dwFlags & MONITORINFOF_PRIMARY)) + { + return false; + } + + return true; + } + bool test_time_zone() { DYNAMIC_TIME_ZONE_INFORMATION current_dtzi = {}; @@ -908,6 +943,7 @@ int main(const int argc, const char* argv[]) RUN_TEST(test_working_directory, "Working Directory") RUN_TEST(test_registry, "Registry") RUN_TEST(test_system_info, "System Info") + RUN_TEST(test_monitor_info, "Monitor Info") RUN_TEST(test_time_zone, "Time Zone") RUN_TEST(test_threads, "Threads") RUN_TEST(test_threads_winapi, "Threads WinAPI") diff --git a/src/windows-emulator/handles.hpp b/src/windows-emulator/handles.hpp index b6123ee5..d782d36e 100644 --- a/src/windows-emulator/handles.hpp +++ b/src/windows-emulator/handles.hpp @@ -21,6 +21,7 @@ struct handle_types token, window, timer, + monitor, }; }; diff --git a/src/windows-emulator/ports/api_port.cpp b/src/windows-emulator/ports/api_port.cpp index 5b17b4c9..74d81394 100644 --- a/src/windows-emulator/ports/api_port.cpp +++ b/src/windows-emulator/ports/api_port.cpp @@ -9,7 +9,13 @@ namespace { NTSTATUS handle_request(windows_emulator& win_emu, const lpc_request_context& c) override { - // TODO: Fix this. This is broken and wrong. + uint32_t server_dll_index{}; + win_emu.memory.read_memory(c.recv_buffer + 0x18, &server_dll_index, sizeof(server_dll_index)); + + if (server_dll_index != 3) + { + return STATUS_NOT_SUPPORTED; + } try { @@ -17,8 +23,13 @@ namespace const auto dest = data.read(); const auto base = dest.Base; - const auto value = base + 0x10; - win_emu.emu().write_memory(base + 8, &value, sizeof(value)); + const emulator_object shared_obj{win_emu.emu(), base + 8}; + shared_obj.access([&](USER_SHAREDINFO& shared) { + shared.psi = win_emu.process.user_handles.get_server_info().value(); + shared.aheList = win_emu.process.user_handles.get_handle_table().value(); + shared.HeEntrySize = sizeof(USER_HANDLEENTRY); + shared.pDispInfo = win_emu.process.user_handles.get_display_info().value(); + }); } catch (...) { diff --git a/src/windows-emulator/process_context.cpp b/src/windows-emulator/process_context.cpp index ed761c71..f0fc5443 100644 --- a/src/windows-emulator/process_context.cpp +++ b/src/windows-emulator/process_context.cpp @@ -391,6 +391,24 @@ void process_context::setup(x86_64_emulator& emu, memory_manager& memory, regist this->instrumentation_callback = 0; this->default_register_set = emu.save_registers(); + + this->user_handles.setup(memory); + + auto [h, monitor_obj] = this->user_handles.allocate_object(handle_types::monitor); + this->default_monitor_handle = h; + monitor_obj.access([&](USER_MONITOR& monitor) { + monitor.hmon = h.bits; + monitor.monitorRect = {.left = 0, .top = 0, .right = 1920, .bottom = 1080}; + monitor.workRect = monitor.monitorRect; + monitor.monitorDpi = 96; + monitor.nativeDpi = monitor.monitorDpi; + }); + + const auto user_display_info = this->user_handles.get_display_info(); + user_display_info.access([&](USER_DISPINFO& display_info) { + display_info.dwMonitorCount = 1; + display_info.pPrimaryMonitor = monitor_obj.value(); + }); } void process_context::serialize(utils::buffer_serializer& buffer) const @@ -416,6 +434,8 @@ void process_context::serialize(utils::buffer_serializer& buffer) const buffer.write(this->ki_user_exception_dispatcher); buffer.write(this->instrumentation_callback); + buffer.write(this->user_handles); + buffer.write(this->default_monitor_handle); buffer.write(this->events); buffer.write(this->files); buffer.write(this->sections); @@ -462,6 +482,8 @@ void process_context::deserialize(utils::buffer_deserializer& buffer) buffer.read(this->ki_user_exception_dispatcher); buffer.read(this->instrumentation_callback); + buffer.read(this->user_handles); + buffer.read(this->default_monitor_handle); buffer.read(this->events); buffer.read(this->files); buffer.read(this->sections); diff --git a/src/windows-emulator/process_context.hpp b/src/windows-emulator/process_context.hpp index 25a6c1f5..7fa071d0 100644 --- a/src/windows-emulator/process_context.hpp +++ b/src/windows-emulator/process_context.hpp @@ -14,6 +14,7 @@ #include "windows_objects.hpp" #include "emulator_thread.hpp" #include "port.hpp" +#include "user_handle_table.hpp" #include "apiset/apiset.hpp" @@ -118,6 +119,8 @@ struct process_context std::optional> process_params32; std::optional rtl_user_thread_start32{}; + user_handle_table user_handles{}; + handle default_monitor_handle{}; handle_store events{}; handle_store files{}; handle_store sections{}; @@ -125,7 +128,7 @@ struct process_context handle_store semaphores{}; handle_store ports{}; handle_store mutants{}; - handle_store windows{}; + user_handle_store windows{user_handles}; handle_store timers{}; handle_store registry_keys{}; std::map atoms{}; diff --git a/src/windows-emulator/syscalls.cpp b/src/windows-emulator/syscalls.cpp index 9ee433c0..fb9fe7ea 100644 --- a/src/windows-emulator/syscalls.cpp +++ b/src/windows-emulator/syscalls.cpp @@ -463,7 +463,7 @@ namespace syscalls return STATUS_INVALID_HANDLE; } - if (auto* e = c.win_emu.process.events.get(event)) + if (auto* e = c.proc.events.get(event)) { e->signaled = false; } @@ -831,7 +831,7 @@ namespace syscalls const hwnd /*parent*/, const hmenu /*menu*/, const hinstance /*instance*/, const pointer /*l_param*/, const DWORD /*flags*/, const pointer /*acbi_buffer*/) { - window win{}; + auto [handle, win] = c.proc.windows.create(c.win_emu.memory); win.x = x; win.y = y; win.width = width; @@ -840,7 +840,7 @@ namespace syscalls win.class_name = read_large_string(class_name); win.name = read_large_string(window_name); - return c.proc.windows.store(std::move(win)).bits; + return handle.bits; } BOOL handle_NtUserDestroyWindow(const syscall_context& c, const hwnd window) @@ -1027,6 +1027,19 @@ namespace syscalls { return STATUS_NOT_SUPPORTED; } + + BOOL handle_NtUserGetHDevName(const syscall_context& c, handle hdev, emulator_pointer device_name) + { + if (hdev != c.proc.default_monitor_handle) + { + return FALSE; + } + + const std::u16string name = u"\\\\.\\DISPLAY1"; + c.emu.write_memory(device_name, name.c_str(), (name.size() + 1) * sizeof(char16_t)); + + return TRUE; + } } void syscall_dispatcher::add_handlers(std::map& handler_mapping) @@ -1239,6 +1252,7 @@ void syscall_dispatcher::add_handlers(std::map& ha add_handler(NtSetInformationDebugObject); add_handler(NtRemoveProcessDebug); add_handler(NtNotifyChangeDirectoryFileEx); + add_handler(NtUserGetHDevName); #undef add_handler } diff --git a/src/windows-emulator/user_handle_table.hpp b/src/windows-emulator/user_handle_table.hpp new file mode 100644 index 00000000..d03cd22d --- /dev/null +++ b/src/windows-emulator/user_handle_table.hpp @@ -0,0 +1,389 @@ +#pragma once +#include "emulator_utils.hpp" +#include "handles.hpp" + +class user_handle_table +{ + public: + static constexpr uint32_t MAX_HANDLES = 0xFFFF; + + void setup(memory_manager& memory) + { + memory_ = &memory; + + const auto server_info_size = static_cast(page_align_up(sizeof(USER_SERVERINFO))); + server_info_addr_ = memory.allocate_memory(server_info_size, memory_permission::read); + + const auto display_info_size = static_cast(page_align_up(sizeof(USER_DISPINFO))); + display_info_addr_ = memory.allocate_memory(display_info_size, memory_permission::read); + + const emulator_object srv_obj(memory, server_info_addr_); + srv_obj.access([&](USER_SERVERINFO& srv) { + srv.cHandleEntries = MAX_HANDLES - 1; // + }); + + const auto handle_table_size = static_cast(page_align_up(sizeof(USER_HANDLEENTRY) * MAX_HANDLES)); + handle_table_addr_ = memory.allocate_memory(handle_table_size, memory_permission::read); + } + + emulator_object get_server_info() const + { + return {*memory_, server_info_addr_}; + } + + emulator_object get_handle_table() const + { + return {*memory_, handle_table_addr_}; + } + + emulator_object get_display_info() const + { + return {*memory_, display_info_addr_}; + } + + template + std::pair> allocate_object(handle_types::type type) + { + const auto index = find_free_index(); + + const auto alloc_size = static_cast(page_align_up(sizeof(T))); + const auto alloc_ptr = memory_->allocate_memory(alloc_size, memory_permission::read); + const emulator_object alloc_obj(*memory_, alloc_ptr); + + const emulator_object handle_table_obj(*memory_, handle_table_addr_); + handle_table_obj.access( + [&](USER_HANDLEENTRY& entry) { + entry.pHead = alloc_ptr; + entry.bType = get_native_type(type); + entry.wUniq = static_cast(type << 7); + }, + index); + + used_indices_[index] = true; + + return {make_handle(index, type, false), alloc_obj}; + } + + void free_index(uint32_t index) + { + if (index >= used_indices_.size() || !used_indices_[index]) + { + return; + } + + used_indices_[index] = false; + + const emulator_object handle_table_obj(*memory_, handle_table_addr_); + handle_table_obj.access( + [&](USER_HANDLEENTRY& entry) { + memory_->release_memory(entry.pHead, 0); + entry = {}; + }, + index); + } + + void serialize(utils::buffer_serializer& buffer) const + { + buffer.write(server_info_addr_); + buffer.write(handle_table_addr_); + buffer.write(display_info_addr_); + buffer.write(used_indices_); + } + + void deserialize(utils::buffer_deserializer& buffer) + { + buffer.read(server_info_addr_); + buffer.read(handle_table_addr_); + buffer.read(display_info_addr_); + buffer.read(used_indices_); + } + + private: + uint32_t find_free_index() const + { + for (uint32_t i = 1; i < used_indices_.size(); ++i) + { + if (!used_indices_[i]) + { + return i; + } + } + throw std::runtime_error("No more user handles available"); + } + + static uint8_t get_native_type(handle_types::type type) + { + switch (type) + { + case handle_types::type::window: + return TYPE_WINDOW; + case handle_types::type::monitor: + return TYPE_MONITOR; + default: + throw std::runtime_error("Unhandled handle type!"); + } + } + + uint64_t server_info_addr_{}; + uint64_t handle_table_addr_{}; + uint64_t display_info_addr_{}; + std::array used_indices_{}; + memory_manager* memory_{}; +}; + +template + requires(utils::Serializable && std::is_base_of_v) +class user_handle_store : public generic_handle_store +{ + public: + using index_type = uint32_t; + using value_map = std::map; + + explicit user_handle_store(user_handle_table& table) + : table_(&table) + { + } + + std::pair create(memory_interface& memory) + { + if (this->block_mutation_) + { + throw std::runtime_error("Mutation of user object store is blocked!"); + } + + auto [h, guest_obj] = table_->allocate_object(Type); + + T new_obj(memory); + new_obj.guest = std::move(guest_obj); + + const auto index = static_cast(h.value.id); + const auto it = this->store_.emplace(index, std::move(new_obj)).first; + return {h, it->second}; + } + + bool block_mutation(bool blocked) + { + std::swap(this->block_mutation_, blocked); + return blocked; + } + + handle make_handle(const index_type index) const + { + handle h{}; + h.bits = 0; + h.value.is_pseudo = false; + h.value.type = Type; + h.value.id = index; + + return h; + } + + T* get_by_index(const uint32_t index) + { + const auto it = this->store_.find(index); + if (it == this->store_.end()) + { + return nullptr; + } + return &it->second; + } + + T* get(const handle_value h) + { + if (h.type != Type || h.is_pseudo) + { + return nullptr; + } + + return this->get_by_index(static_cast(h.id)); + } + + T* get(const handle h) + { + return this->get(h.value); + } + + T* get(const uint64_t h) + { + handle hh{}; + hh.bits = h; + return this->get(hh); + } + + size_t size() const + { + return this->store_.size(); + } + + std::optional duplicate(const handle h) override + { + auto* entry = this->get(h); + if (!entry) + { + return std::nullopt; + } + + ++entry->ref_count; + return h; + } + + std::pair erase(const typename value_map::iterator& entry) + { + if (this->block_mutation_) + { + throw std::runtime_error("Mutation of handle store is blocked!"); + } + + if (entry == this->store_.end()) + { + return {entry, false}; + } + + if constexpr (handle_detail::has_deleter_function()) + { + if (!T::deleter(entry->second)) + { + return {entry, true}; + } + } + + auto new_iter = this->store_.erase(entry); + return {new_iter, true}; + } + + bool erase(const handle_value h) + { + if (this->block_mutation_) + { + throw std::runtime_error("Mutation of user object store is blocked!"); + } + + if (h.type != Type || h.is_pseudo) + { + return false; + } + + const auto index = static_cast(h.id); + const auto entry = this->store_.find(index); + + if (entry == this->store_.end()) + { + return false; + } + + if constexpr (handle_detail::has_deleter_function()) + { + if (!T::deleter(entry->second)) + { + return false; + } + } + + table_->free_index(index); + this->store_.erase(entry); + + return true; + } + + bool erase(const handle h) override + { + return this->erase(h.value); + } + + bool erase(const uint64_t h) + { + handle hh{}; + hh.bits = h; + return this->erase(hh); + } + + bool erase(const T& value) + { + const auto entry = this->find(value); + if (entry == this->store_.end()) + { + return false; + } + + return this->erase(make_handle(entry->first)); + } + + typename value_map::iterator find(const T& value) + { + auto i = this->store_.begin(); + for (; i != this->store_.end(); ++i) + { + if (&i->second == &value) + { + break; + } + } + return i; + } + + typename value_map::const_iterator find(const T& value) const + { + auto i = this->store_.begin(); + for (; i != this->store_.end(); ++i) + { + if (&i->second == &value) + { + break; + } + } + return i; + } + + handle find_handle(const T& value) const + { + const auto entry = this->find(value); + if (entry == this->end()) + { + return {}; + } + return this->make_handle(entry->first); + } + + handle find_handle(const T* value) const + { + if (!value) + { + return {}; + } + return this->find_handle(*value); + } + + typename value_map::iterator begin() + { + return this->store_.begin(); + } + typename value_map::const_iterator begin() const + { + return this->store_.begin(); + } + typename value_map::iterator end() + { + return this->store_.end(); + } + typename value_map::const_iterator end() const + { + return this->store_.end(); + } + + void serialize(utils::buffer_serializer& buffer) const + { + buffer.write(this->block_mutation_); + buffer.write_map(this->store_); + } + + void deserialize(utils::buffer_deserializer& buffer) + { + buffer.read(this->block_mutation_); + buffer.read_map(this->store_); + } + + private: + user_handle_table* table_; + bool block_mutation_{false}; + value_map store_{}; +}; diff --git a/src/windows-emulator/windows_objects.hpp b/src/windows-emulator/windows_objects.hpp index b4db799a..60432ac0 100644 --- a/src/windows-emulator/windows_objects.hpp +++ b/src/windows-emulator/windows_objects.hpp @@ -54,19 +54,47 @@ struct event : ref_counted_object } }; -struct window : ref_counted_object +template +struct user_object : ref_counted_object +{ + using guest_type = GuestType; + emulator_object guest; + + user_object(memory_interface& memory) + : guest(memory) + { + } + + void serialize_object(utils::buffer_serializer& buffer) const override + { + buffer.write(this->guest); + } + + void deserialize_object(utils::buffer_deserializer& buffer) override + { + buffer.read(this->guest); + } +}; + +struct window : user_object { uint32_t thread_id{}; std::u16string name{}; std::u16string class_name{}; - int32_t width; - int32_t height; - int32_t x; - int32_t y; + int32_t width{}; + int32_t height{}; + int32_t x{}; + int32_t y{}; std::unordered_map props; + window(memory_interface& memory) + : user_object(memory) + { + } + void serialize_object(utils::buffer_serializer& buffer) const override { + user_object::serialize_object(buffer); buffer.write(this->thread_id); buffer.write(this->name); buffer.write(this->class_name); @@ -79,6 +107,7 @@ struct window : ref_counted_object void deserialize_object(utils::buffer_deserializer& buffer) override { + user_object::deserialize_object(buffer); buffer.read(this->thread_id); buffer.read(this->name); buffer.read(this->class_name); @@ -186,7 +215,6 @@ struct file : ref_counted_object utils::file_handle handle{}; std::u16string name{}; std::optional enumeration_state{}; - std::optional deferred_rename; bool is_file() const {