diff --git a/src/encoder.rs b/src/encoder.rs new file mode 100644 index 0000000..6b38f7e --- /dev/null +++ b/src/encoder.rs @@ -0,0 +1,222 @@ +use crate::{ffi, *}; +use core::{ + mem::MaybeUninit, + ops::{Deref, DerefMut}, +}; + +#[repr(transparent)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct EncoderRequest(ffi::EncoderRequest); + +impl Deref for EncoderRequest { + type Target = ffi::EncoderRequest; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl EncoderRequest { + pub fn new32(mnemonic: Mnemonic) -> Self { + Self::new(MachineMode::LONG_COMPAT_32, mnemonic) + } + + pub fn new64(mnemonic: Mnemonic) -> Self { + Self::new(MachineMode::LONG_64, mnemonic) + } + + /// Create a new encoder request from scratch. + pub fn new(machine_mode: MachineMode, mnemonic: Mnemonic) -> Self { + let mut request: ffi::EncoderRequest = unsafe { core::mem::zeroed() }; + request.machine_mode = machine_mode; + request.mnemonic = mnemonic; + Self(request) + } + + /// Sets the branch type. + /// + /// Required for branching instructions only. The default of + /// `ZYDIS_BRANCH_TYPE_NONE` lets the encoder pick size-optimal branch type + /// automatically (`short` and `near` are prioritized over `far`). + pub fn set_branch_type(mut self, branch_type: BranchType) -> Self { + self.0.branch_type = branch_type; + self + } + + /// Sets the branch width. + /// + /// Specifies physical size for relative immediate operands. Use + /// `ZYDIS_BRANCH_WIDTH_NONE` to let encoder pick size-optimal branch width + /// automatically. For segment:offset `far` branches this field applies to + /// physical size of the offset part. For branching instructions without + /// relative operands this field affects effective operand size attribute. + pub fn set_branch_width(mut self, branch_width: BranchWidth) -> Self { + self.0.branch_width = branch_width; + self + } + + /// Sets the address size hint. + pub fn set_address_size_hint(mut self, address_size_hint: AddressSizeHint) -> Self { + self.0.address_size_hint = address_size_hint; + self + } + + /// Sets the operand size hint. + pub fn set_operand_size_hint(mut self, operand_size_hint: OperandSizeHint) -> Self { + self.0.operand_size_hint = operand_size_hint; + self + } + + /// Gets a slice of the operands. + pub fn operands(&self) -> &[EncoderOperand] { + unsafe { + core::slice::from_raw_parts( + self.0.operands.as_ptr() as *const EncoderOperand, + self.0.operand_count as usize, + ) + } + } + + /// Adds an operand to the request. + pub fn add_operand(mut self, operand: EncoderOperand) -> Self { + self.0.operands[self.0.operand_count as usize] = operand.0; + self.0.operand_count += 1; + self + } + + /// Clears the operand list. + pub fn clear_operands(mut self) -> Self { + self.0.operand_count = 0; + self + } + + /// Encodes the instruction into the given buffer. + pub fn encode_into(&self, buf: &mut [u8]) -> Result { + unsafe { + let mut length = buf.len(); + ffi::ZydisEncoderEncodeInstruction(&self.0, buf.as_ptr() as _, &mut length) + .as_result()?; + Ok(length) + } + } + + /// Encodes the instruction into a new buffer. + pub fn encode(&self) -> Result> { + let mut out = vec![0; MAX_INSTRUCTION_LENGTH]; + let length = self.encode_into(&mut out[..])?; + out.resize(length, 0); + Ok(out) + } +} + +impl From>> for EncoderRequest { + fn from(instr: Instruction>) -> Self { + unsafe { + let mut request = MaybeUninit::uninit(); + ffi::ZydisEncoderDecodedInstructionToEncoderRequest( + &*instr, + instr.operands().as_ptr(), + instr.operands().len() as _, + request.as_mut_ptr(), + ) + .as_result() + .expect( + "our rust wrapper for instructions is immutable and unchanged decoded \ + instructions should always be convertible", + ); + Self(request.assume_init()) + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct EncoderOperand(ffi::EncoderOperand); + +impl EncoderOperand { + /// Creates a new register operand. + pub fn reg(reg: Register) -> Self { + Self(ffi::EncoderOperand { + ty: OperandType::REGISTER, + reg: ffi::OperandRegister { + value: reg as _, + is4: false, + }, + mem: unsafe { core::mem::zeroed() }, + ptr: unsafe { core::mem::zeroed() }, + imm: 0, + }) + } + + /// Creates a new memory operand. + pub fn mem(mem: ffi::OperandMemory) -> Self { + Self(ffi::EncoderOperand { + ty: OperandType::MEMORY, + reg: unsafe { core::mem::zeroed() }, + mem, + ptr: unsafe { core::mem::zeroed() }, + imm: 0, + }) + } + + /// Creates a new pointer operand. + pub fn ptr(segment: u16, offset: u32) -> Self { + Self(ffi::EncoderOperand { + ty: OperandType::POINTER, + reg: unsafe { core::mem::zeroed() }, + mem: unsafe { core::mem::zeroed() }, + ptr: ffi::OperandPointer { + segment, + offset, + }, + imm: 0, + }) + } + + /// Creates a new immediate operand (unsigned). + pub fn imm(imm: u64) -> Self { + Self(ffi::EncoderOperand { + ty: OperandType::IMMEDIATE, + reg: unsafe { core::mem::zeroed() }, + mem: unsafe { core::mem::zeroed() }, + ptr: unsafe { core::mem::zeroed() }, + imm, + }) + } + + /// Creates a new immediate operand (signed). + pub fn imm_signed(imm: i64) -> Self { + Self::imm(imm as _) + } +} + +impl Deref for EncoderOperand { + type Target = ffi::EncoderOperand; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn encode_int3() { + let req = EncoderRequest::new64(Mnemonic::INT3); + let enc = req.encode().unwrap(); + assert_eq!(enc, vec![0xCC]); + } + + #[test] + fn encode_mov() { + let mov = EncoderRequest::new64(Mnemonic::MOV) + .add_operand(EncoderOperand::reg(Register::RAX)) + .add_operand(EncoderOperand::imm(0x1337)) + .encode() + .unwrap(); + + assert_eq!(mov, b"\x48\xC7\xC0\x37\x13\x00\x00"); + } +} diff --git a/src/enums.rs b/src/enums.rs index 3be4171..64cb4a0 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -25,6 +25,24 @@ pub(crate) const MAX_INSTRUCTION_SEGMENT_COUNT: usize = 9; /// Maximum number of encoder operands. pub const ENCODER_MAX_OPERANDS: usize = 5; +/// Combination of all user-encodable prefixes. +pub const ENCODABLE_PREFIXES: u64 = InstructionAttributes::HAS_LOCK.bits() + | InstructionAttributes::HAS_REP.bits() + | InstructionAttributes::HAS_REPE.bits() + | InstructionAttributes::HAS_REPNE.bits() + | InstructionAttributes::HAS_BND.bits() + | InstructionAttributes::HAS_XACQUIRE.bits() + | InstructionAttributes::HAS_XRELEASE.bits() + | InstructionAttributes::HAS_BRANCH_NOT_TAKEN.bits() + | InstructionAttributes::HAS_BRANCH_TAKEN.bits() + | InstructionAttributes::HAS_NOTRACK.bits() + | InstructionAttributes::HAS_SEGMENT_CS.bits() + | InstructionAttributes::HAS_SEGMENT_SS.bits() + | InstructionAttributes::HAS_SEGMENT_DS.bits() + | InstructionAttributes::HAS_SEGMENT_ES.bits() + | InstructionAttributes::HAS_SEGMENT_FS.bits() + | InstructionAttributes::HAS_SEGMENT_GS.bits(); + impl Mnemonic { /// Returns the static string corresponding to this mnemonic. /// diff --git a/src/ffi/encoder.rs b/src/ffi/encoder.rs index ad4171d..6f39f93 100644 --- a/src/ffi/encoder.rs +++ b/src/ffi/encoder.rs @@ -33,7 +33,7 @@ pub struct OperandPointer { #[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))] #[derive(Clone, Debug, Eq, PartialEq, Hash)] #[repr(C)] -pub struct Operand { +pub struct EncoderOperand { pub ty: OperandType, pub reg: OperandRegister, pub mem: OperandMemory, @@ -68,7 +68,7 @@ pub struct MvexFeatures { #[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))] #[derive(Clone, Debug, Eq, PartialEq, Hash)] #[repr(C)] -pub struct Request { +pub struct EncoderRequest { pub machine_mode: MachineMode, pub allowed_encodings: EncodableEncoding, pub mnemonic: Mnemonic, @@ -78,21 +78,21 @@ pub struct Request { pub address_size_hint: AddressSizeHint, pub operand_size_hint: OperandSizeHint, pub operand_count: u8, - pub operands: [Operand; ENCODER_MAX_OPERANDS], + pub operands: [EncoderOperand; ENCODER_MAX_OPERANDS], pub evex: EvexFeatures, pub mvex: MvexFeatures, } extern "C" { pub fn ZydisEncoderEncodeInstructionAbsolute( - request: *const Request, + request: *const EncoderRequest, buffer: *mut c_void, length: *mut usize, runtime_address: u64, ) -> Status; pub fn ZydisEncoderEncodeInstruction( - request: *const Request, + request: *const EncoderRequest, buffer: *mut c_void, length: *mut usize, ) -> Status; @@ -101,7 +101,7 @@ extern "C" { instruction: *const DecodedInstruction, operands: *const DecodedOperand, operand_count: u8, - request: *mut Request, + request: *mut EncoderRequest, ) -> Status; pub fn ZydisEncoderNopFill(buffer: *mut c_void, length: usize) -> Status; diff --git a/src/lib.rs b/src/lib.rs index 1515d2f..1698037 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,12 +5,16 @@ #[macro_use] mod status; mod decoder; +#[cfg(feature = "encoder")] +mod encoder; mod enums; pub mod ffi; #[cfg(feature = "formatter")] mod formatter; pub use decoder::*; +#[cfg(feature = "encoder")] +pub use encoder::*; pub use enums::*; #[cfg(feature = "formatter")] pub use formatter::*;