use iced_x86::{
Decoder, DecoderOptions, FlowControl, Instruction, InstructionInfoFactory, Mnemonic, OpAccess,
OpKind, Register,
};
pub mod image;
pub mod memory;
pub mod registers;
pub mod util;
#[doc(inline)]
pub use image::ImageView;
#[doc(inline)]
pub use memory::MemoryStore;
#[doc(inline)]
pub use registers::Registers;
#[derive(Debug, Clone)]
pub struct ProgramState<I: ImageView, D: Clone = ()> {
pub rip: Option<u64>,
pub registers: Registers,
pub memory: MemoryStore<I>,
pub user_data: D,
}
#[derive(Debug)]
pub struct PastFork<I: ImageView, D: Clone = ()> {
pub state: ProgramState<I, D>,
pub basic_block_index: usize,
pub fork_index: usize,
pub branch_count: usize,
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct RunStep<'a, I: ImageView, D: Clone = ()> {
pub instruction: &'a mut Instruction,
pub info_factory: &'a mut InstructionInfoFactory,
pub state: &'a mut ProgramState<I, D>,
pub past_forks: &'a mut [PastFork<I, D>],
pub execution_path: &'a [u64],
pub branch_count: usize,
pub fork_index: usize,
pub basic_block_index: usize,
}
impl<I: ImageView, D: Clone> RunStep<'_, I, D> {
pub fn reborrow(&mut self) -> RunStep<'_, I, D> {
RunStep {
instruction: self.instruction,
info_factory: self.info_factory,
state: self.state,
past_forks: self.past_forks,
execution_path: self.execution_path,
branch_count: self.branch_count,
fork_index: self.fork_index,
basic_block_index: self.basic_block_index,
}
}
pub fn single_step(&mut self) -> Option<ProgramState<I, D>> {
self.state.single_step(self.instruction, self.info_factory)
}
#[allow(dead_code)] pub fn current_fork_path(&self) -> &[u64] {
&self.execution_path[self.fork_index..]
}
pub fn basic_block(&self) -> &[u64] {
&self.execution_path[self.basic_block_index..]
}
#[allow(dead_code)] pub fn fork_path_since(&self, depth: usize) -> &[u64] {
let Some(i_tgt_fork) = self.past_forks.len().checked_sub(depth)
else {
return self.execution_path;
};
let i = self.past_forks.get(i_tgt_fork).map(|f| f.fork_index).unwrap_or(self.fork_index);
&self.execution_path[i..]
}
}
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum StepKind<I: ImageView, D: Clone = (), R = ()> {
SingleStep,
StopFork,
Custom(Option<ProgramState<I, D>>),
Stop(R),
}
impl<I: ImageView, D: Clone> ProgramState<I, D> {
pub fn run<F, R>(self, mut on_step: F) -> Option<R>
where
F: FnMut(RunStep<'_, I, D>) -> StepKind<I, D, R>,
{
let mut instruction = Instruction::default();
let mut info_factory = InstructionInfoFactory::new();
let mut execution_path: Vec<u64> = Vec::with_capacity(0x1000);
let mut fork_stack = vec![PastFork {
basic_block_index: 0,
fork_index: 0,
state: self,
branch_count: 0,
}];
while !fork_stack.is_empty() {
let i_fork = fork_stack.len() - 1;
let (past_forks, tail) = fork_stack.split_at_mut(i_fork);
let PastFork {
state,
branch_count,
basic_block_index,
fork_index,
} = &mut tail[0];
let Some((ip, instr_bytes)) =
state.rip.and_then(|ip| state.memory.image().read(ip, 1).map(|b| (ip, b)))
else {
execution_path.truncate(*fork_index);
fork_stack.pop();
continue;
};
execution_path.push(ip);
let mut decoder = Decoder::with_ip(64, instr_bytes, ip, DecoderOptions::NONE);
decoder.decode_out(&mut instruction);
if instruction.is_invalid() {
log::trace!("invalid instruction at {ip:x}");
execution_path.truncate(*fork_index);
fork_stack.pop();
continue;
}
let run_step = RunStep {
execution_path: &execution_path,
instruction: &mut instruction,
info_factory: &mut info_factory,
state,
past_forks,
branch_count: *branch_count,
fork_index: *fork_index,
basic_block_index: *basic_block_index,
};
let maybe_fork = match on_step(run_step) {
StepKind::Stop(ret) => return Some(ret),
StepKind::StopFork => {
execution_path.truncate(*fork_index);
fork_stack.pop();
continue;
}
StepKind::SingleStep => state.single_step(&instruction, &mut info_factory),
StepKind::Custom(maybe_fork) => maybe_fork,
};
if let Some(forked) = maybe_fork {
*branch_count += 1;
let branch_count = *branch_count;
*basic_block_index = execution_path.len();
fork_stack.push(PastFork {
fork_index: execution_path.len(),
basic_block_index: execution_path.len(),
state: forked,
branch_count,
});
}
}
None
}
pub fn single_step(
&mut self,
instr: &Instruction,
info_factory: &mut InstructionInfoFactory,
) -> Option<ProgramState<I, D>> {
let mut flow_override = None;
match instr.mnemonic() {
Mnemonic::Mov | Mnemonic::Movzx => {
let _ = self.set_operand_value(instr, 0, self.get_operand_value(instr, 1));
}
Mnemonic::Movsx | Mnemonic::Movsxd => {
let sign_extended = self
.get_operand_value(instr, 1)
.map(|arg| util::reinterpret_signed(arg, util::op_size(instr, 0)) as u64);
let _ = self.set_operand_value(instr, 0, sign_extended);
}
Mnemonic::Xchg => self.handle_xchg(instr),
Mnemonic::Lea => {
let addr = self.virtual_address(instr, 1);
let _ = self.set_operand_value(instr, 0, addr);
}
Mnemonic::Add => {
let result = self
.get_operand_value(instr, 0)
.and_then(|lhs| Some(lhs.wrapping_add(self.get_operand_value(instr, 1)?)));
let _ = self.set_operand_value(instr, 0, result);
}
Mnemonic::Sub => {
let result = self
.get_operand_value(instr, 0)
.and_then(|lhs| Some(lhs.wrapping_sub(self.get_operand_value(instr, 1)?)));
let _ = self.set_operand_value(instr, 0, result);
}
Mnemonic::Push => {
if self.registers.rsp().is_some() {
let pushed_value = self
.get_operand_value(instr, 0)
.map(|v| util::reinterpret_signed(v, util::op_size(instr, 0)) as u64);
let rsp = self.registers.rsp_mut().as_mut().unwrap();
*rsp = rsp.wrapping_add_signed(instr.stack_pointer_increment() as i64);
self.memory.write_int(*rsp, pushed_value, 8);
}
}
Mnemonic::Pop => {
if let Some(rsp) = self.registers.rsp_mut() {
let popped_value = self.memory.read_int(*rsp, util::op_size(instr, 0));
*rsp = rsp.wrapping_add_signed(instr.stack_pointer_increment() as i64);
let _ = self.set_operand_value(instr, 0, popped_value);
}
}
Mnemonic::Call => {
flow_override = self.get_operand_value(instr, 0);
self.adjust_rsp(instr.stack_pointer_increment());
if let Some(rsp) = self.registers.rsp() {
self.memory.write_int(rsp, Some(instr.next_ip()), 8);
}
}
Mnemonic::Ret => {
if let Some(rsp) = self.registers.rsp() {
flow_override = self.memory.read_int(rsp, 8);
}
self.adjust_rsp(instr.stack_pointer_increment());
}
m if util::is_cmov(m) => {
let original_value = self.get_operand_value(instr, 0);
let potential_write = self.get_operand_value(instr, 1);
match (original_value, potential_write) {
(Some(_), Some(_)) => {
self.rip = Some(instr.next_ip());
let mut forked = self.clone();
let _ = forked.set_operand_value(instr, 0, potential_write);
return Some(forked);
}
(None, Some(_)) => {
let _ = self.set_operand_value(instr, 0, potential_write);
}
_ => {}
}
}
_ => self.handle_generic(instr, info_factory),
}
if flow_override.is_some() {
self.rip = flow_override;
return None;
}
match instr.flow_control() {
FlowControl::Next => self.rip = Some(instr.next_ip()),
FlowControl::UnconditionalBranch | FlowControl::IndirectBranch => {
self.rip = self.get_operand_value(instr, 0);
}
FlowControl::ConditionalBranch => {
self.rip = Some(instr.next_ip());
return Some(Self {
rip: Some(instr.near_branch_target()),
..self.clone()
});
}
_ => self.rip = None,
}
None
}
pub(crate) fn virtual_address_cb(&self, reg: Register) -> Option<u64> {
match reg {
Register::CS | Register::DS | Register::ES | Register::SS => Some(0),
_ if reg.is_gpr() => self.registers.read_gpr(reg),
_ => None,
}
}
pub fn virtual_address(&self, instr: &Instruction, op: u32) -> Option<u64> {
instr.virtual_address(op, 0, |reg, _, _| self.virtual_address_cb(reg))
}
pub fn get_operand_value(&self, instr: &Instruction, op: u32) -> Option<u64> {
match instr.op_kind(op) {
OpKind::Register => {
let reg = instr.op_register(op);
if !reg.is_gpr() {
return None;
}
self.registers.read_gpr(reg)
}
OpKind::Memory => {
let addr = self.virtual_address(instr, op)?;
self.memory.read_int(addr, instr.memory_size().size())
}
OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64 => {
Some(instr.near_branch_target())
}
_ => instr.try_immediate(op).ok(),
}
}
#[allow(clippy::result_unit_err)]
pub fn set_operand_value(
&mut self,
instr: &Instruction,
op: u32,
val: Option<u64>,
) -> Result<(), ()> {
match instr.op_kind(op) {
OpKind::Register => {
let reg = instr.op_register(op);
if !reg.is_gpr() {
return Err(());
}
self.registers.write_gpr(reg, val)
}
OpKind::Memory => {
let addr = self.virtual_address(instr, op).ok_or(())?;
self.memory.write_int(addr, val, instr.memory_size().size());
}
_ => unimplemented!(),
}
Ok(())
}
fn adjust_rsp(&mut self, increment: i32) {
if let Some(rsp) = self.registers.rsp_mut() {
*rsp = rsp.wrapping_add_signed(increment as i64)
}
}
fn handle_xchg(&mut self, instr: &Instruction) {
let mut to_swap = [(None, None); 2];
for (i, (addr, val)) in to_swap.iter_mut().enumerate() {
*addr = self.virtual_address(instr, i as u32);
*val = match instr.op_kind(i as u32) {
OpKind::Register => self.registers.read_gpr(instr.op_register(i as u32)),
OpKind::Memory => {
addr.and_then(|a| self.memory.read_int(a, instr.memory_size().size()))
}
_ => unreachable!(),
}
}
for (i, (addr, _)) in to_swap.iter().enumerate() {
let other_value = to_swap[1 - i].1;
match instr.op_kind(i as u32) {
OpKind::Register => {
self.registers.write_gpr(instr.op_register(i as u32), other_value)
}
OpKind::Memory => {
if let Some(a) = addr {
self.memory.write_int(*a, other_value, instr.memory_size().size())
}
}
_ => unreachable!(),
}
}
}
fn handle_generic(&mut self, instr: &Instruction, info_factory: &mut InstructionInfoFactory) {
let used_memory = info_factory.info(instr);
for mem in used_memory.used_memory() {
let addr = match mem.virtual_address(0, |reg, _, _| self.virtual_address_cb(reg)) {
Some(addr) => addr,
None => continue,
};
let access_size = mem.memory_size().size();
match mem.access() {
OpAccess::Write | OpAccess::ReadWrite => {
self.memory.invalidate(addr, access_size);
}
OpAccess::CondWrite | OpAccess::ReadCondWrite => {}
_ => {}
}
}
for reg in used_memory.used_registers() {
if reg.register() == Register::RSP
&& instr.is_stack_instruction()
&& !(0..instr.op_count()).any(|i| instr.op_register(i) == Register::RSP)
&& instr.memory_base() != Register::RSP
&& instr.memory_index() != Register::RSP
{
self.adjust_rsp(instr.stack_pointer_increment());
continue;
}
match reg.access() {
OpAccess::Write | OpAccess::ReadWrite if reg.register().is_gpr() => {
self.registers.write_gpr(reg.register(), None);
}
_ => {}
}
}
}
}
#[cfg(test)]
mod tests {
use iced_x86::code_asm::*;
use super::{MemoryStore, ProgramState, Registers, StepKind, image::WithBase};
#[test]
fn test_cmov_branching() {
let mut asm = CodeAssembler::new(64).unwrap();
asm.mov(ecx, 0x42).unwrap();
asm.mov(edx, 0x69).unwrap();
asm.cmove(ecx, edx).unwrap();
asm.add(ecx, 1).unwrap();
asm.cmove(edx, ecx).unwrap();
asm.sub(edx, 4).unwrap();
asm.ret().unwrap();
let code = asm.assemble(0).unwrap();
let image = WithBase::new(&code, 0);
let state = ProgramState {
rip: Some(0),
registers: Registers::default(),
memory: MemoryStore::new(image),
user_data: 1u64, };
let mut steps = Vec::default();
state.run(|step| -> StepKind<_, _> {
steps.push((
step.instruction.code(),
step.state.registers.rcx(),
step.state.registers.rdx(),
));
StepKind::SingleStep
});
const EXPECTED: &[(iced_x86::Code, Option<u64>, Option<u64>)] = &[
(iced_x86::Code::Mov_r32_imm32, None, None),
(iced_x86::Code::Mov_r32_imm32, Some(0x42), None),
(iced_x86::Code::Cmove_r32_rm32, Some(0x42), Some(0x69)),
(iced_x86::Code::Add_rm32_imm8, Some(0x69), Some(0x69)),
(iced_x86::Code::Cmove_r32_rm32, Some(0x6a), Some(0x69)),
(iced_x86::Code::Sub_rm32_imm8, Some(0x6a), Some(0x6a)),
(iced_x86::Code::Retnq, Some(0x6a), Some(0x66)),
(iced_x86::Code::Sub_rm32_imm8, Some(0x6a), Some(0x69)),
(iced_x86::Code::Retnq, Some(0x6a), Some(0x65)),
(iced_x86::Code::Add_rm32_imm8, Some(0x42), Some(0x69)),
(iced_x86::Code::Cmove_r32_rm32, Some(0x43), Some(0x69)),
(iced_x86::Code::Sub_rm32_imm8, Some(0x43), Some(0x43)),
(iced_x86::Code::Retnq, Some(0x43), Some(0x3F)),
(iced_x86::Code::Sub_rm32_imm8, Some(0x43), Some(0x69)),
(iced_x86::Code::Retnq, Some(0x43), Some(0x65)),
];
assert_eq!(&steps, EXPECTED)
}
}