Move much of the analysis logic from the emulation core into the analyzer (#359)

Analysis should not be done in the core. Not everyone using the emulator
needs the analysis.
Much of it was moved to the analyzer. Not all, but the rest will be done
in a follow up PR.
This commit is contained in:
Maurice Heumann
2025-06-07 08:20:44 +02:00
committed by GitHub
38 changed files with 633 additions and 438 deletions

273
src/analyzer/analysis.cpp Normal file
View File

@@ -0,0 +1,273 @@
#include "std_include.hpp"
#include "analysis.hpp"
#include "windows_emulator.hpp"
#include <utils/lazy_object.hpp>
#ifdef OS_EMSCRIPTEN
#include <event_handler.hpp>
#endif
#define STR_VIEW_VA(str) static_cast<int>((str).size()), (str).data()
namespace
{
template <typename Return, typename... Args>
std::function<Return(Args...)> make_callback(analysis_context& c, Return (*callback)(analysis_context&, Args...))
{
return [&c, callback](Args... args) {
return callback(c, std::forward<Args>(args)...); //
};
}
template <typename Return, typename... Args>
std::function<Return(Args...)> make_callback(analysis_context& c,
Return (*callback)(const analysis_context&, Args...))
{
return [&c, callback](Args... args) {
return callback(c, std::forward<Args>(args)...); //
};
}
void handle_suspicious_activity(const analysis_context& c, const std::string_view details)
{
const auto rip = c.win_emu->emu().read_instruction_pointer();
c.win_emu->log.print(color::pink, "Suspicious: %.*s at 0x%" PRIx64 " (via 0x%" PRIx64 ")\n",
STR_VIEW_VA(details), rip, c.win_emu->process.previous_ip);
}
void handle_generic_activity(const analysis_context& c, const std::string_view details)
{
c.win_emu->log.print(color::dark_gray, "%.*s\n", STR_VIEW_VA(details));
}
void handle_generic_access(const analysis_context& c, const std::string_view type, const std::u16string_view name)
{
c.win_emu->log.print(color::dark_gray, "--> %.*s: %s\n", STR_VIEW_VA(type), u16_to_u8(name).c_str()); //
}
void handle_memory_allocate(const analysis_context& c, const uint64_t address, const uint64_t length,
const memory_permission permission, const bool commit)
{
const auto* action = commit ? "Committed" : "Allocated";
c.win_emu->log.print(is_executable(permission) ? color::gray : color::dark_gray,
"--> %s 0x%" PRIx64 " - 0x%" PRIx64 " (%s)\n", action, address, address + length,
get_permission_string(permission).c_str());
}
void handle_memory_protect(const analysis_context& c, const uint64_t address, const uint64_t length,
const memory_permission permission)
{
c.win_emu->log.print(color::dark_gray, "--> Changing protection at 0x%" PRIx64 "-0x%" PRIx64 " to %s\n",
address, address + length, get_permission_string(permission).c_str());
}
void handle_memory_violate(const analysis_context& c, const uint64_t address, const uint64_t size,
const memory_operation operation, const memory_violation_type type)
{
const auto permission = get_permission_string(operation);
const auto ip = c.win_emu->emu().read_instruction_pointer();
const char* name = c.win_emu->mod_manager.find_name(ip);
if (type == memory_violation_type::protection)
{
c.win_emu->log.print(color::gray,
"Protection violation: 0x%" PRIx64 " (%" PRIx64 ") - %s at 0x%" PRIx64 " (%s)\n",
address, size, permission.c_str(), ip, name);
}
else if (type == memory_violation_type::unmapped)
{
c.win_emu->log.print(color::gray,
"Mapping violation: 0x%" PRIx64 " (%" PRIx64 ") - %s at 0x%" PRIx64 " (%s)\n", address,
size, permission.c_str(), ip, name);
}
}
void handle_ioctrl(const analysis_context& c, const io_device&, const std::u16string_view device_name,
const ULONG code)
{
c.win_emu->log.print(color::dark_gray, "--> %s: 0x%X\n", u16_to_u8(device_name).c_str(),
static_cast<uint32_t>(code));
}
void handle_thread_set_name(const analysis_context& c, const emulator_thread& t)
{
c.win_emu->log.print(color::blue, "Setting thread (%d) name: %s\n", t.id, u16_to_u8(t.name).c_str());
}
void handle_thread_switch(const analysis_context& c, const emulator_thread& current_thread,
const emulator_thread& new_thread)
{
c.win_emu->log.print(color::dark_gray, "Performing thread switch: %X -> %X\n", current_thread.id,
new_thread.id);
#ifdef OS_EMSCRIPTEN
debugger::event_context ec{.win_emu = *c.win_emu};
debugger::handle_events(ec);
#endif
}
void handle_module_load(const analysis_context& c, const mapped_module& mod)
{
c.win_emu->log.log("Mapped %s at 0x%" PRIx64 "\n", mod.path.generic_string().c_str(), mod.image_base);
}
void handle_module_unload(const analysis_context& c, const mapped_module& mod)
{
c.win_emu->log.log("Unmapping %s (0x%" PRIx64 ")\n", mod.path.generic_string().c_str(), mod.image_base);
}
void handle_instruction(analysis_context& c, const uint64_t address)
{
auto& win_emu = *c.win_emu;
const auto is_main_exe = win_emu.mod_manager.executable->is_within(address);
const auto is_previous_main_exe = win_emu.mod_manager.executable->is_within(c.win_emu->process.previous_ip);
const auto binary = utils::make_lazy([&] {
if (is_main_exe)
{
return win_emu.mod_manager.executable;
}
return win_emu.mod_manager.find_by_address(address); //
});
const auto previous_binary = utils::make_lazy([&] {
if (is_previous_main_exe)
{
return win_emu.mod_manager.executable;
}
return win_emu.mod_manager.find_by_address(win_emu.process.previous_ip); //
});
const auto is_in_interesting_module = [&] {
if (c.settings->modules.empty())
{
return false;
}
return (binary && c.settings->modules.contains(binary->name)) ||
(previous_binary && c.settings->modules.contains(previous_binary->name));
};
const auto is_interesting_call = is_previous_main_exe //
|| is_main_exe //
|| is_in_interesting_module();
if (!c.has_reached_main && c.settings->concise_logging && !c.settings->silent && is_main_exe)
{
c.has_reached_main = true;
win_emu.log.disable_output(false);
}
if ((!c.settings->verbose_logging && !is_interesting_call) || !binary)
{
return;
}
const auto export_entry = binary->address_names.find(address);
if (export_entry != binary->address_names.end() &&
!c.settings->ignored_functions.contains(export_entry->second))
{
const auto rsp = win_emu.emu().read_stack_pointer();
uint64_t return_address{};
win_emu.emu().try_read_memory(rsp, &return_address, sizeof(return_address));
const auto* mod_name = win_emu.mod_manager.find_name(return_address);
win_emu.log.print(is_interesting_call ? color::yellow : color::dark_gray,
"Executing function: %s - %s (0x%" PRIx64 ") via (0x%" PRIx64 ") %s\n",
binary->name.c_str(), export_entry->second.c_str(), address, return_address, mod_name);
}
else if (address == binary->entry_point)
{
win_emu.log.print(is_interesting_call ? color::yellow : color::gray,
"Executing entry point: %s (0x%" PRIx64 ")\n", binary->name.c_str(), address);
}
}
emulator_callbacks::continuation handle_syscall(const analysis_context& c, const uint32_t syscall_id,
const std::string_view syscall_name)
{
auto& win_emu = *c.win_emu;
auto& emu = win_emu.emu();
const auto address = emu.read_instruction_pointer();
const auto* mod = win_emu.mod_manager.find_by_address(address);
const auto is_sus_module = mod != win_emu.mod_manager.ntdll && mod != win_emu.mod_manager.win32u;
if (is_sus_module)
{
win_emu.log.print(color::blue, "Executing inline syscall: %.*s (0x%X) at 0x%" PRIx64 " (%s)\n",
STR_VIEW_VA(syscall_name), syscall_id, address, mod ? mod->name.c_str() : "<N/A>");
}
else if (mod->is_within(win_emu.process.previous_ip))
{
const auto rsp = emu.read_stack_pointer();
uint64_t return_address{};
emu.try_read_memory(rsp, &return_address, sizeof(return_address));
const auto* caller_mod_name = win_emu.mod_manager.find_name(return_address);
win_emu.log.print(color::dark_gray,
"Executing syscall: %.*s (0x%X) at 0x%" PRIx64 " via 0x%" PRIx64 " (%s)\n",
STR_VIEW_VA(syscall_name), syscall_id, address, return_address, caller_mod_name);
}
else
{
const auto* previous_mod = win_emu.mod_manager.find_by_address(win_emu.process.previous_ip);
win_emu.log.print(color::blue,
"Crafted out-of-line syscall: %.*s (0x%X) at 0x%" PRIx64 " (%s) via 0x%" PRIx64 " (%s)\n",
STR_VIEW_VA(syscall_name), syscall_id, address, mod ? mod->name.c_str() : "<N/A>",
win_emu.process.previous_ip, previous_mod ? previous_mod->name.c_str() : "<N/A>");
}
return instruction_hook_continuation::run_instruction;
}
void handle_stdout(analysis_context& c, const std::string_view data)
{
if (c.settings->silent)
{
(void)fwrite(data.data(), 1, data.size(), stdout);
}
else if (c.settings->buffer_stdout)
{
c.output.append(data);
}
else
{
c.win_emu->log.info("%.*s%s", static_cast<int>(data.size()), data.data(), data.ends_with("\n") ? "" : "\n");
}
}
}
void register_analysis_callbacks(analysis_context& c)
{
auto& cb = c.win_emu->callbacks;
cb.on_stdout = make_callback(c, handle_stdout);
cb.on_syscall = make_callback(c, handle_syscall);
cb.on_ioctrl = make_callback(c, handle_ioctrl);
cb.on_memory_protect = make_callback(c, handle_memory_protect);
cb.on_memory_violate = make_callback(c, handle_memory_violate);
cb.on_memory_allocate = make_callback(c, handle_memory_allocate);
cb.on_module_load = make_callback(c, handle_module_load);
cb.on_module_unload = make_callback(c, handle_module_unload);
cb.on_thread_switch = make_callback(c, handle_thread_switch);
cb.on_thread_set_name = make_callback(c, handle_thread_set_name);
cb.on_instruction = make_callback(c, handle_instruction);
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);
}

28
src/analyzer/analysis.hpp Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include <set>
#include <string>
class windows_emulator;
struct analysis_settings
{
bool concise_logging{false};
bool verbose_logging{false};
bool silent{false};
bool buffer_stdout{false};
std::set<std::string, std::less<>> modules{};
std::set<std::string, std::less<>> ignored_functions{};
};
struct analysis_context
{
const analysis_settings* settings{};
windows_emulator* win_emu{};
std::string output{};
bool has_reached_main{false};
};
void register_analysis_callbacks(analysis_context& c);

View File

@@ -6,10 +6,7 @@
#include "object_watching.hpp"
#include "snapshot.hpp"
#ifdef OS_EMSCRIPTEN
#include <event_handler.hpp>
#endif
#include "analysis.hpp"
#include <utils/interupt_handler.hpp>
@@ -17,18 +14,12 @@
namespace
{
struct analysis_options
struct analysis_options : analysis_settings
{
mutable bool use_gdb{false};
bool concise_logging{false};
bool verbose_logging{false};
bool silent{false};
bool buffer_stdout{false};
std::filesystem::path dump{};
std::string registry_path{"./registry"};
std::string emulation_root{};
std::set<std::string, std::less<>> modules{};
std::set<std::string, std::less<>> ignored_functions{};
std::unordered_map<windows_path, std::filesystem::path> path_mappings{};
};
@@ -59,23 +50,25 @@ namespace
}
void watch_system_objects(windows_emulator& win_emu, const std::set<std::string, std::less<>>& modules,
const bool cache_logging)
const bool verbose)
{
win_emu.setup_process_if_necessary();
(void)win_emu;
(void)modules;
(void)cache_logging;
(void)verbose;
#if !defined(__GNUC__) || defined(__clang__)
watch_object(win_emu, modules, *win_emu.current_thread().teb, cache_logging);
watch_object(win_emu, modules, win_emu.process.peb, cache_logging);
watch_object(win_emu, modules, *win_emu.current_thread().teb, verbose);
watch_object(win_emu, modules, win_emu.process.peb, verbose);
watch_object(win_emu, modules, emulator_object<KUSER_SHARED_DATA64>{win_emu.emu(), kusd_mmio::address()},
cache_logging);
verbose);
auto* params_hook = watch_object(win_emu, modules, win_emu.process.process_params, cache_logging);
auto* params_hook = watch_object(win_emu, modules, win_emu.process.process_params, verbose);
win_emu.emu().hook_memory_write(
win_emu.process.peb.value() + offsetof(PEB64, ProcessParameters), 0x8,
[&win_emu, cache_logging, params_hook, modules](const uint64_t address, const void*, size_t) mutable {
[&win_emu, verbose, params_hook, modules](const uint64_t address, const void*, size_t) mutable {
const auto target_address = win_emu.process.peb.value() + offsetof(PEB64, ProcessParameters);
if (address == target_address)
@@ -86,7 +79,7 @@ namespace
};
win_emu.emu().delete_hook(params_hook);
params_hook = watch_object(win_emu, modules, obj, cache_logging);
params_hook = watch_object(win_emu, modules, obj, verbose);
}
});
#endif
@@ -109,8 +102,19 @@ namespace
}
}
bool run_emulation(windows_emulator& win_emu, const analysis_options& options)
void do_post_emulation_work(const analysis_context& c)
{
if (c.settings->buffer_stdout)
{
c.win_emu->log.info("%.*s%s", static_cast<int>(c.output.size()), c.output.data(),
c.output.ends_with("\n") ? "" : "\n");
}
}
bool run_emulation(const analysis_context& c, const analysis_options& options)
{
auto& win_emu = *c.win_emu;
std::atomic_uint32_t signals_received{0};
utils::interupt_handler _{[&] {
const auto value = signals_received++;
@@ -158,12 +162,14 @@ namespace
}
catch (const std::exception& e)
{
do_post_emulation_work(c);
win_emu.log.error("Emulation failed at: 0x%" PRIx64 " - %s\n", win_emu.emu().read_instruction_pointer(),
e.what());
throw;
}
catch (...)
{
do_post_emulation_work(c);
win_emu.log.error("Emulation failed at: 0x%" PRIx64 "\n", win_emu.emu().read_instruction_pointer());
throw;
}
@@ -171,6 +177,7 @@ namespace
const auto exit_status = win_emu.process.exit_status;
if (!exit_status.has_value())
{
do_post_emulation_work(c);
win_emu.log.error("Emulation terminated without status!\n");
return false;
}
@@ -179,6 +186,7 @@ namespace
if (!options.silent)
{
do_post_emulation_work(c);
win_emu.log.disable_output(false);
win_emu.log.print(success ? color::green : color::red, "Emulation terminated with status: %X\n",
*exit_status);
@@ -206,12 +214,7 @@ namespace
return {
.emulation_root = options.emulation_root,
.registry_directory = options.registry_path,
.verbose_calls = options.verbose_logging,
.disable_logging = options.silent,
.silent_until_main = options.concise_logging,
.path_mappings = options.path_mappings,
.modules = options.modules,
.ignored_functions = options.ignored_functions,
};
}
@@ -253,28 +256,18 @@ namespace
bool run(const analysis_options& options, const std::span<const std::string_view> args)
{
const auto win_emu = setup_emulator(options, args);
#ifdef OS_EMSCRIPTEN
win_emu->callbacks.on_thread_switch = [&] {
debugger::event_context c{.win_emu = *win_emu};
debugger::handle_events(c); //
analysis_context context{
.settings = &options,
};
#endif
const auto win_emu = setup_emulator(options, args);
win_emu->log.disable_output(options.concise_logging || options.silent);
context.win_emu = win_emu.get();
win_emu->log.log("Using emulator: %s\n", win_emu->emu().get_name().c_str());
(void)&watch_system_objects;
watch_system_objects(*win_emu, options.modules, !options.verbose_logging);
win_emu->buffer_stdout = options.buffer_stdout;
if (options.silent)
{
win_emu->buffer_stdout = false;
win_emu->callbacks.on_stdout = [](const std::string_view data) {
(void)fwrite(data.data(), 1, data.size(), stdout);
};
}
register_analysis_callbacks(context);
watch_system_objects(*win_emu, options.modules, options.verbose_logging);
const auto& exe = *win_emu->mod_manager.executable;
@@ -334,7 +327,7 @@ namespace
win_emu->emu().hook_memory_write(section.region.start, section.region.length, std::move(write_handler));
}
return run_emulation(*win_emu, options);
return run_emulation(context, options);
}
std::vector<std::string_view> bundle_arguments(const int argc, char** argv)

View File

@@ -6,23 +6,23 @@
template <typename T>
emulator_hook* watch_object(windows_emulator& emu, const std::set<std::string, std::less<>>& modules,
emulator_object<T> object, const bool cache_logging = false)
emulator_object<T> object, const auto verbose)
{
const reflect_type_info<T> info{};
return emu.emu().hook_memory_read(
object.value(), static_cast<size_t>(object.size()),
[i = std::move(info), object, &emu, cache_logging, modules](const uint64_t address, const void*, size_t) {
[i = std::move(info), object, &emu, verbose, modules](const uint64_t address, const void*, size_t) {
const auto rip = emu.emu().read_instruction_pointer();
const auto* mod = emu.mod_manager.find_by_address(rip);
const auto is_main_access = mod == emu.mod_manager.executable || modules.contains(mod->name);
if (!emu.verbose_calls && !is_main_access)
if (!verbose && !is_main_access)
{
return;
}
if (cache_logging)
if (!verbose)
{
static std::unordered_set<uint64_t> logged_addresses{};
if (is_main_access && !logged_addresses.insert(address).second)

View File

@@ -66,4 +66,8 @@ using USHORT = WORD;
#define FALSE 0
#endif
static_assert(sizeof(DWORD) == 4);
static_assert(sizeof(ULONG) == 4);
static_assert(sizeof(int) == 4);
// NOLINTEND(modernize-use-using)

View File

@@ -45,7 +45,7 @@ struct arm_emulator : arch_emulator<Traits>
enum class x86_hookable_instructions
{
invalid,
invalid, // TODO: Get rid of that
syscall,
cpuid,
rdtsc,

View File

@@ -28,12 +28,22 @@ namespace
void run_emulation(windows_emulator& win_emu)
{
bool has_exception = false;
const auto _ = utils::finally([&] {
win_emu.callbacks.on_exception = {}; //
});
try
{
win_emu.callbacks.on_exception = [&] {
has_exception = true;
win_emu.stop();
};
win_emu.log.disable_output(true);
win_emu.start();
if (win_emu.process.exception_rip.has_value())
if (has_exception)
{
throw std::runtime_error("Exception!");
}
@@ -68,7 +78,6 @@ namespace
fuzzer_executer(const std::span<const std::byte> data)
: emulator_data(data)
{
emu.fuzzing = true;
emu.emu().hook_basic_block([&](const basic_block& block) {
if (this->handler && visited_blocks.emplace(block.address).second)
{

View File

@@ -68,7 +68,9 @@ namespace test
if (is_verbose)
{
settings.disable_logging = false;
callbacks.on_stdout = [](const std::string_view data) {
std::cout << data; //
};
}
settings.emulation_root = get_emulator_root();
@@ -93,8 +95,9 @@ namespace test
if (is_verbose)
{
settings.disable_logging = false;
// settings.verbose_calls = true;
callbacks.on_stdout = [](const std::string_view data) {
std::cout << data; //
};
}
settings.emulation_root = get_emulator_root();
@@ -116,7 +119,6 @@ namespace test
inline windows_emulator create_sample_emulator(const sample_configuration& config = {})
{
emulator_settings settings{
.disable_logging = true,
.use_relative_time = true,
};
@@ -126,7 +128,6 @@ namespace test
inline windows_emulator create_empty_emulator()
{
emulator_settings settings{
.disable_logging = true,
.use_relative_time = true,
};

View File

@@ -12,7 +12,6 @@ namespace test
};
const emulator_settings settings{
.disable_logging = true,
.use_relative_time = false,
};

View File

@@ -519,14 +519,12 @@ namespace
{
if (_AFD_BASE(c.io_control_code) != FSCTL_AFD_BASE)
{
win_emu.log.print(color::cyan, "Bad AFD IOCTL: 0x%X\n", c.io_control_code);
win_emu.log.error("Bad AFD IOCTL: 0x%X\n", static_cast<uint32_t>(c.io_control_code));
return STATUS_NOT_SUPPORTED;
}
const auto request = _AFD_REQUEST(c.io_control_code);
win_emu.log.print(color::dark_gray, "--> AFD IOCTL: 0x%X (%d)\n", c.io_control_code, request);
switch (request)
{
case AFD_BIND:
@@ -562,7 +560,8 @@ namespace
case AFD_TRANSPORT_IOCTL:
return STATUS_SUCCESS;
default:
win_emu.log.print(color::gray, "Unsupported AFD IOCTL: 0x%X (%d)\n", c.io_control_code, request);
win_emu.log.error("Unsupported AFD IOCTL: 0x%X (%u)\n", static_cast<uint32_t>(c.io_control_code),
static_cast<uint32_t>(request));
return STATUS_NOT_SUPPORTED;
}
}
@@ -995,8 +994,8 @@ namespace
{
const auto timeout_callback = [](windows_emulator& win_emu, const io_device_context& c) {
const emulator_object<AFD_POLL_INFO64> info_obj{win_emu.emu(), c.input_buffer};
info_obj.access([&](AFD_POLL_INFO64& info) {
info.NumberOfHandles = 0; //
info_obj.access([&](AFD_POLL_INFO64& poll_info) {
poll_info.NumberOfHandles = 0; //
});
};
@@ -1275,4 +1274,4 @@ std::unique_ptr<io_device> create_afd_endpoint()
std::unique_ptr<io_device> create_afd_async_connect_hlp()
{
return std::make_unique<afd_async_connect_hlp>();
}
}

View File

@@ -26,8 +26,6 @@ namespace
NTSTATUS io_control(windows_emulator& win_emu, const io_device_context& c) override
{
win_emu.log.print(color::dark_gray, "--> KSEC IOCTL: 0x%X\n", c.io_control_code);
if (c.io_control_code != 0x390400)
{
return STATUS_NOT_SUPPORTED;

View File

@@ -0,0 +1,28 @@
#pragma once
#include <utils/object.hpp>
#if (defined(__clang__) || defined(__GNUC__)) && !defined(__MINGW64__)
#define FORMAT_ATTRIBUTE(fmt_pos, var_pos) __attribute__((format(printf, fmt_pos, var_pos)))
#else
#define FORMAT_ATTRIBUTE(fmt_pos, var_pos)
#endif
enum class color
{
black,
red,
green,
yellow,
blue,
cyan,
pink,
white,
gray,
dark_gray,
};
struct generic_logger : utils::object
{
virtual void print(color c, std::string_view message) = 0;
virtual void print(color c, const char* message, ...) FORMAT_ATTRIBUTE(3, 4) = 0;
};

View File

@@ -1,5 +1,6 @@
#include "std_include.hpp"
#include "io_device.hpp"
#include "windows_emulator.hpp"
#include "devices/afd_endpoint.hpp"
#include "devices/mount_point_manager.hpp"
#include "devices/security_support_provider.hpp"
@@ -49,3 +50,31 @@ std::unique_ptr<io_device> create_device(const std::u16string_view device)
throw std::runtime_error("Unsupported device: " + u16_to_u8(device));
}
NTSTATUS io_device_container::io_control(windows_emulator& win_emu, const io_device_context& context)
{
this->assert_validity();
win_emu.callbacks.on_ioctrl(*this->device_, this->device_name_, context.io_control_code);
return this->device_->io_control(win_emu, context);
}
void io_device_container::work(windows_emulator& win_emu)
{
this->assert_validity();
this->device_->work(win_emu);
}
void io_device_container::serialize_object(utils::buffer_serializer& buffer) const
{
this->assert_validity();
buffer.write_string(this->device_name_);
this->device_->serialize(buffer);
}
void io_device_container::deserialize_object(utils::buffer_deserializer& buffer)
{
buffer.read_string(this->device_name_);
this->setup();
this->device_->deserialize(buffer);
}

View File

@@ -146,32 +146,11 @@ class io_device_container : public io_device
this->device_->create(win_emu, data);
}
NTSTATUS io_control(windows_emulator& win_emu, const io_device_context& context) override
{
this->assert_validity();
return this->device_->io_control(win_emu, context);
}
void work(windows_emulator& win_emu) override;
NTSTATUS io_control(windows_emulator& win_emu, const io_device_context& context) override;
void work(windows_emulator& win_emu) override
{
this->assert_validity();
this->device_->work(win_emu);
}
void serialize_object(utils::buffer_serializer& buffer) const override
{
this->assert_validity();
buffer.write_string(this->device_name_);
this->device_->serialize(buffer);
}
void deserialize_object(utils::buffer_deserializer& buffer) override
{
buffer.read_string(this->device_name_);
this->setup();
this->device_->deserialize(buffer);
}
void serialize_object(utils::buffer_serializer& buffer) const override;
void deserialize_object(utils::buffer_deserializer& buffer) override;
template <typename T = io_device>
requires(std::is_base_of_v<io_device, T> || std::is_same_v<io_device, T>)

View File

@@ -116,8 +116,13 @@ void logger::print_message(const color c, const std::string_view message, const
print_colored(message, get_color_type(c));
}
void logger::print(const color c, const std::string_view message)
{
this->print_message(c, message);
}
// NOLINTNEXTLINE(cert-dcl50-cpp)
void logger::print(const color c, const char* message, ...) const
void logger::print(const color c, const char* message, ...)
{
format_to_string(message, data);
this->print_message(c, data);

View File

@@ -1,29 +1,11 @@
#pragma once
#include "generic_logger.hpp"
#ifdef OS_WINDOWS
#define FORMAT_ATTRIBUTE(fmt_pos, var_pos)
#else
#define FORMAT_ATTRIBUTE(fmt_pos, var_pos) __attribute__((format(printf, fmt_pos, var_pos)))
#endif
enum class color
{
black,
red,
green,
yellow,
blue,
cyan,
pink,
white,
gray,
dark_gray,
};
class logger
class logger : public generic_logger
{
public:
void print(color c, const char* message, ...) const FORMAT_ATTRIBUTE(3, 4);
void print(color c, std::string_view message) override;
void print(color c, const char* message, ...) override FORMAT_ATTRIBUTE(3, 4);
void info(const char* message, ...) const FORMAT_ATTRIBUTE(2, 3);
void warn(const char* message, ...) const FORMAT_ATTRIBUTE(2, 3);
void error(const char* message, ...) const FORMAT_ATTRIBUTE(2, 3);

View File

@@ -122,8 +122,6 @@ mapped_module* module_manager::map_local_module(const std::filesystem::path& fil
auto mod = map_module_from_file(*this->memory_, std::move(local_file));
mod.is_static = is_static;
logger.log("Mapped %s at 0x%" PRIx64 "\n", mod.path.generic_string().c_str(), mod.image_base);
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);
@@ -145,9 +143,9 @@ void module_manager::serialize(utils::buffer_serializer& buffer) const
{
buffer.write_map(this->modules_);
buffer.write(this->executable->image_base);
buffer.write(this->ntdll->image_base);
buffer.write(this->win32u->image_base);
buffer.write(this->executable ? this->executable->image_base : 0);
buffer.write(this->ntdll ? this->ntdll->image_base : 0);
buffer.write(this->win32u ? this->win32u->image_base : 0);
}
void module_manager::deserialize(utils::buffer_deserializer& buffer)
@@ -158,12 +156,12 @@ void module_manager::deserialize(utils::buffer_deserializer& buffer)
const auto ntdll_base = buffer.read<uint64_t>();
const auto win32u_base = buffer.read<uint64_t>();
this->executable = this->find_by_address(executable_base);
this->ntdll = this->find_by_address(ntdll_base);
this->win32u = this->find_by_address(win32u_base);
this->executable = executable_base ? this->find_by_address(executable_base) : nullptr;
this->ntdll = ntdll_base ? this->find_by_address(ntdll_base) : nullptr;
this->win32u = win32u_base ? this->find_by_address(win32u_base) : nullptr;
}
bool module_manager::unmap(const uint64_t address, const logger& logger)
bool module_manager::unmap(const uint64_t address)
{
const auto mod = this->modules_.find(address);
if (mod == this->modules_.end())
@@ -176,8 +174,6 @@ bool module_manager::unmap(const uint64_t address, const logger& logger)
return true;
}
logger.log("Unmapping %s (0x%" PRIx64 ")\n", mod->second.path.generic_string().c_str(), mod->second.image_base);
this->callbacks_->on_module_unload(mod->second);
unmap_module(*this->memory_, mod->second);
this->modules_.erase(mod);

View File

@@ -51,7 +51,7 @@ class module_manager
void serialize(utils::buffer_serializer& buffer) const;
void deserialize(utils::buffer_deserializer& buffer);
bool unmap(uint64_t address, const logger& logger);
bool unmap(uint64_t address);
const module_map& modules() const
{
return modules_;

View File

@@ -251,7 +251,6 @@ void process_context::serialize(utils::buffer_serializer& buffer) const
buffer.write(this->shared_section_size);
buffer.write(this->dbwin_buffer);
buffer.write(this->dbwin_buffer_size);
buffer.write_optional(this->exception_rip);
buffer.write_optional(this->exit_status);
buffer.write(this->base_allocator);
buffer.write(this->peb);
@@ -291,7 +290,6 @@ void process_context::deserialize(utils::buffer_deserializer& buffer)
buffer.read(this->shared_section_size);
buffer.read(this->dbwin_buffer);
buffer.read(this->dbwin_buffer_size);
buffer.read_optional(this->exception_rip);
buffer.read_optional(this->exit_status);
buffer.read(this->base_allocator);
buffer.read(this->peb);
@@ -361,7 +359,7 @@ handle process_context::create_thread(memory_manager& memory, const uint64_t sta
{
emulator_thread t{memory, *this, start_address, argument, stack_size, suspended, ++this->spawned_thread_count};
auto [h, thr] = this->threads.store_and_get(std::move(t));
this->callbacks_->on_create_thread(h, *thr);
this->callbacks_->on_thread_create(h, *thr);
return h;
}

View File

@@ -32,9 +32,10 @@ struct process_context
{
struct callbacks
{
utils::optional_function<void(handle h, emulator_thread& thr)> on_create_thread{};
utils::optional_function<void(handle h, emulator_thread& thr)> on_thread_create{};
utils::optional_function<void(handle h, emulator_thread& thr)> on_thread_terminated{};
utils::optional_function<void()> on_thread_switch{};
utils::optional_function<void(emulator_thread& current_thread, emulator_thread& new_thread)> on_thread_switch{};
utils::optional_function<void(emulator_thread& current_thread)> on_thread_set_name{};
};
struct atom_entry
@@ -92,7 +93,6 @@ struct process_context
uint64_t dbwin_buffer{0};
uint64_t dbwin_buffer_size{0};
std::optional<uint64_t> exception_rip{};
std::optional<NTSTATUS> exit_status{};
emulator_allocator base_allocator;

View File

@@ -40,6 +40,11 @@ struct registry_key : ref_counted_object
buffer.read(this->hive);
buffer.read(this->path);
}
std::u16string to_string() const
{
return this->hive.get().u16string() + u"\\" + this->path.get().u16string();
}
};
struct registry_value

View File

@@ -24,8 +24,8 @@ void syscall_dispatcher::deserialize(utils::buffer_deserializer& buffer)
this->add_handlers();
}
void syscall_dispatcher::setup(const exported_symbols& ntdll_exports, std::span<const std::byte> ntdll_data,
const exported_symbols& win32u_exports, std::span<const std::byte> win32u_data)
void syscall_dispatcher::setup(const exported_symbols& ntdll_exports, const std::span<const std::byte> ntdll_data,
const exported_symbols& win32u_exports, const std::span<const std::byte> win32u_data)
{
this->handlers_ = {};
@@ -76,8 +76,6 @@ void syscall_dispatcher::dispatch(windows_emulator& win_emu)
try
{
const auto* mod = win_emu.mod_manager.find_by_address(address);
const auto entry = this->handlers_.find(syscall_id);
if (entry == this->handlers_.end())
{
@@ -87,8 +85,7 @@ void syscall_dispatcher::dispatch(windows_emulator& win_emu)
return;
}
const std::string_view mod_name = mod ? mod->name : std::string_view{};
const auto res = win_emu.callbacks.on_syscall(syscall_id, address, mod_name, entry->second.name);
const auto res = win_emu.callbacks.on_syscall(syscall_id, entry->second.name);
if (res == instruction_hook_continuation::skip_instruction)
{
return;
@@ -102,38 +99,6 @@ void syscall_dispatcher::dispatch(windows_emulator& win_emu)
return;
}
if (mod != win_emu.mod_manager.ntdll && mod != win_emu.mod_manager.win32u)
{
win_emu.log.print(color::blue, "Executing inline syscall: %s (0x%X) at 0x%" PRIx64 " (%s)\n",
entry->second.name.c_str(), syscall_id, address, mod ? mod->name.c_str() : "<N/A>");
}
else
{
if (mod->is_within(context.previous_ip))
{
const auto rsp = c.emu.read_stack_pointer();
uint64_t return_address{};
c.emu.try_read_memory(rsp, &return_address, sizeof(return_address));
const auto* caller_mod_name = win_emu.mod_manager.find_name(return_address);
win_emu.log.print(color::dark_gray,
"Executing syscall: %s (0x%X) at 0x%" PRIx64 " via 0x%" PRIx64 " (%s)\n",
entry->second.name.c_str(), syscall_id, address, return_address, caller_mod_name);
}
else
{
const auto* previous_mod = win_emu.mod_manager.find_by_address(context.previous_ip);
win_emu.log.print(color::blue,
"Crafted out-of-line syscall: %s (0x%X) at 0x%" PRIx64 " (%s) via 0x%" PRIx64
" (%s)\n",
entry->second.name.c_str(), syscall_id, address, mod ? mod->name.c_str() : "<N/A>",
context.previous_ip, previous_mod ? previous_mod->name.c_str() : "<N/A>");
}
}
entry->second.handler(c);
}
catch (std::exception& e)
@@ -150,8 +115,10 @@ void syscall_dispatcher::dispatch(windows_emulator& win_emu)
}
}
syscall_dispatcher::syscall_dispatcher(const exported_symbols& ntdll_exports, std::span<const std::byte> ntdll_data,
const exported_symbols& win32u_exports, std::span<const std::byte> win32u_data)
syscall_dispatcher::syscall_dispatcher(const exported_symbols& ntdll_exports,
const std::span<const std::byte> ntdll_data,
const exported_symbols& win32u_exports,
const std::span<const std::byte> win32u_data)
{
this->setup(ntdll_exports, ntdll_data, win32u_exports, win32u_data);
}

View File

@@ -63,7 +63,7 @@ namespace syscalls
if (attributes.ObjectName)
{
name = read_unicode_string(c.emu, attributes.ObjectName);
c.win_emu.log.print(color::dark_gray, "--> Event name: %s\n", u16_to_u8(name).c_str());
c.win_emu.callbacks.on_generic_access("Opening event", name);
}
}
@@ -100,7 +100,7 @@ namespace syscalls
{
const auto attributes = object_attributes.read();
const auto name = read_unicode_string(c.emu, attributes.ObjectName);
c.win_emu.log.print(color::dark_gray, "--> Event name: %s\n", u16_to_u8(name).c_str());
c.win_emu.callbacks.on_generic_access("Opening event", name);
if (name == u"\\KernelObjects\\SystemErrorPortReady")
{

View File

@@ -18,7 +18,7 @@ namespace syscalls
}
c.proc.exit_status = error_status;
c.proc.exception_rip = c.emu.read_instruction_pointer();
c.win_emu.callbacks.on_exception();
c.emu.stop();
return STATUS_SUCCESS;
@@ -27,7 +27,7 @@ namespace syscalls
NTSTATUS handle_NtRaiseException(
const syscall_context& c,
const emulator_object<EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>> /*exception_record*/,
const emulator_object<CONTEXT64> thread_context, const BOOLEAN handle_exception)
const emulator_object<CONTEXT64> /*thread_context*/, const BOOLEAN handle_exception)
{
if (handle_exception)
{
@@ -36,9 +36,9 @@ namespace syscalls
return STATUS_NOT_SUPPORTED;
}
c.proc.exception_rip = thread_context.read().Rip;
c.win_emu.callbacks.on_exception();
c.emu.stop();
return STATUS_SUCCESS;
}
}
}

View File

@@ -141,19 +141,19 @@ namespace syscalls
switch (fs_information_class)
{
case FileFsDeviceInformation:
return handle_query<FILE_FS_DEVICE_INFORMATION>(
c.emu, fs_information, length, io_status_block, [&](FILE_FS_DEVICE_INFORMATION& info) {
if (file_handle == STDOUT_HANDLE && !c.win_emu.buffer_stdout)
{
info.DeviceType = FILE_DEVICE_CONSOLE;
info.Characteristics = 0x20000;
}
else
{
info.DeviceType = FILE_DEVICE_DISK;
info.Characteristics = 0x20020;
}
});
return handle_query<FILE_FS_DEVICE_INFORMATION>(c.emu, fs_information, length, io_status_block,
[&](FILE_FS_DEVICE_INFORMATION& info) {
if (file_handle == STDOUT_HANDLE)
{
info.DeviceType = FILE_DEVICE_CONSOLE;
info.Characteristics = 0x20000;
}
else
{
info.DeviceType = FILE_DEVICE_DISK;
info.Characteristics = 0x20020;
}
});
case FileFsSizeInformation:
return handle_query<FILE_FS_SIZE_INFORMATION>(c.emu, fs_information, length, io_status_block,
@@ -236,16 +236,7 @@ namespace syscalls
if (!f->enumeration_state || query_flags & SL_RESTART_SCAN)
{
const auto mask = file_mask ? read_unicode_string(c.emu, file_mask) : u"";
if (!mask.empty())
{
c.win_emu.log.print(color::dark_gray, "--> Enumerating directory: %s (Mask: \"%s\")\n",
u16_to_u8(f->name).c_str(), u16_to_u8(mask).c_str());
}
else
{
c.win_emu.log.print(color::dark_gray, "--> Enumerating directory: %s\n", u16_to_u8(f->name).c_str());
}
c.win_emu.callbacks.on_generic_access("Enumerating directory", f->name);
f->enumeration_state.emplace(file_enumeration_state{});
f->enumeration_state->files = scan_directory(c.win_emu.file_sys, f->name, mask);
@@ -565,7 +556,7 @@ namespace syscalls
const auto attributes = object_attributes.read();
auto filename = read_unicode_string(c.emu, attributes.ObjectName);
c.win_emu.log.print(color::dark_gray, "--> Query file info: %s\n", u16_to_u8(filename).c_str()); //
c.win_emu.callbacks.on_generic_access("Query file info", filename);
const auto ret = [&](const NTSTATUS status) {
block.Status = status;
@@ -689,13 +680,6 @@ namespace syscalls
c.win_emu.callbacks.on_stdout(temp_buffer);
if (!temp_buffer.ends_with("\n"))
{
temp_buffer.push_back('\n');
}
c.win_emu.log.info("%.*s", static_cast<int>(temp_buffer.size()), temp_buffer.data());
return STATUS_SUCCESS;
}
@@ -805,7 +789,7 @@ namespace syscalls
auto filename = read_unicode_string(c.emu, attributes.ObjectName);
auto printer = utils::finally([&] {
c.win_emu.log.print(color::dark_gray, "--> Opening file: %s\n", u16_to_u8(filename).c_str()); //
c.win_emu.callbacks.on_generic_access("Opening file", filename); //
});
const auto io_device_name = get_io_device_name(filename);
@@ -856,7 +840,7 @@ namespace syscalls
if (is_directory || create_options & FILE_DIRECTORY_FILE)
{
c.win_emu.log.print(color::dark_gray, "--> Opening folder: %s\n", u16_to_u8(f.name).c_str());
c.win_emu.callbacks.on_generic_access("Opening folder", f.name);
if (create_disposition & FILE_CREATE)
{
@@ -878,7 +862,7 @@ namespace syscalls
return STATUS_SUCCESS;
}
c.win_emu.log.print(color::dark_gray, "--> Opening file: %s\n", u16_to_u8(f.name).c_str());
c.win_emu.callbacks.on_generic_access("Opening file", f.name);
std::u16string mode = map_mode(desired_access, create_disposition);
@@ -931,7 +915,7 @@ namespace syscalls
filename = root->name + (has_separator ? u"" : u"\\") + filename;
}
c.win_emu.log.print(color::dark_gray, "--> Querying file attributes: %s\n", u16_to_u8(filename).c_str());
c.win_emu.callbacks.on_generic_access("Querying file attributes", filename);
const auto local_filename = c.win_emu.file_sys.translate(filename).u8string();
@@ -972,7 +956,7 @@ namespace syscalls
const auto filename = read_unicode_string(
c.emu, emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>>{c.emu, attributes.ObjectName});
c.win_emu.log.print(color::dark_gray, "--> Querying file attributes: %s\n", u16_to_u8(filename).c_str());
c.win_emu.callbacks.on_generic_access("Querying file attributes", filename);
const auto local_filename = c.win_emu.file_sys.translate(filename).u8string();

View File

@@ -34,14 +34,13 @@ namespace syscalls
return STATUS_SUCCESS;
}
NTSTATUS handle_NtGetNlsSectionPtr(const syscall_context& c, ULONG section_type, ULONG section_data,
emulator_pointer /*context_data*/, emulator_object<uint64_t> section_pointer,
emulator_object<ULONG> section_size)
NTSTATUS handle_NtGetNlsSectionPtr(const syscall_context& c, const ULONG section_type, const ULONG section_data,
emulator_pointer /*context_data*/,
const emulator_object<uint64_t> section_pointer,
const emulator_object<ULONG> section_size)
{
if (section_type == 11)
{
c.win_emu.log.print(color::dark_gray, "--> Code Page: %d\n", section_data);
const auto file_path = R"(C:\Windows\System32\C_)" + std::to_string(section_data) + ".NLS";
const auto locale_file = utils::io::read_file(c.win_emu.file_sys.translate(file_path));
if (locale_file.empty())
@@ -59,7 +58,7 @@ namespace syscalls
return STATUS_SUCCESS;
}
c.win_emu.log.print(color::gray, "Unsupported section type: %X\n", section_type);
c.win_emu.log.warn("Unsupported section type: %X\n", static_cast<uint32_t>(section_type));
return STATUS_NOT_SUPPORTED;
}
@@ -78,13 +77,13 @@ namespace syscalls
return STATUS_NOT_SUPPORTED;
}
NTSTATUS handle_NtQueryDefaultUILanguage(const syscall_context&, emulator_object<LANGID> language_id)
NTSTATUS handle_NtQueryDefaultUILanguage(const syscall_context&, const emulator_object<LANGID> language_id)
{
language_id.write(0x407);
return STATUS_SUCCESS;
}
NTSTATUS handle_NtQueryInstallUILanguage(const syscall_context&, emulator_object<LANGID> language_id)
NTSTATUS handle_NtQueryInstallUILanguage(const syscall_context&, const emulator_object<LANGID> language_id)
{
language_id.write(0x407);
return STATUS_SUCCESS;

View File

@@ -146,9 +146,7 @@ namespace syscalls
const auto requested_protection = map_nt_to_emulator_protection(protection);
c.win_emu.log.print(color::dark_gray, "--> Changing protection at 0x%" PRIx64 "-0x%" PRIx64 " to %s\n",
aligned_start, aligned_start + aligned_length,
get_permission_string(requested_protection).c_str());
c.win_emu.callbacks.on_memory_protect(aligned_start, aligned_length, requested_protection);
memory_permission old_protection_value{};
@@ -192,8 +190,6 @@ namespace syscalls
if (!potential_base)
{
c.win_emu.log.print(color::dark_gray, "--> Not allocated\n");
return STATUS_MEMORY_NOT_ALLOCATED;
}
@@ -210,16 +206,11 @@ namespace syscalls
if (commit && !reserve &&
c.win_emu.memory.commit_memory(potential_base, static_cast<size_t>(allocation_bytes), protection))
{
c.win_emu.log.print(is_executable(protection) ? color::gray : color::dark_gray,
"--> Committed 0x%" PRIx64 " - 0x%" PRIx64 " (%s)\n", potential_base,
potential_base + allocation_bytes, get_permission_string(protection).c_str());
c.win_emu.callbacks.on_memory_allocate(potential_base, allocation_bytes, protection, true);
return STATUS_SUCCESS;
}
c.win_emu.log.print(is_executable(protection) ? color::gray : color::dark_gray,
"--> Allocated 0x%" PRIx64 " - 0x%" PRIx64 " (%s)\n", potential_base,
potential_base + allocation_bytes, get_permission_string(protection).c_str());
c.win_emu.callbacks.on_memory_allocate(potential_base, allocation_bytes, protection, false);
return c.win_emu.memory.allocate_memory(potential_base, static_cast<size_t>(allocation_bytes), protection,
!commit)

View File

@@ -44,7 +44,7 @@ namespace syscalls
{
name = read_unicode_string(
c.emu, emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>>{c.emu, attributes.ObjectName});
c.win_emu.log.print(color::dark_gray, "--> Mutant name: %s\n", u16_to_u8(name).c_str());
c.win_emu.callbacks.on_generic_access("Opening mutant", name);
}
}
@@ -78,7 +78,7 @@ namespace syscalls
if (attributes.ObjectName)
{
name = read_unicode_string(c.emu, attributes.ObjectName);
c.win_emu.log.print(color::dark_gray, "--> Mutant name: %s\n", u16_to_u8(name).c_str());
c.win_emu.callbacks.on_generic_access("Opening mutant", name);
}
}

View File

@@ -158,8 +158,7 @@ namespace syscalls
if (!is_awaitable_object_type(h))
{
c.win_emu.log.print(color::gray, "Unsupported handle type for NtWaitForMultipleObjects: %d!\n",
h.value.type);
c.win_emu.log.warn("Unsupported handle type for NtWaitForMultipleObjects: %d!\n", h.value.type);
return STATUS_NOT_SUPPORTED;
}
@@ -180,7 +179,7 @@ namespace syscalls
{
if (!is_awaitable_object_type(h))
{
c.win_emu.log.print(color::gray, "Unsupported handle type for NtWaitForSingleObject: %d!\n", h.value.type);
c.win_emu.log.warn("Unsupported handle type for NtWaitForSingleObject: %d!\n", h.value.type);
return STATUS_NOT_SUPPORTED;
}

View File

@@ -14,7 +14,7 @@ namespace syscalls
const emulator_object<ULONG> connection_info_length)
{
auto port_name = read_unicode_string(c.emu, server_port_name);
c.win_emu.log.print(color::dark_gray, "NtConnectPort: %s\n", u16_to_u8(port_name).c_str());
c.win_emu.callbacks.on_generic_access("Connecting port", port_name);
port p{};
p.name = std::move(port_name);

View File

@@ -25,7 +25,7 @@ namespace syscalls
key = full_path.u16string();
}
c.win_emu.log.print(color::dark_gray, "--> Registry key: %s\n", u16_to_u8(key).c_str());
c.win_emu.callbacks.on_generic_access("Registry key", key);
auto entry = c.win_emu.registry.get_key({key});
if (!entry.has_value())
@@ -111,7 +111,7 @@ namespace syscalls
return STATUS_SUCCESS;
}
c.win_emu.log.print(color::gray, "Unsupported registry class: %X\n", key_information_class);
c.win_emu.log.warn("Unsupported registry class: %X\n", key_information_class);
c.emu.stop();
return STATUS_NOT_SUPPORTED;
}
@@ -129,8 +129,12 @@ namespace syscalls
}
const auto query_name = read_unicode_string(c.emu, value_name);
c.win_emu.log.print(color::dark_gray, "--> Query value key: %s (%s\\%s)\n", u16_to_u8(query_name).c_str(),
key->hive.get().string().c_str(), key->path.get().string().c_str());
if (c.win_emu.callbacks.on_generic_access)
{
// TODO: Find a better way to log this
c.win_emu.callbacks.on_generic_access("Querying value key", query_name + u" (" + key->to_string() + u")");
}
const auto value = c.win_emu.registry.get_value(*key, u16_to_u8(query_name));
if (!value)
@@ -224,7 +228,7 @@ namespace syscalls
return STATUS_SUCCESS;
}
c.win_emu.log.print(color::gray, "Unsupported registry value class: %X\n", key_value_information_class);
c.win_emu.log.warn("Unsupported registry value class: %X\n", key_value_information_class);
c.emu.stop();
return STATUS_NOT_SUPPORTED;
}
@@ -332,7 +336,7 @@ namespace syscalls
return STATUS_SUCCESS;
}
c.win_emu.log.print(color::gray, "Unsupported registry enumeration class: %X\n", key_information_class);
c.win_emu.log.warn("Unsupported registry enumeration class: %X\n", key_information_class);
return STATUS_NOT_SUPPORTED;
}
@@ -443,8 +447,7 @@ namespace syscalls
return STATUS_SUCCESS;
}
c.win_emu.log.print(color::gray, "Unsupported registry value enumeration class: %X\n",
key_value_information_class);
c.win_emu.log.warn("Unsupported registry value enumeration class: %X\n", key_value_information_class);
return STATUS_NOT_SUPPORTED;
}
}

View File

@@ -20,7 +20,7 @@ namespace syscalls
const auto* file = c.proc.files.get(file_handle);
if (file)
{
c.win_emu.log.print(color::dark_gray, "--> Section for file %s\n", u16_to_u8(file->name).c_str());
c.win_emu.callbacks.on_generic_access("Section for file", file->name);
s.file_name = file->name;
}
@@ -30,7 +30,7 @@ namespace syscalls
if (attributes.ObjectName)
{
auto name = read_unicode_string(c.emu, attributes.ObjectName);
c.win_emu.log.print(color::dark_gray, "--> Section with name %s\n", u16_to_u8(name).c_str());
c.win_emu.callbacks.on_generic_access("Section with name", name);
s.name = std::move(name);
}
}
@@ -60,7 +60,7 @@ namespace syscalls
const auto attributes = object_attributes.read();
auto filename = read_unicode_string(c.emu, attributes.ObjectName);
c.win_emu.log.print(color::dark_gray, "--> Opening section: %s\n", u16_to_u8(filename).c_str());
c.win_emu.callbacks.on_generic_access("Opening section", filename);
if (filename == u"\\Windows\\SharedSection")
{
@@ -287,7 +287,7 @@ namespace syscalls
const auto* mod = c.win_emu.mod_manager.find_by_address(base_address);
if (mod != nullptr)
{
if (c.win_emu.mod_manager.unmap(base_address, c.win_emu.log))
if (c.win_emu.mod_manager.unmap(base_address))
{
return STATUS_SUCCESS;
}

View File

@@ -109,7 +109,7 @@ namespace syscalls
return STATUS_NOT_SUPPORTED;
case SystemControlFlowTransition:
c.win_emu.log.print(color::pink, "Warbird control flow transition!\n");
c.win_emu.callbacks.on_suspicious_activity("Warbird control flow transition");
return STATUS_NOT_SUPPORTED;
case SystemTimeOfDayInformation:

View File

@@ -26,7 +26,7 @@ namespace syscalls
if (info_class == ThreadHideFromDebugger)
{
c.win_emu.log.print(color::pink, "--> Hiding thread %X from debugger!\n", thread->id);
c.win_emu.callbacks.on_suspicious_activity("Hiding thread from debugger");
return STATUS_SUCCESS;
}
@@ -41,8 +41,7 @@ namespace syscalls
const auto i = info.read();
thread->name = read_unicode_string(c.emu, i.ThreadName);
c.win_emu.log.print(color::blue, "Setting thread (%d) name: %s\n", thread->id,
u16_to_u8(thread->name).c_str());
c.win_emu.callbacks.on_thread_set_name(*thread);
return STATUS_SUCCESS;
}
@@ -325,14 +324,15 @@ namespace syscalls
}
NTSTATUS handle_NtAlertThreadByThreadIdEx(const syscall_context& c, const uint64_t thread_id,
const emulator_object<EMU_RTL_SRWLOCK<EmulatorTraits<Emu64>>> lock)
const emulator_object<EMU_RTL_SRWLOCK<EmulatorTraits<Emu64>>> /*lock*/)
{
if (lock.value())
// TODO: Support lock
/*if (lock.value())
{
c.win_emu.log.print(color::gray, "NtAlertThreadByThreadIdEx with lock not supported yet!");
// c.emu.stop();
// return STATUS_NOT_SUPPORTED;
}
c.win_emu.log.warn("NtAlertThreadByThreadIdEx with lock not supported yet!\n");
// c.emu.stop();
// return STATUS_NOT_SUPPORTED;
}*/
return handle_NtAlertThreadByThreadId(c, thread_id);
}
@@ -425,7 +425,7 @@ namespace syscalls
if (flags != 0)
{
c.win_emu.log.error("NtGetNextThread flags %X not supported\n", flags);
c.win_emu.log.error("NtGetNextThread flags %X not supported\n", static_cast<uint32_t>(flags));
c.emu.stop();
return STATUS_NOT_SUPPORTED;
}
@@ -470,7 +470,7 @@ namespace syscalls
thread_context.access([&](CONTEXT64& context) {
if ((context.ContextFlags & CONTEXT_DEBUG_REGISTERS_64) == CONTEXT_DEBUG_REGISTERS_64)
{
c.win_emu.log.print(color::pink, "--> Reading debug registers!\n");
c.win_emu.callbacks.on_suspicious_activity("Reading debug registers");
}
cpu_context::save(c.emu, context);
@@ -509,7 +509,7 @@ namespace syscalls
if ((context.ContextFlags & CONTEXT_DEBUG_REGISTERS_64) == CONTEXT_DEBUG_REGISTERS_64)
{
c.win_emu.log.print(color::pink, "--> Setting debug registers!\n");
c.win_emu.callbacks.on_suspicious_activity("Setting debug registers");
}
return STATUS_SUCCESS;

View File

@@ -43,7 +43,7 @@ namespace syscalls
if (attributes.ObjectName)
{
name = read_unicode_string(c.emu, attributes.ObjectName);
c.win_emu.log.print(color::dark_gray, "--> Timer name: %s\n", u16_to_u8(name).c_str());
c.win_emu.callbacks.on_generic_access("Opening timer", name);
}
}

View File

@@ -107,7 +107,7 @@ namespace
return;
}
win_emu.log.print(color::dark_gray, "Dispatching APC...\n");
win_emu.callbacks.on_generic_activity("APC Dispatch");
const auto next_apx = apcs.front();
apcs.erase(apcs.begin());
@@ -165,8 +165,7 @@ namespace
{
if (active_thread)
{
win_emu.log.print(color::dark_gray, "Performing thread switch: %X -> %X\n", active_thread->id,
thread.id);
win_emu.callbacks.on_thread_switch(*active_thread, thread);
active_thread->save(emu);
}
@@ -184,7 +183,6 @@ namespace
}
thread.apc_alertable = false;
win_emu.callbacks.on_thread_switch();
return true;
}
@@ -289,7 +287,7 @@ windows_emulator::windows_emulator(std::unique_ptr<x86_64_emulator> emu, applica
: windows_emulator(std::move(emu), settings, std::move(callbacks), std::move(interfaces))
{
fixup_application_settings(app_settings);
this->setup_process(app_settings);
this->application_settings_ = std::move(app_settings);
}
windows_emulator::windows_emulator(std::unique_ptr<x86_64_emulator> emu, const emulator_settings& settings,
@@ -304,8 +302,7 @@ windows_emulator::windows_emulator(std::unique_ptr<x86_64_emulator> emu, const e
registry(emulation_root.empty() ? settings.registry_directory : emulation_root / "registry"),
mod_manager(memory, file_sys, this->callbacks),
process(*this->emu_, memory, *this->clock_, this->callbacks),
modules_(settings.modules),
ignored_functions_(settings.ignored_functions)
use_relative_time_(settings.use_relative_time)
{
#ifndef OS_WINDOWS
if (this->emulation_root.empty())
@@ -324,16 +321,24 @@ windows_emulator::windows_emulator(std::unique_ptr<x86_64_emulator> emu, const e
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->setup_hooks();
}
windows_emulator::~windows_emulator() = default;
void windows_emulator::setup_process_if_necessary()
{
if (!this->application_settings_)
{
return;
}
auto app_settings = std::move(*this->application_settings_);
this->application_settings_ = {};
this->setup_process(app_settings);
}
void windows_emulator::setup_process(const application_settings& app_settings)
{
const auto& emu = this->emu();
@@ -418,89 +423,7 @@ void windows_emulator::on_instruction_execution(const uint64_t address)
this->process.previous_ip = this->process.current_ip;
this->process.current_ip = this->emu().read_instruction_pointer();
const auto is_main_exe = this->mod_manager.executable->is_within(address);
const auto is_previous_main_exe = this->mod_manager.executable->is_within(this->process.previous_ip);
const auto binary = utils::make_lazy([&] {
if (is_main_exe)
{
return this->mod_manager.executable;
}
return this->mod_manager.find_by_address(address); //
});
const auto previous_binary = utils::make_lazy([&] {
if (is_previous_main_exe)
{
return this->mod_manager.executable;
}
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_interesting_call = is_previous_main_exe //
|| 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() && !this->ignored_functions_.contains(export_entry->second))
{
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(x86_register::rax), emu.reg(x86_register::rbx), emu.reg(x86_register::rcx),
emu.reg(x86_register::rdx), emu.reg(x86_register::r8), emu.reg(x86_register::r9),
emu.reg(x86_register::rdi), emu.reg(x86_register::rsi), binary ? binary->name.c_str() : "<N/A>");
this->callbacks.on_instruction(address);
}
void windows_emulator::setup_hooks()
@@ -529,17 +452,13 @@ void windows_emulator::setup_hooks()
return instruction_hook_continuation::skip_instruction;
});
// TODO: Unicorn needs this - This should be handled in the backend
this->emu().hook_instruction(x86_hookable_instructions::invalid, [&] {
const auto ip = this->emu().read_instruction_pointer();
this->log.print(color::gray, "Invalid instruction at: 0x%" PRIx64 " (via 0x%" PRIx64 ")\n", ip,
this->process.previous_ip);
return instruction_hook_continuation::skip_instruction;
return instruction_hook_continuation::skip_instruction; //
});
this->emu().hook_interrupt([&](const int interrupt) {
const auto rip = this->emu().read_instruction_pointer();
this->callbacks.on_exception();
const auto eflags = this->emu().reg<uint32_t>(x86_register::eflags);
switch (interrupt)
@@ -550,63 +469,37 @@ void windows_emulator::setup_hooks()
case 1:
if ((eflags & 0x100) != 0)
{
this->log.print(color::pink, "Singlestep (Trap Flag): 0x%" PRIx64 "\n", rip);
this->emu().reg(x86_register::eflags, eflags & ~0x100);
}
else
{
this->log.print(color::pink, "Singlestep: 0x%" PRIx64 "\n", rip);
}
this->callbacks.on_suspicious_activity("Singlestep");
dispatch_single_step(this->emu(), this->process);
return;
case 3:
this->log.print(color::pink, "Breakpoint: 0x%" PRIx64 "\n", rip);
this->callbacks.on_suspicious_activity("Breakpoint");
dispatch_breakpoint(this->emu(), this->process);
return;
case 6:
this->callbacks.on_suspicious_activity("Illegal instruction");
dispatch_illegal_instruction_violation(this->emu(), this->process);
return;
case 45:
this->log.print(color::pink, "DbgPrint: 0x%" PRIx64 "\n", rip);
this->callbacks.on_suspicious_activity("DbgPrint");
dispatch_breakpoint(this->emu(), this->process);
return;
default:
if (this->callbacks.on_generic_activity)
{
this->callbacks.on_generic_activity("Interrupt " + std::to_string(interrupt));
}
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;
}
this->callbacks.on_memory_violate(address, size, operation, type);
dispatch_access_violation(this->emu(), this->process, address, operation);
return memory_violation_continuation::resume;
});
@@ -619,6 +512,7 @@ void windows_emulator::setup_hooks()
void windows_emulator::start(size_t count)
{
this->should_stop = false;
this->setup_process_if_necessary();
const auto use_count = count > 0;
const auto start_instructions = this->executed_instructions_;
@@ -690,9 +584,11 @@ void windows_emulator::register_factories(utils::buffer_deserializer& buffer)
void windows_emulator::serialize(utils::buffer_serializer& buffer) const
{
buffer.write_optional(this->application_settings_);
buffer.write(this->executed_instructions_);
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);
@@ -704,6 +600,7 @@ void windows_emulator::deserialize(utils::buffer_deserializer& buffer)
{
this->register_factories(buffer);
buffer.read_optional(this->application_settings_);
buffer.read(this->executed_instructions_);
buffer.read(this->switch_thread_);
@@ -726,13 +623,18 @@ void windows_emulator::deserialize(utils::buffer_deserializer& buffer)
void windows_emulator::save_snapshot()
{
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);
utils::buffer_serializer buffer{};
this->process_snapshot_ = serializer.move_buffer();
buffer.write_optional(this->application_settings_);
buffer.write(this->executed_instructions_);
buffer.write(this->switch_thread_);
this->emu().serialize_state(buffer, true);
this->memory.serialize_memory_state(buffer, true);
this->mod_manager.serialize(buffer);
this->process.serialize(buffer);
this->process_snapshot_ = buffer.move_buffer();
// TODO: Make process copyable
// this->process_snapshot_ = this->process;
@@ -746,13 +648,17 @@ void windows_emulator::restore_snapshot()
return;
}
utils::buffer_deserializer deserializer{this->process_snapshot_};
utils::buffer_deserializer buffer{this->process_snapshot_};
this->register_factories(deserializer);
this->register_factories(buffer);
this->emu().deserialize_state(deserializer, true);
this->memory.deserialize_memory_state(deserializer, true);
this->mod_manager.deserialize(deserializer);
this->process.deserialize(deserializer);
buffer.read_optional(this->application_settings_);
buffer.read(this->executed_instructions_);
buffer.read(this->switch_thread_);
this->emu().deserialize_state(buffer, true);
this->memory.deserialize_memory_state(buffer, true);
this->mod_manager.deserialize(buffer);
this->process.deserialize(buffer);
// this->process = *this->process_snapshot_;
}

View File

@@ -13,13 +13,27 @@
#include "module/module_manager.hpp"
#include "network/socket_factory.hpp"
struct io_device;
#define opt_func utils::optional_function
struct emulator_callbacks : module_manager::callbacks, process_context::callbacks
{
utils::optional_function<instruction_hook_continuation(uint32_t syscall_id, x86_64_emulator::pointer_type address,
std::string_view mod_name, std::string_view syscall_name)>
on_syscall{};
using continuation = instruction_hook_continuation;
utils::optional_function<void(std::string_view)> on_stdout{};
opt_func<void()> on_exception{};
opt_func<void(uint64_t address, uint64_t length, memory_permission)> on_memory_protect{};
opt_func<void(uint64_t address, uint64_t length, memory_permission, bool commit)> on_memory_allocate{};
opt_func<void(uint64_t address, uint64_t length, memory_operation, memory_violation_type type)> on_memory_violate{};
opt_func<continuation(uint32_t syscall_id, std::string_view syscall_name)> on_syscall{};
opt_func<void(std::string_view data)> on_stdout{};
opt_func<void(std::string_view type, std::u16string_view name)> on_generic_access{};
opt_func<void(std::string_view description)> on_generic_activity{};
opt_func<void(std::string_view description)> on_suspicious_activity{};
opt_func<void(uint64_t address)> on_instruction{};
opt_func<void(io_device& device, std::u16string_view device_name, ULONG code)> on_ioctrl{};
};
struct application_settings
@@ -27,22 +41,32 @@ struct application_settings
windows_path application{};
windows_path working_directory{};
std::vector<std::u16string> arguments{};
void serialize(utils::buffer_serializer& buffer) const
{
buffer.write(this->application);
buffer.write(this->working_directory);
buffer.write_vector(this->arguments);
}
void deserialize(utils::buffer_deserializer& buffer)
{
buffer.read(this->application);
buffer.read(this->working_directory);
buffer.read_vector(this->arguments);
}
};
struct emulator_settings
{
bool disable_logging{false};
bool use_relative_time{false};
std::filesystem::path emulation_root{};
std::filesystem::path registry_directory{"./registry"};
bool verbose_calls{false};
bool disable_logging{false};
bool silent_until_main{false};
bool use_relative_time{false};
std::unordered_map<uint16_t, uint16_t> port_mappings{};
std::unordered_map<windows_path, std::filesystem::path> path_mappings{};
std::set<std::string, std::less<>> modules{};
std::set<std::string, std::less<>> ignored_functions{};
};
struct emulator_interfaces
@@ -54,6 +78,7 @@ struct emulator_interfaces
class windows_emulator
{
uint64_t executed_instructions_{0};
std::optional<application_settings> application_settings_{};
std::unique_ptr<x86_64_emulator> emu_{};
std::unique_ptr<utils::clock> clock_{};
@@ -128,6 +153,8 @@ class windows_emulator
return this->executed_instructions_;
}
void setup_process_if_necessary();
void start(size_t count = 0);
void stop();
@@ -176,25 +203,17 @@ class windows_emulator
}
}
bool verbose{false};
bool verbose_calls{false};
bool buffer_stdout{false};
bool fuzzing{false};
void yield_thread(bool alertable = false);
bool perform_thread_switch();
bool activate_thread(uint32_t id);
private:
bool switch_thread_{false};
bool use_relative_time_{false};
bool silent_until_main_{false};
bool use_relative_time_{false}; // TODO: Get rid of that
std::atomic_bool should_stop{false};
std::unordered_map<uint16_t, uint16_t> port_mappings_{};
std::set<std::string, std::less<>> modules_{};
std::set<std::string, std::less<>> ignored_functions_{};
std::vector<std::byte> process_snapshot_{};
// std::optional<process_context> process_snapshot_{};

View File

@@ -75,7 +75,8 @@ class windows_path
template <typename T>
requires(!std::is_same_v<std::remove_cvref_t<T>, windows_path> &&
!std::is_same_v<std::remove_cvref_t<T>, std::filesystem::path>)
!std::is_same_v<std::remove_cvref_t<T>, std::filesystem::path> &&
!std::is_same_v<std::remove_cvref_t<T>, utils::buffer_deserializer>)
windows_path(T&& path_like)
: windows_path(std::filesystem::path(std::forward<T>(path_like)))
{