use libunicorn_sys as ffi;
mod arm64_const;
mod arm_const;
mod m68k_const;
mod mips_const;
mod sparc_const;
mod x86_const;
#[macro_use]
mod macros;
use std::{collections::HashMap, mem};
pub use crate::{
arm64_const::*,
arm_const::*,
ffi::{unicorn_const::*, *},
m68k_const::*,
mips_const::*,
sparc_const::*,
x86_const::*,
};
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct Context {
context: uc_context,
}
impl Context {
pub fn new() -> Self {
Context { context: 0 }
}
pub fn is_initialized(&self) -> bool {
self.context != 0
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe { uc_free(self.context) };
}
}
pub trait Register {
fn to_i32(&self) -> i32;
}
implement_register!(RegisterARM);
implement_register!(RegisterARM64);
implement_register!(RegisterM68K);
implement_register!(RegisterMIPS);
implement_register!(RegisterSPARC);
implement_register!(RegisterX86);
pub trait Cpu {
type Reg: Register;
fn emu(&self) -> &Unicorn;
fn mut_emu(&mut self) -> &mut Unicorn;
fn reg_read(&self, reg: Self::Reg) -> Result<u64> {
self.emu().reg_read(reg.to_i32())
}
fn reg_read_i32(&self, reg: Self::Reg) -> Result<i32> {
self.emu().reg_read_i32(reg.to_i32())
}
unsafe fn reg_write_generic<T: Sized>(&self, reg: Self::Reg, value: T) -> Result<()> {
self.emu().reg_write_generic(reg.to_i32(), value)
}
fn reg_write(&self, reg: Self::Reg, value: u64) -> Result<()> {
self.emu().reg_write(reg.to_i32(), value)
}
fn reg_write_i32(&self, reg: Self::Reg, value: i32) -> Result<()> {
self.emu().reg_write_i32(reg.to_i32(), value)
}
fn mem_map(&self, address: u64, size: libc::size_t, perms: Protection) -> Result<()> {
self.emu().mem_map(address, size, perms)
}
unsafe fn mem_map_ptr<T>(
&self,
address: u64,
size: libc::size_t,
perms: Protection,
ptr: *mut T,
) -> Result<()> {
self.emu().mem_map_ptr(address, size, perms, ptr)
}
fn mem_unmap(&self, address: u64, size: libc::size_t) -> Result<()> {
self.emu().mem_unmap(address, size)
}
fn mem_write(&self, address: u64, bytes: &[u8]) -> Result<()> {
self.emu().mem_write(address, bytes)
}
fn mem_read(&self, address: u64, bytes: &mut [u8]) -> Result<()> {
self.emu().mem_read(address, bytes)
}
fn mem_read_as_vec(&self, address: u64, size: usize) -> Result<Vec<u8>> {
self.emu().mem_read_as_vec(address, size)
}
fn mem_protect(&self, address: u64, size: usize, perms: Protection) -> Result<()> {
self.emu().mem_protect(address, size, perms)
}
fn mem_regions(&self) -> Result<Vec<MemRegion>> {
self.emu().mem_regions()
}
fn emu_start(&self, begin: u64, until: u64, timeout: u64, count: usize) -> Result<()> {
self.emu().emu_start(begin, until, timeout, count)
}
fn emu_stop(&self) -> Result<()> {
self.emu().emu_stop()
}
fn add_code_hook<F>(
&mut self,
hook_type: CodeHookType,
begin: u64,
end: u64,
callback: F,
) -> Result<uc_hook>
where
F: Fn(&Unicorn, u64, u32) -> () + 'static,
{
self.mut_emu()
.add_code_hook(hook_type, begin, end, callback)
}
fn add_intr_hook<F>(&mut self, callback: F) -> Result<uc_hook>
where
F: Fn(&Unicorn, u32) + 'static,
{
self.mut_emu().add_intr_hook(callback)
}
fn add_mem_hook<F>(
&mut self,
hook_type: MemHookType,
begin: u64,
end: u64,
callback: F,
) -> Result<uc_hook>
where
F: Fn(&Unicorn, MemType, u64, usize, i64) -> bool + 'static,
{
self.mut_emu().add_mem_hook(hook_type, begin, end, callback)
}
fn add_insn_in_hook<F>(&mut self, callback: F) -> Result<uc_hook>
where
F: Fn(&Unicorn, u32, usize) -> u32 + 'static,
{
self.mut_emu().add_insn_in_hook(callback)
}
fn add_insn_out_hook<F>(&mut self, callback: F) -> Result<uc_hook>
where
F: Fn(&Unicorn, u32, usize, u32) + 'static,
{
self.mut_emu().add_insn_out_hook(callback)
}
fn add_insn_sys_hook<F>(
&mut self,
insn_type: InsnSysX86,
begin: u64,
end: u64,
callback: F,
) -> Result<uc_hook>
where
F: Fn(&Unicorn) + 'static,
{
self.mut_emu()
.add_insn_sys_hook(insn_type, begin, end, callback)
}
fn remove_hook(&mut self, hook: uc_hook) -> Result<()> {
self.mut_emu().remove_hook(hook)
}
fn errno(&self) -> Error {
self.emu().errno()
}
fn query(&self, query: Query) -> Result<usize> {
self.emu().query(query)
}
fn context_save(&self) -> Result<Context> {
self.emu().context_save()
}
fn context_restore(&self, context: &Context) -> Result<()> {
self.emu().context_restore(context)
}
}
implement_emulator!(
doc = "An ARM emulator instance.",
doc = "Create an ARM emulator instance for the specified hardware mode.",
CpuARM,
Arch::ARM,
RegisterARM
);
implement_emulator!(
doc = "An ARM64 emulator instance.",
doc = "Create an ARM64 emulator instance for the specified hardware mode.",
CpuARM64,
Arch::ARM64,
RegisterARM64
);
implement_emulator!(
doc = "A M68K emulator instance.",
doc = "Create a M68K emulator instance for the specified hardware mode.",
CpuM68K,
Arch::M68K,
RegisterM68K
);
implement_emulator!(
doc = "A MIPS emulator instance.",
doc = "Create an MIPS emulator instance for the specified hardware mode.",
CpuMIPS,
Arch::MIPS,
RegisterMIPS
);
implement_emulator!(
doc = "A SPARC emulator instance.",
doc = "Create a SPARC emulator instance for the specified hardware mode.",
CpuSPARC,
Arch::SPARC,
RegisterSPARC
);
implement_emulator!(
doc = "An X86 emulator instance.",
doc = "Create an X86 emulator instance for the specified hardware mode.",
CpuX86,
Arch::X86,
RegisterX86
);
pub struct UnicornHook<F> {
unicorn: *const Unicorn,
callback: F,
}
extern "C" fn code_hook_proxy(_: uc_handle, address: u64, size: u32, user_data: *mut CodeHook) {
let (unicorn, callback) = destructure_hook!(CodeHook, user_data);
callback(unicorn, address, size)
}
extern "C" fn intr_hook_proxy(_: uc_handle, intno: u32, user_data: *mut IntrHook) {
let (unicorn, callback) = destructure_hook!(IntrHook, user_data);
callback(unicorn, intno)
}
extern "C" fn mem_hook_proxy(
_: uc_handle,
mem_type: MemType,
address: u64,
size: usize,
value: i64,
user_data: *mut MemHook,
) -> bool {
let (unicorn, callback) = destructure_hook!(MemHook, user_data);
callback(unicorn, mem_type, address, size, value)
}
extern "C" fn insn_in_hook_proxy(
_: uc_handle,
port: u32,
size: usize,
user_data: *mut InsnInHook,
) -> u32 {
let (unicorn, callback) = destructure_hook!(InsnInHook, user_data);
callback(unicorn, port, size)
}
extern "C" fn insn_out_hook_proxy(
_: uc_handle,
port: u32,
size: usize,
value: u32,
user_data: *mut InsnOutHook,
) {
let (unicorn, callback) = destructure_hook!(InsnOutHook, user_data);
callback(unicorn, port, size, value)
}
extern "C" fn insn_sys_hook_proxy(_: uc_handle, user_data: *mut InsnSysHook) {
let (unicorn, callback) = destructure_hook!(InsnSysHook, user_data);
callback(unicorn)
}
type CodeHook = UnicornHook<Box<FnMut(&Unicorn, u64, u32)>>;
type IntrHook = UnicornHook<Box<FnMut(&Unicorn, u32)>>;
type MemHook = UnicornHook<Box<FnMut(&Unicorn, MemType, u64, usize, i64) -> bool>>;
type InsnInHook = UnicornHook<Box<FnMut(&Unicorn, u32, usize) -> u32>>;
type InsnOutHook = UnicornHook<Box<FnMut(&Unicorn, u32, usize, u32)>>;
type InsnSysHook = UnicornHook<Box<FnMut(&Unicorn)>>;
pub struct Unicorn {
handle: libc::size_t,
code_callbacks: HashMap<uc_hook, Box<CodeHook>>,
intr_callbacks: HashMap<uc_hook, Box<IntrHook>>,
mem_callbacks: HashMap<uc_hook, Box<MemHook>>,
insn_in_callbacks: HashMap<uc_hook, Box<InsnInHook>>,
insn_out_callbacks: HashMap<uc_hook, Box<InsnOutHook>>,
insn_sys_callbacks: HashMap<uc_hook, Box<InsnSysHook>>,
}
pub fn bindings_version() -> (u32, u32) {
(BINDINGS_MAJOR, BINDINGS_MINOR)
}
pub fn unicorn_version() -> (u32, u32) {
let mut major: u32 = Default::default();
let mut minor: u32 = Default::default();
let p_major: *mut _ = &mut major;
let p_minor: *mut _ = &mut minor;
unsafe {
uc_version(p_major, p_minor);
}
(major, minor)
}
pub fn arch_supported(arch: Arch) -> bool {
unsafe { uc_arch_supported(arch) }
}
impl Unicorn {
pub fn new(arch: Arch, mode: Mode) -> Result<Box<Unicorn>> {
let (major, minor) = unicorn_version();
if major != BINDINGS_MAJOR || minor != BINDINGS_MINOR {
return Err(Error::VERSION);
}
let mut handle: libc::size_t = Default::default();
let err = unsafe { uc_open(arch, mode, &mut handle) };
if err == Error::OK {
Ok(Box::new(Unicorn {
handle,
code_callbacks: Default::default(),
intr_callbacks: Default::default(),
mem_callbacks: Default::default(),
insn_in_callbacks: Default::default(),
insn_out_callbacks: Default::default(),
insn_sys_callbacks: Default::default(),
}))
} else {
Err(err)
}
}
pub unsafe fn reg_write_generic<T: Sized>(&self, regid: i32, value: T) -> Result<()> {
let p_value: *const T = &value;
let err = uc_reg_write(self.handle, regid, p_value as *const libc::c_void);
if err == Error::OK {
Ok(())
} else {
Err(err)
}
}
pub fn reg_write(&self, regid: i32, value: u64) -> Result<()> {
unsafe { Self::reg_write_generic::<_>(&self, regid, value) }
}
pub fn reg_write_i32(&self, regid: i32, value: i32) -> Result<()> {
unsafe { Self::reg_write_generic::<_>(&self, regid, value) }
}
unsafe fn reg_read_generic<T: Sized>(&self, regid: i32) -> Result<T> {
let mut value: T = mem::zeroed();
let err = uc_reg_read(
self.handle,
regid as libc::c_int,
&mut value as *mut _ as *mut libc::c_void,
);
if err == Error::OK {
Ok(value)
} else {
Err(err)
}
}
pub fn reg_read(&self, regid: i32) -> Result<u64> {
unsafe { Self::reg_read_generic::<_>(&self, regid) }
}
pub fn reg_read_i32(&self, regid: i32) -> Result<i32> {
unsafe { Self::reg_read_generic::<_>(&self, regid) }
}
pub fn mem_map(&self, address: u64, size: libc::size_t, perms: Protection) -> Result<()> {
let err = unsafe { uc_mem_map(self.handle, address, size, perms.bits()) };
if err == Error::OK {
Ok(())
} else {
Err(err)
}
}
pub unsafe fn mem_map_ptr<T>(
&self,
address: u64,
size: libc::size_t,
perms: Protection,
ptr: *mut T,
) -> Result<()> {
let err = uc_mem_map_ptr(
self.handle,
address,
size,
perms.bits(),
ptr as *mut libc::c_void,
);
if err == Error::OK {
Ok(())
} else {
Err(err)
}
}
pub fn mem_unmap(&self, address: u64, size: libc::size_t) -> Result<()> {
let err = unsafe { uc_mem_unmap(self.handle, address, size) };
if err == Error::OK {
Ok(())
} else {
Err(err)
}
}
pub fn mem_write(&self, address: u64, bytes: &[u8]) -> Result<()> {
let err = unsafe {
uc_mem_write(
self.handle,
address,
bytes.as_ptr(),
bytes.len() as libc::size_t,
)
};
if err == Error::OK {
Ok(())
} else {
Err(err)
}
}
pub fn mem_read(&self, address: u64, bytes: &mut [u8]) -> Result<()> {
let err = unsafe { uc_mem_read(self.handle, address, bytes.as_mut_ptr(), bytes.len()) };
if err == Error::OK {
Ok(())
} else {
Err(err)
}
}
pub fn mem_read_as_vec(&self, address: u64, size: usize) -> Result<Vec<u8>> {
let mut bytes: Vec<u8> = Vec::with_capacity(size);
unsafe { self.mem_read(address, bytes.get_unchecked_mut(0..size)) }.map(|()| unsafe {
bytes.set_len(size);
bytes
})
}
pub fn mem_protect(&self, address: u64, size: usize, perms: Protection) -> Result<()> {
let err =
unsafe { uc_mem_protect(self.handle, address, size as libc::size_t, perms.bits()) };
if err == Error::OK {
Ok(())
} else {
Err(err)
}
}
pub fn mem_regions(&self) -> Result<Vec<MemRegion>> {
let mut nb_regions: u32 = 0;
let p_nb_regions: *mut u32 = &mut nb_regions;
let p_regions: *const MemRegion = std::ptr::null();
let pp_regions: *const *const MemRegion = &p_regions;
let err = unsafe { uc_mem_regions(self.handle, pp_regions, p_nb_regions) };
if err == Error::OK {
let mut regions: Vec<MemRegion> = Vec::new();
let mut i: isize = 0;
while i < nb_regions as isize {
unsafe {
let region: MemRegion = mem::transmute_copy(&*p_regions.offset(i));
regions.push(region);
}
i += 1;
}
unsafe { libc::free(*pp_regions as *mut libc::c_void) };
Ok(regions)
} else {
Err(err)
}
}
pub fn emu_start(&self, begin: u64, until: u64, timeout: u64, count: usize) -> Result<()> {
let err =
unsafe { uc_emu_start(self.handle, begin, until, timeout, count as libc::size_t) };
if err == Error::OK {
Ok(())
} else {
Err(err)
}
}
pub fn emu_stop(&self) -> Result<()> {
let err = unsafe { uc_emu_stop(self.handle) };
if err == Error::OK {
Ok(())
} else {
Err(err)
}
}
pub fn add_code_hook<F>(
&mut self,
hook_type: CodeHookType,
begin: u64,
end: u64,
callback: F,
) -> Result<uc_hook>
where
F: Fn(&Unicorn, u64, u32) + 'static,
{
let mut hook: uc_hook = 0;
let p_hook: *mut libc::size_t = &mut hook;
let user_data = Box::new(CodeHook {
unicorn: self as *mut _,
callback: Box::new(callback),
});
let p_user_data: *mut libc::size_t = unsafe { mem::transmute(&*user_data) };
let _callback: libc::size_t = code_hook_proxy as usize;
let err = unsafe {
uc_hook_add(
self.handle,
p_hook,
mem::transmute(hook_type),
_callback,
p_user_data,
begin,
end,
)
};
if err == Error::OK {
self.code_callbacks.insert(hook, user_data);
Ok(hook)
} else {
Err(err)
}
}
pub fn add_intr_hook<F>(&mut self, callback: F) -> Result<uc_hook>
where
F: Fn(&Unicorn, u32) + 'static,
{
let mut hook: uc_hook = 0;
let p_hook: *mut libc::size_t = &mut hook;
let user_data = Box::new(IntrHook {
unicorn: self as *mut _,
callback: Box::new(callback),
});
let p_user_data: *mut libc::size_t = unsafe { mem::transmute(&*user_data) };
let _callback: libc::size_t = intr_hook_proxy as usize;
let err = unsafe {
uc_hook_add(
self.handle,
p_hook,
HookType::INTR,
_callback,
p_user_data,
0,
0,
)
};
if err == Error::OK {
self.intr_callbacks.insert(hook, user_data);
Ok(hook)
} else {
Err(err)
}
}
pub fn add_mem_hook<F>(
&mut self,
hook_type: MemHookType,
begin: u64,
end: u64,
callback: F,
) -> Result<uc_hook>
where
F: Fn(&Unicorn, MemType, u64, usize, i64) -> bool + 'static,
{
let mut hook: uc_hook = 0;
let p_hook: *mut libc::size_t = &mut hook;
let user_data = Box::new(MemHook {
unicorn: self as *mut _,
callback: Box::new(callback),
});
let p_user_data: *mut libc::size_t = unsafe { mem::transmute(&*user_data) };
let _callback: libc::size_t = mem_hook_proxy as usize;
let err = unsafe {
uc_hook_add(
self.handle,
p_hook,
mem::transmute(hook_type),
_callback,
p_user_data,
begin,
end,
)
};
if err == Error::OK {
self.mem_callbacks.insert(hook, user_data);
Ok(hook)
} else {
Err(err)
}
}
pub fn add_insn_in_hook<F>(&mut self, callback: F) -> Result<uc_hook>
where
F: Fn(&Unicorn, u32, usize) -> u32 + 'static,
{
let mut hook: uc_hook = 0;
let p_hook: *mut libc::size_t = &mut hook;
let user_data = Box::new(InsnInHook {
unicorn: self as *mut _,
callback: Box::new(callback),
});
let p_user_data: *mut libc::size_t = unsafe { mem::transmute(&*user_data) };
let _callback: libc::size_t = insn_in_hook_proxy as usize;
let err = unsafe {
uc_hook_add(
self.handle,
p_hook,
HookType::INSN,
_callback,
p_user_data,
0,
0,
x86_const::InsnX86::IN,
)
};
if err == Error::OK {
self.insn_in_callbacks.insert(hook, user_data);
Ok(hook)
} else {
Err(err)
}
}
pub fn add_insn_out_hook<F>(&mut self, callback: F) -> Result<uc_hook>
where
F: Fn(&Unicorn, u32, usize, u32) + 'static,
{
let mut hook: uc_hook = 0;
let p_hook: *mut libc::size_t = &mut hook;
let user_data = Box::new(InsnOutHook {
unicorn: self as *mut _,
callback: Box::new(callback),
});
let p_user_data: *mut libc::size_t = unsafe { mem::transmute(&*user_data) };
let _callback: libc::size_t = insn_out_hook_proxy as usize;
let err = unsafe {
uc_hook_add(
self.handle,
p_hook,
HookType::INSN,
_callback,
p_user_data,
0,
0,
x86_const::InsnX86::OUT,
)
};
if err == Error::OK {
self.insn_out_callbacks.insert(hook, user_data);
Ok(hook)
} else {
Err(err)
}
}
pub fn add_insn_sys_hook<F>(
&mut self,
insn_type: InsnSysX86,
begin: u64,
end: u64,
callback: F,
) -> Result<uc_hook>
where
F: Fn(&Unicorn) + 'static,
{
let mut hook: uc_hook = 0;
let p_hook: *mut libc::size_t = &mut hook;
let user_data = Box::new(InsnSysHook {
unicorn: self as *mut _,
callback: Box::new(callback),
});
let p_user_data: *mut libc::size_t = unsafe { mem::transmute(&*user_data) };
let _callback: libc::size_t = insn_sys_hook_proxy as usize;
let err = unsafe {
uc_hook_add(
self.handle,
p_hook,
HookType::INSN,
_callback,
p_user_data,
begin,
end,
insn_type,
)
};
if err == Error::OK {
self.insn_sys_callbacks.insert(hook, user_data);
Ok(hook)
} else {
Err(err)
}
}
pub fn remove_hook(&mut self, hook: uc_hook) -> Result<()> {
let err = unsafe { uc_hook_del(self.handle, hook) } as Error;
macro_rules! ignore {
() => {
|_| ()
};
};
self.code_callbacks
.remove(&hook)
.map(ignore!())
.or_else(|| self.intr_callbacks.remove(&hook).map(ignore!()))
.or_else(|| self.mem_callbacks.remove(&hook).map(ignore!()))
.or_else(|| self.insn_in_callbacks.remove(&hook).map(ignore!()))
.or_else(|| self.insn_out_callbacks.remove(&hook).map(ignore!()))
.or_else(|| self.insn_sys_callbacks.remove(&hook).map(ignore!()));
if err == Error::OK {
Ok(())
} else {
Err(err)
}
}
pub fn errno(&self) -> Error {
unsafe { uc_errno(self.handle) }
}
pub fn query(&self, query: Query) -> Result<usize> {
let mut result: libc::size_t = 0;
let p_result: *mut libc::size_t = &mut result;
let err = unsafe { uc_query(self.handle, query, p_result) };
if err == Error::OK {
Ok(result)
} else {
Err(err)
}
}
pub fn context_save(&self) -> Result<Context> {
let mut context: uc_context = 0;
let p_context: *mut uc_context = &mut context;
let err = unsafe { uc_context_alloc(self.handle, p_context) };
if err != Error::OK {
return Err(err);
};
let err = unsafe { uc_context_save(self.handle, context) };
if err != Error::OK {
return Err(err);
};
Ok(Context { context })
}
pub fn context_restore(&self, context: &Context) -> Result<()> {
let err = unsafe { uc_context_restore(self.handle, context.context) };
if err == Error::OK {
Ok(())
} else {
Err(err)
}
}
}
impl Drop for Unicorn {
fn drop(&mut self) {
unsafe { uc_close(self.handle) };
}
}