diff --git a/.gitmodules b/.gitmodules index 385ca7f2..254c45b0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -29,3 +29,6 @@ path = deps/base64 url = https://github.com/tobiaslocker/base64.git shallow = true +[submodule "deps/minidump_cpp"] + path = deps/minidump_cpp + url = https://github.com/redthing1/minidump_cpp diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 9bd3ba8d..3d13f896 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -25,5 +25,9 @@ target_include_directories(reflect INTERFACE ########################################## +add_subdirectory(minidump_cpp) + +########################################## + include(googletest.cmake) include(zlib.cmake) diff --git a/deps/minidump_cpp b/deps/minidump_cpp new file mode 160000 index 00000000..df9ec209 --- /dev/null +++ b/deps/minidump_cpp @@ -0,0 +1 @@ +Subproject commit df9ec209ced694405dfde0b8b714e4c43f093d81 diff --git a/src/analyzer/main.cpp b/src/analyzer/main.cpp index 88b3b08d..6e5d73bd 100644 --- a/src/analyzer/main.cpp +++ b/src/analyzer/main.cpp @@ -18,6 +18,7 @@ namespace { mutable bool use_gdb{false}; std::filesystem::path dump{}; + std::filesystem::path minidump_path{}; std::string registry_path{"./registry"}; std::string emulation_root{}; std::unordered_map path_mappings{}; @@ -142,6 +143,12 @@ namespace win_x64_gdb_stub_handler handler{win_emu, should_stop}; gdb_stub::run_gdb_stub(network::address{"0.0.0.0:28960", AF_INET}, handler); } + else if (!options.minidump_path.empty()) + { + // For minidumps, don't start execution automatically; just report ready state + win_emu.log.print(color::green, "Minidump loaded successfully. Process state ready for analysis.\n"); + return true; // Return success without starting emulation + } else { win_emu.start(); @@ -244,14 +251,23 @@ namespace std::unique_ptr setup_emulator(const analysis_options& options, const std::span args) { - if (options.dump.empty()) + if (!options.dump.empty()) { - return create_application_emulator(options, args); + // load snapshot + auto win_emu = create_empty_emulator(options); + snapshot::load_emulator_snapshot(*win_emu, options.dump); + return win_emu; + } + if (!options.minidump_path.empty()) + { + // load minidump + auto win_emu = create_empty_emulator(options); + win_emu->load_minidump(options.minidump_path); + return win_emu; } - auto win_emu = create_empty_emulator(options); - snapshot::load_emulator_snapshot(*win_emu, options.dump); - return win_emu; + // default: load application + return create_application_emulator(options, args); } bool run(const analysis_options& options, const std::span args) @@ -355,6 +371,7 @@ namespace printf(" -m, --module Specify module to track\n"); printf(" -e, --emulation Set emulation root path\n"); printf(" -a, --snapshot Load snapshot dump from path\n"); + printf(" --minidump Load minidump from path\n"); printf(" -i, --ignore Comma-separated list of functions to ignore\n"); printf(" -p, --path Map Windows path to host path\n"); printf(" -r, --registry Set registry path (default: ./registry)\n\n"); @@ -425,6 +442,15 @@ namespace arg_it = args.erase(arg_it); options.dump = args[0]; } + else if (arg == "--minidump") + { + if (args.size() < 2) + { + throw std::runtime_error("No minidump path provided after --minidump"); + } + arg_it = args.erase(arg_it); + options.minidump_path = args[0]; + } else if (arg == "-i" || arg == "--ignore") { if (args.size() < 2) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 9423e8f1..b3b2e89a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -15,6 +15,7 @@ find_package(Threads REQUIRED) target_link_libraries(emulator-common PUBLIC Threads::Threads zlibstatic + minidump::minidump ) if(WIN) diff --git a/src/windows-emulator/CMakeLists.txt b/src/windows-emulator/CMakeLists.txt index 93a25074..6a585bca 100644 --- a/src/windows-emulator/CMakeLists.txt +++ b/src/windows-emulator/CMakeLists.txt @@ -14,7 +14,7 @@ if(NOT MOMO_ENABLE_CLANG_TIDY) target_precompile_headers(windows-emulator PRIVATE std_include.hpp) endif() -target_link_libraries(windows-emulator PUBLIC emulator) +target_link_libraries(windows-emulator PUBLIC emulator minidump) target_include_directories(windows-emulator INTERFACE "${CMAKE_CURRENT_LIST_DIR}") diff --git a/src/windows-emulator/minidump_loader.cpp b/src/windows-emulator/minidump_loader.cpp new file mode 100644 index 00000000..4c710f91 --- /dev/null +++ b/src/windows-emulator/minidump_loader.cpp @@ -0,0 +1,744 @@ +#include "std_include.hpp" +#include "minidump_loader.hpp" +#include "windows_emulator.hpp" +#include "windows_objects.hpp" +#include "emulator_thread.hpp" + +#include + +namespace +{ + struct dump_statistics + { + size_t thread_count = 0; + size_t module_count = 0; + size_t memory_region_count = 0; + size_t memory_segment_count = 0; + size_t handle_count = 0; + uint64_t total_memory_size = 0; + bool has_exception = false; + bool has_system_info = false; + }; + + std::string get_architecture_string(const minidump::minidump_file* dump_file) + { + if (!dump_file) + { + return "Unknown"; + } + + const auto* sys_info = dump_file->get_system_info(); + if (!sys_info) + { + return "Unknown"; + } + + const auto arch = static_cast(sys_info->processor_architecture); + switch (arch) + { + case minidump::processor_architecture::amd64: + return "x64 (AMD64)"; + case minidump::processor_architecture::intel: + return "x86 (Intel)"; + case minidump::processor_architecture::arm64: + return "ARM64"; + default: + return "Unknown (" + std::to_string(static_cast(arch)) + ")"; + } + } + + bool parse_minidump_file(windows_emulator& win_emu, const std::filesystem::path& minidump_path, + std::unique_ptr& dump_file, + std::unique_ptr& dump_reader) + { + win_emu.log.info("Parsing minidump file\n"); + + if (!std::filesystem::exists(minidump_path)) + { + win_emu.log.error("Minidump file does not exist: %s\n", minidump_path.string().c_str()); + return false; + } + + const auto file_size = std::filesystem::file_size(minidump_path); + win_emu.log.info("File size: %zu bytes\n", file_size); + + auto parsed_file = minidump::minidump_file::parse(minidump_path.string()); + if (!parsed_file) + { + win_emu.log.error("Failed to parse minidump file\n"); + return false; + } + + win_emu.log.info("Minidump header parsed successfully\n"); + + auto reader = parsed_file->get_reader(); + if (!reader) + { + win_emu.log.error("Failed to create minidump reader\n"); + return false; + } + + dump_file = std::move(parsed_file); + dump_reader = std::move(reader); + + win_emu.log.info("Minidump reader created successfully\n"); + return true; + } + + bool validate_dump_compatibility(windows_emulator& win_emu, const minidump::minidump_file* dump_file) + { + win_emu.log.info("Validating dump compatibility\n"); + + if (!dump_file) + { + win_emu.log.error("Dump file not loaded\n"); + return false; + } + + const auto& header = dump_file->header(); + + if (!header.is_valid()) + { + win_emu.log.error("Invalid minidump signature or header\n"); + return false; + } + + win_emu.log.info("Minidump signature: 0x%08X (valid)\n", header.signature); + win_emu.log.info("Version: %u.%u\n", header.version, header.implementation_version); + win_emu.log.info("Number of streams: %u\n", header.number_of_streams); + win_emu.log.info("Flags: 0x%016llX\n", header.flags); + + const auto* sys_info = dump_file->get_system_info(); + if (sys_info) + { + const auto arch = static_cast(sys_info->processor_architecture); + const bool is_x64 = (arch == minidump::processor_architecture::amd64); + + win_emu.log.info("Processor architecture: %s\n", get_architecture_string(dump_file).c_str()); + + if (!is_x64) + { + win_emu.log.error("Only x64 minidumps are currently supported\n"); + return false; + } + + win_emu.log.info("Architecture compatibility: OK (x64)\n"); + } + else + { + win_emu.log.warn("No system info stream found - proceeding with caution\n"); + } + + return true; + } + + void log_dump_summary(windows_emulator& win_emu, const minidump::minidump_file* dump_file, dump_statistics& stats) + { + win_emu.log.info("Generating dump summary\n"); + + stats = {}; + + if (!dump_file) + { + win_emu.log.error("Dump file not loaded\n"); + return; + } + + stats.thread_count = dump_file->threads().size(); + stats.module_count = dump_file->modules().size(); + stats.memory_region_count = dump_file->memory_regions().size(); + stats.memory_segment_count = dump_file->memory_segments().size(); + stats.handle_count = dump_file->handles().size(); + stats.has_exception = (dump_file->get_exception_info() != nullptr); + stats.has_system_info = (dump_file->get_system_info() != nullptr); + + for (const auto& segment : dump_file->memory_segments()) + { + stats.total_memory_size += segment.size; + } + + win_emu.log.info( + "Summary: %s, %zu threads, %zu modules, %zu regions, %zu segments, %zu handles, %" PRIu64 " bytes memory\n", + get_architecture_string(dump_file).c_str(), stats.thread_count, stats.module_count, + stats.memory_region_count, stats.memory_segment_count, stats.handle_count, stats.total_memory_size); + } + + void process_streams(windows_emulator& win_emu, const minidump::minidump_file* dump_file) + { + if (!dump_file) + { + return; + } + + // Process system info + const auto* sys_info = dump_file->get_system_info(); + if (sys_info) + { + win_emu.log.info("System: OS %u.%u.%u, %u processors, type %u, platform %u\n", sys_info->major_version, + sys_info->minor_version, sys_info->build_number, sys_info->number_of_processors, + sys_info->product_type, sys_info->platform_id); + } + + // Process memory info + const auto& memory_regions = dump_file->memory_regions(); + uint64_t total_reserved = 0, total_committed = 0; + size_t guard_pages = 0; + for (const auto& region : memory_regions) + { + total_reserved += region.region_size; + if (region.state & MEM_COMMIT) + total_committed += region.region_size; + if (region.protect & PAGE_GUARD) + guard_pages++; + } + win_emu.log.info("Memory: %zu regions, %" PRIu64 " bytes reserved, %" PRIu64 + " bytes committed, %zu guard pages\n", + memory_regions.size(), total_reserved, total_committed, guard_pages); + + // Process memory content + const auto& memory_segments = dump_file->memory_segments(); + uint64_t min_addr = UINT64_MAX, max_addr = 0; + for (const auto& segment : memory_segments) + { + min_addr = std::min(min_addr, segment.start_virtual_address); + max_addr = std::max(max_addr, segment.end_virtual_address()); + } + if (!memory_segments.empty()) + { + win_emu.log.info("Content: %zu segments, range 0x%" PRIx64 "-0x%" PRIx64 " (%" PRIu64 " bytes span)\n", + memory_segments.size(), min_addr, max_addr, max_addr - min_addr); + } + + // Process modules + const auto& modules = dump_file->modules(); + for (const auto& module : modules) + { + win_emu.log.info("Module: %s at 0x%" PRIx64 " (%u bytes)\n", module.module_name.c_str(), + module.base_of_image, module.size_of_image); + } + + // Process threads + const auto& threads = dump_file->threads(); + for (const auto& thread : threads) + { + win_emu.log.info("Thread %u: TEB 0x%" PRIx64 ", stack 0x%" PRIx64 " (%u bytes), context %u bytes\n", + thread.thread_id, thread.teb, thread.stack_start_of_memory_range, thread.stack_data_size, + thread.context_data_size); + } + + // Process handles + const auto& handles = dump_file->handles(); + if (!handles.empty()) + { + std::map handle_type_counts; + for (const auto& handle : handles) + handle_type_counts[handle.type_name]++; + win_emu.log.info("Handles: %zu total\n", handles.size()); + for (const auto& [type, count] : handle_type_counts) + { + win_emu.log.info(" %s: %zu\n", type.c_str(), count); + } + } + + // Process exception info + const auto* exception = dump_file->get_exception_info(); + if (exception) + { + win_emu.log.info("Exception: thread %u, code 0x%08X at 0x%" PRIx64 "\n", exception->thread_id, + exception->exception_record.exception_code, exception->exception_record.exception_address); + } + } + + void reconstruct_memory_state(windows_emulator& win_emu, const minidump::minidump_file* dump_file, + minidump::minidump_reader* dump_reader) + { + if (!dump_file || !dump_reader) + { + win_emu.log.error("Dump file or reader not loaded\n"); + return; + } + + const auto& memory_regions = dump_file->memory_regions(); + const auto& memory_segments = dump_file->memory_segments(); + + win_emu.log.info("Reconstructing memory: %zu regions, %zu data segments\n", memory_regions.size(), + memory_segments.size()); + size_t reserved_count = 0; + size_t committed_count = 0; + size_t failed_count = 0; + + for (const auto& region : memory_regions) + { + const bool is_reserved = (region.state & MEM_RESERVE) != 0; + const bool is_committed = (region.state & MEM_COMMIT) != 0; + const bool is_free = (region.state & MEM_FREE) != 0; + + if (is_free) + continue; + + memory_permission perms = memory_permission::none; + + // Strip modifier flags like PAGE_GUARD, PAGE_NOCACHE, etc. + const uint32_t base_protect = region.protect & 0xFF; + + switch (base_protect) + { + case PAGE_READWRITE: + perms = memory_permission::read_write; + break; + case PAGE_READONLY: + perms = memory_permission::read; + break; + case PAGE_EXECUTE_READ: + perms = memory_permission::read | memory_permission::exec; + break; + case PAGE_EXECUTE_READWRITE: + perms = memory_permission::all; + break; + case PAGE_NOACCESS: + perms = memory_permission::none; + break; + default: + // For any other protection, default to read-only as safest fallback + perms = memory_permission::read; + break; + } + + try + { + if (is_committed) + { + if (win_emu.memory.allocate_memory(region.base_address, region.region_size, perms, false)) + { + committed_count++; + win_emu.log.info(" Allocated committed 0x%" PRIx64 ": size=%" PRIu64 + ", state=0x%08X, protect=0x%08X\n", + region.base_address, region.region_size, region.state, region.protect); + } + else + { + failed_count++; + win_emu.log.warn(" Failed to allocate committed 0x%" PRIx64 ": size=%" PRIu64 "\n", + region.base_address, region.region_size); + } + } + else if (is_reserved) + { + if (win_emu.memory.allocate_memory(region.base_address, region.region_size, perms, true)) + { + reserved_count++; + win_emu.log.info(" Reserved 0x%" PRIx64 ": size=%" PRIu64 ", state=0x%08X, protect=0x%08X\n", + region.base_address, region.region_size, region.state, region.protect); + } + else + { + failed_count++; + win_emu.log.warn(" Failed to reserve 0x%" PRIx64 ": size=%" PRIu64 "\n", region.base_address, + region.region_size); + } + } + } + catch (const std::exception& e) + { + failed_count++; + win_emu.log.error(" Exception allocating 0x%" PRIx64 ": %s\n", region.base_address, e.what()); + } + } + + win_emu.log.info("Regions: %zu reserved, %zu committed, %zu failed\n", reserved_count, committed_count, + failed_count); + size_t written_count = 0; + size_t write_failed_count = 0; + uint64_t total_bytes_written = 0; + + for (const auto& segment : memory_segments) + { + try + { + auto memory_data = dump_reader->read_memory(segment.start_virtual_address, segment.size); + win_emu.memory.write_memory(segment.start_virtual_address, memory_data.data(), memory_data.size()); + written_count++; + total_bytes_written += memory_data.size(); + win_emu.log.info(" Written segment 0x%" PRIx64 ": %zu bytes\n", segment.start_virtual_address, + memory_data.size()); + } + catch (const std::exception& e) + { + write_failed_count++; + win_emu.log.error(" Failed to write segment 0x%" PRIx64 ": %s\n", segment.start_virtual_address, + e.what()); + } + } + + win_emu.log.info("Content: %zu segments written (%" PRIu64 " bytes), %zu failed\n", written_count, + total_bytes_written, write_failed_count); + } + + bool is_main_executable(const minidump::module_info& module) + { + const auto name = module.module_name; + return name.find(".exe") != std::string::npos; + } + + bool is_ntdll(const minidump::module_info& module) + { + const auto name = module.module_name; + return name == "ntdll.dll" || name == "NTDLL.DLL"; + } + + bool is_win32u(const minidump::module_info& module) + { + const auto name = module.module_name; + return name == "win32u.dll" || name == "WIN32U.DLL"; + } + + void reconstruct_module_state(windows_emulator& win_emu, const minidump::minidump_file* dump_file) + { + if (!dump_file) + { + win_emu.log.error("Dump file not loaded\n"); + return; + } + + const auto& modules = dump_file->modules(); + win_emu.log.info("Reconstructing module state: %zu modules\n", modules.size()); + + size_t mapped_count = 0; + size_t failed_count = 0; + size_t identified_count = 0; + + for (const auto& module : modules) + { + try + { + auto* mapped_module = win_emu.mod_manager.map_memory_module(module.base_of_image, module.size_of_image, + module.module_name, win_emu.log); + + if (mapped_module) + { + mapped_count++; + win_emu.log.info(" Mapped %s at 0x%" PRIx64 " (%u bytes, %zu sections, %zu exports)\n", + module.module_name.c_str(), module.base_of_image, module.size_of_image, + mapped_module->sections.size(), mapped_module->exports.size()); + + if (is_main_executable(module)) + { + win_emu.mod_manager.executable = mapped_module; + identified_count++; + win_emu.log.info(" Identified as main executable\n"); + } + else if (is_ntdll(module)) + { + win_emu.mod_manager.ntdll = mapped_module; + identified_count++; + win_emu.log.info(" Identified as ntdll\n"); + } + else if (is_win32u(module)) + { + win_emu.mod_manager.win32u = mapped_module; + identified_count++; + win_emu.log.info(" Identified as win32u\n"); + } + } + else + { + failed_count++; + win_emu.log.warn(" Failed to map %s at 0x%" PRIx64 "\n", module.module_name.c_str(), + module.base_of_image); + } + } + catch (const std::exception& e) + { + failed_count++; + win_emu.log.error(" Exception mapping %s: %s\n", module.module_name.c_str(), e.what()); + } + } + + win_emu.log.info("Module reconstruction: %zu mapped, %zu failed, %zu system modules identified\n", mapped_count, + failed_count, identified_count); + } + + void setup_kusd_from_dump(windows_emulator& win_emu, const minidump::minidump_file* dump_file) + { + const auto* sys_info = dump_file->get_system_info(); + if (!sys_info) + { + win_emu.log.warn("No system info available - using default KUSD\n"); + return; + } + + win_emu.log.info("Setting up KUSER_SHARED_DATA from dump system info\n"); + + auto& kusd = win_emu.process.kusd.get(); + kusd.NtMajorVersion = sys_info->major_version; + kusd.NtMinorVersion = sys_info->minor_version; + kusd.NtBuildNumber = sys_info->build_number; + kusd.NativeProcessorArchitecture = sys_info->processor_architecture; + kusd.ActiveProcessorCount = sys_info->number_of_processors; + kusd.UnparkedProcessorCount = sys_info->number_of_processors; + kusd.NtProductType = static_cast(sys_info->product_type); + kusd.ProductTypeIsValid = 1; + + win_emu.log.info("KUSD updated: Windows %u.%u.%u, %u processors, product type %u\n", sys_info->major_version, + sys_info->minor_version, sys_info->build_number, sys_info->number_of_processors, + sys_info->product_type); + } + + bool load_thread_context(const std::filesystem::path& minidump_path, const minidump::thread_info& thread_info, + std::vector& context_buffer) + { + if (thread_info.context_data_size == 0) + { + return false; + } + + std::ifstream context_file(minidump_path, std::ios::binary); + if (!context_file.is_open()) + { + return false; + } + + context_file.seekg(thread_info.context_rva); + context_buffer.resize(thread_info.context_data_size); + context_file.read(reinterpret_cast(context_buffer.data()), thread_info.context_data_size); + + return context_file.good(); + } + + void reconstruct_threads(windows_emulator& win_emu, const minidump::minidump_file* dump_file, + const std::filesystem::path& minidump_path) + { + const auto& threads = dump_file->threads(); + if (threads.empty()) + { + win_emu.log.warn("No threads found in minidump\n"); + return; + } + + win_emu.log.info("Reconstructing threads: %zu threads\n", threads.size()); + + size_t success_count = 0; + size_t context_loaded_count = 0; + + for (const auto& thread_info : threads) + { + try + { + emulator_thread thread(win_emu.memory); + thread.id = thread_info.thread_id; + thread.stack_base = thread_info.stack_start_of_memory_range; + thread.stack_size = thread_info.stack_data_size; + + // Load CPU context if available + const bool context_loaded = load_thread_context(minidump_path, thread_info, thread.last_registers); + if (context_loaded) + { + context_loaded_count++; + } + + // Set TEB address if valid + if (thread_info.teb != 0) + { + thread.teb = emulator_object(win_emu.memory); + thread.teb->set_address(thread_info.teb); + } + + win_emu.log.info(" Thread %u: TEB=0x%" PRIx64 ", stack=0x%" PRIx64 " (%u bytes), context=%s\n", + thread_info.thread_id, thread_info.teb, thread.stack_base, thread_info.stack_data_size, + context_loaded ? "loaded" : "unavailable"); + + win_emu.process.threads.store(std::move(thread)); + success_count++; + } + catch (const std::exception& e) + { + win_emu.log.error(" Failed to reconstruct thread %u: %s\n", thread_info.thread_id, e.what()); + } + } + + // Set active thread to first available thread + if (success_count > 0) + { + auto& first_thread = win_emu.process.threads.begin()->second; + win_emu.process.active_thread = &first_thread; + } + + win_emu.log.info("Thread reconstruction: %zu/%zu threads created, %zu with context\n", success_count, + threads.size(), context_loaded_count); + } + + void setup_peb_from_teb(windows_emulator& win_emu, const minidump::minidump_file* dump_file) + { + const auto& threads = dump_file->threads(); + if (threads.empty()) + { + win_emu.log.warn("No threads available for PEB setup\n"); + return; + } + + const auto& first_thread = threads[0]; + if (first_thread.teb == 0) + { + win_emu.log.warn("Thread %u has null TEB address\n", first_thread.thread_id); + return; + } + + try + { + // Read PEB address from TEB+0x60 (standard x64 TEB layout) + constexpr uint64_t teb_peb_offset = 0x60; + uint64_t peb_address = 0; + + win_emu.memory.read_memory(first_thread.teb + teb_peb_offset, &peb_address, sizeof(peb_address)); + + if (peb_address == 0) + { + win_emu.log.warn("PEB address is null in TEB at 0x%" PRIx64 "\n", first_thread.teb); + return; + } + + win_emu.process.peb.set_address(peb_address); + win_emu.log.info("PEB address: 0x%" PRIx64 " (from TEB 0x%" PRIx64 ")\n", peb_address, first_thread.teb); + } + catch (const std::exception& e) + { + win_emu.log.error("Failed to read PEB from TEB: %s\n", e.what()); + } + } + + std::u16string utf8_to_utf16(const std::string& str) + { + std::u16string result; + result.reserve(str.size()); + for (const char c : str) + { + result.push_back(static_cast(static_cast(c))); + } + return result; + } + + void reconstruct_handle_table(windows_emulator& win_emu, const minidump::minidump_file* dump_file) + { + const auto& handles = dump_file->handles(); + if (handles.empty()) + { + return; + } + + win_emu.log.info("Reconstructing handle table: %zu handles\n", handles.size()); + + std::map handle_type_counts; + size_t created_count = 0; + + for (const auto& handle_info : handles) + { + handle_type_counts[handle_info.type_name]++; + + try + { + if (handle_info.type_name == "Event") + { + event evt{}; + evt.name = utf8_to_utf16(handle_info.object_name); + win_emu.process.events.store(std::move(evt)); + created_count++; + } + else if (handle_info.type_name == "File") + { + file f{}; + f.name = utf8_to_utf16(handle_info.object_name); + win_emu.process.files.store(std::move(f)); + created_count++; + } + else if (handle_info.type_name == "Mutant") + { + mutant m{}; + m.name = utf8_to_utf16(handle_info.object_name); + win_emu.process.mutants.store(std::move(m)); + created_count++; + } + // Other handle types can be added here as needed + } + catch (const std::exception& e) + { + win_emu.log.error(" Failed to create %s handle '%s': %s\n", handle_info.type_name.c_str(), + handle_info.object_name.c_str(), e.what()); + } + } + + // Log summary by type + for (const auto& [type, count] : handle_type_counts) + { + win_emu.log.info(" %s: %zu handles\n", type.c_str(), count); + } + + win_emu.log.info("Handle table: %zu/%zu handles reconstructed\n", created_count, handles.size()); + } + + void setup_exception_context(windows_emulator& win_emu, const minidump::minidump_file* dump_file) + { + const auto* exception_info = dump_file->get_exception_info(); + if (!exception_info) + { + return; + } + + win_emu.process.current_ip = exception_info->exception_record.exception_address; + win_emu.log.info("Exception context: address=0x%" PRIx64 ", code=0x%08X, thread=%u\n", + exception_info->exception_record.exception_address, + exception_info->exception_record.exception_code, exception_info->thread_id); + } +} + +minidump_loader::minidump_loader(windows_emulator& win_emu, const std::filesystem::path& minidump_path) + : win_emu_(win_emu), + minidump_path_(minidump_path) +{ +} + +minidump_loader::~minidump_loader() = default; + +void minidump_loader::load_into_emulator() +{ + win_emu_.log.info("Starting minidump loading process\n"); + win_emu_.log.info("Minidump file: %s\n", minidump_path_.string().c_str()); + + try + { + std::unique_ptr dump_file; + std::unique_ptr dump_reader; + + if (!parse_minidump_file(win_emu_, minidump_path_, dump_file, dump_reader)) + { + throw std::runtime_error("Failed to parse minidump file"); + } + + if (!validate_dump_compatibility(win_emu_, dump_file.get())) + { + throw std::runtime_error("Minidump compatibility validation failed"); + } + + setup_kusd_from_dump(win_emu_, dump_file.get()); + + dump_statistics stats; + log_dump_summary(win_emu_, dump_file.get(), stats); + process_streams(win_emu_, dump_file.get()); + + // Existing phases + reconstruct_memory_state(win_emu_, dump_file.get(), dump_reader.get()); + reconstruct_module_state(win_emu_, dump_file.get()); + + // Process state reconstruction phases + setup_peb_from_teb(win_emu_, dump_file.get()); + reconstruct_threads(win_emu_, dump_file.get(), minidump_path_); + reconstruct_handle_table(win_emu_, dump_file.get()); + setup_exception_context(win_emu_, dump_file.get()); + + win_emu_.log.info("Process state reconstruction completed\n"); + } + catch (const std::exception& e) + { + win_emu_.log.error("Minidump loading failed: %s\n", e.what()); + throw; + } +} \ No newline at end of file diff --git a/src/windows-emulator/minidump_loader.hpp b/src/windows-emulator/minidump_loader.hpp new file mode 100644 index 00000000..0507f45a --- /dev/null +++ b/src/windows-emulator/minidump_loader.hpp @@ -0,0 +1,17 @@ +#pragma once +#include + +class windows_emulator; + +class minidump_loader +{ + public: + minidump_loader(windows_emulator& win_emu, const std::filesystem::path& minidump_path); + ~minidump_loader(); + + void load_into_emulator(); + + private: + windows_emulator& win_emu_; + std::filesystem::path minidump_path_; +}; \ No newline at end of file diff --git a/src/windows-emulator/module/module_manager.cpp b/src/windows-emulator/module/module_manager.cpp index 00eb45ab..ae263350 100644 --- a/src/windows-emulator/module/module_manager.cpp +++ b/src/windows-emulator/module/module_manager.cpp @@ -139,6 +139,41 @@ mapped_module* module_manager::map_local_module(const std::filesystem::path& fil } } +mapped_module* module_manager::map_memory_module(uint64_t base_address, uint64_t image_size, + const std::string& module_name, const logger& logger, bool is_static) +{ + for (auto& mod : this->modules_ | std::views::values) + { + if (mod.image_base == base_address) + { + return &mod; + } + } + + try + { + auto mod = ::map_module_from_memory(*this->memory_, base_address, image_size, module_name); + mod.is_static = is_static; + + const auto image_base = mod.image_base; + const auto entry = this->modules_.try_emplace(image_base, std::move(mod)); + this->callbacks_->on_module_load(entry.first->second); + return &entry.first->second; + } + catch (const std::exception& e) + { + logger.error("Failed to map module from memory %s at 0x%016llX: %s\n", module_name.c_str(), base_address, + e.what()); + return nullptr; + } + catch (...) + { + logger.error("Failed to map module from memory %s at 0x%016llX: Unknown error\n", module_name.c_str(), + base_address); + return nullptr; + } +} + void module_manager::serialize(utils::buffer_serializer& buffer) const { buffer.write_map(this->modules_); diff --git a/src/windows-emulator/module/module_manager.hpp b/src/windows-emulator/module/module_manager.hpp index 4123f733..927d5e67 100644 --- a/src/windows-emulator/module/module_manager.hpp +++ b/src/windows-emulator/module/module_manager.hpp @@ -25,6 +25,8 @@ class module_manager mapped_module* map_module(const windows_path& file, const logger& logger, bool is_static = false); mapped_module* map_local_module(const std::filesystem::path& file, const logger& logger, bool is_static = false); + mapped_module* map_memory_module(uint64_t base_address, uint64_t image_size, + const std::string& module_name, const logger& logger, bool is_static = false); mapped_module* find_by_address(const uint64_t address) { diff --git a/src/windows-emulator/module/module_mapping.cpp b/src/windows-emulator/module/module_mapping.cpp index 8f433809..65423921 100644 --- a/src/windows-emulator/module/module_mapping.cpp +++ b/src/windows-emulator/module/module_mapping.cpp @@ -265,6 +265,69 @@ mapped_module map_module_from_file(memory_manager& memory, std::filesystem::path return map_module_from_data(memory, data, std::move(file)); } +mapped_module map_module_from_memory(memory_manager& memory, uint64_t base_address, uint64_t image_size, + const std::string& module_name) +{ + mapped_module binary{}; + binary.name = module_name; + binary.path = module_name; + binary.image_base = base_address; + binary.size_of_image = image_size; + + auto mapped_memory = read_mapped_memory(memory, binary); + utils::safe_buffer_accessor buffer{mapped_memory}; + + try + { + 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(); + const auto& optional_header = nt_headers.OptionalHeader; + + binary.entry_point = binary.image_base + optional_header.AddressOfEntryPoint; + + const auto section_offset = get_first_section_offset(nt_headers, nt_headers_offset); + const auto sections = buffer.as(section_offset); + + for (size_t i = 0; i < nt_headers.FileHeader.NumberOfSections; ++i) + { + const auto section = sections.get(i); + + mapped_section section_info{}; + section_info.region.start = binary.image_base + section.VirtualAddress; + section_info.region.length = + static_cast(page_align_up(std::max(section.SizeOfRawData, section.Misc.VirtualSize))); + + auto permissions = memory_permission::none; + if (section.Characteristics & IMAGE_SCN_MEM_EXECUTE) + permissions |= memory_permission::exec; + if (section.Characteristics & IMAGE_SCN_MEM_READ) + permissions |= memory_permission::read; + if (section.Characteristics & IMAGE_SCN_MEM_WRITE) + permissions |= memory_permission::write; + + section_info.region.permissions = permissions; + + for (size_t j = 0; j < sizeof(section.Name) && section.Name[j]; ++j) + { + section_info.name.push_back(static_cast(section.Name[j])); + } + + binary.sections.push_back(std::move(section_info)); + } + + collect_exports(binary, buffer, optional_header); + } + catch (const std::exception&) + { + // bad! + throw std::runtime_error("Failed to map module from memory at " + std::to_string(base_address) + " with size " + + std::to_string(image_size) + " for module " + module_name); + } + + return binary; +} + bool unmap_module(memory_manager& memory, const mapped_module& mod) { return memory.release_memory(mod.image_base, static_cast(mod.size_of_image)); diff --git a/src/windows-emulator/module/module_mapping.hpp b/src/windows-emulator/module/module_mapping.hpp index 60f0a553..43595956 100644 --- a/src/windows-emulator/module/module_mapping.hpp +++ b/src/windows-emulator/module/module_mapping.hpp @@ -5,5 +5,7 @@ mapped_module map_module_from_data(memory_manager& memory, std::span data, std::filesystem::path file); mapped_module map_module_from_file(memory_manager& memory, std::filesystem::path file); +mapped_module map_module_from_memory(memory_manager& memory, uint64_t base_address, uint64_t image_size, + const std::string& module_name); bool unmap_module(memory_manager& memory, const mapped_module& mod); diff --git a/src/windows-emulator/windows_emulator.cpp b/src/windows-emulator/windows_emulator.cpp index 44175c43..eb019de5 100644 --- a/src/windows-emulator/windows_emulator.cpp +++ b/src/windows-emulator/windows_emulator.cpp @@ -12,6 +12,8 @@ #include "network/static_socket_factory.hpp" +#include "minidump_loader.hpp" + constexpr auto MAX_INSTRUCTIONS_PER_TIME_SLICE = 0x20000; namespace @@ -662,3 +664,9 @@ void windows_emulator::restore_snapshot() this->process.deserialize(buffer); // this->process = *this->process_snapshot_; } + +void windows_emulator::load_minidump(const std::filesystem::path& minidump_path) +{ + minidump_loader mdmp_loader(*this, minidump_path); + mdmp_loader.load_into_emulator(); +} diff --git a/src/windows-emulator/windows_emulator.hpp b/src/windows-emulator/windows_emulator.hpp index c6732a30..34d17068 100644 --- a/src/windows-emulator/windows_emulator.hpp +++ b/src/windows-emulator/windows_emulator.hpp @@ -164,6 +164,8 @@ class windows_emulator void save_snapshot(); void restore_snapshot(); + void load_minidump(const std::filesystem::path& minidump_path); + uint16_t get_host_port(const uint16_t emulator_port) const { const auto entry = this->port_mappings_.find(emulator_port);