From 7222c0fc1dec592829c2696abc830c2ffd2253d3 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Tue, 1 Apr 2025 15:40:53 +0200 Subject: [PATCH] Add icicle mmio support --- src/common/utils/object.hpp | 15 ++ src/icicle-emulator/icicle_x64_emulator.cpp | 70 +++++- src/icicle/src/icicle.rs | 226 +++++++++++++++--- src/icicle/src/lib.rs | 35 ++- src/unicorn-emulator/function_wrapper.hpp | 4 +- src/unicorn-emulator/object.hpp | 12 - src/unicorn-emulator/unicorn_x64_emulator.cpp | 6 +- 7 files changed, 312 insertions(+), 56 deletions(-) create mode 100644 src/common/utils/object.hpp delete mode 100644 src/unicorn-emulator/object.hpp diff --git a/src/common/utils/object.hpp b/src/common/utils/object.hpp new file mode 100644 index 00000000..e0cc9a84 --- /dev/null +++ b/src/common/utils/object.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace utils +{ + struct object + { + object() = default; + virtual ~object() = default; + + object(object&&) = default; + object(const object&) = default; + object& operator=(object&&) = default; + object& operator=(const object&) = default; + }; +} diff --git a/src/icicle-emulator/icicle_x64_emulator.cpp b/src/icicle-emulator/icicle_x64_emulator.cpp index 85a276c5..e2a8f923 100644 --- a/src/icicle-emulator/icicle_x64_emulator.cpp +++ b/src/icicle-emulator/icicle_x64_emulator.cpp @@ -1,13 +1,20 @@ #define ICICLE_EMULATOR_IMPL #include "icicle_x64_emulator.hpp" +#include + using icicle_emulator = struct icicle_emulator_; extern "C" { + 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); + 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); + int32_t icicle_map_mmio(icicle_emulator*, uint64_t address, uint64_t length, icicle_mmio_read_func* read_callback, + void* read_data, icicle_mmio_write_func* write_callback, void* write_data); int32_t icicle_unmap_memory(icicle_emulator*, uint64_t address, uint64_t length); int32_t icicle_read_memory(icicle_emulator*, uint64_t address, void* data, size_t length); int32_t icicle_write_memory(icicle_emulator*, uint64_t address, const void* data, size_t length); @@ -61,6 +68,9 @@ namespace icicle timeout = {}; } + (void)start; + (void)end; + (void)count; icicle_start(this->emu_); } @@ -113,8 +123,47 @@ namespace icicle void map_mmio(const uint64_t address, const size_t size, mmio_read_callback read_cb, mmio_write_callback write_cb) override { - this->map_memory(address, size, memory_permission::read_write); - // throw std::runtime_error("Not implemented"); + struct mmio_wrapper : utils::object + { + uint64_t base{}; + mmio_read_callback read_cb{}; + mmio_write_callback write_cb{}; + }; + + auto wrapper = std::make_unique(); + wrapper->base = address; + wrapper->read_cb = std::move(read_cb); + wrapper->write_cb = std::move(write_cb); + + 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) { + constexpr auto limit = sizeof(uint64_t); + const auto* w = static_cast(user); + + 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); + memcpy(static_cast(data) + offset, &value, max_read); + } + }; + + auto* write_wrapper = +[](void* user, const uint64_t address, const size_t length, const void* data) { + constexpr auto limit = sizeof(uint64_t); + const auto* w = static_cast(user); + + 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); + } + }; + + icicle_map_mmio(this->emu_, address, size, read_wrapper, ptr, write_wrapper, ptr); } void map_memory(const uint64_t address, const size_t size, memory_permission permissions) override @@ -187,6 +236,7 @@ namespace icicle emulator_hook* hook_interrupt(interrupt_hook_callback callback) override { + (void)callback; return nullptr; // throw std::runtime_error("Not implemented"); } @@ -194,6 +244,9 @@ namespace icicle emulator_hook* hook_memory_violation(uint64_t address, size_t size, memory_violation_hook_callback callback) override { + (void)address; + (void)size; + (void)callback; return nullptr; // throw std::runtime_error("Not implemented"); } @@ -206,6 +259,9 @@ namespace icicle return nullptr; } + (void)address; + (void)size; + (void)callback; return nullptr; // throw std::runtime_error("Not implemented"); } @@ -225,12 +281,16 @@ namespace icicle void serialize_state(utils::buffer_serializer& buffer, const bool is_snapshot) const override { - // throw std::runtime_error("Not implemented"); + (void)buffer; + (void)is_snapshot; + throw std::runtime_error("Not implemented"); } void deserialize_state(utils::buffer_deserializer& buffer, const bool is_snapshot) override { - // throw std::runtime_error("Not implemented"); + (void)buffer; + (void)is_snapshot; + throw std::runtime_error("Not implemented"); } std::vector save_registers() override @@ -241,6 +301,7 @@ namespace icicle void restore_registers(const std::vector& register_data) override { + (void)register_data; // throw std::runtime_error("Not implemented"); } @@ -250,6 +311,7 @@ namespace icicle } private: + std::list> storage_{}; using syscall_hook_storage = std::unique_ptr>; std::unordered_map syscall_hooks_{}; icicle_emulator* emu_{}; diff --git a/src/icicle/src/icicle.rs b/src/icicle/src/icicle.rs index 28065eff..39f6aa1d 100644 --- a/src/icicle/src/icicle.rs +++ b/src/icicle/src/icicle.rs @@ -10,7 +10,7 @@ fn create_x64_vm() -> icicle_vm::Vm { cpu_config.track_uninitialized = false; cpu_config.optimize_instructions = true; cpu_config.optimize_block = false; - + return icicle_vm::build(&cpu_config).unwrap(); } @@ -36,22 +36,107 @@ fn map_permissions(foreign_permissions: u8) -> u8 { return permissions; } +#[repr(u8)] +#[allow(dead_code)] +#[derive(PartialEq)] +enum HookType { + Syscall = 1, + Read, + Write, + Execute, + Unknown, +} + +fn u8_to_hook_type_unsafe(value: u8) -> HookType { + // This is unsafe because it assumes the value is valid + unsafe { std::mem::transmute(value) } +} + +fn split_hook_id(id: u32) -> (u32, HookType) { + let hook_id = id & 0xFFFFFF; + let hook_type = u8_to_hook_type_unsafe((id >> 24) as u8); + + return (hook_id, hook_type); +} + +fn qualify_hook_id(hook_id: u32, hook_type: HookType) -> u32 { + let hook_type: u32 = (hook_type as u8).into(); + let hook_type_mask: u32 = hook_type << 24; + return (hook_id | hook_type_mask).into(); +} + +pub struct HookContainer { + hook_id: u32, + hooks: HashMap>, +} + +impl HookContainer { + pub fn new() -> Self { + Self { + hook_id: 0, + hooks: HashMap::new(), + } + } + + pub fn add_hook(&mut self, callback: Box) -> u32 { + self.hook_id += 1; + let id = self.hook_id; + self.hooks.insert(id, callback); + + return id; + } + + pub fn get_hooks(&self) -> &HashMap> { + return &self.hooks; + } + + pub fn remove_hook(&mut self, id: u32) { + self.hooks.remove(&id); + } +} + pub struct IcicleEmulator { vm: icicle_vm::Vm, reg: X64RegisterNodes, - - syscall_hook_id: u32, - syscall_hooks: HashMap>, + syscall_hooks: HookContainer, +} + +pub struct MmioHandler { + read_handler: Box, + write_handler: Box, +} + +impl MmioHandler { + pub fn new( + read_function: Box, + write_function: Box, + ) -> Self { + Self { + read_handler: read_function, + write_handler: write_function, + } + } +} + +impl icicle_cpu::mem::IoMemory for MmioHandler { + fn read(&mut self, addr: u64, buf: &mut [u8]) -> icicle_cpu::mem::MemResult<()> { + (self.read_handler)(addr, buf); + return Ok(()); + } + + fn write(&mut self, addr: u64, value: &[u8]) -> icicle_cpu::mem::MemResult<()> { + (self.write_handler)(addr, value); + return Ok(()); + } } impl IcicleEmulator { pub fn new() -> Self { - let vm_i = create_x64_vm(); + let virtual_machine = create_x64_vm(); Self { - reg: X64RegisterNodes::new(&vm_i.cpu.arch), - vm: vm_i, - syscall_hook_id: 0, - syscall_hooks: HashMap::new(), + reg: X64RegisterNodes::new(&virtual_machine.cpu.arch), + vm: virtual_machine, + syscall_hooks: HookContainer::new(), } } @@ -64,7 +149,9 @@ impl IcicleEmulator { let reason = self.vm.run(); let invoke_syscall = match reason { - icicle_vm::VmExit::UnhandledException((code, _)) => code == icicle_cpu::ExceptionCode::Syscall, + icicle_vm::VmExit::UnhandledException((code, _)) => { + code == icicle_cpu::ExceptionCode::Syscall + } _ => false, }; @@ -72,7 +159,7 @@ impl IcicleEmulator { break; } - for (_key, func) in &self.syscall_hooks { + for (_key, func) in self.syscall_hooks.get_hooks() { func(); } @@ -81,14 +168,17 @@ impl IcicleEmulator { } pub fn add_syscall_hook(&mut self, callback: Box) -> u32 { - self.syscall_hook_id += 1; - let id = self.syscall_hook_id; - self.syscall_hooks.insert(id, callback); - return id; + let hook_id = self.syscall_hooks.add_hook(callback); + return qualify_hook_id(hook_id, HookType::Syscall); } - pub fn remove_syscall_hook(&mut self, id: u32) { - self.syscall_hooks.remove(&id); + pub fn remove_hook(&mut self, id: u32) { + let (hook_id, hook_type) = split_hook_id(id); + + match hook_type { + HookType::Syscall => self.syscall_hooks.remove_hook(hook_id), + _ => {} + } } pub fn map_memory(&mut self, address: u64, length: u64, permissions: u8) -> bool { @@ -113,6 +203,28 @@ impl IcicleEmulator { return res.is_ok(); } + pub fn map_mmio( + &mut self, + address: u64, + length: u64, + read_function: Box, + write_function: Box, + ) -> bool { + let mem = self.get_mem(); + + let handler = MmioHandler::new(read_function, write_function); + let handler_id = mem.register_io_handler(handler); + + let layout = icicle_vm::cpu::mem::AllocLayout { + addr: Some(address), + size: length, + align: 0x1000, + }; + + let res = mem.alloc_memory(layout, handler_id); + return res.is_ok(); + } + pub fn unmap_memory(&mut self, address: u64, length: u64) -> bool { return self.get_mem().unmap_memory_len(address, length); } @@ -162,22 +274,70 @@ impl IcicleEmulator { //self.vm.cpu.write_trunc(reg_node, value); match reg_node.size { - 1 => self.vm.cpu.write_var::<[u8; 1]>(reg_node, buffer[..1].try_into().expect("")), - 2 => self.vm.cpu.write_var::<[u8; 2]>(reg_node, buffer[..2].try_into().expect("")), - 3 => self.vm.cpu.write_var::<[u8; 3]>(reg_node, buffer[..3].try_into().expect("")), - 4 => self.vm.cpu.write_var::<[u8; 4]>(reg_node, buffer[..4].try_into().expect("")), - 5 => self.vm.cpu.write_var::<[u8; 5]>(reg_node, buffer[..5].try_into().expect("")), - 6 => self.vm.cpu.write_var::<[u8; 6]>(reg_node, buffer[..6].try_into().expect("")), - 7 => self.vm.cpu.write_var::<[u8; 7]>(reg_node, buffer[..7].try_into().expect("")), - 8 => self.vm.cpu.write_var::<[u8; 8]>(reg_node, buffer[..8].try_into().expect("")), - 9 => self.vm.cpu.write_var::<[u8; 9]>(reg_node, buffer[..9].try_into().expect("")), - 10 => self.vm.cpu.write_var::<[u8; 10]>(reg_node, buffer[..10].try_into().expect("")), - 11 => self.vm.cpu.write_var::<[u8; 11]>(reg_node, buffer[..11].try_into().expect("")), - 12 => self.vm.cpu.write_var::<[u8; 12]>(reg_node, buffer[..12].try_into().expect("")), - 13 => self.vm.cpu.write_var::<[u8; 13]>(reg_node, buffer[..13].try_into().expect("")), - 14 => self.vm.cpu.write_var::<[u8; 14]>(reg_node, buffer[..14].try_into().expect("")), - 15 => self.vm.cpu.write_var::<[u8; 15]>(reg_node, buffer[..15].try_into().expect("")), - 16 => self.vm.cpu.write_var::<[u8; 16]>(reg_node, buffer[..16].try_into().expect("")), + 1 => self + .vm + .cpu + .write_var::<[u8; 1]>(reg_node, buffer[..1].try_into().expect("")), + 2 => self + .vm + .cpu + .write_var::<[u8; 2]>(reg_node, buffer[..2].try_into().expect("")), + 3 => self + .vm + .cpu + .write_var::<[u8; 3]>(reg_node, buffer[..3].try_into().expect("")), + 4 => self + .vm + .cpu + .write_var::<[u8; 4]>(reg_node, buffer[..4].try_into().expect("")), + 5 => self + .vm + .cpu + .write_var::<[u8; 5]>(reg_node, buffer[..5].try_into().expect("")), + 6 => self + .vm + .cpu + .write_var::<[u8; 6]>(reg_node, buffer[..6].try_into().expect("")), + 7 => self + .vm + .cpu + .write_var::<[u8; 7]>(reg_node, buffer[..7].try_into().expect("")), + 8 => self + .vm + .cpu + .write_var::<[u8; 8]>(reg_node, buffer[..8].try_into().expect("")), + 9 => self + .vm + .cpu + .write_var::<[u8; 9]>(reg_node, buffer[..9].try_into().expect("")), + 10 => self + .vm + .cpu + .write_var::<[u8; 10]>(reg_node, buffer[..10].try_into().expect("")), + 11 => self + .vm + .cpu + .write_var::<[u8; 11]>(reg_node, buffer[..11].try_into().expect("")), + 12 => self + .vm + .cpu + .write_var::<[u8; 12]>(reg_node, buffer[..12].try_into().expect("")), + 13 => self + .vm + .cpu + .write_var::<[u8; 13]>(reg_node, buffer[..13].try_into().expect("")), + 14 => self + .vm + .cpu + .write_var::<[u8; 14]>(reg_node, buffer[..14].try_into().expect("")), + 15 => self + .vm + .cpu + .write_var::<[u8; 15]>(reg_node, buffer[..15].try_into().expect("")), + 16 => self + .vm + .cpu + .write_var::<[u8; 16]>(reg_node, buffer[..16].try_into().expect("")), _ => panic!("invalid dynamic value size"), } diff --git a/src/icicle/src/lib.rs b/src/icicle/src/lib.rs index 426825b8..adbeee0c 100644 --- a/src/icicle/src/lib.rs +++ b/src/icicle/src/lib.rs @@ -25,6 +25,37 @@ pub fn icicle_start(ptr: *mut c_void) { } } +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); + +#[unsafe(no_mangle)] +pub fn icicle_map_mmio( + ptr: *mut c_void, + address: u64, + length: u64, + read_cb: MmioReadFunction, + read_data: *mut c_void, + write_cb: MmioWriteFunction, + write_data: *mut c_void, +) -> i32 { + unsafe { + let emulator = &mut *(ptr as *mut IcicleEmulator); + + let read_wrapper = Box::new(move |addr: u64, data: &mut [u8]| { + let raw_pointer: *mut u8 = data.as_mut_ptr(); + read_cb(read_data, addr, data.len(), raw_pointer as *mut c_void); + }); + + let write_wrapper = Box::new(move |addr: u64, data: &[u8]| { + let raw_pointer: *const u8 = data.as_ptr(); + write_cb(write_data, addr, data.len(), raw_pointer as *const c_void); + }); + + let res = emulator.map_mmio(address, length, read_wrapper, write_wrapper); + return to_cbool(res); + } +} + #[unsafe(no_mangle)] pub fn icicle_map_memory(ptr: *mut c_void, address: u64, length: u64, permissions: u8) -> i32 { unsafe { @@ -83,7 +114,7 @@ type CFunctionPointer = extern "C" fn(*mut c_void); pub fn icicle_add_syscall_hook(ptr: *mut c_void, callback: CFunctionPointer, data: *mut c_void) { unsafe { let emulator = &mut *(ptr as *mut IcicleEmulator); - emulator.add_syscall_hook(Box::new(move || callback(data))); + emulator.add_syscall_hook(Box::new(move || callback(data))); } } @@ -91,7 +122,7 @@ pub fn icicle_add_syscall_hook(ptr: *mut c_void, callback: CFunctionPointer, dat pub fn icicle_remove_syscall_hook(ptr: *mut c_void, id: u32) { unsafe { let emulator = &mut *(ptr as *mut IcicleEmulator); - emulator.remove_syscall_hook(id); + emulator.remove_hook(id); } } diff --git a/src/unicorn-emulator/function_wrapper.hpp b/src/unicorn-emulator/function_wrapper.hpp index f908dac5..a1484c9d 100644 --- a/src/unicorn-emulator/function_wrapper.hpp +++ b/src/unicorn-emulator/function_wrapper.hpp @@ -3,10 +3,10 @@ #include #include -#include "object.hpp" +#include template -class function_wrapper : public object +class function_wrapper : public utils::object { public: using user_data_pointer = void*; diff --git a/src/unicorn-emulator/object.hpp b/src/unicorn-emulator/object.hpp deleted file mode 100644 index 8639c6b3..00000000 --- a/src/unicorn-emulator/object.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -struct object -{ - object() = default; - virtual ~object() = default; - - object(object&&) = default; - object(const object&) = default; - object& operator=(object&&) = default; - object& operator=(const object&) = default; -}; diff --git a/src/unicorn-emulator/unicorn_x64_emulator.cpp b/src/unicorn-emulator/unicorn_x64_emulator.cpp index 29e750ee..d286bd2e 100644 --- a/src/unicorn-emulator/unicorn_x64_emulator.cpp +++ b/src/unicorn-emulator/unicorn_x64_emulator.cpp @@ -75,7 +75,7 @@ namespace unicorn } } - struct hook_object : object + struct hook_object : utils::object { emulator_hook* as_opaque_hook() { @@ -87,7 +87,7 @@ namespace unicorn { public: template - requires(std::is_base_of_v && std::is_move_constructible_v) + requires(std::is_base_of_v && std::is_move_constructible_v) void add(T data, unicorn_hook hook) { hook_entry entry{}; @@ -101,7 +101,7 @@ namespace unicorn private: struct hook_entry { - std::unique_ptr data{}; + std::unique_ptr data{}; unicorn_hook hook{}; };