pub mod ffi;
pub use ffi::{Arch, Error, Mode, OptionType, OptionValue};
use libc::*;
pub type Result<T> = std::result::Result<T, KeystoneError>;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum KeystoneError {
Engine(ffi::Error),
Misc(MiscError),
}
impl std::error::Error for KeystoneError {}
impl std::fmt::Display for KeystoneError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
KeystoneError::Engine(e) => write!(f, "[Engine error] {}", e),
KeystoneError::Misc(e) => write!(f, "[Misc error] {}", e),
}
}
}
impl From<ffi::Error> for KeystoneError {
fn from(error: ffi::Error) -> Self {
KeystoneError::Engine(error)
}
}
impl From<MiscError> for KeystoneError {
fn from(error: MiscError) -> Self {
KeystoneError::Misc(error)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum MiscError {
KsAsm,
}
impl std::error::Error for MiscError {}
impl std::fmt::Display for MiscError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MiscError::KsAsm => write!(f, "an error occured while calling ks_asm"),
}
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct KeystoneOutput {
pub size: u32,
pub stat_count: u32,
pub bytes: Vec<u8>,
}
impl std::fmt::Display for KeystoneOutput {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
for &byte in &self.bytes {
f.write_fmt(format_args!("{:02x}", byte))?;
}
Ok(())
}
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Keystone {
ks: ffi::KsHandle,
}
impl Keystone {
pub fn new(arch: ffi::Arch, mode: ffi::Mode) -> Result<Self> {
if Self::version() != (ffi::API_MAJOR, ffi::API_MINOR) {
return Err(ffi::Error::VERSION)?;
}
let mut ks = None;
let err = unsafe { ffi::ks_open(arch, mode, &mut ks) };
if err == ffi::Error::OK {
Ok(Keystone {
ks: ks.expect("Got NULL engine from ks_open()"),
})
} else {
Err(err)?
}
}
pub fn version() -> (u32, u32) {
let mut major = 0;
let mut minor = 0;
unsafe { ffi::ks_version(&mut major, &mut minor) };
(major, minor)
}
pub fn option(&self, opt_type: ffi::OptionType, value: ffi::OptionValue) -> Result<()> {
let err = unsafe { ffi::ks_option(self.ks, opt_type, value) };
if err == ffi::Error::OK {
Ok(())
} else {
Err(err)?
}
}
pub fn asm(&self, insns: String, address: u64) -> Result<KeystoneOutput> {
let insns_cstr = std::ffi::CString::new(insns).unwrap();
let mut encoding: *mut c_uchar = std::ptr::null_mut();
let mut encoding_size: size_t = 0;
let mut stat_count: size_t = 0;
let err = unsafe {
ffi::ks_asm(
self.ks,
insns_cstr.as_ptr(),
address,
&mut encoding,
&mut encoding_size,
&mut stat_count,
)
};
if err == 0 {
let insns_slice = unsafe { std::slice::from_raw_parts(encoding, encoding_size) };
let insns = insns_slice.to_vec();
unsafe { ffi::ks_free(encoding) };
Ok(KeystoneOutput {
size: encoding_size.try_into().expect("size_t overflowed u32"),
stat_count: stat_count.try_into().expect("size_t overflowed u32"),
bytes: insns,
})
} else {
match Error::new(self.ks) {
Some(e) => Err(e)?,
None => Err(MiscError::KsAsm)?,
}
}
}
}
impl Drop for Keystone {
fn drop(&mut self) {
unsafe { ffi::ks_close(self.ks) };
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let engine_res = Keystone::new(Arch::X86, Mode::MODE_32);
assert!(engine_res.is_ok());
let engine = engine_res.unwrap();
engine
.option(OptionType::SYNTAX, OptionValue::SYNTAX_NASM)
.expect("Could not set option to nasm syntax");
let output_res = engine.asm("mov ah, 0x80".to_string(), 0);
assert!(output_res.is_ok());
let output = output_res.unwrap();
assert_eq!(output.bytes, vec![0xb4, 0x80]);
assert_eq!(output.size, 2);
assert_eq!(output.stat_count, 1);
assert_eq!(
engine.asm("INVALID".to_string(), 0),
Err(KeystoneError::Engine(ffi::Error::ASM_MNEMONICFAIL))
);
}
}