From bdf5d60fd9ceea6afa1af85b7d56aed8ad34dcd4 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Tue, 1 Apr 2025 20:07:26 +0200 Subject: [PATCH 1/7] Implement serialization --- src/emulator/cpu_interface.hpp | 2 +- src/icicle-emulator/icicle_x64_emulator.cpp | 21 ++++++++++++------- src/unicorn-emulator/unicorn_x64_emulator.cpp | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/emulator/cpu_interface.hpp b/src/emulator/cpu_interface.hpp index 83dce7e8..6db2fc05 100644 --- a/src/emulator/cpu_interface.hpp +++ b/src/emulator/cpu_interface.hpp @@ -15,7 +15,7 @@ struct cpu_interface virtual size_t read_raw_register(int reg, void* value, size_t size) = 0; virtual size_t write_raw_register(int reg, const void* value, size_t size) = 0; - virtual std::vector save_registers() = 0; + virtual std::vector save_registers() const = 0; virtual void restore_registers(const std::vector& register_data) = 0; // TODO: Remove this diff --git a/src/icicle-emulator/icicle_x64_emulator.cpp b/src/icicle-emulator/icicle_x64_emulator.cpp index 53b45139..42e84d93 100644 --- a/src/icicle-emulator/icicle_x64_emulator.cpp +++ b/src/icicle-emulator/icicle_x64_emulator.cpp @@ -285,19 +285,26 @@ namespace icicle void serialize_state(utils::buffer_serializer& buffer, const bool is_snapshot) const override { - (void)buffer; - (void)is_snapshot; - throw std::runtime_error("Not implemented"); + if (is_snapshot) + { + throw std::runtime_error("Not implemented"); + } + + buffer.write_vector(this->save_registers()); } void deserialize_state(utils::buffer_deserializer& buffer, const bool is_snapshot) override { - (void)buffer; - (void)is_snapshot; - throw std::runtime_error("Not implemented"); + if (is_snapshot) + { + throw std::runtime_error("Not implemented"); + } + + const auto data = buffer.read_vector(); + this->restore_registers(data); } - std::vector save_registers() override + std::vector save_registers() const override { std::vector data{}; auto* accessor = +[](void* user, const void* data, const size_t length) { diff --git a/src/unicorn-emulator/unicorn_x64_emulator.cpp b/src/unicorn-emulator/unicorn_x64_emulator.cpp index 029ad15e..7e785d18 100644 --- a/src/unicorn-emulator/unicorn_x64_emulator.cpp +++ b/src/unicorn-emulator/unicorn_x64_emulator.cpp @@ -647,7 +647,7 @@ namespace unicorn serializer.deserialize(buffer); } - std::vector save_registers() override + std::vector save_registers() const override { utils::buffer_serializer buffer{}; const uc_context_serializer serializer(this->uc_, false); From 5846d2c0b9174766f127ae1abf47b8b08c076168 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Tue, 1 Apr 2025 20:09:43 +0200 Subject: [PATCH 2/7] Generalize hook store --- src/icicle-emulator/icicle_x64_emulator.cpp | 22 +++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/icicle-emulator/icicle_x64_emulator.cpp b/src/icicle-emulator/icicle_x64_emulator.cpp index 42e84d93..3947e9bd 100644 --- a/src/icicle-emulator/icicle_x64_emulator.cpp +++ b/src/icicle-emulator/icicle_x64_emulator.cpp @@ -41,6 +41,13 @@ namespace icicle throw std::runtime_error(std::string(error)); } } + + template + struct function_object : std::function, utils::object + { + using std::function::function; + ~function_object() override = default; + }; } class icicle_x64_emulator : public x64_emulator @@ -212,16 +219,16 @@ namespace icicle return nullptr; } - auto callback_store = std::make_unique>([c = std::move(callback)] { + auto callback_store = std::make_unique>([c = std::move(callback)] { (void)c(); // }); const auto invoker = +[](void* cb) { - (*static_cast*>(cb))(); // + (*static_cast*>(cb))(); // }; const auto id = icicle_add_syscall_hook(this->emu_, invoker, callback_store.get()); - this->syscall_hooks_[id] = std::move(callback_store); + this->hooks_[id] = std::move(callback_store); return reinterpret_cast(static_cast(id)); } @@ -273,14 +280,14 @@ namespace icicle void delete_hook(emulator_hook* hook) override { const auto id = static_cast(reinterpret_cast(hook)); - const auto entry = this->syscall_hooks_.find(id); - if (entry == this->syscall_hooks_.end()) + const auto entry = this->hooks_.find(id); + if (entry == this->hooks_.end()) { return; } icicle_remove_syscall_hook(this->emu_, id); - this->syscall_hooks_.erase(entry); + this->hooks_.erase(entry); } void serialize_state(utils::buffer_serializer& buffer, const bool is_snapshot) const override @@ -330,8 +337,7 @@ namespace icicle private: std::list> storage_{}; - using syscall_hook_storage = std::unique_ptr>; - std::unordered_map syscall_hooks_{}; + std::unordered_map> hooks_{}; icicle_emulator* emu_{}; }; From ec235202fb82d1f1214be79bebcdcb2620a32d55 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Tue, 1 Apr 2025 20:53:33 +0200 Subject: [PATCH 3/7] Prepare instruction hook injection --- src/icicle/src/icicle.rs | 42 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/icicle/src/icicle.rs b/src/icicle/src/icicle.rs index 85ec2f64..b1b3d9c4 100644 --- a/src/icicle/src/icicle.rs +++ b/src/icicle/src/icicle.rs @@ -96,6 +96,39 @@ impl HookContainer { } } +struct InstructionHookInjector { + hook: pcode::HookId, +} + +impl icicle_vm::CodeInjector for InstructionHookInjector { + fn inject( + &mut self, + _cpu: &mut icicle_vm::cpu::Cpu, + group: &icicle_vm::cpu::BlockGroup, + code: &mut icicle_vm::BlockTable, + ) { + for id in group.range() { + let block = &mut code.blocks[id]; + + let mut tmp_block = pcode::Block::new(); + tmp_block.next_tmp = block.pcode.next_tmp; + + for stmt in block.pcode.instructions.drain(..) { + tmp_block.push(stmt); + if let pcode::Op::InstructionMarker = stmt.op { + tmp_block.push(pcode::Op::Hook(self.hook)); + code.modified.insert(id); + } + } + + std::mem::swap( + &mut tmp_block.instructions, + &mut block.pcode.instructions, + ); + } + } +} + pub struct IcicleEmulator { vm: icicle_vm::Vm, reg: registers::X64RegisterNodes, @@ -133,7 +166,14 @@ impl icicle_cpu::mem::IoMemory for MmioHandler { impl IcicleEmulator { pub fn new() -> Self { - let virtual_machine = create_x64_vm(); + let mut virtual_machine = create_x64_vm(); + let hook = icicle_cpu::InstHook::new(move |_: &mut icicle_cpu::Cpu, addr: u64| { + println!("TEST hook: {:#x}", addr); + }); + + let hook = virtual_machine.cpu.add_hook(hook); + virtual_machine.add_injector(InstructionHookInjector { hook }); + Self { reg: registers::X64RegisterNodes::new(&virtual_machine.cpu.arch), vm: virtual_machine, From 84268bc7e392a2bf011b9c2eadeea6198e4a1463 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 2 Apr 2025 06:28:01 +0200 Subject: [PATCH 4/7] More icicle progress --- src/icicle-emulator/icicle_x64_emulator.cpp | 66 +++++++++++++++++---- src/icicle/src/icicle.rs | 24 +++++++- src/icicle/src/lib.rs | 17 ++++++ 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/icicle-emulator/icicle_x64_emulator.cpp b/src/icicle-emulator/icicle_x64_emulator.cpp index 3947e9bd..b202472d 100644 --- a/src/icicle-emulator/icicle_x64_emulator.cpp +++ b/src/icicle-emulator/icicle_x64_emulator.cpp @@ -7,11 +7,13 @@ using icicle_emulator = struct icicle_emulator_; extern "C" { + using raw_func = void(void*); + using ptr_func = void(void*, uint64_t); + using data_accessor_func = void(void* user, const void* data, size_t length); + using icicle_mmio_read_func = void(void* user, uint64_t address, size_t length, void* data); using icicle_mmio_write_func = void(void* user, uint64_t address, size_t length, const void* data); - using data_accessor_func = void(void* user, const void* data, size_t length); - icicle_emulator* icicle_create_emulator(); int32_t icicle_protect_memory(icicle_emulator*, uint64_t address, uint64_t length, uint8_t permissions); int32_t icicle_map_memory(icicle_emulator*, uint64_t address, uint64_t length, uint8_t permissions); @@ -22,11 +24,13 @@ extern "C" int32_t icicle_write_memory(icicle_emulator*, uint64_t address, const void* data, size_t length); int32_t icicle_save_registers(icicle_emulator*, data_accessor_func* accessor, void* accessor_data); int32_t icicle_restore_registers(icicle_emulator*, const void* data, size_t length); - uint32_t icicle_add_syscall_hook(icicle_emulator*, void (*callback)(void*), void* data); + uint32_t icicle_add_syscall_hook(icicle_emulator*, raw_func* callback, void* data); + uint32_t icicle_add_execution_hook(icicle_emulator*, ptr_func* callback, void* data); void icicle_remove_syscall_hook(icicle_emulator*, uint32_t id); size_t icicle_read_register(icicle_emulator*, int reg, void* data, size_t length); size_t icicle_write_register(icicle_emulator*, int reg, const void* data, size_t length); void icicle_start(icicle_emulator*); + void icicle_stop(icicle_emulator*); void icicle_destroy_emulator(icicle_emulator*); } @@ -48,6 +52,21 @@ namespace icicle using std::function::function; ~function_object() override = default; }; + + template + std::unique_ptr wrap_shared(std::shared_ptr shared_ptr) + { + struct shard_wrapper : utils::object + { + std::shared_ptr ptr{}; + ~shard_wrapper() override = default; + }; + + auto wrapper = std::make_unique(); + wrapper->ptr = std::move(shared_ptr); + + return wrapper; + } } class icicle_x64_emulator : public x64_emulator @@ -87,6 +106,7 @@ namespace icicle void stop() override { + icicle_stop(this->emu_); } void load_gdt(const pointer_type address, const uint32_t limit) override @@ -149,28 +169,30 @@ namespace icicle auto* ptr = wrapper.get(); this->storage_.push_back(std::move(wrapper)); - auto* read_wrapper = +[](void* user, const uint64_t address, const size_t length, void* data) { + auto* read_wrapper = +[](void* user, const uint64_t addr, const size_t length, void* data) { constexpr auto limit = sizeof(uint64_t); const auto* w = static_cast(user); + // TODO: Change interface to get rid of loop for (size_t offset = 0; offset < length; offset += limit) { const auto max_read = std::min(limit, length - offset); - const auto value = w->read_cb(address + offset - w->base, max_read); + const auto value = w->read_cb(addr + offset - w->base, max_read); memcpy(static_cast(data) + offset, &value, max_read); } }; - auto* write_wrapper = +[](void* user, const uint64_t address, const size_t length, const void* data) { + auto* write_wrapper = +[](void* user, const uint64_t addr, const size_t length, const void* data) { constexpr auto limit = sizeof(uint64_t); const auto* w = static_cast(user); + // TODO: Change interface to get rid of loop for (size_t offset = 0; offset < length; offset += limit) { uint64_t value{}; const auto max_read = std::min(limit, length - offset); memcpy(&value, static_cast(data) + offset, max_read); - w->write_cb(address + offset - w->base, max_read, value); + w->write_cb(addr + offset - w->base, max_read, value); } }; @@ -216,6 +238,7 @@ namespace icicle { if (static_cast(instruction_type) != x64_hookable_instructions::syscall) { + // TODO return nullptr; } @@ -235,18 +258,21 @@ namespace icicle emulator_hook* hook_basic_block(basic_block_hook_callback callback) override { + // TODO (void)callback; throw std::runtime_error("Not implemented"); } emulator_hook* hook_edge_generation(edge_generation_hook_callback callback) override { + // TODO (void)callback; throw std::runtime_error("Not implemented"); } emulator_hook* hook_interrupt(interrupt_hook_callback callback) override { + // TODO (void)callback; return nullptr; // throw std::runtime_error("Not implemented"); @@ -255,6 +281,7 @@ namespace icicle emulator_hook* hook_memory_violation(uint64_t address, size_t size, memory_violation_hook_callback callback) override { + // TODO (void)address; (void)size; (void)callback; @@ -270,11 +297,28 @@ namespace icicle return nullptr; } - (void)address; - (void)size; - (void)callback; + auto shared_callback = std::make_shared(std::move(callback)); + + if ((filter & memory_permission::exec) == memory_permission::exec) + { + if (address != 0 || size != std::numeric_limits::max()) + { + throw std::runtime_error("Not supported!"); + } + + auto* ptr = shared_callback.get(); + auto wrapper = wrap_shared(shared_callback); + auto* func = +[](void* user, const uint64_t ptr) { + (*static_cast(user))(ptr, 0, 0, memory_permission::exec); + }; + + const auto id = icicle_add_execution_hook(this->emu_, func, ptr); + this->hooks_[id] = std::move(wrapper); + + return reinterpret_cast(static_cast(id)); + } + return nullptr; - // throw std::runtime_error("Not implemented"); } void delete_hook(emulator_hook* hook) override diff --git a/src/icicle/src/icicle.rs b/src/icicle/src/icicle.rs index b1b3d9c4..c0b7a7ad 100644 --- a/src/icicle/src/icicle.rs +++ b/src/icicle/src/icicle.rs @@ -1,5 +1,5 @@ use icicle_cpu::ValueSource; -use std::collections::HashMap; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::registers; @@ -133,6 +133,7 @@ pub struct IcicleEmulator { vm: icicle_vm::Vm, reg: registers::X64RegisterNodes, syscall_hooks: HookContainer, + execution_hooks: Rc>>, } pub struct MmioHandler { @@ -167,8 +168,14 @@ impl icicle_cpu::mem::IoMemory for MmioHandler { impl IcicleEmulator { pub fn new() -> Self { let mut virtual_machine = create_x64_vm(); + let exec_hooks: Rc>> = Rc::new(RefCell::new(HookContainer::new())); + + let exec_hooks_clone = Rc::clone(&exec_hooks); + let hook = icicle_cpu::InstHook::new(move |_: &mut icicle_cpu::Cpu, addr: u64| { - println!("TEST hook: {:#x}", addr); + for (_key, func) in exec_hooks_clone.borrow().get_hooks() { + func(addr); + } }); let hook = virtual_machine.cpu.add_hook(hook); @@ -178,6 +185,7 @@ impl IcicleEmulator { reg: registers::X64RegisterNodes::new(&virtual_machine.cpu.arch), vm: virtual_machine, syscall_hooks: HookContainer::new(), + execution_hooks: exec_hooks, } } @@ -186,6 +194,8 @@ impl IcicleEmulator { } pub fn start(&mut self) { + self.vm.icount_limit = u64::MAX; + loop { let reason = self.vm.run(); @@ -208,6 +218,15 @@ impl IcicleEmulator { } } + pub fn stop(&mut self) { + self.vm.icount_limit = self.vm.cpu.icount + 1; + } + + pub fn add_execution_hook(&mut self, callback: Box) -> u32 { + let hook_id = self.execution_hooks.borrow_mut().add_hook(callback); + return qualify_hook_id(hook_id, HookType::Execute); + } + pub fn add_syscall_hook(&mut self, callback: Box) -> u32 { let hook_id = self.syscall_hooks.add_hook(callback); return qualify_hook_id(hook_id, HookType::Syscall); @@ -218,6 +237,7 @@ impl IcicleEmulator { match hook_type { HookType::Syscall => self.syscall_hooks.remove_hook(hook_id), + HookType::Execute => self.execution_hooks.borrow_mut().remove_hook(hook_id), _ => {} } } diff --git a/src/icicle/src/lib.rs b/src/icicle/src/lib.rs index d12b64d5..183c2a45 100644 --- a/src/icicle/src/lib.rs +++ b/src/icicle/src/lib.rs @@ -27,7 +27,16 @@ pub fn icicle_start(ptr: *mut c_void) { } } +#[unsafe(no_mangle)] +pub fn icicle_stop(ptr: *mut c_void) { + unsafe { + let emulator = &mut *(ptr as *mut IcicleEmulator); + emulator.stop(); + } +} + type RawFunction = extern "C" fn(*mut c_void); +type PtrFunction = extern "C" fn(*mut c_void, u64); type DataFunction = extern "C" fn(*mut c_void, *const c_void, usize); type MmioReadFunction = extern "C" fn(*mut c_void, u64, usize, *mut c_void); type MmioWriteFunction = extern "C" fn(*mut c_void, u64, usize, *const c_void); @@ -138,6 +147,14 @@ pub fn icicle_add_syscall_hook(ptr: *mut c_void, callback: RawFunction, data: *m } } +#[unsafe(no_mangle)] +pub fn icicle_add_execution_hook(ptr: *mut c_void, callback: PtrFunction, data: *mut c_void) { + unsafe { + let emulator = &mut *(ptr as *mut IcicleEmulator); + emulator.add_execution_hook(Box::new(move |ptr: u64| callback(data, ptr))); + } +} + #[unsafe(no_mangle)] pub fn icicle_remove_syscall_hook(ptr: *mut c_void, id: u32) { unsafe { From f5b77ffa67818250bb4406fcb1da36f69080bc1e Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 2 Apr 2025 07:03:01 +0200 Subject: [PATCH 5/7] Tests --- src/icicle/src/icicle.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/icicle/src/icicle.rs b/src/icicle/src/icicle.rs index c0b7a7ad..b52038a2 100644 --- a/src/icicle/src/icicle.rs +++ b/src/icicle/src/icicle.rs @@ -207,6 +207,11 @@ impl IcicleEmulator { }; if !invoke_syscall { + if reason == icicle_vm::VmExit::InstructionLimit { + break; + } + + println!("NO SYSCALL"); break; } @@ -219,7 +224,7 @@ impl IcicleEmulator { } pub fn stop(&mut self) { - self.vm.icount_limit = self.vm.cpu.icount + 1; + self.vm.icount_limit = self.vm.cpu.icount; } pub fn add_execution_hook(&mut self, callback: Box) -> u32 { From 204159f13740e540905793cae9b59061259b66ba Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Wed, 2 Apr 2025 08:21:37 +0200 Subject: [PATCH 6/7] Support violation hooks --- src/icicle-emulator/icicle_x64_emulator.cpp | 73 +++++++++++++----- src/icicle/src/icicle.rs | 85 +++++++++++++++------ src/icicle/src/lib.rs | 32 ++++++-- 3 files changed, 144 insertions(+), 46 deletions(-) diff --git a/src/icicle-emulator/icicle_x64_emulator.cpp b/src/icicle-emulator/icicle_x64_emulator.cpp index b202472d..a06b804c 100644 --- a/src/icicle-emulator/icicle_x64_emulator.cpp +++ b/src/icicle-emulator/icicle_x64_emulator.cpp @@ -9,6 +9,7 @@ extern "C" { using raw_func = void(void*); using ptr_func = void(void*, uint64_t); + using violation_func = int32_t(void*, uint64_t address, uint8_t operation, int32_t unmapped); using data_accessor_func = void(void* user, const void* data, size_t length); using icicle_mmio_read_func = void(void* user, uint64_t address, size_t length, void* data); @@ -26,6 +27,7 @@ extern "C" int32_t icicle_restore_registers(icicle_emulator*, const void* data, size_t length); uint32_t icicle_add_syscall_hook(icicle_emulator*, raw_func* callback, void* data); uint32_t icicle_add_execution_hook(icicle_emulator*, ptr_func* callback, void* data); + uint32_t icicle_add_violation_hook(icicle_emulator*, violation_func* callback, void* data); void icicle_remove_syscall_hook(icicle_emulator*, uint32_t id); size_t icicle_read_register(icicle_emulator*, int reg, void* data, size_t length); size_t icicle_write_register(icicle_emulator*, int reg, const void* data, size_t length); @@ -46,13 +48,36 @@ namespace icicle } } - template - struct function_object : std::function, utils::object + emulator_hook* wrap_hook(const uint32_t id) { - using std::function::function; + return reinterpret_cast(static_cast(id)); + } + + template + struct function_object : utils::object + { + std::function func{}; + + function_object(std::function f = {}) + : func(std::move(f)) + { + } + + template + auto operator()(Args&&... args) const + { + return this->func.operator()(std::forward(args)...); + } + ~function_object() override = default; }; + template + std::unique_ptr> make_function_object(std::function func) + { + return std::make_unique>(std::move(func)); + } + template std::unique_ptr wrap_shared(std::shared_ptr shared_ptr) { @@ -242,18 +267,18 @@ namespace icicle return nullptr; } - auto callback_store = std::make_unique>([c = std::move(callback)] { - (void)c(); // - }); + auto obj = make_function_object(std::move(callback)); + auto* ptr = obj.get(); const auto invoker = +[](void* cb) { - (*static_cast*>(cb))(); // + const auto& func = *static_cast(cb); + (void)func(); // }; - const auto id = icicle_add_syscall_hook(this->emu_, invoker, callback_store.get()); - this->hooks_[id] = std::move(callback_store); + const auto id = icicle_add_syscall_hook(this->emu_, invoker, ptr); + this->hooks_[id] = std::move(obj); - return reinterpret_cast(static_cast(id)); + return wrap_hook(id); } emulator_hook* hook_basic_block(basic_block_hook_callback callback) override @@ -278,15 +303,29 @@ namespace icicle // throw std::runtime_error("Not implemented"); } - emulator_hook* hook_memory_violation(uint64_t address, size_t size, + emulator_hook* hook_memory_violation(const uint64_t address, const size_t size, memory_violation_hook_callback callback) override { - // TODO (void)address; (void)size; - (void)callback; - return nullptr; - // throw std::runtime_error("Not implemented"); + + auto obj = make_function_object(std::move(callback)); + auto* ptr = obj.get(); + auto* wrapper = + +[](void* user, const uint64_t address, const uint8_t operation, const int32_t unmapped) -> int32_t { + const auto violation_type = unmapped // + ? memory_violation_type::unmapped + : memory_violation_type::protection; + + const auto& func = *static_cast(user); + const auto res = func(address, 1, static_cast(operation), violation_type); + return res == memory_violation_continuation::resume ? 1 : 0; + }; + + const auto id = icicle_add_violation_hook(this->emu_, wrapper, ptr); + this->hooks_[id] = std::move(obj); + + return wrap_hook(id); } emulator_hook* hook_memory_access(const uint64_t address, const size_t size, const memory_operation filter, @@ -297,7 +336,7 @@ namespace icicle return nullptr; } - auto shared_callback = std::make_shared(std::move(callback)); + const auto shared_callback = std::make_shared(std::move(callback)); if ((filter & memory_permission::exec) == memory_permission::exec) { @@ -315,7 +354,7 @@ namespace icicle const auto id = icicle_add_execution_hook(this->emu_, func, ptr); this->hooks_[id] = std::move(wrapper); - return reinterpret_cast(static_cast(id)); + return wrap_hook(id); } return nullptr; diff --git a/src/icicle/src/icicle.rs b/src/icicle/src/icicle.rs index b52038a2..dd604da2 100644 --- a/src/icicle/src/icicle.rs +++ b/src/icicle/src/icicle.rs @@ -1,4 +1,5 @@ use icicle_cpu::ValueSource; +use icicle_cpu::ExceptionCode; use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::registers; @@ -16,11 +17,11 @@ fn create_x64_vm() -> icicle_vm::Vm { return icicle_vm::build(&cpu_config).unwrap(); } -fn map_permissions(foreign_permissions: u8) -> u8 { - const FOREIGN_READ: u8 = 1 << 0; - const FOREIGN_WRITE: u8 = 1 << 1; - const FOREIGN_EXEC: u8 = 1 << 2; +const FOREIGN_READ: u8 = 1 << 0; +const FOREIGN_WRITE: u8 = 1 << 1; +const FOREIGN_EXEC: u8 = 1 << 2; +fn map_permissions(foreign_permissions: u8) -> u8 { let mut permissions: u8 = 0; if (foreign_permissions & FOREIGN_READ) != 0 { @@ -46,6 +47,7 @@ enum HookType { Read, Write, Execute, + Violation, Unknown, } @@ -133,6 +135,7 @@ pub struct IcicleEmulator { vm: icicle_vm::Vm, reg: registers::X64RegisterNodes, syscall_hooks: HookContainer, + violation_hooks: HookContainer bool>, execution_hooks: Rc>>, } @@ -185,6 +188,7 @@ impl IcicleEmulator { reg: registers::X64RegisterNodes::new(&virtual_machine.cpu.arch), vm: virtual_machine, syscall_hooks: HookContainer::new(), + violation_hooks: HookContainer::new(), execution_hooks: exec_hooks, } } @@ -199,34 +203,66 @@ impl IcicleEmulator { loop { let reason = self.vm.run(); - let invoke_syscall = match reason { - icicle_vm::VmExit::UnhandledException((code, _)) => { - code == icicle_cpu::ExceptionCode::Syscall - } - _ => false, + match reason { + icicle_vm::VmExit::InstructionLimit => break, + icicle_vm::VmExit::UnhandledException((code, value)) => { + let continue_execution = self.handle_exception(code, value); + if !continue_execution { + break + } + }, + _ => break, }; - - if !invoke_syscall { - if reason == icicle_vm::VmExit::InstructionLimit { - break; - } - - println!("NO SYSCALL"); - break; - } - - for (_key, func) in self.syscall_hooks.get_hooks() { - func(); - } - - self.vm.cpu.write_pc(self.vm.cpu.read_pc() + 2); } } + fn handle_exception(&mut self, code: ExceptionCode, value: u64) -> bool { + let continue_execution = match code { + ExceptionCode::Syscall => self.handle_syscall(), + ExceptionCode::ReadPerm => self.handle_violation(value, FOREIGN_READ, false), + ExceptionCode::WritePerm => self.handle_violation(value, FOREIGN_WRITE, false), + ExceptionCode::ReadUnmapped => self.handle_violation(value, FOREIGN_READ, true), + ExceptionCode::WriteUnmapped => self.handle_violation(value, FOREIGN_WRITE, true), + ExceptionCode::ExecViolation => self.handle_violation(value, FOREIGN_EXEC, true), + _ => false + }; + + return continue_execution; + } + + fn handle_violation(&mut self, address: u64, permission: u8, unmapped: bool) -> bool { + let hooks = &self.violation_hooks.get_hooks(); + if hooks.is_empty() { + return false; + } + + let mut continue_execution = true; + + for (_key, func) in self.violation_hooks.get_hooks() { + continue_execution &= func(address, permission, unmapped ); + } + + return continue_execution; + } + + fn handle_syscall(&mut self) -> bool{ + for (_key, func) in self.syscall_hooks.get_hooks() { + func(); + } + + self.vm.cpu.write_pc(self.vm.cpu.read_pc() + 2); + return true; + } + pub fn stop(&mut self) { self.vm.icount_limit = self.vm.cpu.icount; } + pub fn add_violation_hook(&mut self, callback: Box bool>) -> u32 { + let hook_id = self.violation_hooks.add_hook(callback); + return qualify_hook_id(hook_id, HookType::Violation); + } + pub fn add_execution_hook(&mut self, callback: Box) -> u32 { let hook_id = self.execution_hooks.borrow_mut().add_hook(callback); return qualify_hook_id(hook_id, HookType::Execute); @@ -242,6 +278,7 @@ impl IcicleEmulator { match hook_type { HookType::Syscall => self.syscall_hooks.remove_hook(hook_id), + HookType::Violation => self.violation_hooks.remove_hook(hook_id), HookType::Execute => self.execution_hooks.borrow_mut().remove_hook(hook_id), _ => {} } diff --git a/src/icicle/src/lib.rs b/src/icicle/src/lib.rs index 183c2a45..1b510a81 100644 --- a/src/icicle/src/lib.rs +++ b/src/icicle/src/lib.rs @@ -40,6 +40,7 @@ type PtrFunction = extern "C" fn(*mut c_void, u64); type DataFunction = extern "C" fn(*mut c_void, *const c_void, usize); type MmioReadFunction = extern "C" fn(*mut c_void, u64, usize, *mut c_void); type MmioWriteFunction = extern "C" fn(*mut c_void, u64, usize, *const c_void); +type ViolationFunction = extern "C" fn(*mut c_void, u64, u8, i32) -> i32; #[unsafe(no_mangle)] pub fn icicle_map_mmio( @@ -116,7 +117,11 @@ pub fn icicle_save_registers(ptr: *mut c_void, accessor: DataFunction, accessor_ unsafe { let emulator = &mut *(ptr as *mut IcicleEmulator); let registers = emulator.save_registers(); - accessor(accessor_data, registers.as_ptr() as *const c_void, registers.len()); + accessor( + accessor_data, + registers.as_ptr() as *const c_void, + registers.len(), + ); } } @@ -140,18 +145,35 @@ pub fn icicle_read_memory(ptr: *mut c_void, address: u64, data: *mut c_void, siz } #[unsafe(no_mangle)] -pub fn icicle_add_syscall_hook(ptr: *mut c_void, callback: RawFunction, data: *mut c_void) { +pub fn icicle_add_violation_hook(ptr: *mut c_void, callback: ViolationFunction, data: *mut c_void) -> u32 { unsafe { let emulator = &mut *(ptr as *mut IcicleEmulator); - emulator.add_syscall_hook(Box::new(move || callback(data))); + return emulator.add_violation_hook(Box::new( + move |address: u64, permission: u8, unmapped: bool| { + let result = callback(data, address, permission, to_cbool(unmapped)); + if result == 0 { + return false; + } + + return true; + }, + )); } } #[unsafe(no_mangle)] -pub fn icicle_add_execution_hook(ptr: *mut c_void, callback: PtrFunction, data: *mut c_void) { +pub fn icicle_add_syscall_hook(ptr: *mut c_void, callback: RawFunction, data: *mut c_void) -> u32 { unsafe { let emulator = &mut *(ptr as *mut IcicleEmulator); - emulator.add_execution_hook(Box::new(move |ptr: u64| callback(data, ptr))); + return emulator.add_syscall_hook(Box::new(move || callback(data))); + } +} + +#[unsafe(no_mangle)] +pub fn icicle_add_execution_hook(ptr: *mut c_void, callback: PtrFunction, data: *mut c_void) -> u32 { + unsafe { + let emulator = &mut *(ptr as *mut IcicleEmulator); + return emulator.add_execution_hook(Box::new(move |ptr: u64| callback(data, ptr))); } } From 24df7c65c2fc94a1b8dbd506896b635565058680 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Fri, 4 Apr 2025 13:13:09 +0200 Subject: [PATCH 7/7] Support accurate instruction counts --- src/emulator/cpu_interface.hpp | 2 +- src/emulator/typed_emulator.hpp | 5 ----- src/icicle-emulator/icicle_x64_emulator.cpp | 15 +++---------- src/icicle/src/icicle.rs | 9 +++++--- src/icicle/src/lib.rs | 4 ++-- src/unicorn-emulator/unicorn_x64_emulator.cpp | 13 ++++-------- src/windows-emulator-test/emulation_test.cpp | 6 +++--- .../emulation_test_utils.hpp | 2 +- .../serialization_test.cpp | 2 +- src/windows-emulator/windows_emulator.cpp | 21 ++----------------- src/windows-emulator/windows_emulator.hpp | 2 +- .../win_x64_gdb_stub_handler.hpp | 2 +- src/windows-gdb-stub/x64_gdb_stub_handler.hpp | 4 ++-- 13 files changed, 27 insertions(+), 60 deletions(-) diff --git a/src/emulator/cpu_interface.hpp b/src/emulator/cpu_interface.hpp index 6db2fc05..155682aa 100644 --- a/src/emulator/cpu_interface.hpp +++ b/src/emulator/cpu_interface.hpp @@ -9,7 +9,7 @@ struct cpu_interface { virtual ~cpu_interface() = default; - virtual void start(uint64_t start, uint64_t end = 0, std::chrono::nanoseconds timeout = {}, size_t count = 0) = 0; + virtual void start(size_t count = 0) = 0; virtual void stop() = 0; virtual size_t read_raw_register(int reg, void* value, size_t size) = 0; diff --git a/src/emulator/typed_emulator.hpp b/src/emulator/typed_emulator.hpp index 63516cba..1a67b038 100644 --- a/src/emulator/typed_emulator.hpp +++ b/src/emulator/typed_emulator.hpp @@ -15,11 +15,6 @@ class typed_emulator : public emulator static constexpr registers stack_pointer = StackPointer; static constexpr registers instruction_pointer = InstructionPointer; - void start_from_ip(const std::chrono::nanoseconds timeout = {}, const size_t count = 0) - { - this->start(this->read_instruction_pointer(), 0, timeout, count); - } - size_t write_register(registers reg, const void* value, const size_t size) { return this->write_raw_register(static_cast(reg), value, size); diff --git a/src/icicle-emulator/icicle_x64_emulator.cpp b/src/icicle-emulator/icicle_x64_emulator.cpp index a06b804c..31b62ce6 100644 --- a/src/icicle-emulator/icicle_x64_emulator.cpp +++ b/src/icicle-emulator/icicle_x64_emulator.cpp @@ -31,7 +31,7 @@ extern "C" void icicle_remove_syscall_hook(icicle_emulator*, uint32_t id); size_t icicle_read_register(icicle_emulator*, int reg, void* data, size_t length); size_t icicle_write_register(icicle_emulator*, int reg, const void* data, size_t length); - void icicle_start(icicle_emulator*); + void icicle_start(icicle_emulator*, size_t count); void icicle_stop(icicle_emulator*); void icicle_destroy_emulator(icicle_emulator*); } @@ -115,18 +115,9 @@ namespace icicle } } - void start(const uint64_t start, const uint64_t end, std::chrono::nanoseconds timeout, - const size_t count) override + void start(const size_t count) override { - if (timeout.count() < 0) - { - timeout = {}; - } - - (void)start; - (void)end; - (void)count; - icicle_start(this->emu_); + icicle_start(this->emu_, count); } void stop() override diff --git a/src/icicle/src/icicle.rs b/src/icicle/src/icicle.rs index dd604da2..292c4fd3 100644 --- a/src/icicle/src/icicle.rs +++ b/src/icicle/src/icicle.rs @@ -197,8 +197,11 @@ impl IcicleEmulator { return &mut self.vm.cpu.mem; } - pub fn start(&mut self) { - self.vm.icount_limit = u64::MAX; + pub fn start(&mut self, count: u64) { + self.vm.icount_limit = match count { + 0 => u64::MAX, + _ => self.vm.cpu.icount + count, + }; loop { let reason = self.vm.run(); @@ -255,7 +258,7 @@ impl IcicleEmulator { } pub fn stop(&mut self) { - self.vm.icount_limit = self.vm.cpu.icount; + self.vm.icount_limit = 0; } pub fn add_violation_hook(&mut self, callback: Box bool>) -> u32 { diff --git a/src/icicle/src/lib.rs b/src/icicle/src/lib.rs index 1b510a81..0e6eab75 100644 --- a/src/icicle/src/lib.rs +++ b/src/icicle/src/lib.rs @@ -20,10 +20,10 @@ pub fn icicle_create_emulator() -> *mut c_void { } #[unsafe(no_mangle)] -pub fn icicle_start(ptr: *mut c_void) { +pub fn icicle_start(ptr: *mut c_void, count: usize) { unsafe { let emulator = &mut *(ptr as *mut IcicleEmulator); - emulator.start(); + emulator.start(count as u64); } } diff --git a/src/unicorn-emulator/unicorn_x64_emulator.cpp b/src/unicorn-emulator/unicorn_x64_emulator.cpp index 7e785d18..f26dddda 100644 --- a/src/unicorn-emulator/unicorn_x64_emulator.cpp +++ b/src/unicorn-emulator/unicorn_x64_emulator.cpp @@ -269,17 +269,12 @@ namespace unicorn uc_close(this->uc_); } - void start(const uint64_t start, const uint64_t end, std::chrono::nanoseconds timeout, - const size_t count) override + void start(const size_t count) override { - if (timeout.count() < 0) - { - timeout = {}; - } - this->has_violation_ = false; - const auto timeoutYs = std::chrono::duration_cast(timeout); - const auto res = uc_emu_start(*this, start, end, static_cast(timeoutYs.count()), count); + const auto start = this->read_instruction_pointer(); + constexpr auto end = std::numeric_limits::max(); + const auto res = uc_emu_start(*this, start, end, 0, count); if (res == UC_ERR_OK) { return; diff --git a/src/windows-emulator-test/emulation_test.cpp b/src/windows-emulator-test/emulation_test.cpp index f5833843..dc600f92 100644 --- a/src/windows-emulator-test/emulation_test.cpp +++ b/src/windows-emulator-test/emulation_test.cpp @@ -15,7 +15,7 @@ namespace test constexpr auto count = 200000; auto emu = create_sample_emulator(); - emu.start({}, count); + emu.start(count); ASSERT_EQ(emu.get_executed_instructions(), count); } @@ -34,12 +34,12 @@ namespace test constexpr auto offset = 1; const auto instructionsToExecute = executedInstructions - offset; - new_emu.start({}, instructionsToExecute); + new_emu.start(instructionsToExecute); ASSERT_EQ(new_emu.get_executed_instructions(), instructionsToExecute); ASSERT_NOT_TERMINATED(new_emu); - new_emu.start({}, offset); + new_emu.start(offset); ASSERT_TERMINATED_SUCCESSFULLY(new_emu); ASSERT_EQ(new_emu.get_executed_instructions(), executedInstructions); diff --git a/src/windows-emulator-test/emulation_test_utils.hpp b/src/windows-emulator-test/emulation_test_utils.hpp index 69b4babc..5437aec1 100644 --- a/src/windows-emulator-test/emulation_test_utils.hpp +++ b/src/windows-emulator-test/emulation_test_utils.hpp @@ -141,7 +141,7 @@ namespace test const auto get_state_for_count = [&](const size_t count) { reset_emulator(); - emu.start({}, count); + emu.start(count); utils::buffer_serializer state{}; emu.serialize(state); diff --git a/src/windows-emulator-test/serialization_test.cpp b/src/windows-emulator-test/serialization_test.cpp index b82de8d3..1aadaabc 100644 --- a/src/windows-emulator-test/serialization_test.cpp +++ b/src/windows-emulator-test/serialization_test.cpp @@ -77,7 +77,7 @@ namespace test TEST(SerializationTest, DeserializedEmulatorBehavesLikeSource) { auto emu = create_sample_emulator(); - emu.start({}, 100); + emu.start(100); utils::buffer_serializer serializer{}; emu.serialize(serializer); diff --git a/src/windows-emulator/windows_emulator.cpp b/src/windows-emulator/windows_emulator.cpp index 8a08ae67..cccbb3fb 100644 --- a/src/windows-emulator/windows_emulator.cpp +++ b/src/windows-emulator/windows_emulator.cpp @@ -514,15 +514,10 @@ void windows_emulator::setup_hooks() [&](const uint64_t address, const size_t, const uint64_t) { this->on_instruction_execution(address); }); } -void windows_emulator::start(std::chrono::nanoseconds timeout, size_t count) +void windows_emulator::start(size_t count) { const auto use_count = count > 0; - const auto use_timeout = timeout != std::chrono::nanoseconds{}; - - const auto start_time = std::chrono::high_resolution_clock::now(); const auto start_instructions = this->executed_instructions_; - - const auto target_time = start_time + timeout; const auto target_instructions = start_instructions + count; while (true) @@ -532,25 +527,13 @@ void windows_emulator::start(std::chrono::nanoseconds timeout, size_t count) this->perform_thread_switch(); } - this->emu().start_from_ip(timeout, count); + this->emu().start(count); if (!this->switch_thread_ && !this->emu().has_violation()) { break; } - if (use_timeout) - { - const auto now = std::chrono::high_resolution_clock::now(); - - if (now >= target_time) - { - break; - } - - timeout = target_time - now; - } - if (use_count) { const auto current_instructions = this->executed_instructions_; diff --git a/src/windows-emulator/windows_emulator.hpp b/src/windows-emulator/windows_emulator.hpp index 7da1b18b..53c17f5c 100644 --- a/src/windows-emulator/windows_emulator.hpp +++ b/src/windows-emulator/windows_emulator.hpp @@ -130,7 +130,7 @@ class windows_emulator return this->executed_instructions_; } - void start(std::chrono::nanoseconds timeout = {}, size_t count = 0); + void start(size_t count = 0); void serialize(utils::buffer_serializer& buffer) const; void deserialize(utils::buffer_deserializer& buffer); diff --git a/src/windows-gdb-stub/win_x64_gdb_stub_handler.hpp b/src/windows-gdb-stub/win_x64_gdb_stub_handler.hpp index b3ec03a4..1755e09a 100644 --- a/src/windows-gdb-stub/win_x64_gdb_stub_handler.hpp +++ b/src/windows-gdb-stub/win_x64_gdb_stub_handler.hpp @@ -37,7 +37,7 @@ class win_x64_gdb_stub_handler : public x64_gdb_stub_handler { try { - this->win_emu_->start({}, 1); + this->win_emu_->start(1); } catch (const std::exception& e) { diff --git a/src/windows-gdb-stub/x64_gdb_stub_handler.hpp b/src/windows-gdb-stub/x64_gdb_stub_handler.hpp index 64dea478..6fedb346 100644 --- a/src/windows-gdb-stub/x64_gdb_stub_handler.hpp +++ b/src/windows-gdb-stub/x64_gdb_stub_handler.hpp @@ -64,7 +64,7 @@ class x64_gdb_stub_handler : public gdb_stub::debugging_handler { try { - this->emu_->start_from_ip(); + this->emu_->start(); } catch (const std::exception& e) { @@ -78,7 +78,7 @@ class x64_gdb_stub_handler : public gdb_stub::debugging_handler { try { - this->emu_->start_from_ip({}, 1); + this->emu_->start(1); } catch (const std::exception& e) {