From 09ad463027eef876d81744927c30111e19d15502 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sat, 9 Aug 2025 14:06:06 +0200 Subject: [PATCH 1/9] Support environment analysis --- src/analyzer/main.cpp | 140 ++++++++++++++++++++++++++++++++--- src/emulator/scoped_hook.hpp | 22 ++++++ 2 files changed, 151 insertions(+), 11 deletions(-) diff --git a/src/analyzer/main.cpp b/src/analyzer/main.cpp index 9e1e4125..d8a6c832 100644 --- a/src/analyzer/main.cpp +++ b/src/analyzer/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "object_watching.hpp" #include "snapshot.hpp" @@ -57,6 +58,113 @@ namespace } } +#if !defined(__GNUC__) || defined(__clang__) + struct analysis_state + { + windows_emulator& win_emu_; + scoped_hook env_data_hook_; + scoped_hook env_ptr_hook_; + scoped_hook params_hook_; + std::set> modules_; + bool verbose_; + + analysis_state(windows_emulator& win_emu, std::set> modules, const bool verbose) + : win_emu_(win_emu), + env_data_hook_(win_emu.emu()), + env_ptr_hook_(win_emu.emu()), + params_hook_(win_emu.emu()), + modules_(std::move(modules)), + verbose_(verbose) + { + } + }; + + emulator_object get_process_params(windows_emulator& win_emu) + { + const auto peb = win_emu.process.peb.read(); + return {win_emu.emu(), peb.ProcessParameters}; + } + + uint64_t get_environment_ptr(windows_emulator& win_emu) + { + const auto process_params = get_process_params(win_emu); + return process_params.read().Environment; + } + + size_t get_environment_size(const x86_64_emulator& emu, const uint64_t env) + { + std::array data{}; + std::array empty{}; + + for (size_t i = 0; i < 0x100000; ++i) + { + if (!emu.try_read_memory(env + i, data.data(), data.size())) + { + return i; + } + + if (data == empty) + { + return i + data.size(); + } + } + + return 0; + } + + emulator_hook* install_env_hook(const std::shared_ptr& state) + { + const auto process_params = get_process_params(state->win_emu_); + + auto install_env_access_hook = [state] { + const auto env_ptr = get_environment_ptr(state->win_emu_); + const auto env_size = get_environment_size(state->win_emu_.emu(), env_ptr); + if (!env_size) + { + state->env_data_hook_.remove(); + return; + } + + auto hook_handler = [state, env_ptr](const uint64_t address, const void*, const size_t size) { + const auto rip = state->win_emu_.emu().read_instruction_pointer(); + const auto* mod = state->win_emu_.mod_manager.find_by_address(rip); + const auto is_main_access = + mod == state->win_emu_.mod_manager.executable || state->modules_.contains(mod->name); + + if (!is_main_access && !state->verbose_) + { + return; + } + + const auto offset = address - env_ptr; + const auto* mod_name = mod ? mod->name.c_str() : ""; + state->win_emu_.log.print(is_main_access ? color::green : color::dark_gray, + "Environment access: 0x%" PRIx64 " (0x%zX) at 0x%" PRIx64 " (%s)\n", offset, + size, rip, mod_name); + }; + + state->env_data_hook_ = state->win_emu_.emu().hook_memory_read(env_ptr, env_size, std::move(hook_handler)); + }; + + install_env_access_hook(); + + auto& win_emu = state->win_emu_; + return state->win_emu_.emu().hook_memory_write( + process_params.value() + offsetof(RTL_USER_PROCESS_PARAMETERS64, Environment), 0x8, + [&win_emu, install = std::move(install_env_access_hook)](const uint64_t address, const void*, size_t) { + const auto new_process_params = get_process_params(win_emu); + + const auto target_address = + new_process_params.value() + offsetof(RTL_USER_PROCESS_PARAMETERS64, Environment); + + if (address == target_address) + { + install(); + } + }); + } +#endif + void watch_system_objects(windows_emulator& win_emu, const std::set>& modules, const bool verbose) { @@ -72,23 +180,33 @@ namespace watch_object(win_emu, modules, emulator_object{win_emu.emu(), kusd_mmio::address()}, verbose); - auto* params_hook = watch_object(win_emu, modules, win_emu.process.process_params, verbose); + auto state = std::make_shared(win_emu, modules, verbose); + + state->params_hook_ = watch_object(win_emu, modules, win_emu.process.process_params, verbose); + + const auto update_env_hook = [state] { + state->env_ptr_hook_ = install_env_hook(state); // + }; + + update_env_hook(); win_emu.emu().hook_memory_write( win_emu.process.peb.value() + offsetof(PEB64, ProcessParameters), 0x8, - [&win_emu, verbose, params_hook, modules](const uint64_t address, const void*, size_t) mutable { - const auto target_address = win_emu.process.peb.value() + offsetof(PEB64, ProcessParameters); + [state, update_env = std::move(update_env_hook)](const uint64_t address, const void*, size_t) { + const auto target_address = state->win_emu_.process.peb.value() + offsetof(PEB64, ProcessParameters); - if (address == target_address) + if (address != target_address) { - const emulator_object obj{ - win_emu.emu(), - win_emu.emu().read_memory(address), - }; - - win_emu.emu().delete_hook(params_hook); - params_hook = watch_object(win_emu, modules, obj, verbose); + return; } + + const emulator_object obj{ + state->win_emu_.emu(), + state->win_emu_.emu().read_memory(address), + }; + + state->params_hook_ = watch_object(state->win_emu_, state->modules_, obj, state->verbose_); + update_env(); }); #endif } diff --git a/src/emulator/scoped_hook.hpp b/src/emulator/scoped_hook.hpp index 2fd10985..105466fb 100644 --- a/src/emulator/scoped_hook.hpp +++ b/src/emulator/scoped_hook.hpp @@ -6,6 +6,11 @@ class scoped_hook public: scoped_hook() = default; + scoped_hook(emulator& emu) + : emu_(&emu) + { + } + scoped_hook(emulator& emu, emulator_hook* hook) : scoped_hook(emu, std::vector{hook}) { @@ -44,6 +49,23 @@ class scoped_hook return *this; } + scoped_hook& operator=(emulator_hook* hook) + { + this->replace({hook}); + return *this; + } + + void replace(std::vector hooks) + { + if (!this->emu_) + { + throw std::runtime_error("Invalid scoped hook"); + } + + this->remove(); + this->hooks_ = std::move(hooks); + } + void remove() { auto hooks = std::move(this->hooks_); From eb6d352a81087e9d97730dd1da9105950cc04324 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sat, 9 Aug 2025 17:07:33 +0200 Subject: [PATCH 2/9] Track import access --- src/analyzer/analysis.cpp | 118 +++++++++++++++++- src/analyzer/analysis.hpp | 14 ++- src/windows-emulator/module/mapped_module.hpp | 8 ++ .../module/module_mapping.cpp | 59 +++++++++ 4 files changed, 195 insertions(+), 4 deletions(-) diff --git a/src/analyzer/analysis.cpp b/src/analyzer/analysis.cpp index f8a307ac..12557045 100644 --- a/src/analyzer/analysis.cpp +++ b/src/analyzer/analysis.cpp @@ -124,7 +124,7 @@ namespace } template - void print_arg_as_string(windows_emulator& win_emu, size_t index) + void print_arg_as_string(windows_emulator& win_emu, const size_t index) { const auto var_ptr = get_function_argument(win_emu.emu(), index); if (var_ptr) @@ -152,9 +152,60 @@ namespace } } + bool is_thread_alive(const analysis_context& c, const uint32_t thread_id) + { + for (const auto& t : c.win_emu->process.threads | std::views::values) + { + if (t.id == thread_id) + { + return true; + } + } + + return false; + } + + void update_import_access(analysis_context& c, const uint64_t address) + { + if (c.accessed_imports.empty()) + { + return; + } + + auto entry = c.accessed_imports.find(address); + if (entry != c.accessed_imports.end()) + { + c.accessed_imports.erase(entry); + } + + const auto& t = c.win_emu->current_thread(); + for (entry = c.accessed_imports.begin(); entry != c.accessed_imports.end();) + { + auto& a = entry->second; + + constexpr auto inst_delay = 100u; + const auto is_same_thread = t.id == a.thread_id; + const auto execution_delay_reached = + is_same_thread && a.access_inst_count + inst_delay <= t.executed_instructions; + + if (!execution_delay_reached && is_thread_alive(c, a.thread_id)) + { + ++entry; + continue; + } + + c.win_emu->log.print(color::green, "Import read access without execution: %s (%s) at 0x%" PRIx64 " (%s)\n", + a.import_name.c_str(), a.import_module.c_str(), a.access_rip, + a.accessor_module.c_str()); + + entry = c.accessed_imports.erase(entry); + } + } + void handle_instruction(analysis_context& c, const uint64_t address) { auto& win_emu = *c.win_emu; + update_import_access(c, address); #ifdef OS_EMSCRIPTEN if ((win_emu.get_executed_instructions() % 0x20000) == 0) @@ -293,6 +344,65 @@ namespace c.win_emu->log.info("%.*s%s", static_cast(data.size()), data.data(), data.ends_with("\n") ? "" : "\n"); } } + + void watch_import_table(analysis_context& c) + { + c.win_emu->setup_process_if_necessary(); + + const auto& import_list = c.win_emu->mod_manager.executable->imports; + if (import_list.empty()) + { + return; + } + + auto min = std::numeric_limits::max(); + auto max = std::numeric_limits::min(); + + for (const auto& imports : import_list | std::views::values) + { + for (const auto& import : imports) + { + min = std::min(import.address, min); + max = std::max(import.address, max); + } + } + + c.win_emu->emu().hook_memory_read(min, max - min, [&c](const uint64_t address, const void*, size_t) { + const auto& import_list = c.win_emu->mod_manager.executable->imports; + + const auto rip = c.win_emu->emu().read_instruction_pointer(); + if (!c.win_emu->mod_manager.executable->is_within(rip)) + { + return; + } + + for (const auto& [module_name, imports] : import_list) + { + for (const auto& import : imports) + { + if (address != import.address) + { + continue; + } + + const auto function_address = c.win_emu->emu().read_memory(address); + + auto& access = c.accessed_imports[function_address]; + access.access_rip = c.win_emu->emu().read_instruction_pointer(); + access.accessor_module = c.win_emu->mod_manager.find_name(access.access_rip); + + access.import_name = import.name; + access.import_module = module_name; + + const auto& t = c.win_emu->current_thread(); + access.thread_id = t.id; + access.access_inst_count = t.executed_instructions; + + return; + } + } + }); + } } void register_analysis_callbacks(analysis_context& c) @@ -317,9 +427,11 @@ void register_analysis_callbacks(analysis_context& c) cb.on_generic_access = make_callback(c, handle_generic_access); cb.on_generic_activity = make_callback(c, handle_generic_activity); cb.on_suspicious_activity = make_callback(c, handle_suspicious_activity); + + watch_import_table(c); } -mapped_module* get_module_if_interesting(module_manager& manager, const string_set& modules, uint64_t address) +mapped_module* get_module_if_interesting(module_manager& manager, const string_set& modules, const uint64_t address) { if (manager.executable->is_within(address)) { @@ -338,4 +450,4 @@ mapped_module* get_module_if_interesting(module_manager& manager, const string_s } return nullptr; -} \ No newline at end of file +} diff --git a/src/analyzer/analysis.hpp b/src/analyzer/analysis.hpp index 1af666ba..57d50661 100644 --- a/src/analyzer/analysis.hpp +++ b/src/analyzer/analysis.hpp @@ -20,6 +20,16 @@ struct analysis_settings string_set ignored_functions{}; }; +struct accessed_import +{ + uint32_t thread_id{}; + uint64_t access_rip{}; + uint64_t access_inst_count{}; + std::string accessor_module{}; + std::string import_name{}; + std::string import_module{}; +}; + struct analysis_context { const analysis_settings* settings{}; @@ -27,7 +37,9 @@ struct analysis_context std::string output{}; bool has_reached_main{false}; + + std::map accessed_imports{}; }; void register_analysis_callbacks(analysis_context& c); -mapped_module* get_module_if_interesting(module_manager& manager, const string_set& modules, uint64_t address); \ No newline at end of file +mapped_module* get_module_if_interesting(module_manager& manager, const string_set& modules, uint64_t address); diff --git a/src/windows-emulator/module/mapped_module.hpp b/src/windows-emulator/module/mapped_module.hpp index 2019243f..fbd4b034 100644 --- a/src/windows-emulator/module/mapped_module.hpp +++ b/src/windows-emulator/module/mapped_module.hpp @@ -9,7 +9,14 @@ struct exported_symbol uint64_t address{}; }; +struct imported_symbol +{ + std::string name{}; + uint64_t address{}; +}; + using exported_symbols = std::vector; +using imported_symbols = std::map>; using address_name_mapping = std::map; struct mapped_section @@ -28,6 +35,7 @@ struct mapped_module uint64_t entry_point{}; exported_symbols exports{}; + imported_symbols imports{}; address_name_mapping address_names{}; std::vector sections{}; diff --git a/src/windows-emulator/module/module_mapping.cpp b/src/windows-emulator/module/module_mapping.cpp index 036088ed..942b0cd9 100644 --- a/src/windows-emulator/module/module_mapping.cpp +++ b/src/windows-emulator/module/module_mapping.cpp @@ -29,6 +29,64 @@ namespace return mem; } + void collect_imports(mapped_module& binary, const utils::safe_buffer_accessor buffer, + const PEOptionalHeader_t& optional_header) + { + const auto& import_directory_entry = optional_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + if (import_directory_entry.VirtualAddress == 0 || import_directory_entry.Size == 0) + { + return; + } + + const auto import_descriptors = buffer.as(import_directory_entry.VirtualAddress); + + for (size_t i = 0;; ++i) + { + const auto descriptor = import_descriptors.get(i); + if (!descriptor.Name) + { + break; + } + + const auto module_name = buffer.as_string(descriptor.Name); + auto& imports = binary.imports[module_name]; + + auto original_thunk_data = buffer.as(descriptor.FirstThunk); + + if (descriptor.OriginalFirstThunk) + { + original_thunk_data = buffer.as(descriptor.OriginalFirstThunk); + } + + for (size_t j = 0;; ++j) + { + const auto original_thunk = original_thunk_data.get(j); + if (!original_thunk.u1.AddressOfData) + { + break; + } + + imported_symbol sym{}; + + const auto thunk_rva = descriptor.FirstThunk // + + sizeof(IMAGE_THUNK_DATA) * j // + + offsetof(IMAGE_THUNK_DATA, u1.Function); + sym.address = thunk_rva + binary.image_base; + + if (IMAGE_SNAP_BY_ORDINAL(original_thunk.u1.Ordinal)) + { + sym.name = "#" + std::to_string(original_thunk.u1.Ordinal); + } + else + { + sym.name = buffer.as_string(original_thunk.u1.AddressOfData + offsetof(IMAGE_IMPORT_BY_NAME, Name)); + } + + imports.push_back(std::move(sym)); + } + } + } + void collect_exports(mapped_module& binary, const utils::safe_buffer_accessor buffer, const PEOptionalHeader_t& optional_header) { @@ -248,6 +306,7 @@ mapped_module map_module_from_data(memory_manager& memory, const std::span Date: Sat, 9 Aug 2025 17:20:22 +0200 Subject: [PATCH 3/9] Add missing types --- src/common/platform/win_pefile.hpp | 41 +++++++++++++++++++ .../module/module_mapping.cpp | 12 +++--- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/common/platform/win_pefile.hpp b/src/common/platform/win_pefile.hpp index d73222af..b154a8b5 100644 --- a/src/common/platform/win_pefile.hpp +++ b/src/common/platform/win_pefile.hpp @@ -283,6 +283,47 @@ typedef struct _IMAGE_BASE_RELOCATION // WORD TypeOffset[1]; } IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION; +#define IMAGE_ORDINAL_FLAG64 0x8000000000000000 +#define IMAGE_ORDINAL_FLAG32 0x80000000 +#define IMAGE_ORDINAL64(Ordinal) (Ordinal & 0xffff) +#define IMAGE_ORDINAL32(Ordinal) (Ordinal & 0xffff) +#define IMAGE_SNAP_BY_ORDINAL64(Ordinal) ((Ordinal & IMAGE_ORDINAL_FLAG64) != 0) +#define IMAGE_SNAP_BY_ORDINAL32(Ordinal) ((Ordinal & IMAGE_ORDINAL_FLAG32) != 0) + +typedef struct _IMAGE_IMPORT_BY_NAME +{ + WORD Hint; + CHAR Name[1]; +} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; + +typedef struct _IMAGE_IMPORT_DESCRIPTOR +{ + union + { + DWORD Characteristics; // 0 for terminating null import descriptor + DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) + } DUMMYUNIONNAME; + DWORD TimeDateStamp; // 0 if not bound, + // -1 if bound, and real date\time stamp + // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) + // O.W. date/time stamp of DLL bound to (Old BIND) + + DWORD ForwarderChain; // -1 if no forwarders + DWORD Name; + DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) +} IMAGE_IMPORT_DESCRIPTOR; + +typedef struct _IMAGE_THUNK_DATA64 +{ + union + { + ULONGLONG ForwarderString; // PBYTE + ULONGLONG Function; // PDWORD + ULONGLONG Ordinal; + ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME + } u1; +} IMAGE_THUNK_DATA64; + #endif template diff --git a/src/windows-emulator/module/module_mapping.cpp b/src/windows-emulator/module/module_mapping.cpp index 942b0cd9..bfedf783 100644 --- a/src/windows-emulator/module/module_mapping.cpp +++ b/src/windows-emulator/module/module_mapping.cpp @@ -51,11 +51,11 @@ namespace const auto module_name = buffer.as_string(descriptor.Name); auto& imports = binary.imports[module_name]; - auto original_thunk_data = buffer.as(descriptor.FirstThunk); + auto original_thunk_data = buffer.as(descriptor.FirstThunk); if (descriptor.OriginalFirstThunk) { - original_thunk_data = buffer.as(descriptor.OriginalFirstThunk); + original_thunk_data = buffer.as(descriptor.OriginalFirstThunk); } for (size_t j = 0;; ++j) @@ -68,12 +68,12 @@ namespace imported_symbol sym{}; - const auto thunk_rva = descriptor.FirstThunk // - + sizeof(IMAGE_THUNK_DATA) * j // - + offsetof(IMAGE_THUNK_DATA, u1.Function); + const auto thunk_rva = descriptor.FirstThunk // + + sizeof(IMAGE_THUNK_DATA64) * j // + + offsetof(IMAGE_THUNK_DATA64, u1.Function); sym.address = thunk_rva + binary.image_base; - if (IMAGE_SNAP_BY_ORDINAL(original_thunk.u1.Ordinal)) + if (IMAGE_SNAP_BY_ORDINAL64(original_thunk.u1.Ordinal)) { sym.name = "#" + std::to_string(original_thunk.u1.Ordinal); } From eb07148bfddb26632b4b2eec3bb635c95fa50827 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sat, 9 Aug 2025 17:23:58 +0200 Subject: [PATCH 4/9] Support hook deletion during hook deletion --- src/backends/icicle-emulator/icicle_x86_64_emulator.cpp | 2 ++ src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp b/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp index cb759e3a..b63b8aa6 100644 --- a/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp +++ b/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp @@ -377,7 +377,9 @@ namespace icicle } icicle_remove_hook(this->emu_, id); + const auto obj = std::move(entry->second); this->hooks_.erase(entry); + (void)obj; } void serialize_state(utils::buffer_serializer& buffer, const bool is_snapshot) const override diff --git a/src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp b/src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp index 52629f76..000027e5 100644 --- a/src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp +++ b/src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp @@ -605,7 +605,9 @@ namespace unicorn if (entry != this->hooks_.end()) { + const auto obj = std::move(*entry); this->hooks_.erase(entry); + (void)obj; } } From 292fc6ce67507cc8d845da1600520980b22c86af Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sat, 9 Aug 2025 17:38:20 +0200 Subject: [PATCH 5/9] Fix compilation --- .../icicle-emulator/icicle_x86_64_emulator.cpp | 3 +++ .../unicorn-emulator/unicorn_x86_64_emulator.cpp | 2 +- src/common/platform/win_pefile.hpp | 10 +++++----- src/common/utils/object.hpp | 8 ++++++++ src/windows-emulator/module/module_mapping.cpp | 4 ++-- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp b/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp index b63b8aa6..b01ed360 100644 --- a/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp +++ b/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp @@ -103,6 +103,9 @@ namespace icicle ~icicle_x86_64_emulator() override { + reset_object_with_delayed_destruction(this->hooks_); + reset_object_with_delayed_destruction(this->storage_); + if (this->emu_) { icicle_destroy_emulator(this->emu_); diff --git a/src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp b/src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp index 000027e5..0ffa5b46 100644 --- a/src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp +++ b/src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp @@ -206,7 +206,7 @@ namespace unicorn ~unicorn_x86_64_emulator() override { - this->hooks_.clear(); + reset_object_with_delayed_destruction(this->hooks_); uc_close(this->uc_); } diff --git a/src/common/platform/win_pefile.hpp b/src/common/platform/win_pefile.hpp index b154a8b5..d2c83b60 100644 --- a/src/common/platform/win_pefile.hpp +++ b/src/common/platform/win_pefile.hpp @@ -298,11 +298,11 @@ typedef struct _IMAGE_IMPORT_BY_NAME typedef struct _IMAGE_IMPORT_DESCRIPTOR { - union - { - DWORD Characteristics; // 0 for terminating null import descriptor - DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) - } DUMMYUNIONNAME; + // union + //{ + // DWORD Characteristics; // 0 for terminating null import descriptor + DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) + //} DUMMYUNIONNAME; DWORD TimeDateStamp; // 0 if not bound, // -1 if bound, and real date\time stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) diff --git a/src/common/utils/object.hpp b/src/common/utils/object.hpp index e0cc9a84..e1f206f7 100644 --- a/src/common/utils/object.hpp +++ b/src/common/utils/object.hpp @@ -12,4 +12,12 @@ namespace utils object& operator=(object&&) = default; object& operator=(const object&) = default; }; + + template + void reset_object_with_delayed_destruction(T& obj) + { + T new_obj{}; + const auto old = std::move(obj); + obj = std::move(new_obj); + } } diff --git a/src/windows-emulator/module/module_mapping.cpp b/src/windows-emulator/module/module_mapping.cpp index bfedf783..496d89b8 100644 --- a/src/windows-emulator/module/module_mapping.cpp +++ b/src/windows-emulator/module/module_mapping.cpp @@ -52,7 +52,6 @@ namespace auto& imports = binary.imports[module_name]; auto original_thunk_data = buffer.as(descriptor.FirstThunk); - if (descriptor.OriginalFirstThunk) { original_thunk_data = buffer.as(descriptor.OriginalFirstThunk); @@ -79,7 +78,8 @@ namespace } else { - sym.name = buffer.as_string(original_thunk.u1.AddressOfData + offsetof(IMAGE_IMPORT_BY_NAME, Name)); + sym.name = buffer.as_string( + static_cast(original_thunk.u1.AddressOfData + offsetof(IMAGE_IMPORT_BY_NAME, Name))); } imports.push_back(std::move(sym)); From 2abe1737f3e5b8c224eee749c63d8663cd9bbabc Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sat, 9 Aug 2025 17:42:33 +0200 Subject: [PATCH 6/9] Fix android compilation --- src/windows-emulator/module/module_mapping.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/windows-emulator/module/module_mapping.cpp b/src/windows-emulator/module/module_mapping.cpp index 496d89b8..d58aaf00 100644 --- a/src/windows-emulator/module/module_mapping.cpp +++ b/src/windows-emulator/module/module_mapping.cpp @@ -67,9 +67,8 @@ namespace imported_symbol sym{}; - const auto thunk_rva = descriptor.FirstThunk // - + sizeof(IMAGE_THUNK_DATA64) * j // - + offsetof(IMAGE_THUNK_DATA64, u1.Function); + static_assert(sizeof(IMAGE_THUNK_DATA64) == sizeof(uint64_t)); + const auto thunk_rva = descriptor.FirstThunk + sizeof(IMAGE_THUNK_DATA64) * j; sym.address = thunk_rva + binary.image_base; if (IMAGE_SNAP_BY_ORDINAL64(original_thunk.u1.Ordinal)) From 3b9320fd6229e31ee19cf3ef066e851ae8e23fdd Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sat, 9 Aug 2025 18:02:37 +0200 Subject: [PATCH 7/9] Better import access tracking --- src/analyzer/analysis.cpp | 29 ++++++++++++++++------------- src/analyzer/analysis.hpp | 3 ++- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/analyzer/analysis.cpp b/src/analyzer/analysis.cpp index 12557045..67c45408 100644 --- a/src/analyzer/analysis.cpp +++ b/src/analyzer/analysis.cpp @@ -172,19 +172,19 @@ namespace return; } - auto entry = c.accessed_imports.find(address); - if (entry != c.accessed_imports.end()) - { - c.accessed_imports.erase(entry); - } - const auto& t = c.win_emu->current_thread(); - for (entry = c.accessed_imports.begin(); entry != c.accessed_imports.end();) + for (auto entry = c.accessed_imports.begin(); entry != c.accessed_imports.end();) { - auto& a = entry->second; + auto& a = *entry; + const auto is_same_thread = t.id == a.thread_id; + + if (is_same_thread && address == a.address) + { + entry = c.accessed_imports.erase(entry); + continue; + } constexpr auto inst_delay = 100u; - const auto is_same_thread = t.id == a.thread_id; const auto execution_delay_reached = is_same_thread && a.access_inst_count + inst_delay <= t.executed_instructions; @@ -273,8 +273,8 @@ namespace const auto* mod_name = win_emu.mod_manager.find_name(return_address); win_emu.log.print(is_interesting_call ? color::yellow : color::dark_gray, - "Executing function: %s - %s (0x%" PRIx64 ") via (0x%" PRIx64 ") %s\n", - binary->name.c_str(), export_entry->second.c_str(), address, return_address, mod_name); + "Executing function: %s (%s) (0x%" PRIx64 ") via (0x%" PRIx64 ") %s\n", + export_entry->second.c_str(), binary->name.c_str(), address, return_address, mod_name); if (is_interesting_call) { @@ -385,9 +385,10 @@ namespace continue; } - const auto function_address = c.win_emu->emu().read_memory(address); + accessed_import access{}; + + access.address = c.win_emu->emu().read_memory(address); - auto& access = c.accessed_imports[function_address]; access.access_rip = c.win_emu->emu().read_instruction_pointer(); access.accessor_module = c.win_emu->mod_manager.find_name(access.access_rip); @@ -398,6 +399,8 @@ namespace access.thread_id = t.id; access.access_inst_count = t.executed_instructions; + c.accessed_imports.push_back(std::move(access)); + return; } } diff --git a/src/analyzer/analysis.hpp b/src/analyzer/analysis.hpp index 57d50661..eba795ca 100644 --- a/src/analyzer/analysis.hpp +++ b/src/analyzer/analysis.hpp @@ -22,6 +22,7 @@ struct analysis_settings struct accessed_import { + uint64_t address{}; uint32_t thread_id{}; uint64_t access_rip{}; uint64_t access_inst_count{}; @@ -38,7 +39,7 @@ struct analysis_context std::string output{}; bool has_reached_main{false}; - std::map accessed_imports{}; + std::vector accessed_imports{}; }; void register_analysis_callbacks(analysis_context& c); From b3bdfc9d6b2af91de002971b0ef6a9660c9705f2 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sat, 9 Aug 2025 20:44:09 +0200 Subject: [PATCH 8/9] Support hooking/unhooking within hooks for icicle --- .../icicle-bridge/src/icicle.rs | 125 ++++++++-- .../icicle-emulator/icicle-bridge/src/lib.rs | 8 + .../icicle_x86_64_emulator.cpp | 232 ++++++++++++++---- 3 files changed, 297 insertions(+), 68 deletions(-) diff --git a/src/backends/icicle-emulator/icicle-bridge/src/icicle.rs b/src/backends/icicle-emulator/icicle-bridge/src/icicle.rs index 660ade77..3eec79dd 100644 --- a/src/backends/icicle-emulator/icicle-bridge/src/icicle.rs +++ b/src/backends/icicle-emulator/icicle-bridge/src/icicle.rs @@ -1,5 +1,6 @@ use icicle_cpu::ExceptionCode; use icicle_cpu::ValueSource; +use std::collections::HashSet; use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::registers; @@ -73,31 +74,96 @@ fn qualify_hook_id(hook_id: u32, hook_type: HookType) -> u32 { pub struct HookContainer { hook_id: u32, + is_iterating: bool, hooks: HashMap>, + hooks_to_add: HashMap>, + hooks_to_remove: HashSet, } impl HookContainer { pub fn new() -> Self { Self { hook_id: 0, + is_iterating: false, hooks: HashMap::new(), + hooks_to_add: HashMap::new(), + hooks_to_remove: HashSet::new(), } } pub fn add_hook(&mut self, callback: Box) -> u32 { self.hook_id += 1; let id = self.hook_id; - self.hooks.insert(id, callback); + + if self.is_iterating { + self.hooks_to_add.insert(id, callback); + } else { + self.hooks.insert(id, callback); + } return id; } - pub fn get_hooks(&self) -> &HashMap> { - return &self.hooks; + pub fn for_each_hook(&mut self, mut callback: F) + where + F: FnMut(&Func), + { + let was_iterating = self.do_pre_access_work(); + + for (_, func) in &self.hooks { + callback(func.as_ref()); + } + + self.do_post_access_work(was_iterating); + } + + pub fn access_hook(&mut self, id: u32, mut callback: F) + where + F: FnMut(&Func), + { + let was_iterating = self.do_pre_access_work(); + + let hook = self.hooks.get(&id); + if hook.is_some() { + callback(hook.unwrap().as_ref()); + } + + self.do_post_access_work(was_iterating); + } + + pub fn is_empty(&self) -> bool { + return self.hooks.is_empty(); } pub fn remove_hook(&mut self, id: u32) { - self.hooks.remove(&id); + if self.is_iterating { + self.hooks_to_remove.insert(id); + } else { + self.hooks.remove(&id); + } + } + + fn do_pre_access_work(&mut self) -> bool { + let was_iterating = self.is_iterating; + self.is_iterating = true; + return was_iterating; + } + + fn do_post_access_work(&mut self, was_iterating: bool) { + self.is_iterating = was_iterating; + if self.is_iterating { + return; + } + + let to_remove = std::mem::take(&mut self.hooks_to_remove); + for id in &to_remove { + self.hooks.remove(&id); + } + + let to_add = std::mem::take(&mut self.hooks_to_add); + for (id, func) in to_add { + self.hooks.insert(id, func); + } } } @@ -157,6 +223,7 @@ struct ExecutionHooks { specific_hooks: HookContainer, block_hooks: HookContainer, address_mapping: HashMap>, + one_time_callbacks: Vec>, } impl ExecutionHooks { @@ -167,31 +234,38 @@ impl ExecutionHooks { specific_hooks: HookContainer::new(), block_hooks: HookContainer::new(), address_mapping: HashMap::new(), + one_time_callbacks: Vec::new(), } } - fn run_hooks(&self, address: u64) { - for (_key, func) in self.generic_hooks.get_hooks() { - func(address); + fn run_hooks(&mut self, address: u64) { + if !self.one_time_callbacks.is_empty() { + let callbacks = std::mem::take(&mut self.one_time_callbacks); + for cb in callbacks { + cb.as_ref()(); + } } + self.generic_hooks.for_each_hook(|func| { + func(address); + }); + let mapping = self.address_mapping.get(&address); if mapping.is_none() { return; } for id in mapping.unwrap() { - let func = self.specific_hooks.get_hooks().get(&id); - if func.is_some() { - func.unwrap()(address); - } + self.specific_hooks.access_hook(*id, |func| { + func(address); + }); } } pub fn on_block(&mut self, address: u64, instructions: u64) { - for (_key, func) in self.block_hooks.get_hooks() { + self.block_hooks.for_each_hook(|func| { func(address, instructions); - } + }); } pub fn execute(&mut self, cpu: &mut icicle_cpu::Cpu, address: u64) { @@ -224,6 +298,10 @@ impl ExecutionHooks { return id; } + pub fn schedule(&mut self, callback: Box) { + self.one_time_callbacks.push(callback); + } + pub fn remove_generic_hook(&mut self, id: u32) { self.generic_hooks.remove_hook(id); } @@ -368,10 +446,10 @@ impl IcicleEmulator { } } - fn handle_interrupt(&self, code: i32) -> bool { - for (_key, func) in self.interrupt_hooks.get_hooks() { + fn handle_interrupt(&mut self, code: i32) -> bool { + self.interrupt_hooks.for_each_hook(|func| { func(code); - } + }); return true; } @@ -393,16 +471,15 @@ impl IcicleEmulator { } fn handle_violation(&mut self, address: u64, permission: u8, unmapped: bool) -> bool { - let hooks = &self.violation_hooks.get_hooks(); - if hooks.is_empty() { + if self.violation_hooks.is_empty() { return false; } let mut continue_execution = true; - for (_key, func) in self.violation_hooks.get_hooks() { + self.violation_hooks.for_each_hook(|func| { continue_execution &= func(address, permission, unmapped); - } + }); return continue_execution; } @@ -412,9 +489,9 @@ impl IcicleEmulator { return self.handle_interrupt(value as i32); } - for (_key, func) in self.syscall_hooks.get_hooks() { + self.syscall_hooks.for_each_hook(|func| { func(); - } + }); self.vm.cpu.write_pc(self.vm.cpu.read_pc() + 2); return true; @@ -521,6 +598,10 @@ impl IcicleEmulator { } } + pub fn run_on_next_instruction(&mut self, callback: Box) { + self.execution_hooks.borrow_mut().schedule(callback); + } + pub fn map_memory(&mut self, address: u64, length: u64, permissions: u8) -> bool { const MAPPING_PERMISSIONS: u8 = icicle_vm::cpu::mem::perm::MAP | icicle_vm::cpu::mem::perm::INIT diff --git a/src/backends/icicle-emulator/icicle-bridge/src/lib.rs b/src/backends/icicle-emulator/icicle-bridge/src/lib.rs index 74e07b54..c8efe281 100644 --- a/src/backends/icicle-emulator/icicle-bridge/src/lib.rs +++ b/src/backends/icicle-emulator/icicle-bridge/src/lib.rs @@ -307,6 +307,14 @@ pub fn icicle_remove_hook(ptr: *mut c_void, id: u32) { } } +#[unsafe(no_mangle)] +pub fn icicle_run_on_next_instruction(ptr: *mut c_void, callback: RawFunction, data: *mut c_void) { + unsafe { + let emulator = &mut *(ptr as *mut IcicleEmulator); + emulator.run_on_next_instruction(Box::new(move || callback(data))); + } +} + #[unsafe(no_mangle)] pub fn icicle_read_register( ptr: *mut c_void, diff --git a/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp b/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp index b01ed360..20ed80bb 100644 --- a/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp +++ b/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp @@ -1,7 +1,9 @@ #define ICICLE_EMULATOR_IMPL #include "icicle_x86_64_emulator.hpp" +#include #include +#include using icicle_emulator = struct icicle_emulator_; @@ -44,6 +46,7 @@ extern "C" void icicle_start(icicle_emulator*, size_t count); void icicle_stop(icicle_emulator*); void icicle_destroy_emulator(icicle_emulator*); + void icicle_run_on_next_instruction(icicle_emulator*, raw_func* callback, void* data); } namespace icicle @@ -58,24 +61,35 @@ namespace icicle } } - emulator_hook* wrap_hook(const uint32_t id) - { - return reinterpret_cast(static_cast(id)); - } - template struct function_object : utils::object { + bool* hook_state{}; std::function func{}; - function_object(std::function f = {}) - : func(std::move(f)) + function_object(std::function f = {}, bool* state = nullptr) + : hook_state(state), + func(std::move(f)) { } template auto operator()(Args&&... args) const { + bool old_state{}; + if (this->hook_state) + { + old_state = *this->hook_state; + *this->hook_state = true; + } + + const auto _ = utils::finally([&] { + if (this->hook_state) + { + *this->hook_state = old_state; + } + }); + return this->func.operator()(std::forward(args)...); } @@ -83,10 +97,18 @@ namespace icicle }; template - std::unique_ptr> make_function_object(std::function func) + std::unique_ptr> make_function_object(std::function func, bool& hook_state) { - return std::make_unique>(std::move(func)); + return std::make_unique>(std::move(func), &hook_state); } + + struct memory_access_hook + { + uint64_t address{}; + uint64_t size{}; + memory_access_hook_callback callback{}; + bool is_read{}; + }; } class icicle_x86_64_emulator : public x86_64_emulator @@ -105,6 +127,7 @@ namespace icicle { reset_object_with_delayed_destruction(this->hooks_); reset_object_with_delayed_destruction(this->storage_); + utils::reset_object_with_delayed_destruction(this->hooks_to_install_); if (this->emu_) { @@ -239,7 +262,7 @@ namespace icicle return nullptr; } - auto obj = make_function_object(std::move(callback)); + auto obj = make_function_object(std::move(callback), this->is_in_hook_); auto* ptr = obj.get(); const auto invoker = +[](void* cb) { @@ -255,7 +278,7 @@ namespace icicle emulator_hook* hook_basic_block(basic_block_hook_callback callback) override { - auto object = make_function_object(std::move(callback)); + auto object = make_function_object(std::move(callback), this->is_in_hook_); auto* ptr = object.get(); auto* wrapper = +[](void* user, const uint64_t addr, const uint64_t instructions) { basic_block block{}; @@ -274,7 +297,7 @@ namespace icicle emulator_hook* hook_interrupt(interrupt_hook_callback callback) override { - auto obj = make_function_object(std::move(callback)); + auto obj = make_function_object(std::move(callback), this->is_in_hook_); auto* ptr = obj.get(); auto* wrapper = +[](void* user, const int32_t code) { const auto& func = *static_cast(user); @@ -289,7 +312,7 @@ namespace icicle emulator_hook* hook_memory_violation(memory_violation_hook_callback callback) override { - auto obj = make_function_object(std::move(callback)); + auto obj = make_function_object(std::move(callback), this->is_in_hook_); auto* ptr = obj.get(); auto* wrapper = +[](void* user, const uint64_t address, const uint8_t operation, const int32_t unmapped) -> int32_t { @@ -310,7 +333,7 @@ namespace icicle emulator_hook* hook_memory_execution(const uint64_t address, memory_execution_hook_callback callback) override { - auto object = make_function_object(std::move(callback)); + auto object = make_function_object(std::move(callback), this->is_in_hook_); auto* ptr = object.get(); auto* wrapper = +[](void* user, const uint64_t addr) { const auto& func = *static_cast(user); @@ -325,7 +348,7 @@ namespace icicle emulator_hook* hook_memory_execution(memory_execution_hook_callback callback) override { - auto object = make_function_object(std::move(callback)); + auto object = make_function_object(std::move(callback), this->is_in_hook_); auto* ptr = object.get(); auto* wrapper = +[](void* user, const uint64_t addr) { const auto& func = *static_cast(user); @@ -341,48 +364,36 @@ namespace icicle emulator_hook* hook_memory_read(const uint64_t address, const uint64_t size, memory_access_hook_callback callback) override { - auto obj = make_function_object(std::move(callback)); - auto* ptr = obj.get(); - auto* wrapper = +[](void* user, const uint64_t address, const void* data, size_t length) { - const auto& func = *static_cast(user); - func(address, data, length); - }; - - const auto id = icicle_add_read_hook(this->emu_, address, address + size, wrapper, ptr); - this->hooks_[id] = std::move(obj); - - return wrap_hook(id); + return this->try_install_memory_access_hook(memory_access_hook{ + .address = address, + .size = size, + .callback = std::move(callback), + .is_read = true, + }); } emulator_hook* hook_memory_write(const uint64_t address, const uint64_t size, memory_access_hook_callback callback) override { - auto obj = make_function_object(std::move(callback)); - auto* ptr = obj.get(); - auto* wrapper = +[](void* user, const uint64_t address, const void* data, size_t length) { - const auto& func = *static_cast(user); - func(address, data, length); - }; - - const auto id = icicle_add_write_hook(this->emu_, address, address + size, wrapper, ptr); - this->hooks_[id] = std::move(obj); - - return wrap_hook(id); + return this->try_install_memory_access_hook(memory_access_hook{ + .address = address, + .size = size, + .callback = std::move(callback), + .is_read = false, + }); } void delete_hook(emulator_hook* hook) override { - const auto id = static_cast(reinterpret_cast(hook)); - const auto entry = this->hooks_.find(id); - if (entry == this->hooks_.end()) + if (this->is_in_hook_) { - return; + this->hooks_to_delete_.insert(hook); + this->schedule_action_execution(); + } + else + { + this->delete_hook_internal(hook); } - - icicle_remove_hook(this->emu_, id); - const auto obj = std::move(entry->second); - this->hooks_.erase(entry); - (void)obj; } void serialize_state(utils::buffer_serializer& buffer, const bool is_snapshot) const override @@ -442,9 +453,138 @@ namespace icicle } private: + bool is_in_hook_{false}; std::list> storage_{}; std::unordered_map> hooks_{}; + std::unordered_map> id_mapping_{}; icicle_emulator* emu_{}; + uint32_t index_{0}; + + std::unordered_set hooks_to_delete_{}; + std::unordered_map hooks_to_install_{}; + + emulator_hook* wrap_hook(const std::optional icicle_id) + { + const auto id = ++this->index_; + auto* hook = reinterpret_cast(static_cast(id)); + + this->id_mapping_[hook] = icicle_id; + + return hook; + } + + emulator_hook* hook_memory_access(memory_access_hook hook, emulator_hook* hook_id) + { + auto obj = make_function_object(std::move(hook.callback), this->is_in_hook_); + auto* ptr = obj.get(); + auto* wrapper = +[](void* user, const uint64_t address, const void* data, size_t length) { + const auto& func = *static_cast(user); + func(address, data, length); + }; + + auto* installer = hook.is_read ? &icicle_add_read_hook : &icicle_add_write_hook; + const auto id = installer(this->emu_, hook.address, hook.address + hook.size, wrapper, ptr); + this->hooks_[id] = std::move(obj); + + if (hook_id) + { + this->id_mapping_[hook_id] = id; + return hook_id; + } + + return wrap_hook(id); + } + + void delete_hook_internal(emulator_hook* hook) + { + auto hook_id = this->id_mapping_.find(hook); + if (hook_id == this->id_mapping_.end()) + { + return; + } + + if (!hook_id->second.has_value()) + { + this->hooks_to_delete_.insert(hook); + return; + } + + const auto id = *hook_id->second; + this->id_mapping_.erase(hook_id); + + const auto entry = this->hooks_.find(id); + if (entry == this->hooks_.end()) + { + return; + } + + icicle_remove_hook(this->emu_, id); + const auto obj = std::move(entry->second); + this->hooks_.erase(entry); + (void)obj; + } + + void perform_pending_actions() + { + const auto hooks_to_delete = std::move(this->hooks_to_delete_); + const auto hooks_to_install = std::move(this->hooks_to_install_); + + this->hooks_to_delete_ = {}; + this->hooks_to_install_ = {}; + + for (auto& hook : hooks_to_install) + { + this->hook_memory_access(std::move(hook.second), hook.first); + } + + for (auto* hook : hooks_to_delete) + { + this->delete_hook_internal(hook); + } + } + + emulator_hook* try_install_memory_access_hook(memory_access_hook hook) + { + if (!this->is_in_hook_) + { + return this->hook_memory_access(std::move(hook), nullptr); + } + + auto* hook_id = wrap_hook(std::nullopt); + this->hooks_to_install_[hook_id] = std::move(hook); + + this->schedule_action_execution(); + + return hook_id; + } + + void schedule_action_execution() + { + this->run_on_next_instruction([this] { + this->perform_pending_actions(); // + }); + } + + void run_on_next_instruction(std::function func) const + { + auto* heap_func = new std::function(std::move(func)); + auto* callback = +[](void* data) { + auto* cb = static_cast*>(data); + + try + { + (*cb)(); + } + catch (...) + { + // Ignore + } + + delete cb; + }; + + icicle_run_on_next_instruction(this->emu_, callback, heap_func); + } }; std::unique_ptr create_x86_64_emulator() From 9e45d2a33338a26cb23b3cf7aee8925f7ca3e31e Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sat, 9 Aug 2025 21:00:10 +0200 Subject: [PATCH 9/9] Fix warning --- src/backends/icicle-emulator/icicle_x86_64_emulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp b/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp index 20ed80bb..7e5600ca 100644 --- a/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp +++ b/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp @@ -526,8 +526,8 @@ namespace icicle void perform_pending_actions() { + auto hooks_to_install = std::move(this->hooks_to_install_); const auto hooks_to_delete = std::move(this->hooks_to_delete_); - const auto hooks_to_install = std::move(this->hooks_to_install_); this->hooks_to_delete_ = {}; this->hooks_to_install_ = {};