diff --git a/README.md b/README.md index 508c4f4b..e69ce6d5 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ A high-performance Windows process emulator that operates at syscall level, providing full control over process execution through comprehensive hooking capabilities. +Perfect for security research, malware analysis, and DRM research where fine-grained control over process execution is required. + Built in C++ and powered by the Unicorn Engine. ## Key Features @@ -28,8 +30,6 @@ Built in C++ and powered by the Unicorn Engine. * 💻 __Debugging Interface__ * Implements GDB serial protocol for integration with common debugging tools (IDA Pro, GDB, LLDB, VS Code, ...) -Perfect for security research, malware analysis, and DRM research where fine-grained control over process execution is required. - ## > [!NOTE] > The project is still in a very early, prototypy state. The code still needs a lot of cleanup and many features and syscalls need to be implemented. However, constant progress is being made :) @@ -83,6 +83,12 @@ Release build: cmake --workflow --preset=release ``` +## Dumping the Registry + +The emulator needs a registry dump to run, otherwise it will print `Bad hive file` errors. +You can create one by running the src/grab-registry.bat script as administrator. +This will create a `registry` folder that needs to be placed in the working directory of the emulator. + ## Running Tests The project uses CTest for testing. Choose your preferred method: diff --git a/deps/googletest b/deps/googletest index 35d0c365..7d76a231 160000 --- a/deps/googletest +++ b/deps/googletest @@ -1 +1 @@ -Subproject commit 35d0c365609296fa4730d62057c487e3cfa030ff +Subproject commit 7d76a231b0e29caf86e68d1df858308cd53b2a66 diff --git a/src/analyzer/main.cpp b/src/analyzer/main.cpp index 5c2b53c1..8396bf73 100644 --- a/src/analyzer/main.cpp +++ b/src/analyzer/main.cpp @@ -5,19 +5,24 @@ #include "object_watching.hpp" -bool use_gdb = false; - namespace { - void watch_system_objects(windows_emulator& win_emu) + struct analysis_options { - //watch_object(win_emu, *win_emu.current_thread().teb); - //watch_object(win_emu, win_emu.process().peb); - //watch_object(win_emu, win_emu.process().kusd); - auto* params_hook = watch_object(win_emu, win_emu.process().process_params); + bool use_gdb{false}; + bool concise_logging{false}; + }; + + void watch_system_objects(windows_emulator& win_emu, const bool cache_logging) + { + watch_object(win_emu, *win_emu.current_thread().teb, cache_logging); + watch_object(win_emu, win_emu.process().peb, cache_logging); + watch_object(win_emu, emulator_object{win_emu.emu(), kusd_mmio::address()}, cache_logging); + + auto* params_hook = watch_object(win_emu, win_emu.process().process_params, cache_logging); win_emu.emu().hook_memory_write(win_emu.process().peb.value() + offsetof(PEB64, ProcessParameters), 0x8, - [&](const uint64_t address, size_t, const uint64_t value) + [&, cache_logging](const uint64_t address, size_t, const uint64_t value) { const auto target_address = win_emu.process().peb.value() + offsetof( PEB64, ProcessParameters); @@ -29,18 +34,18 @@ namespace }; win_emu.emu().delete_hook(params_hook); - params_hook = watch_object(win_emu, obj); + params_hook = watch_object(win_emu, obj, cache_logging); } }); } - void run_emulation(windows_emulator& win_emu) + void run_emulation(windows_emulator& win_emu, const analysis_options& options) { try { - if (use_gdb) + if (options.use_gdb) { - const auto* address = "0.0.0.0:28960"; + const auto* address = "127.0.0.1:28960"; win_emu.logger.print(color::pink, "Waiting for GDB connection on %s...\n", address); win_x64_gdb_stub_handler handler{win_emu}; @@ -51,6 +56,12 @@ namespace win_emu.start(); } } + catch (const std::exception& e) + { + win_emu.logger.print(color::red, "Emulation failed at: 0x%llX - %s\n", + win_emu.emu().read_instruction_pointer(), e.what()); + throw; + } catch (...) { win_emu.logger.print(color::red, "Emulation failed at: 0x%llX\n", win_emu.emu().read_instruction_pointer()); @@ -68,80 +79,158 @@ namespace } } - std::vector parse_arguments(char* argv[], const size_t argc) + std::vector parse_arguments(const std::span args) { - std::vector args{}; - args.reserve(argc - 1); + std::vector wide_args{}; + wide_args.reserve(args.size() - 1); - for (size_t i = 1; i < argc; ++i) + for (size_t i = 1; i < args.size(); ++i) { - std::string_view arg(argv[i]); - args.emplace_back(arg.begin(), arg.end()); + const auto& arg = args[i]; + wide_args.emplace_back(arg.begin(), arg.end()); } - return args; + return wide_args; } - void run(char* argv[], const size_t argc) + void run(const analysis_options& options, const std::span args) { - if (argc < 1) + if (args.empty()) { return; } - const emulator_settings settings{ - .application = argv[0], - .arguments = parse_arguments(argv, argc), + emulator_settings settings{ + .application = args[0], + .arguments = parse_arguments(args), + .silent_until_main = options.concise_logging, }; - windows_emulator win_emu{settings}; + windows_emulator win_emu{std::move(settings)}; (void)&watch_system_objects; - //watch_system_objects(win_emu); + watch_system_objects(win_emu, options.concise_logging); win_emu.buffer_stdout = true; //win_emu.verbose_calls = true; const auto& exe = *win_emu.process().executable; - const auto text_start = exe.image_base + 0x1000; - const auto text_end = exe.image_base + 0x52000; - constexpr auto scan_size = 0x100; + const auto concise_logging = options.concise_logging; - win_emu.emu().hook_memory_read(text_start, scan_size, [&](const uint64_t address, size_t, uint64_t) + for (const auto& section : exe.sections) { - const auto rip = win_emu.emu().read_instruction_pointer(); - if (rip >= text_start && rip < text_end) + if ((section.region.permissions & memory_permission::exec) != memory_permission::exec) { - win_emu.logger.print(color::green, "Reading from executable .text: 0x%llX at 0x%llX\n", address, rip); + continue; } - }); - run_emulation(win_emu); + auto read_handler = [&, section, concise_logging](const uint64_t address, size_t, uint64_t) + { + const auto rip = win_emu.emu().read_instruction_pointer(); + if (win_emu.process().module_manager.find_by_address(rip) != win_emu.process().executable) + { + return; + } + + if (concise_logging) + { + static uint64_t count{0}; + ++count; + if (count > 100 && count % 10000 != 0) return; + } + + win_emu.logger.print( + color::green, + "Reading from executable section %s at 0x%llX via 0x%llX\n", + section.name.c_str(), address, rip); + }; + + const auto write_handler = [&, section, concise_logging](const uint64_t address, size_t, uint64_t) + { + const auto rip = win_emu.emu().read_instruction_pointer(); + if (win_emu.process().module_manager.find_by_address(rip) != win_emu.process().executable) + { + return; + } + + if (concise_logging) + { + static uint64_t count{0}; + ++count; + if (count > 100 && count % 10000 != 0) return; + } + + win_emu.logger.print( + color::blue, + "Writing to executable section %s at 0x%llX via 0x%llX\n", + section.name.c_str(), address, rip); + }; + + win_emu.emu().hook_memory_read(section.region.start, section.region.length, std::move(read_handler)); + win_emu.emu().hook_memory_write(section.region.start, section.region.length, std::move(write_handler)); + } + + run_emulation(win_emu, options); + } + + std::vector bundle_arguments(const int argc, char** argv) + { + std::vector args{}; + + for (int i = 1; i < argc; ++i) + { + args.push_back(argv[i]); + } + + return args; + } + + analysis_options parse_options(std::vector& args) + { + analysis_options options{}; + + while (!args.empty()) + { + auto arg_it = args.begin(); + const auto& arg = *arg_it; + + if (arg == "-d") + { + options.use_gdb = true; + } + else if (arg == "-c") + { + options.concise_logging = true; + } + else + { + break; + } + + args.erase(arg_it); + } + + return options; } } int main(const int argc, char** argv) { - if (argc <= 1) - { - puts("Application not specified!"); - return 1; - } - - //setvbuf(stdout, nullptr, _IOFBF, 0x10000); - if (argc > 2 && argv[1] == "-d"sv) - { - use_gdb = true; - } - try { + auto args = bundle_arguments(argc, argv); + const auto options = parse_options(args); + + if (args.empty()) + { + throw std::runtime_error("Application not specified!"); + } + do { - const auto offset = use_gdb ? 2 : 1; - run(argv + offset, static_cast(argc - offset)); + run(options, args); } - while (use_gdb); + while (options.use_gdb); return 0; } diff --git a/src/analyzer/object_watching.hpp b/src/analyzer/object_watching.hpp index a9425668..4ebb62b8 100644 --- a/src/analyzer/object_watching.hpp +++ b/src/analyzer/object_watching.hpp @@ -3,19 +3,38 @@ #include "reflect_type_info.hpp" template -emulator_hook* watch_object(windows_emulator& emu, emulator_object object) +emulator_hook* watch_object(windows_emulator& emu, emulator_object object, const bool cache_logging = false) { const reflect_type_info info{}; return emu.emu().hook_memory_read(object.value(), object.size(), - [i = std::move(info), object, &emu](const uint64_t address, size_t, uint64_t) + [i = std::move(info), object, &emu, cache_logging]( + const uint64_t address, size_t, uint64_t) { const auto rip = emu.emu().read_instruction_pointer(); + const auto* mod = emu.process().module_manager.find_by_address(rip); + const auto is_main_access = mod == emu.process().executable; + + if (!emu.verbose_calls && !is_main_access) + { + return; + } + + if (cache_logging) + { + static std::unordered_set logged_addresses{}; + if (is_main_access && !logged_addresses.insert(address).second) + { + return; + } + } const auto offset = address - object.value(); - emu.logger.log("Object access: %s - 0x%llX (%s) at 0x%llX (%s)\n", i.get_type_name().c_str(), - offset, - i.get_member_name(offset).c_str(), rip, - emu.process().module_manager.find_name(rip)); + emu.logger.print(is_main_access ? color::green : color::dark_gray, + "Object access: %s - 0x%llX (%s) at 0x%llX (%s)\n", + i.get_type_name().c_str(), + offset, + i.get_member_name(offset).c_str(), rip, + mod ? mod->name.c_str() : ""); }); } diff --git a/src/common/platform/file_management.hpp b/src/common/platform/file_management.hpp index edd18d5f..0d9ead13 100644 --- a/src/common/platform/file_management.hpp +++ b/src/common/platform/file_management.hpp @@ -63,6 +63,10 @@ #define PS_ATTRIBUTE_INPUT 0x00020000 // input only #define PS_ATTRIBUTE_ADDITIVE 0x00040000 // "accumulated" e.g. bitmasks, counters, etc. +#define SL_RESTART_SCAN 0x01 +#define SL_RETURN_SINGLE_ENTRY 0x02 +#define SL_NO_CURSOR_UPDATE 0x10 + typedef enum _FSINFOCLASS { FileFsVolumeInformation = 1, // q: FILE_FS_VOLUME_INFORMATION @@ -267,6 +271,70 @@ typedef struct _FILE_STANDARD_INFORMATION BOOLEAN Directory; } FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION; +typedef struct _FILE_NAME_INFORMATION +{ + ULONG FileNameLength; + char16_t FileName[1]; +} FILE_NAME_INFORMATION, * PFILE_NAME_INFORMATION; + +typedef struct _FILE_BASIC_INFORMATION +{ + LARGE_INTEGER CreationTime; // Specifies the time that the file was created. + LARGE_INTEGER LastAccessTime; // Specifies the time that the file was last accessed. + LARGE_INTEGER LastWriteTime; // Specifies the time that the file was last written to. + LARGE_INTEGER ChangeTime; // Specifies the last time the file was changed. + ULONG FileAttributes; // Specifies one or more FILE_ATTRIBUTE_XXX flags. +} FILE_BASIC_INFORMATION, * PFILE_BASIC_INFORMATION; + +typedef struct _FILE_DIRECTORY_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + char16_t FileName[1]; +} FILE_DIRECTORY_INFORMATION, * PFILE_DIRECTORY_INFORMATION; + +typedef struct _FILE_FULL_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + char16_t FileName[1]; +} FILE_FULL_DIR_INFORMATION, * PFILE_FULL_DIR_INFORMATION; + +typedef struct _FILE_BOTH_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + char16_t FileName[1]; +} FILE_BOTH_DIR_INFORMATION, * PFILE_BOTH_DIR_INFORMATION; + #ifndef OS_WINDOWS typedef BOOLEAN SECURITY_CONTEXT_TRACKING_MODE, * PSECURITY_CONTEXT_TRACKING_MODE; diff --git a/src/common/platform/kernel_mapped.hpp b/src/common/platform/kernel_mapped.hpp index ac3c040f..e01d7ca1 100644 --- a/src/common/platform/kernel_mapped.hpp +++ b/src/common/platform/kernel_mapped.hpp @@ -824,4 +824,51 @@ typedef struct _PROCESS_BASIC_INFORMATION64 EMULATOR_CAST(std::uint32_t, KPRIORITY) BasePriority; EMULATOR_CAST(std::uint64_t, HANDLE) UniqueProcessId; EMULATOR_CAST(std::uint64_t, HANDLE) InheritedFromUniqueProcessId; -} PROCESS_BASIC_INFORMATION64, *PPROCESS_BASIC_INFORMATION64; \ No newline at end of file +} PROCESS_BASIC_INFORMATION64, *PPROCESS_BASIC_INFORMATION64; + +typedef struct _KERNEL_USER_TIMES +{ + LARGE_INTEGER CreateTime; + LARGE_INTEGER ExitTime; + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; +} KERNEL_USER_TIMES, * PKERNEL_USER_TIMES; + +struct THREAD_TLS_INFO +{ + ULONG Flags; + + union + { + EmulatorTraits::PVOID* TlsVector; + PVOID TlsModulePointer; + }; + + EMULATOR_CAST(std::uint64_t, ULONG_PTR) ThreadId; +}; + +static_assert(sizeof(THREAD_TLS_INFO) == 0x18); + +typedef enum _PROCESS_TLS_INFORMATION_TYPE +{ + ProcessTlsReplaceIndex, + ProcessTlsReplaceVector, + MaxProcessTlsOperation +} PROCESS_TLS_INFORMATION_TYPE, * PPROCESS_TLS_INFORMATION_TYPE; + +struct PROCESS_TLS_INFO +{ + ULONG Unknown; + PROCESS_TLS_INFORMATION_TYPE TlsRequest; + ULONG ThreadDataCount; + + union + { + ULONG TlsIndex; + ULONG TlsVectorLength; + }; + + THREAD_TLS_INFO ThreadData[1]; +}; + +static_assert(sizeof(PROCESS_TLS_INFO) - sizeof(THREAD_TLS_INFO) == 0x10); diff --git a/src/common/platform/process.hpp b/src/common/platform/process.hpp index c8b70e41..f5784778 100644 --- a/src/common/platform/process.hpp +++ b/src/common/platform/process.hpp @@ -679,6 +679,17 @@ struct TOKEN_USER64 { SID_AND_ATTRIBUTES64 User; }; +typedef struct _TOKEN_SECURITY_ATTRIBUTES_INFORMATION +{ + USHORT Version; + USHORT Reserved; + ULONG AttributeCount; + union + { + EmulatorTraits::PVOID pAttributeV1; + } Attribute; +} TOKEN_SECURITY_ATTRIBUTES_INFORMATION, * PTOKEN_SECURITY_ATTRIBUTES_INFORMATION; + struct GDI_HANDLE_ENTRY64 { union diff --git a/src/common/platform/status.hpp b/src/common/platform/status.hpp index f60a6369..af8b8d8c 100644 --- a/src/common/platform/status.hpp +++ b/src/common/platform/status.hpp @@ -16,10 +16,15 @@ using NTSTATUS = std::uint32_t; #endif #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define STATUS_WAIT_1 ((NTSTATUS)0x00000001L) #define STATUS_UNSUCCESSFUL ((NTSTATUS)0x00000001L) #define STATUS_ALERTED ((NTSTATUS)0x00000101L) +#define STATUS_OBJECT_NAME_EXISTS ((NTSTATUS)0x40000000L) + +#define STATUS_NO_MORE_FILES ((NTSTATUS)0x80000006L) + #define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L) #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) #define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034L) diff --git a/src/common/platform/synchronisation.hpp b/src/common/platform/synchronisation.hpp index f65f4de1..c210de6f 100644 --- a/src/common/platform/synchronisation.hpp +++ b/src/common/platform/synchronisation.hpp @@ -5,4 +5,13 @@ typedef enum _EVENT_TYPE { NotificationEvent, SynchronizationEvent -} EVENT_TYPE; \ No newline at end of file +} EVENT_TYPE; + +typedef enum _WAIT_TYPE +{ + WaitAll, + WaitAny, + WaitNotification, + WaitDequeue, + WaitDpc, +} WAIT_TYPE; diff --git a/src/common/platform/unicode.hpp b/src/common/platform/unicode.hpp index 3171b9fa..c0f45dc1 100644 --- a/src/common/platform/unicode.hpp +++ b/src/common/platform/unicode.hpp @@ -9,7 +9,7 @@ struct UNICODE_STRING { EMULATOR_CAST(typename Traits::PVOID, char16_t*) Buffer; }; -inline std::string u16_to_u8(std::u16string_view u16_view) { +inline std::string u16_to_u8(const std::u16string_view u16_view) { std::string utf8_str; utf8_str.reserve(u16_view.size() * 2); for (char16_t ch : u16_view) { @@ -27,7 +27,7 @@ inline std::string u16_to_u8(std::u16string_view u16_view) { return utf8_str; } -inline std::string w_to_u8(std::wstring_view w_view) { +inline std::string w_to_u8(const std::wstring_view w_view) { std::string utf8_str; utf8_str.reserve(w_view.size() * 2); for (char16_t ch : w_view) { @@ -56,7 +56,7 @@ inline std::string w_to_u8(std::wstring_view w_view) { return std::wstring(reinterpret_cast(u16str.data()), u16str.size()); } - inline auto open_unicode(FILE** handle, std::u16string fileName, std::u16string mode) + inline auto open_unicode(FILE** handle, const std::u16string& fileName, const std::u16string& mode) { return _wfopen_s(handle, u16_to_w(fileName).c_str(), u16_to_w(mode).c_str()); } diff --git a/src/common/utils/string.hpp b/src/common/utils/string.hpp new file mode 100644 index 00000000..dac98317 --- /dev/null +++ b/src/common/utils/string.hpp @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include +#include + +namespace utils::string +{ + inline char char_to_lower(const char val) + { + return static_cast(std::tolower(static_cast(val))); + } + + inline char16_t char_to_lower(const char16_t val) + { + if (val >= u'A' && val <= u'Z') + { + return val + 32; + } + + return val; + } + + inline wchar_t char_to_lower(const wchar_t val) + { + return std::towlower(val); + } + + template + void to_lower_inplace(std::basic_string& str) + { + std::ranges::transform(str, str.begin(), [](const Elem e) + { + return char_to_lower(e); + }); + } + + template + std::basic_string to_lower(std::basic_string str) + { + to_lower_inplace(str); + return str; + } + + template + std::basic_string to_lower_consume(std::basic_string& str) + { + return to_lower(std::move(str)); + } +} diff --git a/src/emulator/memory_manager.cpp b/src/emulator/memory_manager.cpp index 89719379..1f076c33 100644 --- a/src/emulator/memory_manager.cpp +++ b/src/emulator/memory_manager.cpp @@ -1,547 +1,547 @@ -#include "memory_manager.hpp" - -#include "memory_region.hpp" -#include "address_utils.hpp" - -#include -#include -#include -#include - -namespace -{ - constexpr auto MIN_ALLOCATION_ADDRESS = 0x0000000000010000ULL; - constexpr auto MAX_ALLOCATION_ADDRESS = 0x00007ffffffeffffULL; - - void split_regions(memory_manager::committed_region_map& regions, const std::vector& split_points) - { - for (auto i = regions.begin(); i != regions.end(); ++i) - { - for (const auto split_point : split_points) - { - if (is_within_start_and_length(split_point, i->first, i->second.length) && i->first != split_point) - { - const auto first_length = split_point - i->first; - const auto second_length = i->second.length - first_length; - - i->second.length = first_length; - - regions[split_point] = memory_manager::committed_region{second_length, i->second.pemissions}; - } - } - } - } - - void merge_regions(memory_manager::committed_region_map& regions) - { - for (auto i = regions.begin(); i != regions.end();) - { - assert(i->second.length > 0); - - auto next = i; - std::advance(next, 1); - - if (next == regions.end()) - { - break; - } - - assert(next->second.length > 0); - - const auto end = i->first + i->second.length; - assert(end <= next->first); - - if (end != next->first || i->second.pemissions != next->second.pemissions) - { - ++i; - continue; - } - - i->second.length += next->second.length; - regions.erase(next); - } - } -} - -static void serialize(utils::buffer_serializer& buffer, const memory_manager::committed_region& region) -{ - buffer.write(region.length); - buffer.write(region.pemissions); -} - -static void deserialize(utils::buffer_deserializer& buffer, memory_manager::committed_region& region) -{ - region.length = static_cast(buffer.read()); - region.pemissions = buffer.read(); -} - -static void serialize(utils::buffer_serializer& buffer, const memory_manager::reserved_region& region) -{ - buffer.write(region.is_mmio); - buffer.write(region.length); - buffer.write_map(region.committed_regions); -} - -static void deserialize(utils::buffer_deserializer& buffer, memory_manager::reserved_region& region) -{ - buffer.read(region.is_mmio); - region.length = static_cast(buffer.read()); - buffer.read_map(region.committed_regions); -} - -void memory_manager::serialize_memory_state(utils::buffer_serializer& buffer, const bool is_snapshot) const -{ - buffer.write_map(this->reserved_regions_); - - if (is_snapshot) - { - return; - } - - std::vector data{}; - - for (const auto& reserved_region : this->reserved_regions_) - { - if (reserved_region.second.is_mmio) - { - continue; - } - - for (const auto& region : reserved_region.second.committed_regions) - { - data.resize(region.second.length); - - this->read_memory(region.first, data.data(), region.second.length); - - buffer.write(data.data(), region.second.length); - } - } -} - -void memory_manager::deserialize_memory_state(utils::buffer_deserializer& buffer, const bool is_snapshot) -{ - if (!is_snapshot) - { - for (const auto& reserved_region : this->reserved_regions_) - { - for (const auto& region : reserved_region.second.committed_regions) - { - this->unmap_memory(region.first, region.second.length); - } - } - } - - buffer.read_map(this->reserved_regions_); - - if (is_snapshot) - { - return; - } - - std::vector data{}; - - for (auto i = this->reserved_regions_.begin(); i != this->reserved_regions_.end();) - { - auto& reserved_region = i->second; - if (reserved_region.is_mmio) - { - i = this->reserved_regions_.erase(i); - continue; - } - - ++i; - - for (const auto& region : reserved_region.committed_regions) - { - data.resize(region.second.length); - - buffer.read(data.data(), region.second.length); - - this->map_memory(region.first, region.second.length, region.second.pemissions); - this->write_memory(region.first, data.data(), region.second.length); - } - } -} - -bool memory_manager::protect_memory(const uint64_t address, const size_t size, const memory_permission permissions, - memory_permission* old_permissions) -{ - const auto entry = this->find_reserved_region(address); - if (entry == this->reserved_regions_.end()) - { - return false; - } - - const auto end = address + size; - const auto region_end = entry->first + entry->second.length; - - if (region_end < end) - { - throw std::runtime_error("Cross region protect not supported yet!"); - } - - std::optional old_first_permissions{}; - - auto& committed_regions = entry->second.committed_regions; - split_regions(committed_regions, {address, end}); - - for (auto& sub_region : committed_regions) - { - if (sub_region.first >= end) - { - break; - } - - const auto sub_region_end = sub_region.first + sub_region.second.length; - if (sub_region.first >= address && sub_region_end <= end) - { - if (!old_first_permissions.has_value()) - { - old_first_permissions = sub_region.second.pemissions; - } - - this->apply_memory_protection(sub_region.first, sub_region.second.length, permissions); - sub_region.second.pemissions = permissions; - } - } - - if (old_permissions) - { - *old_permissions = old_first_permissions.value_or(memory_permission::none); - } - - merge_regions(committed_regions); - return true; -} - -bool memory_manager::allocate_mmio(const uint64_t address, const size_t size, mmio_read_callback read_cb, - mmio_write_callback write_cb) -{ - if (this->overlaps_reserved_region(address, size)) - { - return false; - } - - this->map_mmio(address, size, std::move(read_cb), std::move(write_cb)); - - const auto entry = this->reserved_regions_.try_emplace(address, - reserved_region{ - .length = size, - .is_mmio = true, - }).first; - - entry->second.committed_regions[address] = committed_region{size, memory_permission::read_write}; - - return true; -} - -bool memory_manager::allocate_memory(const uint64_t address, const size_t size, const memory_permission permissions, - const bool reserve_only) -{ - if (this->overlaps_reserved_region(address, size)) - { - return false; - } - - const auto entry = this->reserved_regions_.try_emplace(address, size).first; - - if (!reserve_only) - { - this->map_memory(address, size, permissions); - entry->second.committed_regions[address] = committed_region{size, memory_permission::read_write}; - } - - return true; -} - -bool memory_manager::commit_memory(const uint64_t address, const size_t size, const memory_permission permissions) -{ - const auto entry = this->find_reserved_region(address); - if (entry == this->reserved_regions_.end()) - { - return false; - } - - const auto end = address + size; - const auto region_end = entry->first + entry->second.length; - - if (region_end < end) - { - throw std::runtime_error("Cross region commit not supported yet!"); - } - - auto& committed_regions = entry->second.committed_regions; - split_regions(committed_regions, {address, end}); - - uint64_t last_region_start{}; - const committed_region* last_region{nullptr}; - - for (auto& sub_region : committed_regions) - { - if (sub_region.first >= end) - { - break; - } - - const auto sub_region_end = sub_region.first + sub_region.second.length; - if (sub_region.first >= address && sub_region_end <= end) - { - const auto map_start = last_region ? (last_region_start + last_region->length) : address; - const auto map_length = sub_region.first - map_start; - - if (map_length > 0) - { - this->map_memory(map_start, map_length, permissions); - committed_regions[map_start] = committed_region{map_length, permissions}; - } - - last_region_start = sub_region.first; - last_region = &sub_region.second; - } - } - - if (!last_region || (last_region_start + last_region->length) < end) - { - const auto map_start = last_region ? (last_region_start + last_region->length) : address; - const auto map_length = end - map_start; - - this->map_memory(map_start, map_length, permissions); - committed_regions[map_start] = committed_region{map_length, permissions}; - } - - merge_regions(committed_regions); - return true; -} - -bool memory_manager::decommit_memory(const uint64_t address, const size_t size) -{ - const auto entry = this->find_reserved_region(address); - if (entry == this->reserved_regions_.end()) - { - return false; - } - - if (entry->second.is_mmio) - { - throw std::runtime_error("Not allowed to decommit MMIO!"); - } - - const auto end = address + size; - const auto region_end = entry->first + entry->second.length; - - if (region_end < end) - { - throw std::runtime_error("Cross region decommit not supported yet!"); - } - - auto& committed_regions = entry->second.committed_regions; - - split_regions(committed_regions, {address, end}); - - for (auto i = committed_regions.begin(); i != committed_regions.end();) - { - if (i->first >= end) - { - break; - } - - const auto sub_region_end = i->first + i->second.length; - if (i->first >= address && sub_region_end <= end) - { - this->unmap_memory(i->first, i->second.length); - i = committed_regions.erase(i); - continue; - } - - ++i; - } - - return true; -} - -bool memory_manager::release_memory(const uint64_t address, size_t size) -{ - const auto entry = this->reserved_regions_.find(address); - if (entry == this->reserved_regions_.end()) - { - return false; - } - - if (!size) - { - size = entry->second.length; - } - - if (size > entry->second.length) - { - throw std::runtime_error("Cross region release not supported yet!"); - } - - const auto end = address + size; - auto& committed_regions = entry->second.committed_regions; - - split_regions(committed_regions, {end}); - - for (auto i = committed_regions.begin(); i != committed_regions.end();) - { - if (i->first >= end) - { - break; - } - - const auto sub_region_end = i->first + i->second.length; - if (i->first >= address && sub_region_end <= end) - { - this->unmap_memory(i->first, i->second.length); - i = committed_regions.erase(i); - } - else - { - ++i; - } - } - - entry->second.length -= size; - if (entry->second.length > 0) - { - this->reserved_regions_[address + size] = std::move(entry->second); - } - - this->reserved_regions_.erase(entry); - return true; -} - -uint64_t memory_manager::find_free_allocation_base(const size_t size, const uint64_t start) const -{ - uint64_t start_address = - std::max(MIN_ALLOCATION_ADDRESS, start ? start : 0x100000000ULL); - - for (const auto& region : this->reserved_regions_) - { - const auto region_end = region.first + region.second.length; - if (region_end < start_address) - { - continue; - } - - if (!regions_with_length_intersect(start_address, size, region.first, region.second.length)) - { - return start_address; - } - - start_address = page_align_up(region_end); - } - - if (start_address + size <= MAX_ALLOCATION_ADDRESS) - { - return start_address; - } - - return 0; -} - -region_info memory_manager::get_region_info(const uint64_t address) -{ - region_info result{}; - result.start = MIN_ALLOCATION_ADDRESS; - result.length = MAX_ALLOCATION_ADDRESS - result.start; - result.pemissions = memory_permission::none; - result.allocation_base = {}; - result.allocation_length = result.length; - result.is_committed = false; - result.is_reserved = false; - - if (this->reserved_regions_.empty()) - { - return result; - } - - auto upper_bound = this->reserved_regions_.upper_bound(address); - if (upper_bound == this->reserved_regions_.begin()) - { - result.length = upper_bound->first - result.start; - return result; - } - - const auto entry = --upper_bound; - const auto lower_end = entry->first + entry->second.length; - if (lower_end <= address) - { - result.start = lower_end; - result.length = MAX_ALLOCATION_ADDRESS - result.start; - return result; - } - - // We have a reserved region - const auto& reserved_region = entry->second; - const auto& committed_regions = reserved_region.committed_regions; - - result.is_reserved = true; - result.allocation_base = entry->first; - result.allocation_length = reserved_region.length; - result.start = result.allocation_base; - result.length = result.allocation_length; - - if (committed_regions.empty()) - { - return result; - } - - auto committed_bound = committed_regions.upper_bound(address); - if (committed_bound == committed_regions.begin()) - { - result.length = committed_bound->first - result.start; - return result; - } - - const auto committed_entry = --committed_bound; - const auto committed_lower_end = committed_entry->first + committed_entry->second.length; - if (committed_lower_end <= address) - { - result.start = committed_lower_end; - result.length = lower_end - result.start; - return result; - } - - result.is_committed = true; - result.start = committed_entry->first; - result.length = committed_entry->second.length; - result.pemissions = committed_entry->second.pemissions; - - return result; -} - -memory_manager::reserved_region_map::iterator memory_manager::find_reserved_region(const uint64_t address) -{ - if (this->reserved_regions_.empty()) - { - return this->reserved_regions_.end(); - } - - auto upper_bound = this->reserved_regions_.upper_bound(address); - if (upper_bound == this->reserved_regions_.begin()) - { - return this->reserved_regions_.end(); - } - - const auto entry = --upper_bound; - if (entry->first + entry->second.length <= address) - { - return this->reserved_regions_.end(); - } - - return entry; -} - -bool memory_manager::overlaps_reserved_region(const uint64_t address, const size_t size) const -{ - for (const auto& region : this->reserved_regions_) - { - if (regions_with_length_intersect(address, size, region.first, region.second.length)) - { - return true; - } - } - - return false; -} +#include "memory_manager.hpp" + +#include "memory_region.hpp" +#include "address_utils.hpp" + +#include +#include +#include +#include + +namespace +{ + constexpr auto MIN_ALLOCATION_ADDRESS = 0x0000000000010000ULL; + constexpr auto MAX_ALLOCATION_ADDRESS = 0x00007ffffffeffffULL; + + void split_regions(memory_manager::committed_region_map& regions, const std::vector& split_points) + { + for (auto i = regions.begin(); i != regions.end(); ++i) + { + for (const auto split_point : split_points) + { + if (is_within_start_and_length(split_point, i->first, i->second.length) && i->first != split_point) + { + const auto first_length = split_point - i->first; + const auto second_length = i->second.length - first_length; + + i->second.length = first_length; + + regions[split_point] = memory_manager::committed_region{second_length, i->second.pemissions}; + } + } + } + } + + void merge_regions(memory_manager::committed_region_map& regions) + { + for (auto i = regions.begin(); i != regions.end();) + { + assert(i->second.length > 0); + + auto next = i; + std::advance(next, 1); + + if (next == regions.end()) + { + break; + } + + assert(next->second.length > 0); + + const auto end = i->first + i->second.length; + assert(end <= next->first); + + if (end != next->first || i->second.pemissions != next->second.pemissions) + { + ++i; + continue; + } + + i->second.length += next->second.length; + regions.erase(next); + } + } +} + +static void serialize(utils::buffer_serializer& buffer, const memory_manager::committed_region& region) +{ + buffer.write(region.length); + buffer.write(region.pemissions); +} + +static void deserialize(utils::buffer_deserializer& buffer, memory_manager::committed_region& region) +{ + region.length = static_cast(buffer.read()); + region.pemissions = buffer.read(); +} + +static void serialize(utils::buffer_serializer& buffer, const memory_manager::reserved_region& region) +{ + buffer.write(region.is_mmio); + buffer.write(region.length); + buffer.write_map(region.committed_regions); +} + +static void deserialize(utils::buffer_deserializer& buffer, memory_manager::reserved_region& region) +{ + buffer.read(region.is_mmio); + region.length = static_cast(buffer.read()); + buffer.read_map(region.committed_regions); +} + +void memory_manager::serialize_memory_state(utils::buffer_serializer& buffer, const bool is_snapshot) const +{ + buffer.write_map(this->reserved_regions_); + + if (is_snapshot) + { + return; + } + + std::vector data{}; + + for (const auto& reserved_region : this->reserved_regions_) + { + if (reserved_region.second.is_mmio) + { + continue; + } + + for (const auto& region : reserved_region.second.committed_regions) + { + data.resize(region.second.length); + + this->read_memory(region.first, data.data(), region.second.length); + + buffer.write(data.data(), region.second.length); + } + } +} + +void memory_manager::deserialize_memory_state(utils::buffer_deserializer& buffer, const bool is_snapshot) +{ + if (!is_snapshot) + { + for (const auto& reserved_region : this->reserved_regions_) + { + for (const auto& region : reserved_region.second.committed_regions) + { + this->unmap_memory(region.first, region.second.length); + } + } + } + + buffer.read_map(this->reserved_regions_); + + if (is_snapshot) + { + return; + } + + std::vector data{}; + + for (auto i = this->reserved_regions_.begin(); i != this->reserved_regions_.end();) + { + auto& reserved_region = i->second; + if (reserved_region.is_mmio) + { + i = this->reserved_regions_.erase(i); + continue; + } + + ++i; + + for (const auto& region : reserved_region.committed_regions) + { + data.resize(region.second.length); + + buffer.read(data.data(), region.second.length); + + this->map_memory(region.first, region.second.length, region.second.pemissions); + this->write_memory(region.first, data.data(), region.second.length); + } + } +} + +bool memory_manager::protect_memory(const uint64_t address, const size_t size, const memory_permission permissions, + memory_permission* old_permissions) +{ + const auto entry = this->find_reserved_region(address); + if (entry == this->reserved_regions_.end()) + { + return false; + } + + const auto end = address + size; + const auto region_end = entry->first + entry->second.length; + + if (region_end < end) + { + throw std::runtime_error("Cross region protect not supported yet!"); + } + + std::optional old_first_permissions{}; + + auto& committed_regions = entry->second.committed_regions; + split_regions(committed_regions, {address, end}); + + for (auto& sub_region : committed_regions) + { + if (sub_region.first >= end) + { + break; + } + + const auto sub_region_end = sub_region.first + sub_region.second.length; + if (sub_region.first >= address && sub_region_end <= end) + { + if (!old_first_permissions.has_value()) + { + old_first_permissions = sub_region.second.pemissions; + } + + this->apply_memory_protection(sub_region.first, sub_region.second.length, permissions); + sub_region.second.pemissions = permissions; + } + } + + if (old_permissions) + { + *old_permissions = old_first_permissions.value_or(memory_permission::none); + } + + merge_regions(committed_regions); + return true; +} + +bool memory_manager::allocate_mmio(const uint64_t address, const size_t size, mmio_read_callback read_cb, + mmio_write_callback write_cb) +{ + if (this->overlaps_reserved_region(address, size)) + { + return false; + } + + this->map_mmio(address, size, std::move(read_cb), std::move(write_cb)); + + const auto entry = this->reserved_regions_.try_emplace(address, + reserved_region{ + .length = size, + .is_mmio = true, + }).first; + + entry->second.committed_regions[address] = committed_region{size, memory_permission::read_write}; + + return true; +} + +bool memory_manager::allocate_memory(const uint64_t address, const size_t size, const memory_permission permissions, + const bool reserve_only) +{ + if (this->overlaps_reserved_region(address, size)) + { + return false; + } + + const auto entry = this->reserved_regions_.try_emplace(address, size).first; + + if (!reserve_only) + { + this->map_memory(address, size, permissions); + entry->second.committed_regions[address] = committed_region{size, memory_permission::read_write}; + } + + return true; +} + +bool memory_manager::commit_memory(const uint64_t address, const size_t size, const memory_permission permissions) +{ + const auto entry = this->find_reserved_region(address); + if (entry == this->reserved_regions_.end()) + { + return false; + } + + const auto end = address + size; + const auto region_end = entry->first + entry->second.length; + + if (region_end < end) + { + throw std::runtime_error("Cross region commit not supported yet!"); + } + + auto& committed_regions = entry->second.committed_regions; + split_regions(committed_regions, {address, end}); + + uint64_t last_region_start{}; + const committed_region* last_region{nullptr}; + + for (auto& sub_region : committed_regions) + { + if (sub_region.first >= end) + { + break; + } + + const auto sub_region_end = sub_region.first + sub_region.second.length; + if (sub_region.first >= address && sub_region_end <= end) + { + const auto map_start = last_region ? (last_region_start + last_region->length) : address; + const auto map_length = sub_region.first - map_start; + + if (map_length > 0) + { + this->map_memory(map_start, map_length, permissions); + committed_regions[map_start] = committed_region{map_length, permissions}; + } + + last_region_start = sub_region.first; + last_region = &sub_region.second; + } + } + + if (!last_region || (last_region_start + last_region->length) < end) + { + const auto map_start = last_region ? (last_region_start + last_region->length) : address; + const auto map_length = end - map_start; + + this->map_memory(map_start, map_length, permissions); + committed_regions[map_start] = committed_region{map_length, permissions}; + } + + merge_regions(committed_regions); + return true; +} + +bool memory_manager::decommit_memory(const uint64_t address, const size_t size) +{ + const auto entry = this->find_reserved_region(address); + if (entry == this->reserved_regions_.end()) + { + return false; + } + + if (entry->second.is_mmio) + { + throw std::runtime_error("Not allowed to decommit MMIO!"); + } + + const auto end = address + size; + const auto region_end = entry->first + entry->second.length; + + if (region_end < end) + { + throw std::runtime_error("Cross region decommit not supported yet!"); + } + + auto& committed_regions = entry->second.committed_regions; + + split_regions(committed_regions, {address, end}); + + for (auto i = committed_regions.begin(); i != committed_regions.end();) + { + if (i->first >= end) + { + break; + } + + const auto sub_region_end = i->first + i->second.length; + if (i->first >= address && sub_region_end <= end) + { + this->unmap_memory(i->first, i->second.length); + i = committed_regions.erase(i); + continue; + } + + ++i; + } + + return true; +} + +bool memory_manager::release_memory(const uint64_t address, size_t size) +{ + const auto entry = this->reserved_regions_.find(address); + if (entry == this->reserved_regions_.end()) + { + return false; + } + + if (!size) + { + size = entry->second.length; + } + + if (size > entry->second.length) + { + throw std::runtime_error("Cross region release not supported yet!"); + } + + const auto end = address + size; + auto& committed_regions = entry->second.committed_regions; + + split_regions(committed_regions, {end}); + + for (auto i = committed_regions.begin(); i != committed_regions.end();) + { + if (i->first >= end) + { + break; + } + + const auto sub_region_end = i->first + i->second.length; + if (i->first >= address && sub_region_end <= end) + { + this->unmap_memory(i->first, i->second.length); + i = committed_regions.erase(i); + } + else + { + ++i; + } + } + + entry->second.length -= size; + if (entry->second.length > 0) + { + this->reserved_regions_[address + size] = std::move(entry->second); + } + + this->reserved_regions_.erase(entry); + return true; +} + +uint64_t memory_manager::find_free_allocation_base(const size_t size, const uint64_t start) const +{ + uint64_t start_address = + std::max(MIN_ALLOCATION_ADDRESS, start ? start : 0x100000000ULL); + + for (const auto& region : this->reserved_regions_) + { + const auto region_end = region.first + region.second.length; + if (region_end < start_address) + { + continue; + } + + if (!regions_with_length_intersect(start_address, size, region.first, region.second.length)) + { + return start_address; + } + + start_address = page_align_up(region_end); + } + + if (start_address + size <= MAX_ALLOCATION_ADDRESS) + { + return start_address; + } + + return 0; +} + +region_info memory_manager::get_region_info(const uint64_t address) +{ + region_info result{}; + result.start = MIN_ALLOCATION_ADDRESS; + result.length = MAX_ALLOCATION_ADDRESS - result.start; + result.permissions = memory_permission::none; + result.allocation_base = {}; + result.allocation_length = result.length; + result.is_committed = false; + result.is_reserved = false; + + if (this->reserved_regions_.empty()) + { + return result; + } + + auto upper_bound = this->reserved_regions_.upper_bound(address); + if (upper_bound == this->reserved_regions_.begin()) + { + result.length = upper_bound->first - result.start; + return result; + } + + const auto entry = --upper_bound; + const auto lower_end = entry->first + entry->second.length; + if (lower_end <= address) + { + result.start = lower_end; + result.length = MAX_ALLOCATION_ADDRESS - result.start; + return result; + } + + // We have a reserved region + const auto& reserved_region = entry->second; + const auto& committed_regions = reserved_region.committed_regions; + + result.is_reserved = true; + result.allocation_base = entry->first; + result.allocation_length = reserved_region.length; + result.start = result.allocation_base; + result.length = result.allocation_length; + + if (committed_regions.empty()) + { + return result; + } + + auto committed_bound = committed_regions.upper_bound(address); + if (committed_bound == committed_regions.begin()) + { + result.length = committed_bound->first - result.start; + return result; + } + + const auto committed_entry = --committed_bound; + const auto committed_lower_end = committed_entry->first + committed_entry->second.length; + if (committed_lower_end <= address) + { + result.start = committed_lower_end; + result.length = lower_end - result.start; + return result; + } + + result.is_committed = true; + result.start = committed_entry->first; + result.length = committed_entry->second.length; + result.permissions = committed_entry->second.pemissions; + + return result; +} + +memory_manager::reserved_region_map::iterator memory_manager::find_reserved_region(const uint64_t address) +{ + if (this->reserved_regions_.empty()) + { + return this->reserved_regions_.end(); + } + + auto upper_bound = this->reserved_regions_.upper_bound(address); + if (upper_bound == this->reserved_regions_.begin()) + { + return this->reserved_regions_.end(); + } + + const auto entry = --upper_bound; + if (entry->first + entry->second.length <= address) + { + return this->reserved_regions_.end(); + } + + return entry; +} + +bool memory_manager::overlaps_reserved_region(const uint64_t address, const size_t size) const +{ + for (const auto& region : this->reserved_regions_) + { + if (regions_with_length_intersect(address, size, region.first, region.second.length)) + { + return true; + } + } + + return false; +} diff --git a/src/emulator/memory_manager.hpp b/src/emulator/memory_manager.hpp index d78d43aa..0d7bbb98 100644 --- a/src/emulator/memory_manager.hpp +++ b/src/emulator/memory_manager.hpp @@ -71,6 +71,12 @@ public: this->write_memory(address, &value, sizeof(value)); } + template + void write_memory(void* address, const T& value) + { + this->write_memory(reinterpret_cast(address), &value, sizeof(value)); + } + void write_memory(void* address, const void* data, const size_t size) { this->write_memory(reinterpret_cast(address), data, size); diff --git a/src/emulator/memory_region.hpp b/src/emulator/memory_region.hpp index d3c533f3..880f3666 100644 --- a/src/emulator/memory_region.hpp +++ b/src/emulator/memory_region.hpp @@ -6,7 +6,7 @@ struct basic_memory_region { uint64_t start{}; size_t length{}; - memory_permission pemissions{}; + memory_permission permissions{}; }; struct memory_region : basic_memory_region diff --git a/src/fuzzer/main.cpp b/src/fuzzer/main.cpp index f075001f..4cc4bc5d 100644 --- a/src/fuzzer/main.cpp +++ b/src/fuzzer/main.cpp @@ -144,11 +144,11 @@ namespace void run(const std::string_view application) { - const emulator_settings settings{ + emulator_settings settings{ .application = application, }; - windows_emulator win_emu{settings}; + windows_emulator win_emu{std::move(settings)}; forward_emulator(win_emu); run_fuzzer(win_emu); diff --git a/src/test-sample/test.cpp b/src/test-sample/test.cpp index e8a8460f..6842432c 100644 --- a/src/test-sample/test.cpp +++ b/src/test-sample/test.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -16,6 +17,18 @@ using namespace std::literals; // to trick compiler optimizations __declspec(dllexport) bool do_the_task = true; +struct tls_struct +{ + DWORD num = 1337; + + tls_struct() + { + num = GetCurrentThreadId(); + } +}; + +thread_local tls_struct tls_var{}; + // getenv is broken right now :( std::string read_env(const char* env) { @@ -58,6 +71,50 @@ bool test_threads() return counter == (thread_count * 3ULL); } +bool test_tls() +{ + std::atomic_bool kill{false}; + std::atomic_uint32_t successes{0}; + constexpr uint32_t thread_count = 2; + + std::vector ts{}; + kill = false; + + for (size_t i = 0; i < thread_count; ++i) + { + ts.emplace_back([&] + { + while (!kill) + { + std::this_thread::yield(); + } + + if (tls_var.num == GetCurrentThreadId()) + { + ++successes; + } + }); + } + + LoadLibraryA("d3dcompiler_47.dll"); + LoadLibraryA("dsound.dll"); + /*LoadLibraryA("d3d9.dll"); + LoadLibraryA("dxgi.dll"); + LoadLibraryA("wlanapi.dll");*/ + + kill = true; + + for (auto& t : ts) + { + if (t.joinable()) + { + t.join(); + } + } + + return successes == thread_count; +} + bool test_env() { const auto computername = read_env("COMPUTERNAME"); @@ -97,6 +154,22 @@ bool test_io() return text == buffer; } +bool test_dir_io() +{ + size_t count = 0; + + for (auto i : std::filesystem::directory_iterator(R"(C:\Windows\System32\)")) + { + ++count; + if (count > 30) + { + return true; + } + } + + return count > 30; +} + std::optional read_registry_string(const HKEY root, const char* path, const char* value) { HKEY key{}; @@ -161,7 +234,7 @@ bool test_exceptions() } } -void throw_native_exception() +void throw_access_violation() { if (do_the_task) { @@ -169,19 +242,55 @@ void throw_native_exception() } } -bool test_native_exceptions() +bool test_access_violation_exception() { __try { - throw_native_exception(); + throw_access_violation(); return false; } __except (EXCEPTION_EXECUTE_HANDLER) { - return true; + return GetExceptionCode() == STATUS_ACCESS_VIOLATION; } } +bool test_ud2_exception(void* address) +{ + __try + { + static_cast(address)(); + return false; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode() == STATUS_ILLEGAL_INSTRUCTION; + } +} + +bool test_illegal_instruction_exception() +{ + const auto address = VirtualAlloc(nullptr, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); + if (!address) + { + return false; + } + + memcpy(address, "\x0F\x0B", 2); // ud2 + + const auto res = test_ud2_exception(address); + + VirtualFree(address, 0x1000, MEM_RELEASE); + + return res; +} + +bool test_native_exceptions() +{ + return test_access_violation_exception() + && test_illegal_instruction_exception(); +} + void print_time() { const auto epoch_time = std::chrono::system_clock::now().time_since_epoch(); @@ -198,7 +307,7 @@ void print_time() int main(int argc, const char* argv[]) { - if(argc == 2 && argv[1] == "-time"sv) + if (argc == 2 && argv[1] == "-time"sv) { print_time(); return 0; @@ -207,11 +316,13 @@ int main(int argc, const char* argv[]) bool valid = true; RUN_TEST(test_io, "I/O") + RUN_TEST(test_dir_io, "Dir I/O") RUN_TEST(test_registry, "Registry") RUN_TEST(test_threads, "Threads") RUN_TEST(test_env, "Environment") RUN_TEST(test_exceptions, "Exceptions") RUN_TEST(test_native_exceptions, "Native Exceptions") + RUN_TEST(test_tls, "TLS") return valid ? 0 : 1; } diff --git a/src/unicorn-emulator/unicorn_x64_emulator.cpp b/src/unicorn-emulator/unicorn_x64_emulator.cpp index dd9caf77..4152e7de 100644 --- a/src/unicorn-emulator/unicorn_x64_emulator.cpp +++ b/src/unicorn-emulator/unicorn_x64_emulator.cpp @@ -243,6 +243,7 @@ namespace unicorn unicorn_x64_emulator() { uce(uc_open(UC_ARCH_X86, UC_MODE_64, &this->uc_)); + uce(uc_ctl_set_tcg_buffer_size(this->uc_, 2 << 30 /* 2 gb */)); } ~unicorn_x64_emulator() override diff --git a/src/windows-emulator-test/emulation_test_utils.hpp b/src/windows-emulator-test/emulation_test_utils.hpp index c45db025..cc934220 100644 --- a/src/windows-emulator-test/emulation_test_utils.hpp +++ b/src/windows-emulator-test/emulation_test_utils.hpp @@ -20,10 +20,10 @@ namespace test { - inline windows_emulator create_sample_emulator(emulator_settings& settings) + inline windows_emulator create_sample_emulator(emulator_settings settings) { settings.application = "./test-sample.exe"; - return windows_emulator{settings}; + return windows_emulator{std::move(settings)}; } inline windows_emulator create_sample_emulator() @@ -34,6 +34,6 @@ namespace test .use_relative_time = true, }; - return create_sample_emulator(settings); + return create_sample_emulator(std::move(settings)); } } diff --git a/src/windows-emulator/emulator_utils.hpp b/src/windows-emulator/emulator_utils.hpp index 8e561925..2f7d30c1 100644 --- a/src/windows-emulator/emulator_utils.hpp +++ b/src/windows-emulator/emulator_utils.hpp @@ -5,7 +5,7 @@ // TODO: Replace with pointer handling structure for future 32 bit support using emulator_pointer = uint64_t; -template +template class object_wrapper { T* obj_; @@ -101,6 +101,14 @@ public: this->emu_->write_memory(this->address_ + index * this->size(), &value, sizeof(value)); } + void write_if_valid(const T& value, const size_t index = 0) const + { + if (this->operator bool()) + { + this->write(value, index); + } + } + template void access(const F& accessor, const size_t index = 0) const { @@ -122,6 +130,11 @@ public: buffer.read(this->address_); } + void set_address(const uint64_t address) + { + this->address_ = address; + } + private: emulator* emu_{}; uint64_t address_{}; diff --git a/src/windows-emulator/handles.hpp b/src/windows-emulator/handles.hpp index c47b0621..4be1a622 100644 --- a/src/windows-emulator/handles.hpp +++ b/src/windows-emulator/handles.hpp @@ -15,6 +15,8 @@ struct handle_types port, thread, registry, + mutant, + token, }; }; @@ -24,7 +26,8 @@ struct handle_value { uint64_t id : 32; uint64_t type : 16; - uint64_t padding : 15; + uint64_t padding : 14; + uint64_t is_system : 1; uint64_t is_pseudo : 1; }; #pragma pack(pop) @@ -73,11 +76,19 @@ constexpr handle make_handle(const uint32_t id, const handle_types::type type, c value.padding = 0; value.id = id; value.type = type; + value.is_system = false; value.is_pseudo = is_pseudo; return {value}; } +constexpr handle make_handle(const uint64_t value) +{ + handle h{}; + h.bits = value; + return h; +} + constexpr handle make_pseudo_handle(const uint32_t id, const handle_types::type type) { return make_handle(id, type, true); @@ -97,9 +108,15 @@ namespace handle_detail }; } +struct generic_handle_store +{ + virtual ~generic_handle_store() = default; + virtual bool erase(const handle h) = 0; +}; + template requires(utils::Serializable) -class handle_store +class handle_store : public generic_handle_store { public: using index_type = uint32_t; @@ -199,7 +216,7 @@ public: return this->erase(entry); } - bool erase(const handle h) + bool erase(const handle h) override { return this->erase(h.value); } @@ -328,10 +345,21 @@ private: value_map store_{}; }; -constexpr auto KNOWN_DLLS_DIRECTORY = make_pseudo_handle(0x1337, handle_types::directory); -constexpr auto KNOWN_DLLS_SYMLINK = make_pseudo_handle(0x1337, handle_types::symlink); -constexpr auto SHARED_SECTION = make_pseudo_handle(0x1337, handle_types::section); +constexpr auto KNOWN_DLLS_DIRECTORY = make_pseudo_handle(0x1, handle_types::directory); +constexpr auto BASE_NAMED_OBJECTS_DIRECTORY = make_pseudo_handle(0x2, handle_types::directory); + +constexpr auto KNOWN_DLLS_SYMLINK = make_pseudo_handle(0x1, handle_types::symlink); +constexpr auto SHARED_SECTION = make_pseudo_handle(0x1, handle_types::section); constexpr auto CONSOLE_HANDLE = make_pseudo_handle(0x1, handle_types::file); constexpr auto STDOUT_HANDLE = make_pseudo_handle(0x2, handle_types::file); constexpr auto STDIN_HANDLE = make_pseudo_handle(0x3, handle_types::file); + +constexpr auto DUMMY_IMPERSONATION_TOKEN = make_pseudo_handle(0x1, handle_types::token); + +constexpr auto CURRENT_PROCESS = make_handle(~0ULL); +constexpr auto CURRENT_THREAD = make_handle(~1ULL); + +constexpr auto CURRENT_PROCESS_TOKEN = make_handle(~3ULL); +constexpr auto CURRENT_THREAD_TOKEN = make_handle(~4ULL); +constexpr auto CURRENT_THREAD_EFFECTIVE_TOKEN = make_handle(~5ULL); diff --git a/src/windows-emulator/io_device.cpp b/src/windows-emulator/io_device.cpp index c619db60..00374b39 100644 --- a/src/windows-emulator/io_device.cpp +++ b/src/windows-emulator/io_device.cpp @@ -16,6 +16,7 @@ std::unique_ptr create_device(const std::u16string_view device) { if (device == u"CNG" || device == u"KsecDD" + || device == u"PcwDrv" || device == u"DeviceApi\\CMApi" || device == u"ConDrv\\Server") { diff --git a/src/windows-emulator/logger.hpp b/src/windows-emulator/logger.hpp index be4ffe91..139d0a1d 100644 --- a/src/windows-emulator/logger.hpp +++ b/src/windows-emulator/logger.hpp @@ -54,6 +54,11 @@ public: this->disable_output_ = value; } + bool is_output_disabled() const + { + return this->disable_output_; + } + private: bool disable_output_{false}; }; diff --git a/src/windows-emulator/module/mapped_module.hpp b/src/windows-emulator/module/mapped_module.hpp index 5b49a4c7..cf2b91dd 100644 --- a/src/windows-emulator/module/mapped_module.hpp +++ b/src/windows-emulator/module/mapped_module.hpp @@ -1,4 +1,5 @@ #pragma once +#include struct exported_symbol { @@ -11,6 +12,12 @@ struct exported_symbol using exported_symbols = std::vector; using address_name_mapping = std::map; +struct mapped_section +{ + std::string name{}; + basic_memory_region region{}; +}; + struct mapped_module { std::string name{}; @@ -23,6 +30,8 @@ struct mapped_module exported_symbols exports{}; address_name_mapping address_names{}; + std::vector sections{}; + bool is_within(const uint64_t address) const { return address >= this->image_base && address < (this->image_base + this->size_of_image); diff --git a/src/windows-emulator/module/module_manager.cpp b/src/windows-emulator/module/module_manager.cpp index ca5f4412..477dcc49 100644 --- a/src/windows-emulator/module/module_manager.cpp +++ b/src/windows-emulator/module/module_manager.cpp @@ -3,6 +3,22 @@ #include "module_mapping.hpp" #include "windows-emulator/logger.hpp" +namespace +{ + std::filesystem::path canonicalize_module_path(const std::filesystem::path& file) + { + constexpr std::u16string_view nt_prefix = u"\\??\\"; + const auto wide_file = file.u16string(); + + if (!wide_file.starts_with(nt_prefix)) + { + return canonical(absolute(file)); + } + + return canonicalize_module_path(wide_file.substr(nt_prefix.size())); + } +} + static void serialize(utils::buffer_serializer& buffer, const exported_symbol& sym) { buffer.write(sym.name); @@ -22,7 +38,7 @@ static void deserialize(utils::buffer_deserializer& buffer, exported_symbol& sym static void serialize(utils::buffer_serializer& buffer, const mapped_module& mod) { buffer.write_string(mod.name); - buffer.write_string(mod.path.wstring()); + buffer.write(mod.path.u16string()); buffer.write(mod.image_base); buffer.write(mod.size_of_image); @@ -35,7 +51,7 @@ static void serialize(utils::buffer_serializer& buffer, const mapped_module& mod static void deserialize(utils::buffer_deserializer& buffer, mapped_module& mod) { mod.name = buffer.read_string(); - mod.path = buffer.read_string(); + mod.path = buffer.read_string(); buffer.read(mod.image_base); buffer.read(mod.size_of_image); @@ -52,7 +68,7 @@ module_manager::module_manager(emulator& emu) mapped_module* module_manager::map_module(const std::filesystem::path& file, logger& logger) { - const auto canonical_file = canonical(absolute(file)); + auto canonical_file = canonicalize_module_path(file); for (auto& mod : this->modules_) { @@ -62,18 +78,26 @@ mapped_module* module_manager::map_module(const std::filesystem::path& file, log } } - auto mod = map_module_from_file(*this->emu_, std::move(canonical_file)); - if (!mod) + try { - logger.error("Failed to map %s\n", file.generic_string().c_str()); + auto mod = map_module_from_file(*this->emu_, std::move(canonical_file)); + + logger.log("Mapped %s at 0x%llX\n", mod.path.generic_string().c_str(), mod.image_base); + + const auto image_base = mod.image_base; + const auto entry = this->modules_.try_emplace(image_base, std::move(mod)); + return &entry.first->second; + } + catch (const std::exception& e) + { + logger.error("Failed to map %s: %s\n", file.generic_string().c_str(), e.what()); + return nullptr; + } + catch (...) + { + logger.error("Failed to map %s: Unknown error\n", file.generic_string().c_str()); return nullptr; } - - logger.log("Mapped %s at 0x%llX\n", mod->path.generic_string().c_str(), mod->image_base); - - const auto image_base = mod->image_base; - const auto entry = this->modules_.try_emplace(image_base, std::move(*mod)); - return &entry.first->second; } void module_manager::serialize(utils::buffer_serializer& buffer) const @@ -85,3 +109,17 @@ void module_manager::deserialize(utils::buffer_deserializer& buffer) { buffer.read_map(this->modules_); } + +bool module_manager::unmap(const uint64_t address) +{ + const auto mod = this->modules_.find(address); + if (mod == this->modules_.end()) + { + return false; + } + + unmap_module(*this->emu_, mod->second); + this->modules_.erase(mod); + + return true; +} diff --git a/src/windows-emulator/module/module_manager.hpp b/src/windows-emulator/module/module_manager.hpp index 361890e0..3f5fb255 100644 --- a/src/windows-emulator/module/module_manager.hpp +++ b/src/windows-emulator/module/module_manager.hpp @@ -36,6 +36,8 @@ public: void serialize(utils::buffer_serializer& buffer) const; void deserialize(utils::buffer_deserializer& buffer); + bool unmap(const uint64_t address); + private: emulator* emu_{}; diff --git a/src/windows-emulator/module/module_mapping.cpp b/src/windows-emulator/module/module_mapping.cpp index b68658b3..1a1ebe54 100644 --- a/src/windows-emulator/module/module_mapping.cpp +++ b/src/windows-emulator/module/module_mapping.cpp @@ -19,7 +19,7 @@ namespace return nt_headers_offset + (first_section_absolute - absolute_base); } - std::vector read_mapped_memory(emulator& emu, const mapped_module& binary) + std::vector read_mapped_memory(const emulator& emu, const mapped_module& binary) { std::vector memory{}; memory.resize(binary.size_of_image); @@ -40,20 +40,24 @@ namespace const auto export_directory = buffer.as(export_directory_entry. VirtualAddress).get(); - //const auto function_count = export_directory->NumberOfFunctions; const auto names_count = export_directory.NumberOfNames; + //const auto function_count = export_directory.NumberOfFunctions; const auto names = buffer.as(export_directory.AddressOfNames); const auto ordinals = buffer.as(export_directory.AddressOfNameOrdinals); const auto functions = buffer.as(export_directory.AddressOfFunctions); + binary.exports.reserve(names_count); + for (DWORD i = 0; i < names_count; i++) { + const auto ordinal = ordinals.get(i); + exported_symbol symbol{}; - symbol.ordinal = ordinals.get(i); - symbol.name = buffer.as_string(names.get(i)); - symbol.rva = functions.get(symbol.ordinal); + symbol.ordinal = export_directory.Base + ordinal; + symbol.rva = functions.get(ordinal); symbol.address = binary.image_base + symbol.rva; + symbol.name = buffer.as_string(names.get(i)); binary.exports.push_back(std::move(symbol)); } @@ -137,7 +141,7 @@ namespace } } - void map_sections(emulator& emu, const mapped_module& binary, + void map_sections(emulator& emu, mapped_module& binary, const utils::safe_buffer_accessor buffer, const PENTHeaders_t& nt_headers, const uint64_t nt_headers_offset) { @@ -176,81 +180,85 @@ namespace const auto size_of_section = page_align_up(std::max(section.SizeOfRawData, section.Misc.VirtualSize)); emu.protect_memory(target_ptr, size_of_section, permissions, nullptr); - } - } - std::optional map_module(emulator& emu, const std::span data, - std::filesystem::path file) - { - mapped_module binary{}; - binary.path = std::move(file); - binary.name = binary.path.filename().string(); + mapped_section section_info{}; + section_info.region.start = target_ptr; + section_info.region.length = size_of_section; + section_info.region.permissions = permissions; - utils::safe_buffer_accessor buffer{data}; - - const auto dos_header = buffer.as(0).get(); - const auto nt_headers_offset = dos_header.e_lfanew; - - const auto nt_headers = buffer.as>(nt_headers_offset).get(); - auto& optional_header = nt_headers.OptionalHeader; - - binary.image_base = optional_header.ImageBase; - binary.size_of_image = optional_header.SizeOfImage; // TODO: Sanitize - - if (!emu.allocate_memory(binary.image_base, binary.size_of_image, memory_permission::read)) - { - binary.image_base = emu.find_free_allocation_base(binary.size_of_image); - const auto is_dll = nt_headers.FileHeader.Characteristics & IMAGE_FILE_DLL; - const auto has_dynamic_base = - optional_header.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE; - const auto is_relocatable = is_dll || has_dynamic_base; - - if (!is_relocatable || !emu.allocate_memory(binary.image_base, binary.size_of_image, - memory_permission::read)) + for (size_t j = 0; j < sizeof(section.Name) && section.Name[j]; ++j) { - return {}; + section_info.name.push_back(static_cast(section.Name[j])); } + + binary.sections.push_back(std::move(section_info)); } - - binary.entry_point = binary.image_base + optional_header.AddressOfEntryPoint; - - const auto* header_buffer = buffer.get_pointer_for_range(0, optional_header.SizeOfHeaders); - emu.write_memory(binary.image_base, header_buffer, - optional_header.SizeOfHeaders); - - map_sections(emu, binary, buffer, nt_headers, nt_headers_offset); - - auto mapped_memory = read_mapped_memory(emu, binary); - utils::safe_buffer_accessor mapped_buffer{mapped_memory}; - - apply_relocations(binary, mapped_buffer, optional_header); - collect_exports(binary, mapped_buffer, optional_header); - - emu.write_memory(binary.image_base, mapped_memory.data(), mapped_memory.size()); - - return binary; } } -std::optional map_module_from_data(emulator& emu, const std::span data, - std::filesystem::path file) +mapped_module map_module_from_data(emulator& emu, const std::span data, + std::filesystem::path file) { - try + mapped_module binary{}; + binary.path = std::move(file); + binary.name = binary.path.filename().string(); + + utils::safe_buffer_accessor buffer{data}; + + const auto dos_header = buffer.as(0).get(); + const auto nt_headers_offset = dos_header.e_lfanew; + + const auto nt_headers = buffer.as>(nt_headers_offset).get(); + auto& optional_header = nt_headers.OptionalHeader; + + if (nt_headers.FileHeader.Machine != PEMachineType::AMD64) { - return map_module(emu, data, std::move(file)); + throw std::runtime_error("Unsupported architecture!"); } - catch (...) + + binary.image_base = optional_header.ImageBase; + binary.size_of_image = page_align_up(optional_header.SizeOfImage); // TODO: Sanitize + + if (!emu.allocate_memory(binary.image_base, binary.size_of_image, memory_permission::read)) { - return {}; + binary.image_base = emu.find_free_allocation_base(binary.size_of_image); + const auto is_dll = nt_headers.FileHeader.Characteristics & IMAGE_FILE_DLL; + const auto has_dynamic_base = + optional_header.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE; + const auto is_relocatable = is_dll || has_dynamic_base; + + if (!is_relocatable || !emu.allocate_memory(binary.image_base, binary.size_of_image, + memory_permission::read)) + { + throw std::runtime_error("Memory range not allocatable"); + } } + + binary.entry_point = binary.image_base + optional_header.AddressOfEntryPoint; + + const auto* header_buffer = buffer.get_pointer_for_range(0, optional_header.SizeOfHeaders); + emu.write_memory(binary.image_base, header_buffer, + optional_header.SizeOfHeaders); + + map_sections(emu, binary, buffer, nt_headers, nt_headers_offset); + + auto mapped_memory = read_mapped_memory(emu, binary); + utils::safe_buffer_accessor mapped_buffer{mapped_memory}; + + apply_relocations(binary, mapped_buffer, optional_header); + collect_exports(binary, mapped_buffer, optional_header); + + emu.write_memory(binary.image_base, mapped_memory.data(), mapped_memory.size()); + + return binary; } -std::optional map_module_from_file(emulator& emu, std::filesystem::path file) +mapped_module map_module_from_file(emulator& emu, std::filesystem::path file) { const auto data = utils::io::read_file(file); if (data.empty()) { - return {}; + throw std::runtime_error("Bad file data"); } return map_module_from_data(emu, data, std::move(file)); diff --git a/src/windows-emulator/module/module_mapping.hpp b/src/windows-emulator/module/module_mapping.hpp index a88f285d..8d69db80 100644 --- a/src/windows-emulator/module/module_mapping.hpp +++ b/src/windows-emulator/module/module_mapping.hpp @@ -3,8 +3,8 @@ #include #include "mapped_module.hpp" -std::optional map_module_from_data(emulator& emu, std::span data, +mapped_module map_module_from_data(emulator& emu, std::span data, std::filesystem::path file); -std::optional map_module_from_file(emulator& emu, std::filesystem::path file); +mapped_module map_module_from_file(emulator& emu, std::filesystem::path file); bool unmap_module(emulator& emu, const mapped_module& mod); diff --git a/src/windows-emulator/process_context.hpp b/src/windows-emulator/process_context.hpp index 23795743..9e553d60 100644 --- a/src/windows-emulator/process_context.hpp +++ b/src/windows-emulator/process_context.hpp @@ -84,25 +84,159 @@ struct event : ref_counted_object } }; +struct mutant : ref_counted_object +{ + uint32_t locked_count{0}; + uint32_t owning_thread_id{}; + std::u16string name{}; + + bool try_lock(const uint32_t thread_id) + { + if (this->locked_count == 0) + { + ++this->locked_count; + this->owning_thread_id = thread_id; + return true; + } + + if (this->owning_thread_id != thread_id) + { + return false; + } + + ++this->locked_count; + return true; + } + + uint32_t release() + { + const auto old_count = this->locked_count; + + if (this->locked_count <= 0) + { + return old_count; + } + + --this->locked_count; + return old_count; + } + + void serialize(utils::buffer_serializer& buffer) const + { + buffer.write(this->locked_count); + buffer.write(this->owning_thread_id); + buffer.write(this->name); + + ref_counted_object::serialize(buffer); + } + + void deserialize(utils::buffer_deserializer& buffer) + { + buffer.read(this->locked_count); + buffer.read(this->owning_thread_id); + buffer.read(this->name); + + ref_counted_object::deserialize(buffer); + } +}; + +struct file_entry +{ + std::filesystem::path file_path{}; + + void serialize(utils::buffer_serializer& buffer) const + { + buffer.write(this->file_path); + } + + void deserialize(utils::buffer_deserializer& buffer) + { + buffer.read(this->file_path); + } +}; + +struct file_enumeration_state +{ + size_t current_index{0}; + std::vector files{}; + + void serialize(utils::buffer_serializer& buffer) const + { + buffer.write(this->current_index); + buffer.write_vector(this->files); + } + + void deserialize(utils::buffer_deserializer& buffer) + { + buffer.read(this->current_index); + buffer.read_vector(this->files); + } +}; + struct file { utils::file_handle handle{}; std::u16string name{}; + std::optional enumeration_state{}; + + bool is_file() const + { + return this->handle; + } + + bool is_directory() const + { + return !this->is_file(); + } void serialize(utils::buffer_serializer& buffer) const { - buffer.write(this->name); // TODO: Serialize handle + buffer.write(this->name); + buffer.write_optional(this->enumeration_state); } void deserialize(utils::buffer_deserializer& buffer) { buffer.read(this->name); + buffer.read_optional(this->enumeration_state); this->handle = {}; } }; -struct semaphore +struct section +{ + std::u16string name{}; + std::u16string file_name{}; + uint64_t maximum_size{}; + uint32_t section_page_protection{}; + uint32_t allocation_attributes{}; + + bool is_image() const + { + return this->allocation_attributes & SEC_IMAGE; + } + + void serialize(utils::buffer_serializer& buffer) const + { + buffer.write(this->name); + buffer.write(this->file_name); + buffer.write(this->maximum_size); + buffer.write(this->section_page_protection); + buffer.write(this->allocation_attributes); + } + + void deserialize(utils::buffer_deserializer& buffer) + { + buffer.read(this->name); + buffer.read(this->file_name); + buffer.read(this->maximum_size); + buffer.read(this->section_page_protection); + buffer.read(this->allocation_attributes); + } +}; + +struct semaphore : ref_counted_object { std::u16string name{}; volatile uint32_t current_count{}; @@ -113,6 +247,8 @@ struct semaphore buffer.write(this->name); buffer.write(this->current_count); buffer.write(this->max_count); + + ref_counted_object::serialize(buffer); } void deserialize(utils::buffer_deserializer& buffer) @@ -120,6 +256,8 @@ struct semaphore buffer.read(this->name); buffer.read(this->current_count); buffer.read(this->max_count); + + ref_counted_object::deserialize(buffer); } }; @@ -221,7 +359,8 @@ public: std::u16string name{}; std::optional exit_status{}; - std::optional await_object{}; + std::vector await_objects{}; + bool await_any{false}; bool waiting_for_alert{false}; bool alerted{false}; std::optional await_time{}; @@ -285,7 +424,8 @@ public: buffer.write_string(this->name); buffer.write_optional(this->exit_status); - buffer.write_optional(this->await_object); + buffer.write_vector(this->await_objects); + buffer.write(this->await_any); buffer.write(this->waiting_for_alert); buffer.write(this->alerted); @@ -317,7 +457,8 @@ public: buffer.read_string(this->name); buffer.read_optional(this->exit_status); - buffer.read_optional(this->await_object); + buffer.read_vector(this->await_objects); + buffer.read(this->await_any); buffer.read(this->waiting_for_alert); buffer.read(this->alerted); @@ -395,13 +536,13 @@ struct process_context uint64_t rtl_user_thread_start{}; uint64_t ki_user_exception_dispatcher{}; - uint64_t shared_section_size{}; - handle_store events{}; handle_store files{}; + handle_store sections{}; handle_store devices{}; handle_store semaphores{}; handle_store ports{}; + handle_store mutants{}; handle_store registry_keys{}; std::map atoms{}; @@ -433,12 +574,13 @@ struct process_context buffer.write(this->rtl_user_thread_start); buffer.write(this->ki_user_exception_dispatcher); - buffer.write(this->shared_section_size); buffer.write(this->events); buffer.write(this->files); + buffer.write(this->sections); buffer.write(this->devices); buffer.write(this->semaphores); buffer.write(this->ports); + buffer.write(this->mutants); buffer.write(this->registry_keys); buffer.write_map(this->atoms); @@ -475,12 +617,13 @@ struct process_context buffer.read(this->rtl_user_thread_start); buffer.read(this->ki_user_exception_dispatcher); - buffer.read(this->shared_section_size); buffer.read(this->events); buffer.read(this->files); + buffer.read(this->sections); buffer.read(this->devices); buffer.read(this->semaphores); buffer.read(this->ports); + buffer.read(this->mutants); buffer.read(this->registry_keys); buffer.read_map(this->atoms); diff --git a/src/windows-emulator/registry/hive_parser.cpp b/src/windows-emulator/registry/hive_parser.cpp index e9825f3e..a71f7b2f 100644 --- a/src/windows-emulator/registry/hive_parser.cpp +++ b/src/windows-emulator/registry/hive_parser.cpp @@ -1,4 +1,5 @@ #include "hive_parser.hpp" +#include // Based on this implementation: https://github.com/reahly/windows-hive-parser @@ -130,11 +131,6 @@ namespace throw std::runtime_error("Bad hive file '" + file_path.string() + "': " + e.what()); } } - - char char_to_lower(const char val) - { - return static_cast(std::tolower(static_cast(val))); - } } const hive_value* hive_key::get_value(std::ifstream& file, const std::string_view name) @@ -188,7 +184,7 @@ void hive_key::parse(std::ifstream& file) raw_value.data_offset = offset + static_cast(offsetof(value_block_t, offset)); } - std::ranges::transform(value_name, value_name.begin(), char_to_lower); + utils::string::to_lower_inplace(value_name); this->values_[std::move(value_name)] = std::move(raw_value); } @@ -211,7 +207,7 @@ void hive_key::parse(std::ifstream& file) const auto subkey = read_file_object(file, subkey_block_offset); std::string subkey_name(subkey.name, std::min(subkey.len, static_cast(sizeof(subkey.name)))); - std::ranges::transform(subkey_name, subkey_name.begin(), char_to_lower); + utils::string::to_lower_inplace(subkey_name); this->sub_keys_.emplace(std::move(subkey_name), hive_key{subkey.subkeys, subkey.value_count, subkey.offsets}); } diff --git a/src/windows-emulator/registry/registry_manager.cpp b/src/windows-emulator/registry/registry_manager.cpp index 193bf65a..9947035e 100644 --- a/src/windows-emulator/registry/registry_manager.cpp +++ b/src/windows-emulator/registry/registry_manager.cpp @@ -1,25 +1,16 @@ #include "registry_manager.hpp" -#include #include #include "hive_parser.hpp" +#include namespace { - void string_to_lower(std::string& str) - { - std::ranges::transform(str, str.begin(), [](const char val) - { - return static_cast(std::tolower(static_cast(val))); - }); - } - std::filesystem::path canonicalize_path(const std::filesystem::path& key) { auto path = key.lexically_normal().wstring(); - std::ranges::transform(path, path.begin(), std::towlower); - return {std::move(path)}; + return utils::string::to_lower_consume(path); } bool is_subpath(const std::filesystem::path& root, const std::filesystem::path& p) @@ -144,7 +135,7 @@ std::optional registry_manager::get_key(const std::filesystem::pat std::optional registry_manager::get_value(const registry_key& key, std::string name) { - string_to_lower(name); + utils::string::to_lower_inplace(name); const auto iterator = this->hives_.find(key.hive); if (iterator == this->hives_.end()) diff --git a/src/windows-emulator/syscall_dispatcher.cpp b/src/windows-emulator/syscall_dispatcher.cpp index 8990399a..1cb0a9bf 100644 --- a/src/windows-emulator/syscall_dispatcher.cpp +++ b/src/windows-emulator/syscall_dispatcher.cpp @@ -24,15 +24,16 @@ void syscall_dispatcher::deserialize(utils::buffer_deserializer& buffer) } -void syscall_dispatcher::setup(const exported_symbols& ntdll_exports, const exported_symbols& win32u_exports) +void syscall_dispatcher::setup(const exported_symbols& ntdll_exports, std::span ntdll_data, + const exported_symbols& win32u_exports, std::span win32u_data) { this->handlers_ = {}; - const auto ntdll_syscalls = find_syscalls(ntdll_exports); - const auto win32u_syscalls = find_syscalls(win32u_exports); + const auto ntdll_syscalls = find_syscalls(ntdll_exports, ntdll_data); + const auto win32u_syscalls = find_syscalls(win32u_exports, win32u_data); - map_syscalls(this->handlers_, ntdll_syscalls, 0); - map_syscalls(this->handlers_, win32u_syscalls, 0x1000); + map_syscalls(this->handlers_, ntdll_syscalls); + map_syscalls(this->handlers_, win32u_syscalls); this->add_handlers(); } @@ -99,10 +100,26 @@ void syscall_dispatcher::dispatch(windows_emulator& win_emu) } 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); + if (mod->is_within(context.previous_ip)) + { + const auto rsp = c.emu.read_stack_pointer(); + const auto return_address = c.emu.read_memory(rsp); + const auto* mod_name = context.module_manager.find_name(return_address); + + win_emu.logger.print(color::dark_gray, "Executing syscall: %s (0x%X) at 0x%llX via 0x%llX (%s) %lld\n", + entry->second.name.c_str(), + syscall_id, address, return_address, mod_name, c.proc.executed_instructions); + } + else + { + const auto* previous_mod = context.module_manager.find_by_address(context.previous_ip); + win_emu.logger.print(color::blue, + "Crafted out-of-line syscall: %s (0x%X) at 0x%llX (%s) via 0x%llX (%s)\n", + entry->second.name.c_str(), + syscall_id, + address, mod ? mod->name.c_str() : "", context.previous_ip, + previous_mod ? previous_mod->name.c_str() : ""); + } } entry->second.handler(c); @@ -121,8 +138,8 @@ void syscall_dispatcher::dispatch(windows_emulator& win_emu) } } -syscall_dispatcher::syscall_dispatcher(const exported_symbols& ntdll_exports, - const exported_symbols& win32u_exports) +syscall_dispatcher::syscall_dispatcher(const exported_symbols& ntdll_exports, std::span ntdll_data, + const exported_symbols& win32u_exports, std::span win32u_data) { - this->setup(ntdll_exports, win32u_exports); + this->setup(ntdll_exports, ntdll_data, win32u_exports, win32u_data); } diff --git a/src/windows-emulator/syscall_dispatcher.hpp b/src/windows-emulator/syscall_dispatcher.hpp index ac5fc439..23a5185e 100644 --- a/src/windows-emulator/syscall_dispatcher.hpp +++ b/src/windows-emulator/syscall_dispatcher.hpp @@ -17,14 +17,16 @@ class syscall_dispatcher { public: syscall_dispatcher() = default; - syscall_dispatcher(const exported_symbols& ntdll_exports, const exported_symbols& win32u_exports); + syscall_dispatcher(const exported_symbols& ntdll_exports, std::span ntdll_data, + const exported_symbols& win32u_exports, std::span win32u_data); void dispatch(windows_emulator& win_emu); void serialize(utils::buffer_serializer& buffer) const; void deserialize(utils::buffer_deserializer& buffer); - void setup(const exported_symbols& ntdll_exports, const exported_symbols& win32u_exports); + void setup(const exported_symbols& ntdll_exports, std::span ntdll_data, + const exported_symbols& win32u_exports, std::span win32u_data); std::string get_syscall_name(const uint64_t id) { diff --git a/src/windows-emulator/syscall_utils.hpp b/src/windows-emulator/syscall_utils.hpp index 5f5d5b8e..f9ae5c2a 100644 --- a/src/windows-emulator/syscall_utils.hpp +++ b/src/windows-emulator/syscall_utils.hpp @@ -38,32 +38,51 @@ 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) +inline std::optional extract_syscall_id(const exported_symbol& symbol, std::span data) { - // 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 + if (!is_syscall(symbol.name)) + { + return std::nullopt; + } - std::map reference_count{}; - std::map ordered_syscalls{}; + constexpr auto instruction_size = 5; + constexpr auto instruction_offset = 3; + constexpr auto instruction_operand_offset = 1; + constexpr auto instruction_opcode = static_cast(0xB8); + + const auto instruction_rva = symbol.rva + instruction_offset; + + if (data.size() < (instruction_rva + instruction_size) || data[instruction_rva] != instruction_opcode) + { + return std::nullopt; + } + + uint32_t syscall_id{0}; + static_assert(sizeof(syscall_id) <= (instruction_size - instruction_operand_offset)); + memcpy(&syscall_id, data.data() + instruction_rva + instruction_operand_offset, sizeof(syscall_id)); + + return syscall_id; +} + +inline std::map find_syscalls(const exported_symbols& exports, std::span data) +{ + std::map syscalls{}; for (const auto& symbol : exports) { - if (is_syscall(symbol.name)) + const auto id = extract_syscall_id(symbol, data); + if (id) { - ++reference_count[symbol.address]; - ordered_syscalls[symbol.address] = symbol.name; - } - } + auto& entry = syscalls[*id]; - std::vector syscalls{}; - syscalls.reserve(ordered_syscalls.size()); + if (!entry.empty()) + { + throw std::runtime_error( + "Syscall with id " + std::to_string(*id) + ", which is mapping to " + symbol.name + + ", was already mapped to " + entry); + } - for (auto& syscall : ordered_syscalls) - { - if (reference_count[syscall.first] == 1) - { - syscalls.push_back(std::move(syscall.second)); + entry = symbol.name; } } @@ -71,14 +90,20 @@ inline std::vector find_syscalls(const exported_symbols& exports) } inline void map_syscalls(std::map& handlers, - const std::vector& syscalls, const uint64_t base_index) + std::map syscalls) { - for (size_t i = 0; i < syscalls.size(); ++i) + for (auto& [id, name] : syscalls) { - const auto& syscall = syscalls[i]; + auto& entry = handlers[id]; - auto& entry = handlers[base_index + i]; - entry.name = syscall; + if (!entry.name.empty()) + { + throw std::runtime_error( + "Syscall with id " + std::to_string(id) + ", which is mapping to " + name + + ", was previously mapped to " + entry.name); + } + + entry.name = std::move(name); entry.handler = nullptr; } } @@ -243,3 +268,10 @@ inline std::chrono::system_clock::time_point convert_from_ksystem_time(const vol { return convert_from_ksystem_time(*const_cast(&time)); } + +inline LARGE_INTEGER convert_unix_to_windows_time(const __time64_t unix_time) +{ + LARGE_INTEGER windows_time{}; + windows_time.QuadPart = (unix_time + EPOCH_DIFFERENCE_1601_TO_1970_SECONDS) * HUNDRED_NANOSECONDS_IN_ONE_SECOND; + return windows_time; +} diff --git a/src/windows-emulator/syscalls.cpp b/src/windows-emulator/syscalls.cpp index c43ec421..93a6cb8e 100644 --- a/src/windows-emulator/syscalls.cpp +++ b/src/windows-emulator/syscalls.cpp @@ -9,8 +9,8 @@ #include #include #include - -#include "utils/finally.hpp" +#include +#include namespace { @@ -298,7 +298,7 @@ namespace const uint64_t thread_information, const uint32_t thread_information_length) { - auto* thread = thread_handle == ~1ULL + auto* thread = thread_handle == CURRENT_THREAD ? c.proc.active_thread : c.proc.threads.get(thread_handle); @@ -312,6 +312,12 @@ namespace return STATUS_SUCCESS; } + if (info_class == ThreadHideFromDebugger) + { + c.win_emu.logger.print(color::pink, "--> Hiding thread %X from debugger!\n", thread->id); + return STATUS_SUCCESS; + } + if (info_class == ThreadNameInformation) { if (thread_information_length != sizeof(THREAD_NAME_INFORMATION>)) @@ -328,7 +334,36 @@ namespace return STATUS_SUCCESS; } - printf("Unsupported thread info class: %X\n", info_class); + if (info_class == ThreadImpersonationToken) + { + if (thread_information_length != sizeof(handle)) + { + return STATUS_BUFFER_OVERFLOW; + } + + const emulator_object info{c.emu, thread_information}; + info.write(DUMMY_IMPERSONATION_TOKEN); + + return STATUS_SUCCESS; + } + + if (info_class == ThreadZeroTlsCell) + { + if (thread_information_length != sizeof(ULONG)) + { + return STATUS_BUFFER_OVERFLOW; + } + + const auto tls_index = c.emu.read_memory(thread_information); + const auto teb = thread->teb->read(); + + auto* tls_vector = teb.ThreadLocalStoragePointer; + c.emu.write_memory(tls_vector + tls_index, nullptr); + + return STATUS_SUCCESS; + } + + printf("Unsupported thread set info class: %X\n", info_class); c.emu.stop(); return STATUS_NOT_SUPPORTED; } @@ -351,6 +386,33 @@ namespace return STATUS_SUCCESS; } + generic_handle_store* get_handle_store(process_context& proc, const handle h) + { + switch (h.value.type) + { + case handle_types::thread: + return &proc.threads; + case handle_types::event: + return &proc.events; + case handle_types::file: + return &proc.files; + case handle_types::device: + return &proc.devices; + case handle_types::semaphore: + return &proc.semaphores; + case handle_types::registry: + return &proc.registry_keys; + case handle_types::mutant: + return &proc.mutants; + case handle_types::port: + return &proc.ports; + case handle_types::section: + return &proc.sections; + default: + return nullptr; + } + } + NTSTATUS handle_NtClose(const syscall_context& c, const handle h) { const auto value = h.value; @@ -359,32 +421,8 @@ namespace return STATUS_SUCCESS; } - if (value.type == handle_types::thread && c.proc.threads.erase(h)) - { - return STATUS_SUCCESS; - } - - if (value.type == handle_types::event && c.proc.events.erase(h)) - { - return STATUS_SUCCESS; - } - - if (value.type == handle_types::file && c.proc.files.erase(h)) - { - return STATUS_SUCCESS; - } - - if (value.type == handle_types::device && c.proc.devices.erase(h)) - { - return STATUS_SUCCESS; - } - - if (value.type == handle_types::semaphore && c.proc.semaphores.erase(h)) - { - return STATUS_SUCCESS; - } - - if (value.type == handle_types::registry && c.proc.registry_keys.erase(h)) + auto* handle_store = get_handle_store(c.proc, h); + if (handle_store && handle_store->erase(h)) { return STATUS_SUCCESS; } @@ -397,9 +435,70 @@ namespace return STATUS_SUCCESS; } - NTSTATUS handle_NtOpenThreadToken() + NTSTATUS handle_NtReleaseMutant(const syscall_context& c, const handle mutant_handle, + const emulator_object previous_count) { - return STATUS_NO_TOKEN; + if (mutant_handle.value.type != handle_types::mutant) + { + c.win_emu.logger.error("Bad handle type for NtReleaseMutant\n"); + c.emu.stop(); + return STATUS_NOT_SUPPORTED; + } + + auto* mutant = c.proc.mutants.get(mutant_handle); + if (!mutant) + { + return STATUS_INVALID_HANDLE; + } + + const auto old_count = mutant->release(); + + if (previous_count) + { + previous_count.write(static_cast(old_count)); + } + + return STATUS_SUCCESS; + } + + NTSTATUS handle_NtCreateMutant(const syscall_context& c, const emulator_object mutant_handle, + const ACCESS_MASK /*desired_access*/, + const emulator_object>> object_attributes, + const BOOLEAN initial_owner) + { + std::u16string name{}; + if (object_attributes) + { + const auto attributes = object_attributes.read(); + if (attributes.ObjectName) + { + name = read_unicode_string(c.emu, emulator_object>>{c.emu, attributes.ObjectName}); + } + } + + if (!name.empty()) + { + for (const auto& mutant : c.proc.mutants) + { + if (mutant.second.name == name) + { + return STATUS_OBJECT_NAME_EXISTS; + } + } + } + + mutant e{}; + e.name = std::move(name); + + if (initial_owner) + { + e.try_lock(c.win_emu.current_thread().id); + } + + const auto handle = c.proc.mutants.store(std::move(e)); + mutant_handle.write(handle); + + return STATUS_SUCCESS; } NTSTATUS handle_NtCreateEvent(const syscall_context& c, const emulator_object event_handle, @@ -417,6 +516,17 @@ namespace } } + if (!name.empty()) + { + for (const auto& event : c.proc.events) + { + if (event.second.name == name) + { + return STATUS_OBJECT_NAME_EXISTS; + } + } + } + event e{}; e.type = event_type; e.signaled = initial_state != FALSE; @@ -488,7 +598,7 @@ namespace const auto attributes = object_attributes.read(); auto filename = read_unicode_string(c.emu, reinterpret_cast>*>(attributes.ObjectName)); - c.win_emu.logger.print(color::gray, "Opening section: %S\n", filename.c_str()); + c.win_emu.logger.print(color::dark_gray, "--> Opening section: %S\n", filename.c_str()); if (filename == u"\\Windows\\SharedSection") { @@ -503,19 +613,18 @@ namespace return STATUS_NOT_SUPPORTED; } - filename = u"C:\\WINDOWS\\System32\\" + filename; - if (!std::filesystem::exists(filename)) + utils::string::to_lower_inplace(filename); + + for (auto& section_entry : c.proc.sections) { - return STATUS_FILE_INVALID; + if (section_entry.second.is_image() && section_entry.second.name == filename) + { + section_handle.write(c.proc.sections.make_handle(section_entry.first)); + return STATUS_SUCCESS; + } } - file f{}; - f.name = std::move(filename); - - const auto handle = c.proc.files.store(std::move(f)); - section_handle.write(handle); - - return STATUS_SUCCESS; + return STATUS_OBJECT_NAME_NOT_FOUND; } NTSTATUS handle_NtMapViewOfSection(const syscall_context& c, const handle section_handle, @@ -526,16 +635,17 @@ namespace const SECTION_INHERIT /*inherit_disposition*/, const ULONG /*allocation_type*/, const ULONG /*win32_protect*/) { - if (process_handle != ~0ULL) + if (process_handle != CURRENT_PROCESS) { return STATUS_INVALID_HANDLE; } if (section_handle == SHARED_SECTION) { - const auto address = c.emu.find_free_allocation_base(c.proc.shared_section_size); - c.emu.allocate_memory(address, - c.proc.shared_section_size, memory_permission::read_write); + constexpr auto shared_section_size = 0x10000; + + const auto address = c.emu.find_free_allocation_base(shared_section_size); + c.emu.allocate_memory(address, shared_section_size, memory_permission::read_write); const std::u16string_view windows_dir = c.proc.kusd.get().NtSystemRoot.arr; const auto windows_dir_size = windows_dir.size() * 2; @@ -556,16 +666,24 @@ namespace }); - const emulator_object>> sysdir_obj{c.emu, obj_address + windir_obj.size()}; + const emulator_object>> sysdir_obj{c.emu, windir_obj.value() + windir_obj.size()}; sysdir_obj.access([&](UNICODE_STRING>& ucs) + { c.proc.base_allocator.make_unicode_string(ucs, u"C:\\WINDOWS\\System32"); ucs.Buffer = ucs.Buffer - obj_address; }); - if (view_size.value()) + const emulator_object>> base_dir_obj{c.emu, sysdir_obj.value() + sysdir_obj.size()}; + base_dir_obj.access([&](UNICODE_STRING>& ucs) { - view_size.write(c.proc.shared_section_size); + c.proc.base_allocator.make_unicode_string(ucs, u"\\Sessions\\1\\BaseNamedObjects"); + ucs.Buffer = ucs.Buffer - obj_address; + }); + + if (view_size) + { + view_size.write(shared_section_size); } base_address.write(address); @@ -573,25 +691,60 @@ namespace return STATUS_SUCCESS; } - const auto section_entry = c.proc.files.get(section_handle); + const auto section_entry = c.proc.sections.get(section_handle); if (!section_entry) { return STATUS_INVALID_HANDLE; } - const auto binary = c.proc.module_manager.map_module(section_entry->name, c.win_emu.logger); - if (!binary) + if (section_entry->is_image()) { - return STATUS_FILE_INVALID; + const auto binary = c.proc.module_manager.map_module(section_entry->file_name, c.win_emu.logger); + if (!binary) + { + return STATUS_FILE_INVALID; + } + + std::u16string wide_name(binary->name.begin(), binary->name.end()); + section_entry->name = utils::string::to_lower_consume(wide_name); + + if (view_size.value()) + { + view_size.write(binary->size_of_image); + } + + base_address.write(binary->image_base); + + return STATUS_SUCCESS; } - if (view_size.value()) + uint64_t size = section_entry->maximum_size; + std::vector file_data{}; + + if (!section_entry->file_name.empty()) { - view_size.write(binary->size_of_image); + if (!utils::io::read_file(section_entry->file_name, &file_data)) + { + return STATUS_INVALID_PARAMETER; + } + + size = page_align_up(file_data.size()); } - base_address.write(binary->image_base); + const auto protection = map_nt_to_emulator_protection(section_entry->section_page_protection); + const auto address = c.emu.allocate_memory(size, protection); + if (!file_data.empty()) + { + c.emu.write_memory(address, file_data.data(), file_data.size()); + } + + if (view_size) + { + view_size.write(size); + } + + base_address.write(address); return STATUS_SUCCESS; } @@ -616,7 +769,7 @@ namespace const uint64_t memory_information, const uint32_t memory_information_length, const emulator_object return_length) { - if (process_handle != ~0ULL) + if (process_handle != CURRENT_PROCESS) { return STATUS_NOT_SUPPORTED; } @@ -657,7 +810,7 @@ namespace : (region_info.is_reserved ? MEM_RESERVE : MEM_FREE); - image_info.Protect = map_emulator_to_nt_protection(region_info.pemissions); + image_info.Protect = map_emulator_to_nt_protection(region_info.permissions); image_info.Type = MEM_PRIVATE; }); @@ -689,6 +842,7 @@ namespace { image_info.ImageBase = reinterpret_cast(mod->image_base); image_info.SizeOfImage = mod->size_of_image; + image_info.ImageFlags = 0; }); return STATUS_SUCCESS; @@ -904,7 +1058,7 @@ namespace const emulator_object target_handle, const ACCESS_MASK /*desired_access*/, const ULONG /*handle_attributes*/, const ULONG /*options*/) { - if (source_process_handle != ~0ULL || target_process_handle != ~0ULL) + if (source_process_handle != CURRENT_PROCESS || target_process_handle != CURRENT_PROCESS) { return STATUS_NOT_SUPPORTED; } @@ -1008,7 +1162,7 @@ namespace const uint32_t process_information_length, const emulator_object return_length) { - if (process_handle != ~0ULL) + if (process_handle != CURRENT_PROCESS) { return STATUS_NOT_SUPPORTED; } @@ -1096,7 +1250,7 @@ namespace return STATUS_SUCCESS; } - if (info_class == ProcessDefaultHardErrorMode) + if (info_class == ProcessDefaultHardErrorMode || info_class == ProcessWx86Information) { if (return_length) { @@ -1120,6 +1274,24 @@ namespace return STATUS_NOT_SUPPORTED; } + if (info_class == ProcessTimes) + { + if (return_length) + { + return_length.write(sizeof(KERNEL_USER_TIMES)); + } + + if (process_information_length != sizeof(KERNEL_USER_TIMES)) + { + return STATUS_BUFFER_OVERFLOW; + } + + const emulator_object info{c.emu, process_information}; + info.write(KERNEL_USER_TIMES{}); + + return STATUS_SUCCESS; + } + if (info_class == ProcessBasicInformation) { if (return_length) @@ -1184,9 +1356,13 @@ namespace const uint32_t thread_information_length, const emulator_object return_length) { - if (thread_handle != ~1ULL) + const auto* thread = thread_handle == CURRENT_THREAD + ? c.proc.active_thread + : c.proc.threads.get(thread_handle); + + if (!thread) { - return STATUS_NOT_SUPPORTED; + return STATUS_INVALID_HANDLE; } if (info_class == ThreadBasicInformation) @@ -1204,8 +1380,8 @@ namespace const emulator_object info{c.emu, thread_information}; info.access([&](THREAD_BASIC_INFORMATION64& i) { - i.TebBaseAddress = c.win_emu.current_thread().teb->ptr(); - i.ClientId = c.win_emu.current_thread().teb->read().ClientId; + i.TebBaseAddress = thread->teb->ptr(); + i.ClientId = thread->teb->read().ClientId; }); return STATUS_SUCCESS; @@ -1229,7 +1405,25 @@ namespace return STATUS_SUCCESS; } - printf("Unsupported thread info class: %X\n", info_class); + if (info_class == ThreadQuerySetWin32StartAddress) + { + if (return_length) + { + return_length.write(sizeof(ULONG_PTR)); + } + + if (thread_information_length != sizeof(ULONG_PTR)) + { + return STATUS_BUFFER_OVERFLOW; + } + + const emulator_object info{c.emu, thread_information}; + info.write(thread->start_address); + + return STATUS_SUCCESS; + } + + printf("Unsupported thread query info class: %X\n", info_class); c.emu.stop(); return STATUS_NOT_SUPPORTED; @@ -1282,6 +1476,144 @@ namespace return STATUS_NOT_SUPPORTED; } + std::vector scan_directory(const std::filesystem::path& dir) + { + std::vector files{ + {"."}, + {".."}, + }; + + for (const auto& file : std::filesystem::directory_iterator(dir)) + { + files.emplace_back(file.path().filename()); + } + + return files; + } + + template + NTSTATUS handle_file_enumeration(const syscall_context& c, const emulator_object>> io_status_block, + const uint64_t file_information, const uint32_t length, const ULONG query_flags, + file* f) + { + if (!f->enumeration_state || query_flags & SL_RESTART_SCAN) + { + f->enumeration_state.emplace(file_enumeration_state{}); + f->enumeration_state->files = scan_directory(f->name); + } + + auto& enum_state = *f->enumeration_state; + + size_t current_offset{0}; + emulator_object object{c.emu}; + + size_t current_index = enum_state.current_index; + + do + { + if (current_index >= enum_state.files.size()) + { + break; + } + + const auto new_offset = align_up(current_offset, 8); + const auto& current_file = enum_state.files[current_index]; + const auto file_name = current_file.file_path.u16string(); + const auto required_size = sizeof(T) + (file_name.size() * 2) - 2; + const auto end_offset = new_offset + required_size; + + if (end_offset > length) + { + if (current_offset == 0) + { + IO_STATUS_BLOCK> block{}; + block.Information = end_offset; + io_status_block.write(block); + + return STATUS_BUFFER_OVERFLOW; + } + + break; + } + + if (object) + { + const auto object_offset = object.value() - file_information; + + object.access([&](T& dir_info) + { + dir_info.NextEntryOffset = static_cast(new_offset - object_offset); + }); + } + + T info{}; + info.NextEntryOffset = 0; + info.FileIndex = static_cast(current_index); + info.FileAttributes = FILE_ATTRIBUTE_NORMAL; + info.FileNameLength = static_cast(file_name.size() * 2); + + object.set_address(file_information + new_offset); + object.write(info); + + c.emu.write_memory(object.value() + offsetof(T, FileName), file_name.data(), + info.FileNameLength); + + ++current_index; + current_offset = end_offset; + } + while ((query_flags & SL_RETURN_SINGLE_ENTRY) == 0); + + if ((query_flags & SL_NO_CURSOR_UPDATE) == 0) + { + enum_state.current_index = current_index; + } + + IO_STATUS_BLOCK> block{}; + block.Information = current_offset; + io_status_block.write(block); + + return current_index < enum_state.files.size() ? STATUS_SUCCESS : STATUS_NO_MORE_FILES; + } + + NTSTATUS handle_NtQueryDirectoryFileEx(const syscall_context& c, const handle file_handle, + const handle /*event_handle*/, + const emulator_pointer /*PIO_APC_ROUTINE*/ /*apc_routine*/, + const emulator_pointer /*apc_context*/, + const emulator_object>> io_status_block, + const uint64_t file_information, const uint32_t length, + const uint32_t info_class, const ULONG query_flags, + const emulator_object>> /*file_name*/) + { + auto* f = c.proc.files.get(file_handle); + if (!f || !f->is_directory()) + { + return STATUS_INVALID_HANDLE; + } + + if (info_class == FileDirectoryInformation) + { + return handle_file_enumeration(c, io_status_block, file_information, length, + query_flags, f); + } + + if (info_class == FileFullDirectoryInformation) + { + return handle_file_enumeration(c, io_status_block, file_information, length, + query_flags, f); + } + + if (info_class == FileBothDirectoryInformation) + { + return handle_file_enumeration(c, io_status_block, file_information, length, + query_flags, f); + } + + printf("Unsupported query directory file info class: %X\n", info_class); + c.emu.stop(); + + return STATUS_NOT_SUPPORTED; + } + NTSTATUS handle_NtQueryInformationFile(const syscall_context& c, const handle file_handle, const emulator_object>> io_status_block, const uint64_t file_information, const uint32_t length, @@ -1293,6 +1625,32 @@ namespace return STATUS_INVALID_HANDLE; } + if (info_class == FileNameInformation) + { + const auto required_length = sizeof(FILE_NAME_INFORMATION) + (f->name.size() * 2); + + if (io_status_block) + { + IO_STATUS_BLOCK> block{}; + block.Information = sizeof(FILE_NAME_INFORMATION) + required_length; + io_status_block.write(block); + } + + if (length != required_length) + { + return STATUS_BUFFER_OVERFLOW; + } + + c.emu.write_memory(file_information, FILE_NAME_INFORMATION{ + .FileNameLength = static_cast(f->name.size() * 2), + }); + + c.emu.write_memory(file_information + offsetof(FILE_NAME_INFORMATION, FileName), f->name.c_str(), + (f->name.size() + 1) * 2); + + return STATUS_SUCCESS; + } + if (info_class == FileStandardInformation) { if (io_status_block) @@ -1309,7 +1667,7 @@ namespace const emulator_object info{c.emu, file_information}; FILE_STANDARD_INFORMATION i{}; - i.Directory = f->handle ? FALSE : TRUE; + i.Directory = f->is_directory() ? TRUE : FALSE; if (f->handle) { @@ -1357,16 +1715,15 @@ namespace } NTSTATUS handle_NtSetInformationProcess(const syscall_context& c, const handle process_handle, - const uint32_t info_class, const uint64_t /*process_information*/, - const uint32_t /*process_information_length*/) + const uint32_t info_class, const uint64_t process_information, + const uint32_t process_information_length) { - if (process_handle != ~0ULL) + if (process_handle != CURRENT_PROCESS) { return STATUS_NOT_SUPPORTED; } if (info_class == ProcessSchedulerSharedData - || info_class == ProcessTlsInformation || info_class == ProcessConsoleHostProcess || info_class == ProcessFaultInformation || info_class == ProcessDefaultHardErrorMode @@ -1375,12 +1732,84 @@ namespace return STATUS_SUCCESS; } + if (info_class == ProcessTlsInformation) + { + constexpr auto thread_data_offset = offsetof(PROCESS_TLS_INFO, ThreadData); + if (process_information_length < thread_data_offset) + { + return STATUS_BUFFER_OVERFLOW; + } + + const emulator_object data{c.emu, process_information + thread_data_offset}; + + PROCESS_TLS_INFO tls_info{}; + c.emu.read_memory(process_information, &tls_info, thread_data_offset); + + for (uint32_t i = 0; i < tls_info.ThreadDataCount; ++i) + { + auto entry = data.read(i); + + const auto _ = utils::finally([&] + { + data.write(entry, i); + }); + + if (i >= c.proc.threads.size()) + { + entry.Flags = 0; + continue; + } + + auto thread_iterator = c.proc.threads.begin(); + std::advance(thread_iterator, i); + + entry.Flags = 2; + + thread_iterator->second.teb->access([&](TEB64& teb) + { + entry.ThreadId = teb.ClientId.UniqueThread; + + const auto tls_vector = teb.ThreadLocalStoragePointer; + + if (tls_info.TlsRequest == ProcessTlsReplaceIndex) + { + const auto tls_entry_ptr = tls_vector + tls_info.TlsIndex; + + const auto old_entry = c.emu.read_memory(tls_entry_ptr); + c.emu.write_memory(tls_entry_ptr, entry.TlsModulePointer); + + entry.TlsModulePointer = old_entry; + } + else if (tls_info.TlsRequest == ProcessTlsReplaceVector) + { + const auto new_tls_vector = entry.TlsVector; + + for (uint32_t index = 0; index < tls_info.TlsVectorLength; ++index) + { + const auto old_entry = c.emu.read_memory(tls_vector + index); + c.emu.write_memory(new_tls_vector + index, old_entry); + } + + teb.ThreadLocalStoragePointer =new_tls_vector; + entry.TlsVector = tls_vector; + } + }); + } + + return STATUS_SUCCESS; + } + printf("Unsupported info process class: %X\n", info_class); c.emu.stop(); return STATUS_NOT_SUPPORTED; } + NTSTATUS handle_NtSetInformationKey() + { + return STATUS_NOT_SUPPORTED; + } + NTSTATUS handle_NtApphelpCacheControl() { return STATUS_NOT_SUPPORTED; @@ -1392,7 +1821,7 @@ namespace const uint32_t protection, const emulator_object old_protection) { - if (process_handle != ~0ULL) + if (process_handle != CURRENT_PROCESS) { return STATUS_NOT_SUPPORTED; } @@ -1442,6 +1871,12 @@ namespace return STATUS_SUCCESS; } + if (object_name == u"\\Sessions\\1\\BaseNamedObjects") + { + directory_handle.write(BASE_NAMED_OBJECTS_DIRECTORY); + return STATUS_SUCCESS; + } + return STATUS_NOT_SUPPORTED; } @@ -1501,12 +1936,14 @@ namespace const uint32_t allocation_type, const uint32_t page_protection) { - if (process_handle != ~0ULL) + if (process_handle != CURRENT_PROCESS) { return STATUS_NOT_SUPPORTED; } - const auto allocation_bytes = bytes_to_allocate.read(); + auto allocation_bytes = bytes_to_allocate.read(); + allocation_bytes = page_align_up(allocation_bytes); + bytes_to_allocate.write(allocation_bytes); const auto protection = map_nt_to_emulator_protection(page_protection); @@ -1531,11 +1968,9 @@ namespace throw std::runtime_error("Unsupported allocation type!"); } - if (commit && !reserve) + if (commit && !reserve && c.emu.commit_memory(potential_base, allocation_bytes, protection)) { - return c.emu.commit_memory(potential_base, allocation_bytes, protection) - ? STATUS_SUCCESS - : STATUS_MEMORY_NOT_ALLOCATED; + return STATUS_SUCCESS; } return c.emu.allocate_memory(potential_base, allocation_bytes, protection, !commit) @@ -1556,7 +1991,7 @@ namespace const emulator_object base_address, const emulator_object bytes_to_allocate, const uint32_t free_type) { - if (process_handle != ~0ULL) + if (process_handle != CURRENT_PROCESS) { return STATUS_NOT_SUPPORTED; } @@ -1583,19 +2018,48 @@ namespace NTSTATUS handle_NtCreateSection(const syscall_context& c, const emulator_object section_handle, const ACCESS_MASK /*desired_access*/, - const emulator_object>> /*object_attributes*/, + const emulator_object>> object_attributes, const emulator_object maximum_size, - const ULONG /*section_page_protection*/, const ULONG /*allocation_attributes*/, - const handle /*file_handle*/) + const ULONG section_page_protection, const ULONG allocation_attributes, + const handle file_handle) { - //puts("NtCreateSection not supported"); - section_handle.write(SHARED_SECTION); + section s{}; + s.section_page_protection = section_page_protection; + s.allocation_attributes = allocation_attributes; - maximum_size.access([&c](ULARGE_INTEGER& large_int) + const auto* file = c.proc.files.get(file_handle); + if (file) { - large_int.QuadPart = page_align_up(large_int.QuadPart); - c.proc.shared_section_size = large_int.QuadPart; - }); + c.win_emu.logger.print(color::dark_gray, "--> Section for file %S\n", file->name.c_str()); + s.file_name = file->name; + } + + if (object_attributes) + { + const auto attributes = object_attributes.read(); + if (attributes.ObjectName) + { + const auto name = read_unicode_string(c.emu, reinterpret_cast>*>(attributes.ObjectName)); + c.win_emu.logger.print(color::dark_gray, "--> Section with name %S\n", name.c_str()); + s.name = std::move(name); + } + } + + if (maximum_size) + { + maximum_size.access([&](ULARGE_INTEGER& large_int) + { + large_int.QuadPart = page_align_up(large_int.QuadPart); + s.maximum_size = large_int.QuadPart; + }); + } + else if (!file) + { + return STATUS_INVALID_PARAMETER; + } + + const auto h = c.proc.sections.store(std::move(s)); + section_handle.write(h); return STATUS_SUCCESS; } @@ -1642,7 +2106,7 @@ namespace { number_of_bytes_read.write(0); - if (process_handle != ~0ULL) + if (process_handle != CURRENT_PROCESS) { return STATUS_NOT_SUPPORTED; } @@ -1703,10 +2167,46 @@ namespace return STATUS_SUCCESS; } - NTSTATUS handle_NtOpenProcessToken() + NTSTATUS handle_NtOpenThreadToken(const syscall_context&, const handle thread_handle, + const ACCESS_MASK /*desired_access*/, const BOOLEAN /*open_as_self*/, + const emulator_object token_handle) { - //puts("NtOpenProcessToken not supported"); - return STATUS_NOT_SUPPORTED; + if (thread_handle != CURRENT_THREAD) + { + return STATUS_NOT_SUPPORTED; + } + + token_handle.write(CURRENT_THREAD_TOKEN); + + return STATUS_SUCCESS; + } + + NTSTATUS handle_NtOpenThreadTokenEx(const syscall_context& c, const handle thread_handle, + const ACCESS_MASK desired_access, const BOOLEAN open_as_self, + const ULONG /*handle_attributes*/, + const emulator_object token_handle) + { + return handle_NtOpenThreadToken(c, thread_handle, desired_access, open_as_self, token_handle); + } + + NTSTATUS handle_NtOpenProcessToken(const syscall_context&, const handle process_handle, + const ACCESS_MASK /*desired_access*/, const emulator_object token_handle) + { + if (process_handle != CURRENT_PROCESS) + { + return STATUS_NOT_SUPPORTED; + } + + token_handle.write(CURRENT_PROCESS_TOKEN); + + return STATUS_SUCCESS; + } + + NTSTATUS handle_NtOpenProcessTokenEx(const syscall_context& c, const handle process_handle, + const ACCESS_MASK desired_access, const ULONG /*handle_attributes*/, + const emulator_object token_handle) + { + return handle_NtOpenProcessToken(c, process_handle, desired_access, token_handle); } NTSTATUS handle_NtQuerySecurityAttributesToken() @@ -1727,29 +2227,74 @@ namespace return STATUS_NOT_SUPPORTED; } + NTSTATUS handle_NtUserSystemParametersInfo() + { + return STATUS_NOT_SUPPORTED; + } + + TOKEN_TYPE get_token_type(const handle token_handle) + { + return token_handle == DUMMY_IMPERSONATION_TOKEN // + ? TokenImpersonation + : TokenPrimary; + } + + NTSTATUS handle_NtDuplicateToken(const syscall_context&, const handle existing_token_handle, + ACCESS_MASK /*desired_access*/, + const emulator_object>> /*object_attributes*/, + const BOOLEAN /*effective_only*/, const TOKEN_TYPE type, + const emulator_object new_token_handle) + { + if (get_token_type(existing_token_handle) == type) + { + new_token_handle.write(existing_token_handle); + } + else if (type == TokenPrimary) + { + new_token_handle.write(CURRENT_PROCESS_TOKEN); + } + else + { + new_token_handle.write(DUMMY_IMPERSONATION_TOKEN); + } + + return STATUS_SUCCESS; + } + + NTSTATUS handle_NtQueryTimerResolution(const syscall_context&, const emulator_object maximum_time, + const emulator_object minimum_time, + const emulator_object current_time) + { + maximum_time.write_if_valid(0x0002625a); + minimum_time.write_if_valid(0x00001388); + current_time.write_if_valid(0x00002710); + return STATUS_SUCCESS; + } + NTSTATUS handle_NtQueryInformationToken(const syscall_context& c, const handle token_handle, const TOKEN_INFORMATION_CLASS token_information_class, const uint64_t token_information, const ULONG token_information_length, const emulator_object return_length) { - if (token_handle != ~3ULL // NtCurrentProcessToken - && token_handle != ~4ULL // NtCurrentThreadToken - && token_handle != ~5ULL // NtCurrentThreadEffectiveToken + if (token_handle != CURRENT_PROCESS_TOKEN + && token_handle != CURRENT_THREAD_TOKEN + && token_handle != CURRENT_THREAD_EFFECTIVE_TOKEN + && token_handle != DUMMY_IMPERSONATION_TOKEN ) { return STATUS_NOT_SUPPORTED; } + const uint8_t sid[] = + { + 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x15, 0x00, 0x00, 0x00, 0x84, 0x94, + 0xD4, 0x04, 0x4B, 0x68, 0x42, 0x34, 0x23, + 0xBE, 0x69, 0x4E, 0xE9, 0x03, 0x00, 0x00, + }; + if (token_information_class == TokenUser) { - const uint8_t sid[] = - { - 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x05, 0x15, 0x00, 0x00, 0x00, 0x84, 0x94, - 0xD4, 0x04, 0x4B, 0x68, 0x42, 0x34, 0x23, - 0xBE, 0x69, 0x4E, 0xE9, 0x03, 0x00, 0x00, - }; - constexpr auto required_size = sizeof(sid) + 0x10; return_length.write(required_size); @@ -1767,6 +2312,78 @@ namespace return STATUS_SUCCESS; } + if (token_information_class == TokenType) + { + constexpr auto required_size = sizeof(TOKEN_TYPE); + return_length.write(required_size); + + if (required_size > token_information_length) + { + return STATUS_BUFFER_TOO_SMALL; + } + + emulator_object{c.emu, token_information}.write(get_token_type(token_handle)); + return STATUS_SUCCESS; + } + + if (token_information_class == TokenSessionId) + { + constexpr auto required_size = sizeof(ULONG); + return_length.write(required_size); + + if (required_size > token_information_length) + { + return STATUS_BUFFER_TOO_SMALL; + } + + emulator_object{c.emu, token_information}.write(1); + return STATUS_SUCCESS; + } + + if (token_information_class == TokenPrivateNameSpace) + { + constexpr auto required_size = sizeof(ULONG); + return_length.write(required_size); + + if (required_size > token_information_length) + { + return STATUS_BUFFER_TOO_SMALL; + } + + emulator_object{c.emu, token_information}.write(0); + return STATUS_SUCCESS; + } + + if (token_information_class == TokenUIAccess) + { + constexpr auto required_size = sizeof(ULONG); + return_length.write(required_size); + + if (required_size > token_information_length) + { + return STATUS_BUFFER_TOO_SMALL; + } + + emulator_object{c.emu, token_information}.write(1); + return STATUS_SUCCESS; + } + + if (token_information_class == TokenElevation) + { + constexpr auto required_size = sizeof(TOKEN_ELEVATION); + return_length.write(required_size); + + if (required_size > token_information_length) + { + return STATUS_BUFFER_TOO_SMALL; + } + + c.emu.write_memory(token_information, TOKEN_ELEVATION{ + .TokenIsElevated = 0, + }); + return STATUS_SUCCESS; + } + if (token_information_class == TokenIsAppContainer) { constexpr auto required_size = sizeof(ULONG); @@ -1781,6 +2398,76 @@ namespace return STATUS_SUCCESS; } + if (token_information_class == TokenStatistics) + { + constexpr auto required_size = sizeof(TOKEN_STATISTICS); + return_length.write(required_size); + + if (required_size > token_information_length) + { + return STATUS_BUFFER_TOO_SMALL; + } + + c.emu.write_memory(token_information, TOKEN_STATISTICS{}); + + return STATUS_SUCCESS; + } + + if (token_information_class == TokenSecurityAttributes) + { + constexpr auto required_size = sizeof(TOKEN_SECURITY_ATTRIBUTES_INFORMATION); + return_length.write(required_size); + + if (required_size > token_information_length) + { + return STATUS_BUFFER_TOO_SMALL; + } + + c.emu.write_memory(token_information, TOKEN_SECURITY_ATTRIBUTES_INFORMATION{ + .Version = 0, + .AttributeCount = 0, + }); + + return STATUS_SUCCESS; + } + + if (token_information_class == TokenIntegrityLevel) + { + constexpr auto required_size = sizeof(sid) + sizeof(TOKEN_MANDATORY_LABEL); + return_length.write(required_size); + + if (required_size > token_information_length) + { + return STATUS_BUFFER_TOO_SMALL; + } + + TOKEN_MANDATORY_LABEL label{}; + label.Label.Attributes = 0; + label.Label.Sid = reinterpret_cast(token_information + sizeof(TOKEN_MANDATORY_LABEL)); + + emulator_object{c.emu, token_information}.write(label); + c.emu.write_memory(token_information + sizeof(TOKEN_MANDATORY_LABEL), sid, sizeof(sid)); + return STATUS_SUCCESS; + } + + if (token_information_class == TokenBnoIsolation) + { + constexpr auto required_size = sizeof(TOKEN_BNO_ISOLATION_INFORMATION); + return_length.write(required_size); + + if (required_size > token_information_length) + { + return STATUS_BUFFER_TOO_SMALL; + } + + c.emu.write_memory(token_information, TOKEN_BNO_ISOLATION_INFORMATION{ + .IsolationPrefix = nullptr, + .IsolationEnabled = 0, + }); + + return STATUS_SUCCESS; + } + printf("Unsupported token info class: %X\n", token_information_class); c.emu.stop(); return STATUS_NOT_SUPPORTED; @@ -1803,7 +2490,7 @@ namespace return STATUS_NOT_SUPPORTED; } - NTSTATUS handle_NtGdiInit2(const syscall_context& c) + NTSTATUS handle_NtGdiInit(const syscall_context& c) { c.proc.peb.access([&](PEB64& peb) { @@ -1813,6 +2500,12 @@ namespace } }); + return STATUS_WAIT_1; + } + + NTSTATUS handle_NtGdiInit2(const syscall_context& c) + { + handle_NtGdiInit(c); return STATUS_NOT_SUPPORTED; } @@ -1870,6 +2563,8 @@ namespace return STATUS_NOT_SUPPORTED; } + // TODO: Fix this. This is broken and wrong. + const emulator_object>> data{c.emu, receive_message.value() + 0x48}; const auto dest = data.read(); const auto base = dest.Base; @@ -1927,7 +2622,7 @@ namespace return STATUS_SUCCESS; } - if (process_handle == ~0ULL) + if (process_handle == CURRENT_PROCESS) { c.proc.exit_status = exit_status; c.emu.stop(); @@ -2038,7 +2733,7 @@ namespace { mode = u"r+b"; } - else if (desired_access & GENERIC_READ) + else if (desired_access & GENERIC_READ || desired_access & SYNCHRONIZE) { mode = u"rb"; } @@ -2152,14 +2847,14 @@ namespace std::u16string mode = map_mode(desired_access, create_disposition); - if (mode.length() == 0 || mode == u"") + if (mode.empty()) { return STATUS_NOT_SUPPORTED; } FILE* file{}; - const auto error = open_unicode(&file, f.name.c_str(), mode.c_str()); + const auto error = open_unicode(&file, f.name, mode); if (!file) { @@ -2184,6 +2879,46 @@ namespace return STATUS_SUCCESS; } + NTSTATUS handle_NtQueryAttributesFile(const syscall_context& c, + const emulator_object>> object_attributes, + const emulator_object file_information) + { + if (!object_attributes) + { + return STATUS_INVALID_PARAMETER; + } + + const auto attributes = object_attributes.read(); + if (!attributes.ObjectName) + { + return STATUS_INVALID_PARAMETER; + } + + const auto filename = read_unicode_string(c.emu, emulator_object>>{c.emu, attributes.ObjectName}); + const auto u8_filename = u16_to_u8(filename); + + struct _stat64 file_stat{}; +#ifdef OS_WINDOWS + if (_stat64(u8_filename.c_str(), &file_stat) != 0) +#else + if (stat64(u8_filename.c_str(), &file_stat) != 0) +#endif + { + return STATUS_OBJECT_NAME_NOT_FOUND; + } + + file_information.access([&](FILE_BASIC_INFORMATION& info) + { + info.CreationTime = convert_unix_to_windows_time(file_stat.st_atime); + info.LastAccessTime = convert_unix_to_windows_time(file_stat.st_atime); + info.LastWriteTime = convert_unix_to_windows_time(file_stat.st_mtime); + info.ChangeTime = info.LastWriteTime; + info.FileAttributes = FILE_ATTRIBUTE_NORMAL; + }); + + return STATUS_SUCCESS; + } + NTSTATUS handle_NtOpenFile(const syscall_context& c, const emulator_object file_handle, const ACCESS_MASK desired_access, @@ -2220,6 +2955,11 @@ namespace return STATUS_NOT_SUPPORTED; } + NTSTATUS handle_NtUserGetKeyboardLayout() + { + return STATUS_NOT_SUPPORTED; + } + NTSTATUS handle_NtRaiseHardError(const syscall_context& c, const NTSTATUS error_status, const ULONG /*number_of_parameters*/, const emulator_object>> /*unicode_string_parameter_mask*/, @@ -2232,7 +2972,7 @@ namespace response.write(ResponseAbort); } - printf("Hard error: %X\n", static_cast(error_status)); + c.proc.exit_status = error_status; c.proc.exception_rip = c.emu.read_instruction_pointer(); c.emu.stop(); @@ -2256,6 +2996,39 @@ namespace return STATUS_SUCCESS; } + NTSTATUS handle_NtOpenSemaphore(const syscall_context& c, const emulator_object semaphore_handle, + const ACCESS_MASK /*desired_access*/, + const emulator_object>> object_attributes) + { + if (!object_attributes) + { + return STATUS_INVALID_PARAMETER; + } + + const auto attributes = object_attributes.read(); + if (!attributes.ObjectName) + { + return STATUS_INVALID_PARAMETER; + } + + const auto name = read_unicode_string(c.emu, emulator_object>>{c.emu, attributes.ObjectName}); + if (name.empty()) + { + return STATUS_INVALID_PARAMETER; + } + + for (const auto& semaphore : c.proc.semaphores) + { + if (semaphore.second.name == name) + { + semaphore_handle.write(c.proc.semaphores.make_handle(semaphore.first)); + return STATUS_SUCCESS; + } + } + + return STATUS_OBJECT_NAME_NOT_FOUND; + } + NTSTATUS handle_NtCreateSemaphore(const syscall_context& c, const emulator_object semaphore_handle, const ACCESS_MASK /*desired_access*/, const emulator_object>> object_attributes, @@ -2274,6 +3047,17 @@ namespace } } + if (!s.name.empty()) + { + for (const auto& semaphore : c.proc.semaphores) + { + if (semaphore.second.name == s.name) + { + return STATUS_OBJECT_NAME_EXISTS; + } + } + } + const auto handle = c.proc.semaphores.store(std::move(s)); semaphore_handle.write(handle); @@ -2335,7 +3119,7 @@ namespace NTSTATUS handle_NtUnmapViewOfSection(const syscall_context& c, const handle process_handle, const uint64_t base_address) { - if (process_handle != ~0ULL) + if (process_handle != CURRENT_PROCESS) { return STATUS_NOT_SUPPORTED; } @@ -2344,14 +3128,22 @@ namespace if (!mod) { puts("Unmapping non-module section not supported!"); - } - else - { - printf("Unmapping section %s not supported!\n", mod->name.c_str()); + c.emu.stop(); + return STATUS_NOT_SUPPORTED; } - c.emu.stop(); - return STATUS_NOT_SUPPORTED; + if (c.proc.module_manager.unmap(base_address)) + { + return STATUS_SUCCESS; + } + + return STATUS_INVALID_PARAMETER; + } + + NTSTATUS handle_NtUnmapViewOfSectionEx(const syscall_context& c, const handle process_handle, + const uint64_t base_address, const ULONG /*flags*/) + { + return handle_NtUnmapViewOfSection(c, process_handle, base_address); } NTSTATUS handle_NtCreateThreadEx(const syscall_context& c, const emulator_object thread_handle, @@ -2362,7 +3154,7 @@ namespace const EmulatorTraits::SIZE_T stack_size, const EmulatorTraits::SIZE_T /*maximum_stack_size*/, const emulator_object>> attribute_list) { - if (process_handle != ~0ULL) + if (process_handle != CURRENT_PROCESS) { return STATUS_NOT_SUPPORTED; } @@ -2417,6 +3209,54 @@ namespace return FALSE; } + bool is_awaitable_object_type(const handle h) + { + return h.value.type == handle_types::thread // + || h.value.type == handle_types::mutant // + || h.value.type == handle_types::event; + } + + NTSTATUS handle_NtWaitForMultipleObjects(const syscall_context& c, const ULONG count, + const emulator_object handles, const WAIT_TYPE wait_type, + const BOOLEAN alertable, const emulator_object timeout) + { + if (alertable) + { + c.win_emu.logger.print(color::gray, "Alertable NtWaitForMultipleObjects not supported yet!\n"); + } + + if (wait_type != WaitAny && wait_type != WaitAll) + { + puts("Wait type not supported!"); + c.emu.stop(); + return STATUS_NOT_SUPPORTED; + } + + auto& t = c.win_emu.current_thread(); + t.await_objects.clear(); + t.await_any = wait_type == WaitAny; + + for (ULONG i = 0; i < count; ++i) + { + const auto h = handles.read(i); + + if (!is_awaitable_object_type(h)) + { + c.win_emu.logger.print(color::gray, "Unsupported handle type for NtWaitForMultipleObjects: %d!\n", + h.value.type); + return STATUS_NOT_SUPPORTED; + } + } + + if (timeout.value() && !t.await_time.has_value()) + { + t.await_time = convert_delay_interval_to_time_point(timeout.read()); + } + + c.win_emu.yield_thread(); + return STATUS_SUCCESS; + } + NTSTATUS handle_NtWaitForSingleObject(const syscall_context& c, const handle h, const BOOLEAN alertable, const emulator_object timeout) @@ -2426,14 +3266,16 @@ namespace c.win_emu.logger.print(color::gray, "Alertable NtWaitForSingleObject not supported yet!\n"); } - if (h.value.type != handle_types::thread && h.value.type != handle_types::event) + if (!is_awaitable_object_type(h)) { - c.win_emu.logger.print(color::gray, "Unsupported handle type for NtWaitForSingleObject!\n"); + c.win_emu.logger.print(color::gray, + "Unsupported handle type for NtWaitForSingleObject: %d!\n", h.value.type); return STATUS_NOT_SUPPORTED; } auto& t = c.win_emu.current_thread(); - t.await_object = h; + t.await_objects = {h}; + t.await_any = false; if (timeout.value() && !t.await_time.has_value()) { @@ -2533,6 +3375,39 @@ namespace processor_number.write(number); return STATUS_SUCCESS; } + + NTSTATUS handle_NtGetContextThread(const syscall_context& c, handle thread_handle, + const emulator_object thread_context) + { + const auto* thread = thread_handle == CURRENT_THREAD + ? c.proc.active_thread + : c.proc.threads.get(thread_handle); + + if (!thread) + { + return STATUS_INVALID_HANDLE; + } + + c.proc.active_thread->save(c.emu); + const auto _ = utils::finally([&] + { + c.proc.active_thread->restore(c.emu); + }); + + thread->restore(c.emu); + + thread_context.access([&](CONTEXT64& context) + { + if (context.ContextFlags & CONTEXT_DEBUG_REGISTERS) + { + c.win_emu.logger.print(color::pink, "--> Reading debug registers!\n"); + } + + context_frame::save(c.emu, context); + }); + + return STATUS_SUCCESS; + } } void syscall_dispatcher::add_handlers(std::map& handler_mapping) @@ -2553,6 +3428,7 @@ void syscall_dispatcher::add_handlers(std::map& ha add_handler(NtFreeVirtualMemory); add_handler(NtQueryVirtualMemory); add_handler(NtOpenThreadToken); + add_handler(NtOpenThreadTokenEx); add_handler(NtQueryPerformanceCounter); add_handler(NtQuerySystemInformation); add_handler(NtCreateEvent); @@ -2578,6 +3454,7 @@ void syscall_dispatcher::add_handlers(std::map& ha add_handler(NtDeviceIoControlFile); add_handler(NtQueryWnfStateData); add_handler(NtOpenProcessToken); + add_handler(NtOpenProcessTokenEx); add_handler(NtQuerySecurityAttributesToken); add_handler(NtQueryLicenseValue); add_handler(NtTestAlert); @@ -2586,16 +3463,19 @@ void syscall_dispatcher::add_handlers(std::map& ha add_handler(NtWriteFile); add_handler(NtRaiseHardError); add_handler(NtCreateSemaphore); + add_handler(NtOpenSemaphore); add_handler(NtReadVirtualMemory); add_handler(NtQueryInformationToken); add_handler(NtDxgkIsFeatureEnabled); add_handler(NtAddAtomEx); add_handler(NtInitializeNlsFiles); add_handler(NtUnmapViewOfSection); + add_handler(NtUnmapViewOfSectionEx); add_handler(NtDuplicateObject); add_handler(NtQueryInformationThread); add_handler(NtQueryWnfStateNameInformation); add_handler(NtAlpcSendWaitReceivePort); + add_handler(NtGdiInit); add_handler(NtGdiInit2); add_handler(NtUserGetThreadState); add_handler(NtOpenKeyEx); @@ -2628,6 +3508,17 @@ void syscall_dispatcher::add_handlers(std::map& ha add_handler(NtNotifyChangeKey); add_handler(NtGetCurrentProcessorNumberEx); add_handler(NtQueryObject); + add_handler(NtQueryAttributesFile); + add_handler(NtWaitForMultipleObjects); + add_handler(NtCreateMutant); + add_handler(NtReleaseMutant); + add_handler(NtDuplicateToken); + add_handler(NtQueryTimerResolution); + add_handler(NtSetInformationKey); + add_handler(NtUserGetKeyboardLayout); + add_handler(NtQueryDirectoryFileEx); + add_handler(NtUserSystemParametersInfo); + add_handler(NtGetContextThread); #undef add_handler } diff --git a/src/windows-emulator/windows_emulator.cpp b/src/windows-emulator/windows_emulator.cpp index 1853df2e..ba306f93 100644 --- a/src/windows-emulator/windows_emulator.cpp +++ b/src/windows-emulator/windows_emulator.cpp @@ -175,7 +175,7 @@ namespace std::filesystem::path canonicalize_path(const std::filesystem::path& path) { - return canonical(absolute(path).parent_path()).make_preferred(); + return canonical(absolute(path)).make_preferred(); } void setup_context(windows_emulator& win_emu, const emulator_settings& settings) @@ -268,6 +268,10 @@ namespace peb.HeapDeCommitFreeBlockThreshold = 0x0000000000001000; peb.NumberOfHeaps = 0x00000000; peb.MaximumNumberOfHeaps = 0x00000010; + + peb.OSPlatformId = 2; + peb.OSMajorVersion = 0x0000000a; + peb.OSBuildNumber = 0x00006c51; }); } @@ -426,6 +430,27 @@ namespace dispatch_exception_pointers(emu, dispatcher, pointers); } + void dispatch_illegal_instruction_violation(x64_emulator& emu, const uint64_t dispatcher) + { + CONTEXT64 ctx{}; + ctx.ContextFlags = CONTEXT64_ALL; + context_frame::save(emu, ctx); + + EXCEPTION_RECORD record{}; + memset(&record, 0, sizeof(record)); + record.ExceptionCode = static_cast(STATUS_ILLEGAL_INSTRUCTION); + record.ExceptionFlags = 0; + record.ExceptionRecord = nullptr; + record.ExceptionAddress = reinterpret_cast(emu.read_instruction_pointer()); + record.NumberParameters = 0; + + EMU_EXCEPTION_POINTERS> pointers{}; + pointers.ContextRecord = reinterpret_cast::PVOID>(&ctx); + pointers.ExceptionRecord = reinterpret_cast::PVOID>(&record); + + dispatch_exception_pointers(emu, dispatcher, pointers); + } + void perform_context_switch_work(windows_emulator& win_emu) { auto& devices = win_emu.process().devices; @@ -463,7 +488,7 @@ namespace if (active_thread) { - win_emu.logger.print(color::green, "Performing thread switch...\n"); + win_emu.logger.print(color::dark_gray, "Performing thread switch...\n"); active_thread->save(emu); } @@ -524,7 +549,7 @@ namespace return false; } - bool is_object_signaled(process_context& c, const handle h) + bool is_object_signaled(process_context& c, const handle h, uint32_t current_thread_id) { const auto type = h.value.type; @@ -544,6 +569,17 @@ namespace break; } + case handle_types::mutant: + { + auto* e = c.mutants.get(h); + if (e) + { + return e->try_lock(current_thread_id); + } + + break; + } + case handle_types::thread: { const auto* t = c.threads.get(h); @@ -596,7 +632,7 @@ void emulator_thread::mark_as_ready(const NTSTATUS status) { this->pending_status = status; this->await_time = {}; - this->await_object = {}; + this->await_objects = {}; // TODO: Find out if this is correct if (this->waiting_for_alert) @@ -630,11 +666,26 @@ bool emulator_thread::is_thread_ready(windows_emulator& win_emu) return false; } - if (this->await_object.has_value()) + if (!this->await_objects.empty()) { - if (is_object_signaled(win_emu.process(), *this->await_object)) + bool all_signaled = true; + for (uint32_t i = 0; i < this->await_objects.size(); ++i) { - this->mark_as_ready(STATUS_WAIT_0); + const auto& obj = this->await_objects[i]; + + const auto signaled = is_object_signaled(win_emu.process(), obj, this->id); + all_signaled &= signaled; + + if (signaled && this->await_any) + { + this->mark_as_ready(STATUS_WAIT_0 + i); + return true; + } + } + + if (!this->await_any && all_signaled) + { + this->mark_as_ready(STATUS_SUCCESS); return true; } @@ -691,13 +742,14 @@ std::unique_ptr create_default_x64_emulator() return unicorn::create_x64_emulator(); } -windows_emulator::windows_emulator(const emulator_settings& settings, +windows_emulator::windows_emulator(emulator_settings settings, std::unique_ptr emu) : windows_emulator(std::move(emu)) { + this->silent_until_main_ = settings.silent_until_main && !settings.disable_logging; this->stdout_callback_ = std::move(settings.stdout_callback); this->use_relative_time_ = settings.use_relative_time; - this->logger.disable_output(settings.disable_logging); + this->logger.disable_output(settings.disable_logging || this->silent_until_main_); this->setup_process(settings); } @@ -727,7 +779,10 @@ void windows_emulator::setup_process(const emulator_settings& settings) context.ntdll = context.module_manager.map_module(R"(C:\Windows\System32\ntdll.dll)", this->logger); context.win32u = context.module_manager.map_module(R"(C:\Windows\System32\win32u.dll)", this->logger); - this->dispatcher_.setup(context.ntdll->exports, context.win32u->exports); + const auto ntdll_data = emu.read_memory(context.ntdll->image_base, context.ntdll->size_of_image); + const auto win32u_data = emu.read_memory(context.win32u->image_base, context.win32u->size_of_image); + + this->dispatcher_.setup(context.ntdll->exports, ntdll_data, context.win32u->exports, win32u_data); context.ldr_initialize_thunk = context.ntdll->find_export("LdrInitializeThunk"); context.rtl_user_thread_start = context.ntdll->find_export("RtlUserThreadStart"); @@ -755,6 +810,75 @@ void windows_emulator::perform_thread_switch() } } +void windows_emulator::on_instruction_execution(uint64_t address) +{ + auto& process = this->process(); + auto& thread = this->current_thread(); + + ++process.executed_instructions; + const auto thread_insts = ++thread.executed_instructions; + if (thread_insts % MAX_INSTRUCTIONS_PER_TIME_SLICE == 0) + { + this->switch_thread = true; + this->emu().stop(); + } + + process.previous_ip = process.current_ip; + process.current_ip = this->emu().read_instruction_pointer(); + + const auto is_main_exe = process.executable->is_within(address); + const auto is_interesting_call = process.executable->is_within(process.previous_ip) || is_main_exe; + + if (this->silent_until_main_ && is_main_exe) + { + this->silent_until_main_ = false; + this->logger.disable_output(false); + } + + if (!this->verbose && !this->verbose_calls && !is_interesting_call) + { + return; + } + + const auto* binary = this->process().module_manager.find_by_address(address); + + if (binary) + { + const auto export_entry = binary->address_names.find(address); + if (export_entry != binary->address_names.end()) + { + logger.print(is_interesting_call ? color::yellow : color::dark_gray, + "Executing function: %s - %s (0x%llX)\n", + binary->name.c_str(), + export_entry->second.c_str(), address); + } + else if (address == binary->entry_point) + { + logger.print(is_interesting_call ? color::yellow : color::gray, + "Executing entry point: %s (0x%llX)\n", + binary->name.c_str(), + address); + } + } + + if (!this->verbose) + { + return; + } + + auto& emu = this->emu(); + + printf( + "Inst: %16llX - RAX: %16llX - RBX: %16llX - RCX: %16llX - RDX: %16llX - R8: %16llX - R9: %16llX - RDI: %16llX - RSI: %16llX - %s\n", + address, + emu.reg(x64_register::rax), emu.reg(x64_register::rbx), + emu.reg(x64_register::rcx), + emu.reg(x64_register::rdx), emu.reg(x64_register::r8), + emu.reg(x64_register::r9), + emu.reg(x64_register::rdi), emu.reg(x64_register::rsi), + binary ? binary->name.c_str() : ""); +} + void windows_emulator::setup_hooks() { this->emu().hook_instruction(x64_hookable_instructions::syscall, [&] @@ -782,16 +906,24 @@ void windows_emulator::setup_hooks() this->emu().hook_instruction(x64_hookable_instructions::invalid, [&] { const auto ip = this->emu().read_instruction_pointer(); - printf("Invalid instruction at: 0x%zX\n", ip); + + printf("Invalid instruction at: 0x%llX\n", ip); + return instruction_hook_continuation::skip_instruction; }); this->emu().hook_interrupt([&](const int interrupt) { + if (interrupt == 6) + { + dispatch_illegal_instruction_violation(this->emu(), this->process().ki_user_exception_dispatcher); + return; + } + const auto rip = this->emu().read_instruction_pointer(); printf("Interrupt: %i 0x%zX\n", interrupt, rip); - if (this->fuzzing) + if (this->fuzzing || true) // TODO: Fix { this->process().exception_rip = rip; this->emu().stop(); @@ -829,80 +961,12 @@ void windows_emulator::setup_hooks() return memory_violation_continuation::resume; }); - this->emu().hook_memory_execution(0, std::numeric_limits::max(), - [&](const uint64_t address, const size_t, const uint64_t) - { - auto& process = this->process(); - auto& thread = this->current_thread(); - - ++process.executed_instructions; - const auto thread_insts = ++thread.executed_instructions; - if (thread_insts % MAX_INSTRUCTIONS_PER_TIME_SLICE == 0) - { - this->switch_thread = true; - this->emu().stop(); - } - - process.previous_ip = process.current_ip; - process.current_ip = this->emu().read_instruction_pointer(); - - const auto is_interesting_call = process.executable->is_within( - process.previous_ip) || process.executable->is_within(address); - - /*if (address == 0x180038B65) - { - puts("!!! DLL init failed"); - } - if (address == 0x180038A20) - { - const auto* name = this->process().module_manager.find_name( - this->emu().reg(x64_register::rcx)); - printf("!!! DLL init: %s\n", name); - }*/ - - if (!this->verbose && !this->verbose_calls && !is_interesting_call) - { - return; - } - - const auto* binary = this->process().module_manager.find_by_address(address); - - if (binary) - { - const auto export_entry = binary->address_names.find(address); - if (export_entry != binary->address_names.end()) - { - logger.print(is_interesting_call ? color::yellow : color::dark_gray, - "Executing function: %s - %s (0x%llX)\n", - binary->name.c_str(), - export_entry->second.c_str(), address); - } - else if (address == binary->entry_point) - { - logger.print(is_interesting_call ? color::yellow : color::gray, - "Executing entry point: %s (0x%llX)\n", - binary->name.c_str(), - address); - } - } - - if (!this->verbose) - { - return; - } - - auto& emu = this->emu(); - - printf( - "Inst: %16zX - RAX: %16zX - RBX: %16zX - RCX: %16zX - RDX: %16zX - R8: %16zX - R9: %16zX - RDI: %16zX - RSI: %16zX - %s\n", - address, - emu.reg(x64_register::rax), emu.reg(x64_register::rbx), - emu.reg(x64_register::rcx), - emu.reg(x64_register::rdx), emu.reg(x64_register::r8), - emu.reg(x64_register::r9), - emu.reg(x64_register::rdi), emu.reg(x64_register::rsi), - binary ? binary->name.c_str() : ""); - }); + this->emu().hook_memory_execution( + 0, std::numeric_limits::max(), + [&](const uint64_t address, const size_t, const uint64_t) + { + this->on_instruction_execution(address); + }); } void windows_emulator::start(std::chrono::nanoseconds timeout, size_t count) diff --git a/src/windows-emulator/windows_emulator.hpp b/src/windows-emulator/windows_emulator.hpp index e5f59f89..a1f9d3c5 100644 --- a/src/windows-emulator/windows_emulator.hpp +++ b/src/windows-emulator/windows_emulator.hpp @@ -18,6 +18,7 @@ struct emulator_settings std::vector arguments{}; std::function stdout_callback{}; bool disable_logging{false}; + bool silent_until_main{false}; bool use_relative_time{false}; }; @@ -25,7 +26,7 @@ class windows_emulator { public: windows_emulator(std::unique_ptr emu = create_default_x64_emulator()); - windows_emulator(const emulator_settings& settings, + windows_emulator(emulator_settings settings, std::unique_ptr emu = create_default_x64_emulator()); windows_emulator(windows_emulator&&) = delete; @@ -113,6 +114,7 @@ public: private: bool use_relative_time_{false}; + bool silent_until_main_{false}; std::unique_ptr emu_{}; std::vector syscall_hooks_{}; std::function stdout_callback_{}; @@ -125,4 +127,5 @@ private: void setup_hooks(); void setup_process(const emulator_settings& settings); + void on_instruction_execution(uint64_t address); };