#include "std_include.hpp" #include "windows_emulator.hpp" #include "cpu_context.hpp" #include #include #include #include #include #include "exception_dispatch.hpp" #include "apiset/apiset.hpp" constexpr auto MAX_INSTRUCTIONS_PER_TIME_SLICE = 100000; namespace { void adjust_working_directory(application_settings& app_settings) { if (!app_settings.working_directory.empty()) { // Do nothing } #ifdef OS_WINDOWS else if (app_settings.application.is_relative()) { app_settings.working_directory = std::filesystem::current_path(); } #endif else { app_settings.working_directory = app_settings.application.parent(); } } void adjust_application(application_settings& app_settings) { if (app_settings.application.is_relative()) { app_settings.application = app_settings.working_directory / app_settings.application; } } void fixup_application_settings(application_settings& app_settings) { adjust_working_directory(app_settings); adjust_application(app_settings); } void perform_context_switch_work(windows_emulator& win_emu) { auto& devices = win_emu.process.devices; // Crappy mechanism to prevent mutation while iterating. const auto was_blocked = devices.block_mutation(true); const auto _ = utils::finally([&] { devices.block_mutation(was_blocked); }); for (auto& dev : devices | std::views::values) { dev.work(win_emu); } } emulator_thread* get_thread_by_id(process_context& process, const uint32_t id) { for (auto& t : process.threads | std::views::values) { if (t.id == id) { return &t; } } return nullptr; } bool switch_to_thread(windows_emulator& win_emu, emulator_thread& thread, const bool force = false) { if (thread.is_terminated()) { return false; } auto& emu = win_emu.emu(); auto& context = win_emu.process; const auto is_ready = thread.is_thread_ready(context, win_emu.clock()); if (!is_ready && !force) { return false; } auto* active_thread = context.active_thread; if (active_thread == &thread) { thread.setup_if_necessary(emu, context); return true; } if (active_thread) { win_emu.log.print(color::dark_gray, "Performing thread switch: %X -> %X\n", active_thread->id, thread.id); active_thread->save(emu); } context.active_thread = &thread; thread.restore(emu); thread.setup_if_necessary(emu, context); return true; } bool switch_to_thread(windows_emulator& win_emu, const handle thread_handle) { auto* thread = win_emu.process.threads.get(thread_handle); if (!thread) { throw std::runtime_error("Bad thread handle"); } return switch_to_thread(win_emu, *thread); } bool switch_to_next_thread(windows_emulator& win_emu) { perform_context_switch_work(win_emu); auto& context = win_emu.process; bool next_thread = false; for (auto& t : context.threads | std::views::values) { if (next_thread) { if (switch_to_thread(win_emu, t)) { return true; } continue; } if (&t == context.active_thread) { next_thread = true; } } for (auto& t : context.threads | std::views::values) { if (switch_to_thread(win_emu, t)) { return true; } } return false; } struct instruction_tick_clock : utils::tick_clock { windows_emulator* win_emu_{}; instruction_tick_clock(windows_emulator& win_emu, const system_time_point system_start = {}, const steady_time_point steady_start = {}) : tick_clock(1000, system_start, steady_start), win_emu_(&win_emu) { } uint64_t ticks() override { if (!this->win_emu_->base_constructed_) // TODO: Remove that { throw std::runtime_error("Requesting ticks before construction!"); } return this->win_emu_->process.executed_instructions; } }; std::unique_ptr get_clock(windows_emulator& win_emu, const bool use_relative_time) { if (use_relative_time) { return std::make_unique(win_emu); } return std::make_unique(); } } std::unique_ptr create_default_x64_emulator() { return unicorn::create_x64_emulator(); } windows_emulator::windows_emulator(application_settings app_settings, const emulator_settings& settings, emulator_callbacks callbacks, std::unique_ptr emu) : windows_emulator(settings, std::move(emu)) { this->callbacks = std::move(callbacks); fixup_application_settings(app_settings); this->setup_process(app_settings); } windows_emulator::windows_emulator(const emulator_settings& settings, std::unique_ptr emu) : emu_(std::move(emu)), clock_(get_clock(*this, settings.use_relative_time)), emulation_root{settings.emulation_root.empty() ? settings.emulation_root : absolute(settings.emulation_root)}, file_sys(emulation_root.empty() ? emulation_root : emulation_root / "filesys"), memory(*this->emu_), registry(emulation_root.empty() ? settings.registry_directory : emulation_root / "registry"), mod_manager(memory, file_sys, callbacks), process(*this->emu_, memory, *this->clock_, callbacks) { this->base_constructed_ = true; #ifndef OS_WINDOWS if (this->emulation_root.empty()) { throw std::runtime_error("Emulation root directory can not be empty!"); } #endif for (const auto& mapping : settings.path_mappings) { this->file_sys.map(mapping.first, mapping.second); } for (const auto& mapping : settings.port_mappings) { this->map_port(mapping.first, mapping.second); } this->verbose_calls = settings.verbose_calls; this->silent_until_main_ = settings.silent_until_main && !settings.disable_logging; this->use_relative_time_ = settings.use_relative_time; this->log.disable_output(settings.disable_logging || this->silent_until_main_); this->modules_ = settings.modules; this->setup_hooks(); } windows_emulator::~windows_emulator() = default; void windows_emulator::setup_process(const application_settings& app_settings) { const auto& emu = this->emu(); auto& context = this->process; this->mod_manager.map_main_modules(app_settings.application, R"(C:\Windows\System32\ntdll.dll)", R"(C:\Windows\System32\win32u.dll)", this->log); const auto* executable = this->mod_manager.executable; const auto* ntdll = this->mod_manager.ntdll; const auto* win32u = this->mod_manager.win32u; const auto apiset_data = apiset::obtain(this->emulation_root); this->process.setup(this->emu(), this->memory, app_settings, *executable, *ntdll, apiset_data); const auto ntdll_data = emu.read_memory(ntdll->image_base, ntdll->size_of_image); const auto win32u_data = emu.read_memory(win32u->image_base, win32u->size_of_image); this->dispatcher.setup(ntdll->exports, ntdll_data, win32u->exports, win32u_data); const auto main_thread_id = context.create_thread(this->memory, this->mod_manager.executable->entry_point, 0, 0); switch_to_thread(*this, main_thread_id); } void windows_emulator::yield_thread() { this->switch_thread_ = true; this->emu().stop(); } void windows_emulator::perform_thread_switch() { this->switch_thread_ = false; while (!switch_to_next_thread(*this)) { if (this->use_relative_time_) { this->process.executed_instructions += MAX_INSTRUCTIONS_PER_TIME_SLICE; } else { std::this_thread::sleep_for(1ms); } } } bool windows_emulator::activate_thread(const uint32_t id) { const auto thread = get_thread_by_id(this->process, id); if (!thread) { return false; } return switch_to_thread(*this, *thread, true); } void windows_emulator::on_instruction_execution(const uint64_t address) { auto& thread = this->current_thread(); ++this->process.executed_instructions; const auto thread_insts = ++thread.executed_instructions; if (thread_insts % MAX_INSTRUCTIONS_PER_TIME_SLICE == 0) { this->yield_thread(); } this->process.previous_ip = this->process.current_ip; this->process.current_ip = this->emu().read_instruction_pointer(); const auto binary = utils::make_lazy([&] { return this->mod_manager.find_by_address(address); // }); const auto previous_binary = utils::make_lazy([&] { return this->mod_manager.find_by_address(this->process.previous_ip); // }); const auto is_in_interesting_module = [&] { if (this->modules_.empty()) { return false; } return (binary && this->modules_.contains(binary->name)) || (previous_binary && this->modules_.contains(previous_binary->name)); }; const auto is_main_exe = this->mod_manager.executable->is_within(address); const auto is_interesting_call = this->mod_manager.executable->is_within(this->process.previous_ip) // || is_main_exe // || is_in_interesting_module(); if (this->silent_until_main_ && is_main_exe) { this->silent_until_main_ = false; this->log.disable_output(false); } if (!this->verbose && !this->verbose_calls && !is_interesting_call) { return; } if (binary) { const auto export_entry = binary->address_names.find(address); if (export_entry != binary->address_names.end()) { const auto rsp = this->emu().read_stack_pointer(); uint64_t return_address{}; this->emu().try_read_memory(rsp, &return_address, sizeof(return_address)); const auto* mod_name = this->mod_manager.find_name(return_address); 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); } else if (address == binary->entry_point) { log.print(is_interesting_call ? color::yellow : color::gray, "Executing entry point: %s (0x%" PRIx64 ")\n", binary->name.c_str(), address); } } if (!this->verbose) { return; } auto& emu = this->emu(); // TODO: Remove or cleanup log.print(color::gray, "Inst: %16" PRIx64 " - RAX: %16" PRIx64 " - RBX: %16" PRIx64 " - RCX: %16" PRIx64 " - RDX: %16" PRIx64 " - R8: %16" PRIx64 " - R9: %16" PRIx64 " - RDI: %16" PRIx64 " - RSI: %16" PRIx64 " - %s\n", address, emu.reg(x64_register::rax), emu.reg(x64_register::rbx), emu.reg(x64_register::rcx), emu.reg(x64_register::rdx), emu.reg(x64_register::r8), emu.reg(x64_register::r9), emu.reg(x64_register::rdi), emu.reg(x64_register::rsi), binary ? binary->name.c_str() : ""); } void windows_emulator::setup_hooks() { this->emu().hook_instruction(x64_hookable_instructions::syscall, [&] { this->dispatcher.dispatch(*this); return instruction_hook_continuation::skip_instruction; }); this->emu().hook_instruction(x64_hookable_instructions::rdtsc, [&] { const auto instructions = this->process.executed_instructions; this->emu().reg(x64_register::rax, instructions & 0xFFFFFFFF); this->emu().reg(x64_register::rdx, (instructions >> 32) & 0xFFFFFFFF); return instruction_hook_continuation::skip_instruction; }); this->emu().hook_instruction(x64_hookable_instructions::invalid, [&] { const auto ip = this->emu().read_instruction_pointer(); this->log.print(color::gray, "Invalid instruction at: 0x%" PRIx64 "\n", ip); return instruction_hook_continuation::skip_instruction; }); this->emu().hook_interrupt([&](const int interrupt) { const auto rip = this->emu().read_instruction_pointer(); switch (interrupt) { case 0: dispatch_integer_division_by_zero(this->emu(), this->process); return; case 1: this->log.print(color::pink, "Singlestep: 0x%" PRIx64 "\n", rip); dispatch_single_step(this->emu(), this->process); return; case 6: dispatch_illegal_instruction_violation(this->emu(), this->process); return; default: break; } this->log.print(color::gray, "Interrupt: %i 0x%" PRIx64 "\n", interrupt, rip); if (this->fuzzing || true) // TODO: Fix { this->process.exception_rip = rip; this->emu().stop(); } }); this->emu().hook_memory_violation([&](const uint64_t address, const size_t size, const memory_operation operation, const memory_violation_type type) { const auto permission = get_permission_string(operation); const auto ip = this->emu().read_instruction_pointer(); const char* name = this->mod_manager.find_name(ip); if (type == memory_violation_type::protection) { this->log.print(color::gray, "Protection violation: 0x%" PRIx64 " (%zX) - %s at 0x%" PRIx64 " (%s)\n", address, size, permission.c_str(), ip, name); } else if (type == memory_violation_type::unmapped) { this->log.print(color::gray, "Mapping violation: 0x%" PRIx64 " (%zX) - %s at 0x%" PRIx64 " (%s)\n", address, size, permission.c_str(), ip, name); } if (this->fuzzing) { this->process.exception_rip = ip; this->emu().stop(); return memory_violation_continuation::stop; } dispatch_access_violation(this->emu(), this->process, address, operation); return memory_violation_continuation::resume; }); this->emu().hook_memory_execution( 0, std::numeric_limits::max(), [&](const uint64_t address, const size_t, const uint64_t) { this->on_instruction_execution(address); }); } void windows_emulator::start(std::chrono::nanoseconds timeout, size_t count) { const auto use_count = count > 0; const auto use_timeout = timeout != std::chrono::nanoseconds{}; const auto start_time = std::chrono::high_resolution_clock::now(); const auto start_instructions = this->process.executed_instructions; const auto target_time = start_time + timeout; const auto target_instructions = start_instructions + count; while (true) { if (this->switch_thread_ || !this->current_thread().is_thread_ready(this->process, this->clock())) { this->perform_thread_switch(); } this->emu().start_from_ip(timeout, count); if (!this->switch_thread_ && !this->emu().has_violation()) { break; } if (use_timeout) { const auto now = std::chrono::high_resolution_clock::now(); if (now >= target_time) { break; } timeout = target_time - now; } if (use_count) { const auto current_instructions = this->process.executed_instructions; if (current_instructions >= target_instructions) { break; } count = target_instructions - current_instructions; } } } void windows_emulator::serialize(utils::buffer_serializer& buffer) const { buffer.write(this->switch_thread_); buffer.write(this->use_relative_time_); this->emu().serialize_state(buffer, false); this->memory.serialize_memory_state(buffer, false); this->mod_manager.serialize(buffer); this->process.serialize(buffer); this->dispatcher.serialize(buffer); } void windows_emulator::deserialize(utils::buffer_deserializer& buffer) { buffer.register_factory([this] { return memory_manager_wrapper{this->memory}; // }); buffer.register_factory([this] { return module_manager_wrapper{this->mod_manager}; // }); buffer.register_factory([this] { return x64_emulator_wrapper{this->emu()}; // }); buffer.register_factory([this] { return windows_emulator_wrapper{*this}; // }); buffer.register_factory([this] { return clock_wrapper{this->clock()}; // }); buffer.read(this->switch_thread_); const auto old_relative_time = this->use_relative_time_; buffer.read(this->use_relative_time_); if (old_relative_time != this->use_relative_time_) { throw std::runtime_error("Can not deserialize emulator with different time dimensions"); } this->memory.unmap_all_memory(); this->emu().deserialize_state(buffer, false); this->memory.deserialize_memory_state(buffer, false); this->mod_manager.deserialize(buffer); this->process.deserialize(buffer); this->dispatcher.deserialize(buffer); } void windows_emulator::save_snapshot() { throw std::runtime_error("Not supported"); /*utils::buffer_serializer serializer{}; this->emu().serialize_state(serializer, true); this->memory.serialize_memory_state(serializer, true); this->mod_manager.serialize(serializer); this->process.serialize(serializer); this->process_snapshot_ = serializer.move_buffer(); // TODO: Make process copyable // this->process_snapshot_ = this->process;*/ } void windows_emulator::restore_snapshot() { throw std::runtime_error("Not supported"); /*if (this->process_snapshot_.empty()) { assert(false); return; } utils::buffer_deserializer deserializer{this->process_snapshot_}; this->emu().deserialize_state(deserializer, true); this->memory.deserialize_memory_state(deserializer, true); this->mod_manager.deserialize(deserializer); this->process.deserialize(deserializer); // this->process = *this->process_snapshot_;*/ }