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