Isolate more analysis into analyzer

This commit is contained in:
momo5502
2025-06-05 18:59:27 +02:00
parent f046246740
commit f2e0e91630
9 changed files with 130 additions and 137 deletions

View File

@@ -7,24 +7,34 @@
namespace
{
template <typename Return, typename... Args>
std::function<Return(Args...)> make_callback(windows_emulator& win_emu,
Return (*callback)(windows_emulator&, Args...))
std::function<Return(Args...)> make_callback(analysis_context& c, Return (*callback)(analysis_context&, Args...))
{
return [&win_emu, callback](Args... args) {
return callback(win_emu, std::forward<Args>(args)...); //
return [&c, callback](Args... args) {
return callback(c, std::forward<Args>(args)...); //
};
}
void handle_suspicious_activity(windows_emulator& win_emu, const std::string_view details)
template <typename Return, typename... Args>
std::function<Return(Args...)> make_callback(analysis_context& c,
Return (*callback)(const analysis_context&, Args...))
{
const auto rip = win_emu.emu().read_instruction_pointer();
win_emu.log.print(color::pink, "Suspicious: %.*s (0x%" PRIx64 ")\n", STR_VIEW_VA(details), rip);
return [&c, callback](Args... args) {
return callback(c, std::forward<Args>(args)...); //
};
}
void handle_instruction(windows_emulator& win_emu, const uint64_t address)
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 (0x%" PRIx64 ")\n", STR_VIEW_VA(details), rip);
}
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(win_emu.process.previous_ip);
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)
@@ -45,75 +55,56 @@ namespace
});
const auto is_in_interesting_module = [&] {
if (win_emu.modules_.empty())
if (c.settings->modules.empty())
{
return false;
}
return (binary && win_emu.modules_.contains(binary->name)) ||
(previous_binary && win_emu.modules_.contains(previous_binary->name));
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 (win_emu.silent_until_main_ && is_main_exe)
if (!c.has_reached_main && c.settings->concise_logging && !c.settings->silent && is_main_exe)
{
win_emu.silent_until_main_ = false;
c.has_reached_main = true;
win_emu.log.disable_output(false);
}
if (!win_emu.verbose && !win_emu.verbose_calls && !is_interesting_call)
if ((!c.settings->verbose_logging && !is_interesting_call) || !binary)
{
return;
}
if (binary)
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 export_entry = binary->address_names.find(address);
if (export_entry != binary->address_names.end() &&
!win_emu.ignored_functions_.contains(export_entry->second))
{
const auto rsp = win_emu.emu().read_stack_pointer();
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));
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);
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);
}
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);
}
if (!win_emu.verbose)
else if (address == binary->entry_point)
{
return;
win_emu.log.print(is_interesting_call ? color::yellow : color::gray,
"Executing entry point: %s (0x%" PRIx64 ")\n", binary->name.c_str(), address);
}
auto& emu = win_emu.emu();
// TODO: Remove or cleanup
win_emu.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>");
}
emulator_callbacks::continuation handle_syscall(windows_emulator& win_emu, const uint32_t syscall_id,
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();
@@ -150,13 +141,30 @@ namespace
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(windows_emulator& win_emu)
void register_analysis_callbacks(analysis_context& c)
{
auto& cb = win_emu.callbacks;
auto& cb = c.win_emu->callbacks;
cb.on_syscall = make_callback(win_emu, handle_syscall);
cb.on_instruction = make_callback(win_emu, handle_instruction);
cb.on_suspicious_activity = make_callback(win_emu, handle_suspicious_activity);
cb.on_stdout = make_callback(c, handle_stdout);
cb.on_syscall = make_callback(c, handle_syscall);
cb.on_instruction = make_callback(c, handle_instruction);
cb.on_suspicious_activity = make_callback(c, handle_suspicious_activity);
}

View File

@@ -2,4 +2,24 @@
class windows_emulator;
void register_analysis_callbacks(windows_emulator& win_emu);
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,6 +6,7 @@
#include "object_watching.hpp"
#include "snapshot.hpp"
#include "analysis.hpp"
#ifdef OS_EMSCRIPTEN
#include <event_handler.hpp>
@@ -17,18 +18,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 +54,23 @@ namespace
}
void watch_system_objects(windows_emulator& win_emu, const std::set<std::string, std::less<>>& modules,
const bool cache_logging)
const bool verbose)
{
(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 +81,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
@@ -206,12 +201,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,8 +243,16 @@ namespace
bool run(const analysis_options& options, const std::span<const std::string_view> args)
{
const auto win_emu = setup_emulator(options, args);
analysis_context context{
.settings = &options,
};
const auto win_emu = setup_emulator(options, args);
win_emu->log.disable_output(options.concise_logging || options.silent);
context.win_emu = win_emu.get();
// TODO: Move to analysis
#ifdef OS_EMSCRIPTEN
win_emu->callbacks.on_thread_switch = [&] {
debugger::event_context c{.win_emu = *win_emu};
@@ -265,16 +263,9 @@ namespace
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;
watch_system_objects(*win_emu, options.modules, options.verbose_logging);
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);
const auto& exe = *win_emu->mod_manager.executable;

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

@@ -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

@@ -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,
@@ -689,13 +689,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;
}

View File

@@ -304,8 +304,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,11 +323,6 @@ 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();
}

View File

@@ -32,18 +32,13 @@ struct application_settings
struct emulator_settings
{
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
@@ -184,14 +179,6 @@ class windows_emulator
bool perform_thread_switch();
bool activate_thread(uint32_t id);
// TODO: Move to analyzer
bool verbose{false};
bool verbose_calls{false};
bool buffer_stdout{false};
bool silent_until_main_{false};
std::set<std::string, std::less<>> modules_{};
std::set<std::string, std::less<>> ignored_functions_{};
private:
bool switch_thread_{false};
bool use_relative_time_{false};