keystone_engine/
lib.rs

1//! Unofficial Rust bindings for the Keystone Engine
2//!
3//! These Rust bindings are an alternative to the ones available in the
4//! [official repository](https://github.com/keystone-engine/keystone/tree/master/bindings/rust)
5//! and support version 0.9.2 of the Keystone Engine.
6//!
7//! ## Why Release New Bindings If Official Ones Exist?
8//!
9//! To publish a crate on crates.io, all its dependencies must also come from crates.io. There is
10//! already a [crate](https://crates.io/crates/keystone) providing bindings for Keystone. However,
11//! the latest version it supports, is version 0.9.0 of Keystone. This version has bugs, which,
12//! fortunately, are fixed in version 0.9.2. For this reason, and because the current crate of
13//! Keystone has not been updated for years, these bindings can be used instead.
14//!
15//! These bindings are heavily inspired from the official ones and do not alter the API too much
16//! compared to the original.
17//!
18//! ## Example
19//!
20//! This example, taken from the
21//! [official repo](https://github.com/keystone-engine/keystone/blob/0.9.2/bindings/rust/examples/asm.rs),
22//! should work out of the box.
23//!
24//! Add the following dependency to `Cargo.toml`:
25//!
26//! ```
27//! keystone_engine = { version = "0.1.0", features = ["build-from-src"] }
28//! ```
29//!
30//! **Note:** You can use either the `build-from-src` feature to build the Keystone Engine or
31//!           `use-system-lib` if you have already installed Keystone on your system.
32//!
33//! You should now be able to run the following code:
34//!
35//! ```
36//! use keystone_engine::*;
37//!
38//! let engine =
39//!     Keystone::new(Arch::X86, Mode::MODE_32).expect("Could not initialize Keystone engine");
40//!
41//! engine
42//!     .option(OptionType::SYNTAX, OptionValue::SYNTAX_NASM)
43//!     .expect("Could not set option to nasm syntax");
44//!
45//! let result = engine
46//!     .asm("mov ah, 0x80".to_string(), 0)
47//!     .expect("Could not assemble");
48//!
49//! println!("ASM result: {}", result);
50//!
51//! if let Err(err) = engine.asm("INVALID".to_string(), 0) {
52//!     println!("Error: {}", err);
53//! }
54//! ```
55//!
56//! ## Credits
57//!
58//!  * [Keystone Assembler Engine](http://www.keystone-engine.org/) by
59//!    [Nguyen Anh Quynh](mailto:aquynh@gmail.com)
60//!  * [Rust bindings](https://github.com/keystone-engine/keystone/tree) by
61//!    [Remco Verhoef](mailto:remco@dutchcoders.io)
62
63pub mod ffi;
64
65pub use ffi::{Arch, Error, Mode, OptionType, OptionValue};
66
67use libc::*;
68
69// -----------------------------------------------------------------------------------------------
70// Errors
71// -----------------------------------------------------------------------------------------------
72
73/// Convenient Result type wrapping Keystone errors.
74pub type Result<T> = std::result::Result<T, KeystoneError>;
75
76/// Keystone errors.
77#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
78pub enum KeystoneError {
79    /// Errors directly handled by Keystone.
80    Engine(ffi::Error),
81    /// Additional error types to handle bindings-specific cases.
82    Misc(MiscError),
83}
84
85impl std::error::Error for KeystoneError {}
86
87impl std::fmt::Display for KeystoneError {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        match self {
90            KeystoneError::Engine(e) => write!(f, "[Engine error] {}", e),
91            KeystoneError::Misc(e) => write!(f, "[Misc error] {}", e),
92        }
93    }
94}
95
96impl From<ffi::Error> for KeystoneError {
97    fn from(error: ffi::Error) -> Self {
98        KeystoneError::Engine(error)
99    }
100}
101
102impl From<MiscError> for KeystoneError {
103    fn from(error: MiscError) -> Self {
104        KeystoneError::Misc(error)
105    }
106}
107
108/// Miscellaneous errors.
109#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
110pub enum MiscError {
111    /// Error returned when a call to `ks_asm` fails.
112    KsAsm,
113}
114
115impl std::error::Error for MiscError {}
116
117impl std::fmt::Display for MiscError {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            MiscError::KsAsm => write!(f, "an error occured while calling ks_asm"),
121        }
122    }
123}
124
125// -----------------------------------------------------------------------------------------------
126// API
127// -----------------------------------------------------------------------------------------------
128
129/// Output object created after assembling instructions.
130#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
131pub struct KeystoneOutput {
132    /// Size of the array storing the encoded instructions.
133    pub size: u32,
134    /// Number of instructions that were successfully encoded.
135    pub stat_count: u32,
136    /// Array of encoded instructions.
137    pub bytes: Vec<u8>,
138}
139
140impl std::fmt::Display for KeystoneOutput {
141    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
142        for &byte in &self.bytes {
143            f.write_fmt(format_args!("{:02x}", byte))?;
144        }
145        Ok(())
146    }
147}
148
149/// Reprensents a Keystone instance.
150#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
151pub struct Keystone {
152    /// Handle to the keystone instance.
153    ks: ffi::KsHandle,
154}
155
156impl Keystone {
157    /// Creates a new Keystone object.
158    pub fn new(arch: ffi::Arch, mode: ffi::Mode) -> Result<Self> {
159        // Check if the version returned by the library matches with the bindings'.
160        if Self::version() != (ffi::API_MAJOR, ffi::API_MINOR) {
161            return Err(ffi::Error::VERSION)?;
162        }
163        // Opens the Keystone instance.
164        let mut ks = None;
165        let err = unsafe { ffi::ks_open(arch, mode, &mut ks) };
166        if err == ffi::Error::OK {
167            Ok(Keystone {
168                ks: ks.expect("Got NULL engine from ks_open()"),
169            })
170        } else {
171            Err(err)?
172        }
173    }
174
175    // Returns the major and minor version numbers from the library.
176    pub fn version() -> (u32, u32) {
177        let mut major = 0;
178        let mut minor = 0;
179        unsafe { ffi::ks_version(&mut major, &mut minor) };
180        (major, minor)
181    }
182
183    /// Sets an option of the Keystone engine after the instance has been created.
184    pub fn option(&self, opt_type: ffi::OptionType, value: ffi::OptionValue) -> Result<()> {
185        let err = unsafe { ffi::ks_option(self.ks, opt_type, value) };
186        if err == ffi::Error::OK {
187            Ok(())
188        } else {
189            Err(err)?
190        }
191    }
192
193    /// Assembles a program from an input string containing assembly instructions.
194    ///
195    /// The resulting machine code depends on the input buffer, its size, a base address and the
196    /// number of instructions to encode. The method returns a [`KeystoneOutput`] object that
197    /// contains the encoded instructions.
198    pub fn asm(&self, insns: String, address: u64) -> Result<KeystoneOutput> {
199        let insns_cstr = std::ffi::CString::new(insns).unwrap();
200        let mut encoding: *mut c_uchar = std::ptr::null_mut();
201        let mut encoding_size: size_t = 0;
202        let mut stat_count: size_t = 0;
203        // Encode the input instructions.
204        let err = unsafe {
205            ffi::ks_asm(
206                self.ks,
207                insns_cstr.as_ptr(),
208                address,
209                &mut encoding,
210                &mut encoding_size,
211                &mut stat_count,
212            )
213        };
214        if err == 0 {
215            // Converting the output machine code to a Vec<u8>.
216            let insns_slice = unsafe { std::slice::from_raw_parts(encoding, encoding_size) };
217            let insns = insns_slice.to_vec();
218            // Freeing memory allocated by `ks_asm`.
219            unsafe { ffi::ks_free(encoding) };
220            Ok(KeystoneOutput {
221                size: encoding_size.try_into().expect("size_t overflowed u32"),
222                stat_count: stat_count.try_into().expect("size_t overflowed u32"),
223                bytes: insns,
224            })
225        } else {
226            // If an error occured after calling ks_asm, check if an strerrno has been set and
227            // return the corresponding error. Otherwise, just return a generic error.
228            match Error::new(self.ks) {
229                Some(e) => Err(e)?,
230                None => Err(MiscError::KsAsm)?,
231            }
232        }
233    }
234}
235
236impl Drop for Keystone {
237    fn drop(&mut self) {
238        unsafe { ffi::ks_close(self.ks) };
239    }
240}
241
242// -----------------------------------------------------------------------------------------------
243// Tests
244// -----------------------------------------------------------------------------------------------
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    fn test() {
252        // Create an instance of Keystone.
253        let engine_res = Keystone::new(Arch::X86, Mode::MODE_32);
254        assert!(engine_res.is_ok());
255        let engine = engine_res.unwrap();
256        // Change an option of the engine.
257        engine
258            .option(OptionType::SYNTAX, OptionValue::SYNTAX_NASM)
259            .expect("Could not set option to nasm syntax");
260        // Assemble instructions
261        let output_res = engine.asm("mov ah, 0x80".to_string(), 0);
262        assert!(output_res.is_ok());
263        // Make sure the output object is sane.
264        let output = output_res.unwrap();
265        assert_eq!(output.bytes, vec![0xb4, 0x80]);
266        assert_eq!(output.size, 2);
267        assert_eq!(output.stat_count, 1);
268        // Ensure an error is returned when invalid instructions are provided.
269        assert_eq!(
270            engine.asm("INVALID".to_string(), 0),
271            Err(KeystoneError::Engine(ffi::Error::ASM_MNEMONICFAIL))
272        );
273    }
274}