mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-10 16:16:16 +00:00
minidump support: dump loading and process reconstruction
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -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
|
||||
|
||||
4
deps/CMakeLists.txt
vendored
4
deps/CMakeLists.txt
vendored
@@ -25,5 +25,9 @@ target_include_directories(reflect INTERFACE
|
||||
|
||||
##########################################
|
||||
|
||||
add_subdirectory(minidump_cpp)
|
||||
|
||||
##########################################
|
||||
|
||||
include(googletest.cmake)
|
||||
include(zlib.cmake)
|
||||
|
||||
1
deps/minidump_cpp
vendored
Submodule
1
deps/minidump_cpp
vendored
Submodule
Submodule deps/minidump_cpp added at df9ec209ce
@@ -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<windows_path, std::filesystem::path> 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<windows_emulator> setup_emulator(const analysis_options& options,
|
||||
const std::span<const std::string_view> 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<const std::string_view> args)
|
||||
@@ -355,6 +371,7 @@ namespace
|
||||
printf(" -m, --module <module> Specify module to track\n");
|
||||
printf(" -e, --emulation <path> Set emulation root path\n");
|
||||
printf(" -a, --snapshot <path> Load snapshot dump from path\n");
|
||||
printf(" --minidump <path> Load minidump from path\n");
|
||||
printf(" -i, --ignore <funcs> Comma-separated list of functions to ignore\n");
|
||||
printf(" -p, --path <src> <dst> Map Windows path to host path\n");
|
||||
printf(" -r, --registry <path> 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)
|
||||
|
||||
@@ -15,6 +15,7 @@ find_package(Threads REQUIRED)
|
||||
target_link_libraries(emulator-common PUBLIC
|
||||
Threads::Threads
|
||||
zlibstatic
|
||||
minidump::minidump
|
||||
)
|
||||
|
||||
if(WIN)
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
744
src/windows-emulator/minidump_loader.cpp
Normal file
744
src/windows-emulator/minidump_loader.cpp
Normal file
@@ -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 <minidump/minidump.hpp>
|
||||
|
||||
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<minidump::processor_architecture>(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<int>(arch)) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
bool parse_minidump_file(windows_emulator& win_emu, const std::filesystem::path& minidump_path,
|
||||
std::unique_ptr<minidump::minidump_file>& dump_file,
|
||||
std::unique_ptr<minidump::minidump_reader>& 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<minidump::processor_architecture>(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<std::string, size_t> 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<NT_PRODUCT_TYPE>(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<std::byte>& 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<char*>(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<TEB64>(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<char16_t>(static_cast<unsigned char>(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<std::string, size_t> 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<minidump::minidump_file> dump_file;
|
||||
std::unique_ptr<minidump::minidump_reader> 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;
|
||||
}
|
||||
}
|
||||
17
src/windows-emulator/minidump_loader.hpp
Normal file
17
src/windows-emulator/minidump_loader.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include <filesystem>
|
||||
|
||||
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_;
|
||||
};
|
||||
@@ -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_);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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<const std::byte> buffer{mapped_memory};
|
||||
|
||||
try
|
||||
{
|
||||
const auto dos_header = buffer.as<PEDosHeader_t>(0).get();
|
||||
const auto nt_headers_offset = dos_header.e_lfanew;
|
||||
const auto nt_headers = buffer.as<PENTHeaders_t<std::uint64_t>>(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<IMAGE_SECTION_HEADER>(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<size_t>(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<char>(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<size_t>(mod.size_of_image));
|
||||
|
||||
@@ -5,5 +5,7 @@
|
||||
|
||||
mapped_module map_module_from_data(memory_manager& memory, std::span<const uint8_t> 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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user