From e58d5ab4bd2b5e2e1de868a47c481293f8e990ea Mon Sep 17 00:00:00 2001 From: momo5502 Date: Mon, 21 Oct 2024 20:45:09 +0200 Subject: [PATCH] Cleanup syscall dispatcher --- src/windows-emulator/syscall_dispatcher.cpp | 140 ++++++++ .../{syscalls.hpp => syscall_dispatcher.hpp} | 1 + src/windows-emulator/syscall_utils.hpp | 200 +++++++++++ src/windows-emulator/syscalls.cpp | 336 +----------------- src/windows-emulator/windows_emulator.hpp | 2 +- 5 files changed, 345 insertions(+), 334 deletions(-) create mode 100644 src/windows-emulator/syscall_dispatcher.cpp rename src/windows-emulator/{syscalls.hpp => syscall_dispatcher.hpp} (90%) create mode 100644 src/windows-emulator/syscall_utils.hpp diff --git a/src/windows-emulator/syscall_dispatcher.cpp b/src/windows-emulator/syscall_dispatcher.cpp new file mode 100644 index 0000000..926259b --- /dev/null +++ b/src/windows-emulator/syscall_dispatcher.cpp @@ -0,0 +1,140 @@ +#include "syscall_dispatcher.hpp" +#include "syscall_utils.hpp" + +static void serialize(utils::buffer_serializer& buffer, const syscall_handler_entry& obj) +{ + buffer.write(obj.name); +} + +static void deserialize(utils::buffer_deserializer& buffer, syscall_handler_entry& obj) +{ + buffer.read(obj.name); + obj.handler = nullptr; +} + +void syscall_dispatcher::serialize(utils::buffer_serializer& buffer) const +{ + buffer.write_map(this->handlers_); +} + +void syscall_dispatcher::deserialize(utils::buffer_deserializer& buffer) +{ + buffer.read_map(this->handlers_); + this->add_handlers(); +} + + +void syscall_dispatcher::setup(const exported_symbols& ntdll_exports, const exported_symbols& win32u_exports) +{ + this->handlers_ = {}; + + const auto ntdll_syscalls = find_syscalls(ntdll_exports); + const auto win32u_syscalls = find_syscalls(win32u_exports); + + map_syscalls(this->handlers_, ntdll_syscalls, 0); + map_syscalls(this->handlers_, win32u_syscalls, 0x1000); + + this->add_handlers(); +} + +void syscall_dispatcher::add_handlers() +{ + std::unordered_map handler_mapping{}; + handler_mapping.reserve(this->handlers_.size()); + + this->add_handlers(handler_mapping); + + for (auto& entry : this->handlers_) + { + const auto handler = handler_mapping.find(entry.second.name); + if (handler == handler_mapping.end()) + { + continue; + } + + entry.second.handler = handler->second; + +#ifndef NDEBUG + handler_mapping.erase(handler); +#endif + } + +#ifndef NDEBUG + if (!handler_mapping.empty()) + { + puts("Unmapped handlers:"); + for (const auto& h : handler_mapping) + { + printf(" %s\n", h.first.c_str()); + } + } +#endif +} + +void syscall_dispatcher::dispatch(windows_emulator& win_emu) +{ + auto& emu = win_emu.emu(); + auto& context = win_emu.process(); + + const auto address = emu.read_instruction_pointer(); + const auto syscall_id = emu.reg(x64_register::eax); + + + const syscall_context c{win_emu, emu, context, true}; + + try + { + const auto entry = this->handlers_.find(syscall_id); + if (entry == this->handlers_.end()) + { + printf("Unknown syscall: 0x%X\n", syscall_id); + c.emu.reg(x64_register::rax, STATUS_NOT_SUPPORTED); + c.emu.stop(); + return; + } + + if (!entry->second.handler) + { + printf("Unimplemented syscall: %s - 0x%X\n", entry->second.name.c_str(), syscall_id); + c.emu.reg(x64_register::rax, STATUS_NOT_SUPPORTED); + c.emu.stop(); + return; + } + + const auto* mod = context.module_manager.find_by_address(address); + if (mod != context.ntdll && mod != context.win32u) + { + win_emu.logger.print(color::blue, "Executing inline syscall: %s (0x%X) at 0x%llX (%s)\n", + entry->second.name.c_str(), + syscall_id, + address, mod ? mod->name.c_str() : ""); + } + else + { + win_emu.logger.print(color::dark_gray, "Executing syscall: %s (0x%X) at 0x%llX\n", + entry->second.name.c_str(), + syscall_id, + address); + } + + entry->second.handler(c); + } + catch (std::exception& e) + { + printf("Syscall threw an exception: %X (0x%llX) - %s\n", syscall_id, address, e.what()); + emu.reg(x64_register::rax, STATUS_UNSUCCESSFUL); + emu.stop(); + } + catch (...) + { + printf("Syscall threw an unknown exception: %X (0x%llX)\n", syscall_id, address); + emu.reg(x64_register::rax, STATUS_UNSUCCESSFUL); + emu.stop(); + } +} + +syscall_dispatcher::syscall_dispatcher(const exported_symbols& ntdll_exports, + const exported_symbols& win32u_exports) +{ + this->setup(ntdll_exports, win32u_exports); +} diff --git a/src/windows-emulator/syscalls.hpp b/src/windows-emulator/syscall_dispatcher.hpp similarity index 90% rename from src/windows-emulator/syscalls.hpp rename to src/windows-emulator/syscall_dispatcher.hpp index 198bfc9..0a83741 100644 --- a/src/windows-emulator/syscalls.hpp +++ b/src/windows-emulator/syscall_dispatcher.hpp @@ -34,5 +34,6 @@ class syscall_dispatcher private: std::unordered_map handlers_{}; + void add_handlers(std::unordered_map& handler_mapping); void add_handlers(); }; diff --git a/src/windows-emulator/syscall_utils.hpp b/src/windows-emulator/syscall_utils.hpp new file mode 100644 index 0000000..6749643 --- /dev/null +++ b/src/windows-emulator/syscall_utils.hpp @@ -0,0 +1,200 @@ +#pragma once + +#include "windows_emulator.hpp" + +struct syscall_context +{ + windows_emulator& win_emu; + x64_emulator& emu; + process_context& proc; + mutable bool write_status{true}; + mutable bool retrigger_syscall{false}; +}; + +inline uint64_t get_syscall_argument(x64_emulator& emu, const size_t index) +{ + switch (index) + { + case 0: + return emu.reg(x64_register::r10); + case 1: + return emu.reg(x64_register::rdx); + case 2: + return emu.reg(x64_register::r8); + case 3: + return emu.reg(x64_register::r9); + default: + return emu.read_stack(index + 1); + } +} + +inline bool is_uppercase(const char character) +{ + return toupper(character) == character; +} + +inline bool is_syscall(const std::string_view name) +{ + return name.starts_with("Nt") && name.size() > 3 && is_uppercase(name[2]); +} + +inline std::vector find_syscalls(const exported_symbols& exports) +{ + // Makes use of the fact that order of Nt* function addresses + // is equal to the order of syscall IDs. + // So first Nt* function is the first syscall with ID 0 + + std::map reference_count{}; + std::map ordered_syscalls{}; + + for (const auto& symbol : exports) + { + if (is_syscall(symbol.name)) + { + ++reference_count[symbol.address]; + ordered_syscalls[symbol.address] = symbol.name; + } + } + + std::vector syscalls{}; + syscalls.reserve(ordered_syscalls.size()); + + for (auto& syscall : ordered_syscalls) + { + if (reference_count[syscall.first] == 1) + { + syscalls.push_back(std::move(syscall.second)); + } + } + + return syscalls; +} + +inline void map_syscalls(std::unordered_map& handlers, + const std::vector& syscalls, const uint64_t base_index) +{ + for (size_t i = 0; i < syscalls.size(); ++i) + { + const auto& syscall = syscalls[i]; + + auto& entry = handlers[base_index + i]; + entry.name = syscall; + entry.handler = nullptr; + } +} + +template + requires(std::is_integral_v || std::is_enum_v) +T resolve_argument(x64_emulator& emu, const size_t index) +{ + const auto arg = get_syscall_argument(emu, index); + return static_cast(arg); +} + +template + requires(std::is_same_v>) +T resolve_argument(x64_emulator& emu, const size_t index) +{ + const auto arg = get_syscall_argument(emu, index); + return T(emu, arg); +} + +template +T resolve_indexed_argument(x64_emulator& emu, size_t& index) +{ + return resolve_argument(emu, index++); +} + +inline void write_status(const syscall_context& c, const NTSTATUS status, const uint64_t initial_ip) +{ + if (c.write_status && !c.retrigger_syscall) + { + c.emu.reg(x64_register::rax, static_cast(status)); + } + + const auto new_ip = c.emu.read_instruction_pointer(); + if (initial_ip != new_ip || c.retrigger_syscall) + { + c.emu.reg(x64_register::rip, new_ip - 2); + } +} + +inline void forward_syscall(const syscall_context& c, NTSTATUS (*handler)()) +{ + const auto ip = c.emu.read_instruction_pointer(); + + const auto ret = handler(); + write_status(c, ret, ip); +} + +template +void forward_syscall(const syscall_context& c, NTSTATUS (*handler)(const syscall_context&, Args...)) +{ + const auto ip = c.emu.read_instruction_pointer(); + + size_t index = 0; + std::tuple func_args + { + c, + resolve_indexed_argument>>(c.emu, index)... + }; + + const auto ret = std::apply(handler, std::move(func_args)); + write_status(c, ret, ip); +} + +template +syscall_handler make_syscall_handler() +{ + return +[](const syscall_context& c) + { + forward_syscall(c, Handler); + }; +} + +template +void write_attribute(emulator& emu, const PS_ATTRIBUTE& attribute, const T& value) +{ + if (attribute.ReturnLength) + { + emulator_object{emu, attribute.ReturnLength}.write(sizeof(T)); + } + + if (attribute.Size >= sizeof(T)) + { + emulator_object{emu, attribute.Value}.write(value); + } +} + +inline std::chrono::steady_clock::time_point convert_delay_interval_to_time_point(const LARGE_INTEGER delay_interval) +{ + constexpr auto HUNDRED_NANOSECONDS_IN_ONE_SECOND = 10000000LL; + constexpr auto EPOCH_DIFFERENCE_1601_TO_1970_SECONDS = 11644473600LL; + + if (delay_interval.QuadPart < 0) + { + const auto relative_time = -delay_interval.QuadPart; + const auto relative_ticks_in_ms = relative_time / 10; + const auto relative_fraction_ns = (relative_time % 10) * 100; + const auto relative_duration = std::chrono::microseconds(relative_ticks_in_ms) + + std::chrono::nanoseconds(relative_fraction_ns); + + return std::chrono::steady_clock::now() + relative_duration; + } + + const auto delay_seconds_since_1601 = delay_interval.QuadPart / HUNDRED_NANOSECONDS_IN_ONE_SECOND; + const auto delay_fraction_ns = (delay_interval.QuadPart % HUNDRED_NANOSECONDS_IN_ONE_SECOND) * 100; + + const auto delay_seconds_since_1970 = delay_seconds_since_1601 - EPOCH_DIFFERENCE_1601_TO_1970_SECONDS; + + const auto target_time = + std::chrono::system_clock::from_time_t(delay_seconds_since_1970) + + std::chrono::nanoseconds(delay_fraction_ns); + + const auto now_system = std::chrono::system_clock::now(); + + const auto duration_until_target = std::chrono::duration_cast< + std::chrono::microseconds>(target_time - now_system); + + return std::chrono::steady_clock::now() + duration_until_target; +} diff --git a/src/windows-emulator/syscalls.cpp b/src/windows-emulator/syscalls.cpp index 4b48b8b..c3670f1 100644 --- a/src/windows-emulator/syscalls.cpp +++ b/src/windows-emulator/syscalls.cpp @@ -1,214 +1,16 @@ #include "std_include.hpp" -#include "syscalls.hpp" +#include "syscall_dispatcher.hpp" #include #include "context_frame.hpp" #include "emulator_utils.hpp" -#include "windows_emulator.hpp" +#include "syscall_utils.hpp" #include - -struct syscall_context -{ - windows_emulator& win_emu; - x64_emulator& emu; - process_context& proc; - mutable bool write_status{true}; - mutable bool retrigger_syscall{false}; -}; - namespace { - uint64_t get_syscall_argument(x64_emulator& emu, const size_t index) - { - switch (index) - { - case 0: - return emu.reg(x64_register::r10); - case 1: - return emu.reg(x64_register::rdx); - case 2: - return emu.reg(x64_register::r8); - case 3: - return emu.reg(x64_register::r9); - default: - return emu.read_stack(index + 1); - } - } - - bool is_uppercase(const char character) - { - return toupper(character) == character; - } - - bool is_syscall(const std::string_view name) - { - return name.starts_with("Nt") && name.size() > 3 && is_uppercase(name[2]); - } - - std::vector find_syscalls(const exported_symbols& exports) - { - // Makes use of the fact that order of Nt* function addresses - // is equal to the order of syscall IDs. - // So first Nt* function is the first syscall with ID 0 - - std::map reference_count{}; - std::map ordered_syscalls{}; - - for (const auto& symbol : exports) - { - if (is_syscall(symbol.name)) - { - ++reference_count[symbol.address]; - ordered_syscalls[symbol.address] = symbol.name; - } - } - - std::vector syscalls{}; - syscalls.reserve(ordered_syscalls.size()); - - for (auto& syscall : ordered_syscalls) - { - if (reference_count[syscall.first] == 1) - { - syscalls.push_back(std::move(syscall.second)); - } - } - - return syscalls; - } - - void map_syscalls(std::unordered_map& handlers, - const std::vector& syscalls, const uint64_t base_index) - { - for (size_t i = 0; i < syscalls.size(); ++i) - { - const auto& syscall = syscalls[i]; - - auto& entry = handlers[base_index + i]; - entry.name = syscall; - entry.handler = nullptr; - } - } - - template - requires(std::is_integral_v || std::is_enum_v) - T resolve_argument(x64_emulator& emu, const size_t index) - { - const auto arg = get_syscall_argument(emu, index); - return static_cast(arg); - } - - template - requires(std::is_same_v>) - T resolve_argument(x64_emulator& emu, const size_t index) - { - const auto arg = get_syscall_argument(emu, index); - return T(emu, arg); - } - - template - T resolve_indexed_argument(x64_emulator& emu, size_t& index) - { - return resolve_argument(emu, index++); - } - - void write_status(const syscall_context& c, const NTSTATUS status, const uint64_t initial_ip) - { - if (c.write_status && !c.retrigger_syscall) - { - c.emu.reg(x64_register::rax, static_cast(status)); - } - - const auto new_ip = c.emu.read_instruction_pointer(); - if (initial_ip != new_ip || c.retrigger_syscall) - { - c.emu.reg(x64_register::rip, new_ip - 2); - } - } - - void forward_syscall(const syscall_context& c, NTSTATUS (*handler)()) - { - const auto ip = c.emu.read_instruction_pointer(); - - const auto ret = handler(); - write_status(c, ret, ip); - } - - template - void forward_syscall(const syscall_context& c, NTSTATUS (*handler)(const syscall_context&, Args...)) - { - const auto ip = c.emu.read_instruction_pointer(); - - size_t index = 0; - std::tuple func_args - { - c, - resolve_indexed_argument>>(c.emu, index)... - }; - - const auto ret = std::apply(handler, std::move(func_args)); - write_status(c, ret, ip); - } - - template - syscall_handler make_syscall_handler() - { - return +[](const syscall_context& c) - { - forward_syscall(c, Handler); - }; - } - - template - void write_attribute(emulator& emu, const PS_ATTRIBUTE& attribute, const T& value) - { - if (attribute.ReturnLength) - { - emulator_object{emu, attribute.ReturnLength}.write(sizeof(T)); - } - - if (attribute.Size >= sizeof(T)) - { - emulator_object{emu, attribute.Value}.write(value); - } - } - - std::chrono::steady_clock::time_point convert_delay_interval_to_time_point(const LARGE_INTEGER delay_interval) - { - constexpr auto HUNDRED_NANOSECONDS_IN_ONE_SECOND = 10000000LL; - constexpr auto EPOCH_DIFFERENCE_1601_TO_1970_SECONDS = 11644473600LL; - - if (delay_interval.QuadPart < 0) - { - const auto relative_time = -delay_interval.QuadPart; - const auto relative_ticks_in_ms = relative_time / 10; - const auto relative_fraction_ns = (relative_time % 10) * 100; - const auto relative_duration = std::chrono::microseconds(relative_ticks_in_ms) + - std::chrono::nanoseconds(relative_fraction_ns); - - return std::chrono::steady_clock::now() + relative_duration; - } - - const auto delay_seconds_since_1601 = delay_interval.QuadPart / HUNDRED_NANOSECONDS_IN_ONE_SECOND; - const auto delay_fraction_ns = (delay_interval.QuadPart % HUNDRED_NANOSECONDS_IN_ONE_SECOND) * 100; - - const auto delay_seconds_since_1970 = delay_seconds_since_1601 - EPOCH_DIFFERENCE_1601_TO_1970_SECONDS; - - const auto target_time = - std::chrono::system_clock::from_time_t(delay_seconds_since_1970) + - std::chrono::nanoseconds(delay_fraction_ns); - - const auto now_system = std::chrono::system_clock::now(); - - const auto duration_until_target = std::chrono::duration_cast< - std::chrono::microseconds>(target_time - now_system); - - return std::chrono::steady_clock::now() + duration_until_target; - } - NTSTATUS handle_NtQueryPerformanceCounter(const syscall_context&, const emulator_object performance_counter, const emulator_object performance_frequency) @@ -2146,30 +1948,8 @@ namespace } } -void syscall_dispatcher::setup(const exported_symbols& ntdll_exports, const exported_symbols& win32u_exports) -{ - this->handlers_ = {}; - - const auto ntdll_syscalls = find_syscalls(ntdll_exports); - const auto win32u_syscalls = find_syscalls(win32u_exports); - - map_syscalls(this->handlers_, ntdll_syscalls, 0); - map_syscalls(this->handlers_, win32u_syscalls, 0x1000); - - this->add_handlers(); -} - -syscall_dispatcher::syscall_dispatcher(const exported_symbols& ntdll_exports, - const exported_symbols& win32u_exports) +void syscall_dispatcher::add_handlers(std::unordered_map& handler_mapping) { - this->setup(ntdll_exports, win32u_exports); -} - -void syscall_dispatcher::add_handlers() -{ - std::unordered_map handler_mapping{}; - handler_mapping.reserve(this->handlers_.size()); - #define add_handler(syscall) \ do \ { \ @@ -2251,114 +2031,4 @@ void syscall_dispatcher::add_handlers() add_handler(NtAlertThreadByThreadIdEx); #undef add_handler - - for (auto& entry : this->handlers_) - { - const auto handler = handler_mapping.find(entry.second.name); - if (handler == handler_mapping.end()) - { - continue; - } - - entry.second.handler = handler->second; - -#ifndef NDEBUG - handler_mapping.erase(handler); -#endif - } - -#ifndef NDEBUG - if (!handler_mapping.empty()) - { - puts("Unmapped handlers:"); - for (const auto& h : handler_mapping) - { - printf(" %s\n", h.first.c_str()); - } - } -#endif -} - -static void serialize(utils::buffer_serializer& buffer, const syscall_handler_entry& obj) -{ - buffer.write(obj.name); -} - -static void deserialize(utils::buffer_deserializer& buffer, syscall_handler_entry& obj) -{ - buffer.read(obj.name); - obj.handler = nullptr; -} - -void syscall_dispatcher::serialize(utils::buffer_serializer& buffer) const -{ - buffer.write_map(this->handlers_); -} - -void syscall_dispatcher::deserialize(utils::buffer_deserializer& buffer) -{ - buffer.read_map(this->handlers_); - this->add_handlers(); -} - -void syscall_dispatcher::dispatch(windows_emulator& win_emu) -{ - auto& emu = win_emu.emu(); - auto& context = win_emu.process(); - - const auto address = emu.read_instruction_pointer(); - const auto syscall_id = emu.reg(x64_register::eax); - - - const syscall_context c{win_emu, emu, context, true}; - - try - { - const auto entry = this->handlers_.find(syscall_id); - if (entry == this->handlers_.end()) - { - printf("Unknown syscall: 0x%X\n", syscall_id); - c.emu.reg(x64_register::rax, STATUS_NOT_SUPPORTED); - c.emu.stop(); - return; - } - - if (!entry->second.handler) - { - printf("Unimplemented syscall: %s - 0x%X\n", entry->second.name.c_str(), syscall_id); - c.emu.reg(x64_register::rax, STATUS_NOT_SUPPORTED); - c.emu.stop(); - return; - } - - const auto* mod = context.module_manager.find_by_address(address); - if (mod != context.ntdll && mod != context.win32u) - { - win_emu.logger.print(color::blue, "Executing inline syscall: %s (0x%X) at 0x%llX (%s)\n", - entry->second.name.c_str(), - syscall_id, - address, mod ? mod->name.c_str() : ""); - } - else - { - win_emu.logger.print(color::dark_gray, "Executing syscall: %s (0x%X) at 0x%llX\n", - entry->second.name.c_str(), - syscall_id, - address); - } - - entry->second.handler(c); - } - catch (std::exception& e) - { - printf("Syscall threw an exception: %X (0x%llX) - %s\n", syscall_id, address, e.what()); - emu.reg(x64_register::rax, STATUS_UNSUCCESSFUL); - emu.stop(); - } - catch (...) - { - printf("Syscall threw an unknown exception: %X (0x%llX)\n", syscall_id, address); - emu.reg(x64_register::rax, STATUS_UNSUCCESSFUL); - emu.stop(); - } } diff --git a/src/windows-emulator/windows_emulator.hpp b/src/windows-emulator/windows_emulator.hpp index b221b2f..d46baed 100644 --- a/src/windows-emulator/windows_emulator.hpp +++ b/src/windows-emulator/windows_emulator.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include "syscalls.hpp" +#include "syscall_dispatcher.hpp" #include "process_context.hpp" #include "logger.hpp"