-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
345 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,315 @@ | ||
use crate::{ffi, *}; | ||
use core::{ | ||
mem::{self, ManuallyDrop, MaybeUninit}, | ||
ops::Deref, | ||
}; | ||
|
||
/// Workaround for missing `const fn` in `core::mem::zeroed`. | ||
/// | ||
/// Concept borrowed from `const_zero` crate. | ||
macro_rules! zeroed { | ||
($ty:ty) => {{ | ||
union TypeOrArray { | ||
raw: [u8; mem::size_of::<$ty>()], | ||
struc: mem::ManuallyDrop<$ty>, | ||
} | ||
ManuallyDrop::<$ty>::into_inner( | ||
TypeOrArray { | ||
raw: [0; mem::size_of::<$ty>()], | ||
} | ||
.struc, | ||
) | ||
}}; | ||
} | ||
|
||
#[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 const fn new32(mnemonic: Mnemonic) -> Self { | ||
Self::new(MachineMode::LONG_COMPAT_32, mnemonic) | ||
} | ||
|
||
pub const fn new64(mnemonic: Mnemonic) -> Self { | ||
Self::new(MachineMode::LONG_64, mnemonic) | ||
} | ||
|
||
/// Create a new encoder request from scratch. | ||
pub const fn new(machine_mode: MachineMode, mnemonic: Mnemonic) -> Self { | ||
let mut request = unsafe { zeroed!(ffi::EncoderRequest) }; | ||
request.machine_mode = machine_mode; | ||
request.mnemonic = mnemonic; | ||
Self(request) | ||
} | ||
|
||
/// Sets the prefixes. | ||
/// | ||
/// Prefixes are simply represented using the corresponding instruction | ||
/// attributes. So e.g. if you wish to add a `GS` segment prefix, specify | ||
/// [`InstructionAttributes::HAS_SEGMENT_CS`]. | ||
pub const fn set_prefixes(mut self, prefixes: InstructionAttributes) -> Self { | ||
self.0.prefixes = prefixes; | ||
self | ||
} | ||
|
||
/// 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 const 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 const fn set_branch_width(mut self, branch_width: BranchWidth) -> Self { | ||
self.0.branch_width = branch_width; | ||
self | ||
} | ||
|
||
/// Sets the address size hint. | ||
pub const 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 const 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, | ||
) | ||
} | ||
} | ||
|
||
/// Gets a mutable slice of the operands. | ||
pub fn operands_mut(&mut self) -> &mut [EncoderOperand] { | ||
unsafe { | ||
core::slice::from_raw_parts_mut( | ||
self.0.operands.as_mut_ptr() as *mut EncoderOperand, | ||
self.0.operand_count as usize, | ||
) | ||
} | ||
} | ||
|
||
/// Adds an operand to the request. | ||
pub const 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 const 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<usize> { | ||
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<Vec<u8>> { | ||
let mut out = vec![0; MAX_INSTRUCTION_LENGTH]; | ||
let length = self.encode_into(&mut out[..])?; | ||
out.resize(length, 0); | ||
Ok(out) | ||
} | ||
} | ||
|
||
impl<const N: usize> From<Instruction<OperandArrayVec<N>>> for EncoderRequest { | ||
fn from(instr: Instruction<OperandArrayVec<N>>) -> 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 { | ||
const ZERO_MEM: ffi::OperandMemory = unsafe { zeroed!(ffi::OperandMemory) }; | ||
const ZERO_REG: ffi::OperandRegister = unsafe { zeroed!(ffi::OperandRegister) }; | ||
const ZERO_PTR: ffi::OperandPointer = unsafe { zeroed!(ffi::OperandPointer) }; | ||
|
||
/// Creates a new register operand. | ||
pub const fn reg(reg: Register) -> Self { | ||
Self(ffi::EncoderOperand { | ||
ty: OperandType::REGISTER, | ||
reg: ffi::OperandRegister { | ||
value: reg as _, | ||
is4: false, | ||
}, | ||
mem: Self::ZERO_MEM, | ||
ptr: Self::ZERO_PTR, | ||
imm: 0, | ||
}) | ||
} | ||
|
||
/// Creates a new `[disp]` memory operand. | ||
/// | ||
/// Note that only very few instructions actually accept a full 64-bit | ||
/// displacement. You'll typically only be able to use the lower 32 bits | ||
/// or encoding will fail. | ||
pub const fn mem_abs(size_bytes: u16, abs_disp: u64) -> Self { | ||
Self::mem_custom(ffi::OperandMemory { | ||
displacement: abs_disp as i64, | ||
size: size_bytes, | ||
..Self::ZERO_MEM | ||
}) | ||
} | ||
|
||
/// Creates a new `[reg + disp]` memory operand. | ||
pub const fn mem_base_disp(size_bytes: u16, base: Register, disp: i32) -> Self { | ||
Self::mem_custom(ffi::OperandMemory { | ||
base, | ||
displacement: disp as _, | ||
size: size_bytes, | ||
..Self::ZERO_MEM | ||
}) | ||
} | ||
|
||
/// Creates a new `[scale * index]` memory operand. | ||
/// | ||
/// Scale can only be 1, 2, 4, or 8. | ||
pub const fn mem_index_scale(size_bytes: u16, index: Register, scale: u8) -> Self { | ||
Self::mem_custom(ffi::OperandMemory { | ||
index, | ||
scale, | ||
size: size_bytes, | ||
..Self::ZERO_MEM | ||
}) | ||
} | ||
|
||
/// Creates a custom new memory operand. | ||
pub const fn mem_custom(mem: ffi::OperandMemory) -> Self { | ||
Self(ffi::EncoderOperand { | ||
ty: OperandType::MEMORY, | ||
reg: Self::ZERO_REG, | ||
mem, | ||
ptr: Self::ZERO_PTR, | ||
imm: 0, | ||
}) | ||
} | ||
|
||
/// Creates a new pointer operand. | ||
pub const fn ptr(segment: u16, offset: u32) -> Self { | ||
Self(ffi::EncoderOperand { | ||
ty: OperandType::POINTER, | ||
reg: Self::ZERO_REG, | ||
mem: Self::ZERO_MEM, | ||
ptr: ffi::OperandPointer { segment, offset }, | ||
imm: 0, | ||
}) | ||
} | ||
|
||
/// Creates a new immediate operand (unsigned). | ||
pub const fn imm(imm: u64) -> Self { | ||
Self(ffi::EncoderOperand { | ||
ty: OperandType::IMMEDIATE, | ||
reg: Self::ZERO_REG, | ||
mem: Self::ZERO_MEM, | ||
ptr: Self::ZERO_PTR, | ||
imm, | ||
}) | ||
} | ||
|
||
/// Creates a new immediate operand (signed). | ||
pub const 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"); | ||
} | ||
|
||
#[test] | ||
fn reencode() { | ||
let cmp = b"\x48\x81\x78\x7B\x41\x01\x00\x00"; | ||
let dec = Decoder::new64(); | ||
|
||
let insn = dec.decode_first::<VisibleOperands>(cmp).unwrap().unwrap(); | ||
assert_eq!(insn.to_string(), "cmp qword ptr [rax+0x7B], 0x141"); | ||
|
||
let mut req = EncoderRequest::from(insn); | ||
req = req.set_prefixes(InstructionAttributes::HAS_SEGMENT_FS); | ||
req.operands_mut()[0] = EncoderOperand::mem_base_disp(8, Register::RDX, 0xB7); | ||
req.operands_mut()[1] = EncoderOperand::imm(0x1337); | ||
let enc = req.encode().unwrap(); | ||
assert_eq!(enc, b"\x64\x48\x81\xBA\xB7\x00\x00\x00\x37\x13\x00\x00"); | ||
|
||
let redec = dec.decode_first::<VisibleOperands>(&enc).unwrap().unwrap(); | ||
assert_eq!(redec.to_string(), "cmp qword ptr fs:[rdx+0xB7], 0x1337"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.