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}