More diagnostics and fixes (#446)

This commit is contained in:
Maurice Heumann
2025-08-09 21:17:54 +02:00
committed by GitHub
12 changed files with 703 additions and 84 deletions

View File

@@ -124,7 +124,7 @@ namespace
}
template <typename CharType = char>
void print_arg_as_string(windows_emulator& win_emu, size_t index)
void print_arg_as_string(windows_emulator& win_emu, const size_t index)
{
const auto var_ptr = get_function_argument(win_emu.emu(), index);
if (var_ptr)
@@ -152,9 +152,60 @@ namespace
}
}
bool is_thread_alive(const analysis_context& c, const uint32_t thread_id)
{
for (const auto& t : c.win_emu->process.threads | std::views::values)
{
if (t.id == thread_id)
{
return true;
}
}
return false;
}
void update_import_access(analysis_context& c, const uint64_t address)
{
if (c.accessed_imports.empty())
{
return;
}
const auto& t = c.win_emu->current_thread();
for (auto entry = c.accessed_imports.begin(); entry != c.accessed_imports.end();)
{
auto& a = *entry;
const auto is_same_thread = t.id == a.thread_id;
if (is_same_thread && address == a.address)
{
entry = c.accessed_imports.erase(entry);
continue;
}
constexpr auto inst_delay = 100u;
const auto execution_delay_reached =
is_same_thread && a.access_inst_count + inst_delay <= t.executed_instructions;
if (!execution_delay_reached && is_thread_alive(c, a.thread_id))
{
++entry;
continue;
}
c.win_emu->log.print(color::green, "Import read access without execution: %s (%s) at 0x%" PRIx64 " (%s)\n",
a.import_name.c_str(), a.import_module.c_str(), a.access_rip,
a.accessor_module.c_str());
entry = c.accessed_imports.erase(entry);
}
}
void handle_instruction(analysis_context& c, const uint64_t address)
{
auto& win_emu = *c.win_emu;
update_import_access(c, address);
#ifdef OS_EMSCRIPTEN
if ((win_emu.get_executed_instructions() % 0x20000) == 0)
@@ -222,8 +273,8 @@ namespace
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);
"Executing function: %s (%s) (0x%" PRIx64 ") via (0x%" PRIx64 ") %s\n",
export_entry->second.c_str(), binary->name.c_str(), address, return_address, mod_name);
if (is_interesting_call)
{
@@ -293,6 +344,68 @@ namespace
c.win_emu->log.info("%.*s%s", static_cast<int>(data.size()), data.data(), data.ends_with("\n") ? "" : "\n");
}
}
void watch_import_table(analysis_context& c)
{
c.win_emu->setup_process_if_necessary();
const auto& import_list = c.win_emu->mod_manager.executable->imports;
if (import_list.empty())
{
return;
}
auto min = std::numeric_limits<uint64_t>::max();
auto max = std::numeric_limits<uint64_t>::min();
for (const auto& imports : import_list | std::views::values)
{
for (const auto& import : imports)
{
min = std::min(import.address, min);
max = std::max(import.address, max);
}
}
c.win_emu->emu().hook_memory_read(min, max - min, [&c](const uint64_t address, const void*, size_t) {
const auto& import_list = c.win_emu->mod_manager.executable->imports;
const auto rip = c.win_emu->emu().read_instruction_pointer();
if (!c.win_emu->mod_manager.executable->is_within(rip))
{
return;
}
for (const auto& [module_name, imports] : import_list)
{
for (const auto& import : imports)
{
if (address != import.address)
{
continue;
}
accessed_import access{};
access.address = c.win_emu->emu().read_memory<uint64_t>(address);
access.access_rip = c.win_emu->emu().read_instruction_pointer();
access.accessor_module = c.win_emu->mod_manager.find_name(access.access_rip);
access.import_name = import.name;
access.import_module = module_name;
const auto& t = c.win_emu->current_thread();
access.thread_id = t.id;
access.access_inst_count = t.executed_instructions;
c.accessed_imports.push_back(std::move(access));
return;
}
}
});
}
}
void register_analysis_callbacks(analysis_context& c)
@@ -317,9 +430,11 @@ void register_analysis_callbacks(analysis_context& c)
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);
watch_import_table(c);
}
mapped_module* get_module_if_interesting(module_manager& manager, const string_set& modules, uint64_t address)
mapped_module* get_module_if_interesting(module_manager& manager, const string_set& modules, const uint64_t address)
{
if (manager.executable->is_within(address))
{
@@ -338,4 +453,4 @@ mapped_module* get_module_if_interesting(module_manager& manager, const string_s
}
return nullptr;
}
}

View File

@@ -20,6 +20,17 @@ struct analysis_settings
string_set ignored_functions{};
};
struct accessed_import
{
uint64_t address{};
uint32_t thread_id{};
uint64_t access_rip{};
uint64_t access_inst_count{};
std::string accessor_module{};
std::string import_name{};
std::string import_module{};
};
struct analysis_context
{
const analysis_settings* settings{};
@@ -27,7 +38,9 @@ struct analysis_context
std::string output{};
bool has_reached_main{false};
std::vector<accessed_import> accessed_imports{};
};
void register_analysis_callbacks(analysis_context& c);
mapped_module* get_module_if_interesting(module_manager& manager, const string_set& modules, uint64_t address);
mapped_module* get_module_if_interesting(module_manager& manager, const string_set& modules, uint64_t address);

View File

@@ -4,6 +4,7 @@
#include <backend_selection.hpp>
#include <win_x64_gdb_stub_handler.hpp>
#include <minidump_loader.hpp>
#include <scoped_hook.hpp>
#include "object_watching.hpp"
#include "snapshot.hpp"
@@ -57,6 +58,113 @@ namespace
}
}
#if !defined(__GNUC__) || defined(__clang__)
struct analysis_state
{
windows_emulator& win_emu_;
scoped_hook env_data_hook_;
scoped_hook env_ptr_hook_;
scoped_hook params_hook_;
std::set<std::string, std::less<>> modules_;
bool verbose_;
analysis_state(windows_emulator& win_emu, std::set<std::string, std::less<>> modules, const bool verbose)
: win_emu_(win_emu),
env_data_hook_(win_emu.emu()),
env_ptr_hook_(win_emu.emu()),
params_hook_(win_emu.emu()),
modules_(std::move(modules)),
verbose_(verbose)
{
}
};
emulator_object<RTL_USER_PROCESS_PARAMETERS64> get_process_params(windows_emulator& win_emu)
{
const auto peb = win_emu.process.peb.read();
return {win_emu.emu(), peb.ProcessParameters};
}
uint64_t get_environment_ptr(windows_emulator& win_emu)
{
const auto process_params = get_process_params(win_emu);
return process_params.read().Environment;
}
size_t get_environment_size(const x86_64_emulator& emu, const uint64_t env)
{
std::array<uint8_t, 4> data{};
std::array<uint8_t, 4> empty{};
for (size_t i = 0; i < 0x100000; ++i)
{
if (!emu.try_read_memory(env + i, data.data(), data.size()))
{
return i;
}
if (data == empty)
{
return i + data.size();
}
}
return 0;
}
emulator_hook* install_env_hook(const std::shared_ptr<analysis_state>& state)
{
const auto process_params = get_process_params(state->win_emu_);
auto install_env_access_hook = [state] {
const auto env_ptr = get_environment_ptr(state->win_emu_);
const auto env_size = get_environment_size(state->win_emu_.emu(), env_ptr);
if (!env_size)
{
state->env_data_hook_.remove();
return;
}
auto hook_handler = [state, env_ptr](const uint64_t address, const void*, const size_t size) {
const auto rip = state->win_emu_.emu().read_instruction_pointer();
const auto* mod = state->win_emu_.mod_manager.find_by_address(rip);
const auto is_main_access =
mod == state->win_emu_.mod_manager.executable || state->modules_.contains(mod->name);
if (!is_main_access && !state->verbose_)
{
return;
}
const auto offset = address - env_ptr;
const auto* mod_name = mod ? mod->name.c_str() : "<N/A>";
state->win_emu_.log.print(is_main_access ? color::green : color::dark_gray,
"Environment access: 0x%" PRIx64 " (0x%zX) at 0x%" PRIx64 " (%s)\n", offset,
size, rip, mod_name);
};
state->env_data_hook_ = state->win_emu_.emu().hook_memory_read(env_ptr, env_size, std::move(hook_handler));
};
install_env_access_hook();
auto& win_emu = state->win_emu_;
return state->win_emu_.emu().hook_memory_write(
process_params.value() + offsetof(RTL_USER_PROCESS_PARAMETERS64, Environment), 0x8,
[&win_emu, install = std::move(install_env_access_hook)](const uint64_t address, const void*, size_t) {
const auto new_process_params = get_process_params(win_emu);
const auto target_address =
new_process_params.value() + offsetof(RTL_USER_PROCESS_PARAMETERS64, Environment);
if (address == target_address)
{
install();
}
});
}
#endif
void watch_system_objects(windows_emulator& win_emu, const std::set<std::string, std::less<>>& modules,
const bool verbose)
{
@@ -72,23 +180,33 @@ namespace
watch_object(win_emu, modules, emulator_object<KUSER_SHARED_DATA64>{win_emu.emu(), kusd_mmio::address()},
verbose);
auto* params_hook = watch_object(win_emu, modules, win_emu.process.process_params, verbose);
auto state = std::make_shared<analysis_state>(win_emu, modules, verbose);
state->params_hook_ = watch_object(win_emu, modules, win_emu.process.process_params, verbose);
const auto update_env_hook = [state] {
state->env_ptr_hook_ = install_env_hook(state); //
};
update_env_hook();
win_emu.emu().hook_memory_write(
win_emu.process.peb.value() + offsetof(PEB64, ProcessParameters), 0x8,
[&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);
[state, update_env = std::move(update_env_hook)](const uint64_t address, const void*, size_t) {
const auto target_address = state->win_emu_.process.peb.value() + offsetof(PEB64, ProcessParameters);
if (address == target_address)
if (address != target_address)
{
const emulator_object<RTL_USER_PROCESS_PARAMETERS64> obj{
win_emu.emu(),
win_emu.emu().read_memory<uint64_t>(address),
};
win_emu.emu().delete_hook(params_hook);
params_hook = watch_object(win_emu, modules, obj, verbose);
return;
}
const emulator_object<RTL_USER_PROCESS_PARAMETERS64> obj{
state->win_emu_.emu(),
state->win_emu_.emu().read_memory<uint64_t>(address),
};
state->params_hook_ = watch_object(state->win_emu_, state->modules_, obj, state->verbose_);
update_env();
});
#endif
}

View File

@@ -1,5 +1,6 @@
use icicle_cpu::ExceptionCode;
use icicle_cpu::ValueSource;
use std::collections::HashSet;
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use crate::registers;
@@ -73,31 +74,96 @@ fn qualify_hook_id(hook_id: u32, hook_type: HookType) -> u32 {
pub struct HookContainer<Func: ?Sized> {
hook_id: u32,
is_iterating: bool,
hooks: HashMap<u32, Box<Func>>,
hooks_to_add: HashMap<u32, Box<Func>>,
hooks_to_remove: HashSet<u32>,
}
impl<Func: ?Sized> HookContainer<Func> {
pub fn new() -> Self {
Self {
hook_id: 0,
is_iterating: false,
hooks: HashMap::new(),
hooks_to_add: HashMap::new(),
hooks_to_remove: HashSet::new(),
}
}
pub fn add_hook(&mut self, callback: Box<Func>) -> u32 {
self.hook_id += 1;
let id = self.hook_id;
self.hooks.insert(id, callback);
if self.is_iterating {
self.hooks_to_add.insert(id, callback);
} else {
self.hooks.insert(id, callback);
}
return id;
}
pub fn get_hooks(&self) -> &HashMap<u32, Box<Func>> {
return &self.hooks;
pub fn for_each_hook<F>(&mut self, mut callback: F)
where
F: FnMut(&Func),
{
let was_iterating = self.do_pre_access_work();
for (_, func) in &self.hooks {
callback(func.as_ref());
}
self.do_post_access_work(was_iterating);
}
pub fn access_hook<F>(&mut self, id: u32, mut callback: F)
where
F: FnMut(&Func),
{
let was_iterating = self.do_pre_access_work();
let hook = self.hooks.get(&id);
if hook.is_some() {
callback(hook.unwrap().as_ref());
}
self.do_post_access_work(was_iterating);
}
pub fn is_empty(&self) -> bool {
return self.hooks.is_empty();
}
pub fn remove_hook(&mut self, id: u32) {
self.hooks.remove(&id);
if self.is_iterating {
self.hooks_to_remove.insert(id);
} else {
self.hooks.remove(&id);
}
}
fn do_pre_access_work(&mut self) -> bool {
let was_iterating = self.is_iterating;
self.is_iterating = true;
return was_iterating;
}
fn do_post_access_work(&mut self, was_iterating: bool) {
self.is_iterating = was_iterating;
if self.is_iterating {
return;
}
let to_remove = std::mem::take(&mut self.hooks_to_remove);
for id in &to_remove {
self.hooks.remove(&id);
}
let to_add = std::mem::take(&mut self.hooks_to_add);
for (id, func) in to_add {
self.hooks.insert(id, func);
}
}
}
@@ -157,6 +223,7 @@ struct ExecutionHooks {
specific_hooks: HookContainer<dyn Fn(u64)>,
block_hooks: HookContainer<dyn Fn(u64, u64)>,
address_mapping: HashMap<u64, Vec<u32>>,
one_time_callbacks: Vec<Box<dyn Fn()>>,
}
impl ExecutionHooks {
@@ -167,31 +234,38 @@ impl ExecutionHooks {
specific_hooks: HookContainer::new(),
block_hooks: HookContainer::new(),
address_mapping: HashMap::new(),
one_time_callbacks: Vec::new(),
}
}
fn run_hooks(&self, address: u64) {
for (_key, func) in self.generic_hooks.get_hooks() {
func(address);
fn run_hooks(&mut self, address: u64) {
if !self.one_time_callbacks.is_empty() {
let callbacks = std::mem::take(&mut self.one_time_callbacks);
for cb in callbacks {
cb.as_ref()();
}
}
self.generic_hooks.for_each_hook(|func| {
func(address);
});
let mapping = self.address_mapping.get(&address);
if mapping.is_none() {
return;
}
for id in mapping.unwrap() {
let func = self.specific_hooks.get_hooks().get(&id);
if func.is_some() {
func.unwrap()(address);
}
self.specific_hooks.access_hook(*id, |func| {
func(address);
});
}
}
pub fn on_block(&mut self, address: u64, instructions: u64) {
for (_key, func) in self.block_hooks.get_hooks() {
self.block_hooks.for_each_hook(|func| {
func(address, instructions);
}
});
}
pub fn execute(&mut self, cpu: &mut icicle_cpu::Cpu, address: u64) {
@@ -224,6 +298,10 @@ impl ExecutionHooks {
return id;
}
pub fn schedule(&mut self, callback: Box<dyn Fn()>) {
self.one_time_callbacks.push(callback);
}
pub fn remove_generic_hook(&mut self, id: u32) {
self.generic_hooks.remove_hook(id);
}
@@ -368,10 +446,10 @@ impl IcicleEmulator {
}
}
fn handle_interrupt(&self, code: i32) -> bool {
for (_key, func) in self.interrupt_hooks.get_hooks() {
fn handle_interrupt(&mut self, code: i32) -> bool {
self.interrupt_hooks.for_each_hook(|func| {
func(code);
}
});
return true;
}
@@ -393,16 +471,15 @@ impl IcicleEmulator {
}
fn handle_violation(&mut self, address: u64, permission: u8, unmapped: bool) -> bool {
let hooks = &self.violation_hooks.get_hooks();
if hooks.is_empty() {
if self.violation_hooks.is_empty() {
return false;
}
let mut continue_execution = true;
for (_key, func) in self.violation_hooks.get_hooks() {
self.violation_hooks.for_each_hook(|func| {
continue_execution &= func(address, permission, unmapped);
}
});
return continue_execution;
}
@@ -412,9 +489,9 @@ impl IcicleEmulator {
return self.handle_interrupt(value as i32);
}
for (_key, func) in self.syscall_hooks.get_hooks() {
self.syscall_hooks.for_each_hook(|func| {
func();
}
});
self.vm.cpu.write_pc(self.vm.cpu.read_pc() + 2);
return true;
@@ -521,6 +598,10 @@ impl IcicleEmulator {
}
}
pub fn run_on_next_instruction(&mut self, callback: Box<dyn Fn()>) {
self.execution_hooks.borrow_mut().schedule(callback);
}
pub fn map_memory(&mut self, address: u64, length: u64, permissions: u8) -> bool {
const MAPPING_PERMISSIONS: u8 = icicle_vm::cpu::mem::perm::MAP
| icicle_vm::cpu::mem::perm::INIT

View File

@@ -307,6 +307,14 @@ pub fn icicle_remove_hook(ptr: *mut c_void, id: u32) {
}
}
#[unsafe(no_mangle)]
pub fn icicle_run_on_next_instruction(ptr: *mut c_void, callback: RawFunction, data: *mut c_void) {
unsafe {
let emulator = &mut *(ptr as *mut IcicleEmulator);
emulator.run_on_next_instruction(Box::new(move || callback(data)));
}
}
#[unsafe(no_mangle)]
pub fn icicle_read_register(
ptr: *mut c_void,

View File

@@ -1,7 +1,9 @@
#define ICICLE_EMULATOR_IMPL
#include "icicle_x86_64_emulator.hpp"
#include <unordered_set>
#include <utils/object.hpp>
#include <utils/finally.hpp>
using icicle_emulator = struct icicle_emulator_;
@@ -44,6 +46,7 @@ extern "C"
void icicle_start(icicle_emulator*, size_t count);
void icicle_stop(icicle_emulator*);
void icicle_destroy_emulator(icicle_emulator*);
void icicle_run_on_next_instruction(icicle_emulator*, raw_func* callback, void* data);
}
namespace icicle
@@ -58,24 +61,35 @@ namespace icicle
}
}
emulator_hook* wrap_hook(const uint32_t id)
{
return reinterpret_cast<emulator_hook*>(static_cast<size_t>(id));
}
template <typename T>
struct function_object : utils::object
{
bool* hook_state{};
std::function<T> func{};
function_object(std::function<T> f = {})
: func(std::move(f))
function_object(std::function<T> f = {}, bool* state = nullptr)
: hook_state(state),
func(std::move(f))
{
}
template <typename... Args>
auto operator()(Args&&... args) const
{
bool old_state{};
if (this->hook_state)
{
old_state = *this->hook_state;
*this->hook_state = true;
}
const auto _ = utils::finally([&] {
if (this->hook_state)
{
*this->hook_state = old_state;
}
});
return this->func.operator()(std::forward<Args>(args)...);
}
@@ -83,10 +97,18 @@ namespace icicle
};
template <typename T>
std::unique_ptr<function_object<T>> make_function_object(std::function<T> func)
std::unique_ptr<function_object<T>> make_function_object(std::function<T> func, bool& hook_state)
{
return std::make_unique<function_object<T>>(std::move(func));
return std::make_unique<function_object<T>>(std::move(func), &hook_state);
}
struct memory_access_hook
{
uint64_t address{};
uint64_t size{};
memory_access_hook_callback callback{};
bool is_read{};
};
}
class icicle_x86_64_emulator : public x86_64_emulator
@@ -103,6 +125,10 @@ namespace icicle
~icicle_x86_64_emulator() override
{
reset_object_with_delayed_destruction(this->hooks_);
reset_object_with_delayed_destruction(this->storage_);
utils::reset_object_with_delayed_destruction(this->hooks_to_install_);
if (this->emu_)
{
icicle_destroy_emulator(this->emu_);
@@ -236,7 +262,7 @@ namespace icicle
return nullptr;
}
auto obj = make_function_object(std::move(callback));
auto obj = make_function_object(std::move(callback), this->is_in_hook_);
auto* ptr = obj.get();
const auto invoker = +[](void* cb) {
@@ -252,7 +278,7 @@ namespace icicle
emulator_hook* hook_basic_block(basic_block_hook_callback callback) override
{
auto object = make_function_object(std::move(callback));
auto object = make_function_object(std::move(callback), this->is_in_hook_);
auto* ptr = object.get();
auto* wrapper = +[](void* user, const uint64_t addr, const uint64_t instructions) {
basic_block block{};
@@ -271,7 +297,7 @@ namespace icicle
emulator_hook* hook_interrupt(interrupt_hook_callback callback) override
{
auto obj = make_function_object(std::move(callback));
auto obj = make_function_object(std::move(callback), this->is_in_hook_);
auto* ptr = obj.get();
auto* wrapper = +[](void* user, const int32_t code) {
const auto& func = *static_cast<decltype(ptr)>(user);
@@ -286,7 +312,7 @@ namespace icicle
emulator_hook* hook_memory_violation(memory_violation_hook_callback callback) override
{
auto obj = make_function_object(std::move(callback));
auto obj = make_function_object(std::move(callback), this->is_in_hook_);
auto* ptr = obj.get();
auto* wrapper =
+[](void* user, const uint64_t address, const uint8_t operation, const int32_t unmapped) -> int32_t {
@@ -307,7 +333,7 @@ namespace icicle
emulator_hook* hook_memory_execution(const uint64_t address, memory_execution_hook_callback callback) override
{
auto object = make_function_object(std::move(callback));
auto object = make_function_object(std::move(callback), this->is_in_hook_);
auto* ptr = object.get();
auto* wrapper = +[](void* user, const uint64_t addr) {
const auto& func = *static_cast<decltype(ptr)>(user);
@@ -322,7 +348,7 @@ namespace icicle
emulator_hook* hook_memory_execution(memory_execution_hook_callback callback) override
{
auto object = make_function_object(std::move(callback));
auto object = make_function_object(std::move(callback), this->is_in_hook_);
auto* ptr = object.get();
auto* wrapper = +[](void* user, const uint64_t addr) {
const auto& func = *static_cast<decltype(ptr)>(user);
@@ -338,46 +364,36 @@ namespace icicle
emulator_hook* hook_memory_read(const uint64_t address, const uint64_t size,
memory_access_hook_callback callback) override
{
auto obj = make_function_object(std::move(callback));
auto* ptr = obj.get();
auto* wrapper = +[](void* user, const uint64_t address, const void* data, size_t length) {
const auto& func = *static_cast<decltype(ptr)>(user);
func(address, data, length);
};
const auto id = icicle_add_read_hook(this->emu_, address, address + size, wrapper, ptr);
this->hooks_[id] = std::move(obj);
return wrap_hook(id);
return this->try_install_memory_access_hook(memory_access_hook{
.address = address,
.size = size,
.callback = std::move(callback),
.is_read = true,
});
}
emulator_hook* hook_memory_write(const uint64_t address, const uint64_t size,
memory_access_hook_callback callback) override
{
auto obj = make_function_object(std::move(callback));
auto* ptr = obj.get();
auto* wrapper = +[](void* user, const uint64_t address, const void* data, size_t length) {
const auto& func = *static_cast<decltype(ptr)>(user);
func(address, data, length);
};
const auto id = icicle_add_write_hook(this->emu_, address, address + size, wrapper, ptr);
this->hooks_[id] = std::move(obj);
return wrap_hook(id);
return this->try_install_memory_access_hook(memory_access_hook{
.address = address,
.size = size,
.callback = std::move(callback),
.is_read = false,
});
}
void delete_hook(emulator_hook* hook) override
{
const auto id = static_cast<uint32_t>(reinterpret_cast<size_t>(hook));
const auto entry = this->hooks_.find(id);
if (entry == this->hooks_.end())
if (this->is_in_hook_)
{
return;
this->hooks_to_delete_.insert(hook);
this->schedule_action_execution();
}
else
{
this->delete_hook_internal(hook);
}
icicle_remove_hook(this->emu_, id);
this->hooks_.erase(entry);
}
void serialize_state(utils::buffer_serializer& buffer, const bool is_snapshot) const override
@@ -437,9 +453,138 @@ namespace icicle
}
private:
bool is_in_hook_{false};
std::list<std::unique_ptr<utils::object>> storage_{};
std::unordered_map<uint32_t, std::unique_ptr<utils::object>> hooks_{};
std::unordered_map<emulator_hook*, std::optional<uint32_t>> id_mapping_{};
icicle_emulator* emu_{};
uint32_t index_{0};
std::unordered_set<emulator_hook*> hooks_to_delete_{};
std::unordered_map<emulator_hook*, memory_access_hook> hooks_to_install_{};
emulator_hook* wrap_hook(const std::optional<uint32_t> icicle_id)
{
const auto id = ++this->index_;
auto* hook = reinterpret_cast<emulator_hook*>(static_cast<size_t>(id));
this->id_mapping_[hook] = icicle_id;
return hook;
}
emulator_hook* hook_memory_access(memory_access_hook hook, emulator_hook* hook_id)
{
auto obj = make_function_object(std::move(hook.callback), this->is_in_hook_);
auto* ptr = obj.get();
auto* wrapper = +[](void* user, const uint64_t address, const void* data, size_t length) {
const auto& func = *static_cast<decltype(ptr)>(user);
func(address, data, length);
};
auto* installer = hook.is_read ? &icicle_add_read_hook : &icicle_add_write_hook;
const auto id = installer(this->emu_, hook.address, hook.address + hook.size, wrapper, ptr);
this->hooks_[id] = std::move(obj);
if (hook_id)
{
this->id_mapping_[hook_id] = id;
return hook_id;
}
return wrap_hook(id);
}
void delete_hook_internal(emulator_hook* hook)
{
auto hook_id = this->id_mapping_.find(hook);
if (hook_id == this->id_mapping_.end())
{
return;
}
if (!hook_id->second.has_value())
{
this->hooks_to_delete_.insert(hook);
return;
}
const auto id = *hook_id->second;
this->id_mapping_.erase(hook_id);
const auto entry = this->hooks_.find(id);
if (entry == this->hooks_.end())
{
return;
}
icicle_remove_hook(this->emu_, id);
const auto obj = std::move(entry->second);
this->hooks_.erase(entry);
(void)obj;
}
void perform_pending_actions()
{
auto hooks_to_install = std::move(this->hooks_to_install_);
const auto hooks_to_delete = std::move(this->hooks_to_delete_);
this->hooks_to_delete_ = {};
this->hooks_to_install_ = {};
for (auto& hook : hooks_to_install)
{
this->hook_memory_access(std::move(hook.second), hook.first);
}
for (auto* hook : hooks_to_delete)
{
this->delete_hook_internal(hook);
}
}
emulator_hook* try_install_memory_access_hook(memory_access_hook hook)
{
if (!this->is_in_hook_)
{
return this->hook_memory_access(std::move(hook), nullptr);
}
auto* hook_id = wrap_hook(std::nullopt);
this->hooks_to_install_[hook_id] = std::move(hook);
this->schedule_action_execution();
return hook_id;
}
void schedule_action_execution()
{
this->run_on_next_instruction([this] {
this->perform_pending_actions(); //
});
}
void run_on_next_instruction(std::function<void()> func) const
{
auto* heap_func = new std::function(std::move(func));
auto* callback = +[](void* data) {
auto* cb = static_cast<std::function<void()>*>(data);
try
{
(*cb)();
}
catch (...)
{
// Ignore
}
delete cb;
};
icicle_run_on_next_instruction(this->emu_, callback, heap_func);
}
};
std::unique_ptr<x86_64_emulator> create_x86_64_emulator()

View File

@@ -206,7 +206,7 @@ namespace unicorn
~unicorn_x86_64_emulator() override
{
this->hooks_.clear();
reset_object_with_delayed_destruction(this->hooks_);
uc_close(this->uc_);
}
@@ -605,7 +605,9 @@ namespace unicorn
if (entry != this->hooks_.end())
{
const auto obj = std::move(*entry);
this->hooks_.erase(entry);
(void)obj;
}
}

View File

@@ -283,6 +283,47 @@ typedef struct _IMAGE_BASE_RELOCATION
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;
#define IMAGE_ORDINAL_FLAG64 0x8000000000000000
#define IMAGE_ORDINAL_FLAG32 0x80000000
#define IMAGE_ORDINAL64(Ordinal) (Ordinal & 0xffff)
#define IMAGE_ORDINAL32(Ordinal) (Ordinal & 0xffff)
#define IMAGE_SNAP_BY_ORDINAL64(Ordinal) ((Ordinal & IMAGE_ORDINAL_FLAG64) != 0)
#define IMAGE_SNAP_BY_ORDINAL32(Ordinal) ((Ordinal & IMAGE_ORDINAL_FLAG32) != 0)
typedef struct _IMAGE_IMPORT_BY_NAME
{
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
// union
//{
// DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
//} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_THUNK_DATA64
{
union
{
ULONGLONG ForwarderString; // PBYTE
ULONGLONG Function; // PDWORD
ULONGLONG Ordinal;
ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA64;
#endif
template <typename Traits>

View File

@@ -12,4 +12,12 @@ namespace utils
object& operator=(object&&) = default;
object& operator=(const object&) = default;
};
template <typename T>
void reset_object_with_delayed_destruction(T& obj)
{
T new_obj{};
const auto old = std::move(obj);
obj = std::move(new_obj);
}
}

View File

@@ -6,6 +6,11 @@ class scoped_hook
public:
scoped_hook() = default;
scoped_hook(emulator& emu)
: emu_(&emu)
{
}
scoped_hook(emulator& emu, emulator_hook* hook)
: scoped_hook(emu, std::vector{hook})
{
@@ -44,6 +49,23 @@ class scoped_hook
return *this;
}
scoped_hook& operator=(emulator_hook* hook)
{
this->replace({hook});
return *this;
}
void replace(std::vector<emulator_hook*> hooks)
{
if (!this->emu_)
{
throw std::runtime_error("Invalid scoped hook");
}
this->remove();
this->hooks_ = std::move(hooks);
}
void remove()
{
auto hooks = std::move(this->hooks_);

View File

@@ -9,7 +9,14 @@ struct exported_symbol
uint64_t address{};
};
struct imported_symbol
{
std::string name{};
uint64_t address{};
};
using exported_symbols = std::vector<exported_symbol>;
using imported_symbols = std::map<std::string, std::vector<imported_symbol>>;
using address_name_mapping = std::map<uint64_t, std::string>;
struct mapped_section
@@ -28,6 +35,7 @@ struct mapped_module
uint64_t entry_point{};
exported_symbols exports{};
imported_symbols imports{};
address_name_mapping address_names{};
std::vector<mapped_section> sections{};

View File

@@ -29,6 +29,63 @@ namespace
return mem;
}
void collect_imports(mapped_module& binary, const utils::safe_buffer_accessor<const std::byte> buffer,
const PEOptionalHeader_t<std::uint64_t>& optional_header)
{
const auto& import_directory_entry = optional_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (import_directory_entry.VirtualAddress == 0 || import_directory_entry.Size == 0)
{
return;
}
const auto import_descriptors = buffer.as<IMAGE_IMPORT_DESCRIPTOR>(import_directory_entry.VirtualAddress);
for (size_t i = 0;; ++i)
{
const auto descriptor = import_descriptors.get(i);
if (!descriptor.Name)
{
break;
}
const auto module_name = buffer.as_string(descriptor.Name);
auto& imports = binary.imports[module_name];
auto original_thunk_data = buffer.as<IMAGE_THUNK_DATA64>(descriptor.FirstThunk);
if (descriptor.OriginalFirstThunk)
{
original_thunk_data = buffer.as<IMAGE_THUNK_DATA64>(descriptor.OriginalFirstThunk);
}
for (size_t j = 0;; ++j)
{
const auto original_thunk = original_thunk_data.get(j);
if (!original_thunk.u1.AddressOfData)
{
break;
}
imported_symbol sym{};
static_assert(sizeof(IMAGE_THUNK_DATA64) == sizeof(uint64_t));
const auto thunk_rva = descriptor.FirstThunk + sizeof(IMAGE_THUNK_DATA64) * j;
sym.address = thunk_rva + binary.image_base;
if (IMAGE_SNAP_BY_ORDINAL64(original_thunk.u1.Ordinal))
{
sym.name = "#" + std::to_string(original_thunk.u1.Ordinal);
}
else
{
sym.name = buffer.as_string(
static_cast<size_t>(original_thunk.u1.AddressOfData + offsetof(IMAGE_IMPORT_BY_NAME, Name)));
}
imports.push_back(std::move(sym));
}
}
}
void collect_exports(mapped_module& binary, const utils::safe_buffer_accessor<const std::byte> buffer,
const PEOptionalHeader_t<std::uint64_t>& optional_header)
{
@@ -248,6 +305,7 @@ mapped_module map_module_from_data(memory_manager& memory, const std::span<const
apply_relocations(binary, mapped_buffer, optional_header);
collect_exports(binary, mapped_buffer, optional_header);
collect_imports(binary, mapped_buffer, optional_header);
memory.write_memory(binary.image_base, mapped_memory.data(), mapped_memory.size());