Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
athre0z committed Oct 28, 2023
1 parent 6f1523f commit 9ff5e80
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 7 deletions.
315 changes: 315 additions & 0 deletions src/encoder.rs
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");
}
}
18 changes: 18 additions & 0 deletions src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
14 changes: 7 additions & 7 deletions src/ffi/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -77,22 +77,22 @@ pub struct Request {
pub branch_width: BranchWidth,
pub address_size_hint: AddressSizeHint,
pub operand_size_hint: OperandSizeHint,
pub operand_count: u8,
pub operands: [Operand; ENCODER_MAX_OPERANDS],
pub(crate) operand_count: u8,
pub(crate) 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;
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 9ff5e80

Please sign in to comment.