use std::io::{Cursor, Seek, SeekFrom, Write};
use iced_x86::{
BlockEncoder, BlockEncoderOptions, Code, Encoder, FlowControl, Instruction, InstructionBlock,
MemoryOperand, Mnemonic, Register,
};
use crate::HookError;
struct JmpOffsetInfo {
disp_offset: u64,
relative_start_offset: u64,
dest_addr: u64,
}
struct JmpRelocationInfo {
offset_of_addr_to_relocate: u64,
relative_start_offset: u64,
is_jmp_to_new_insts: bool,
dest_addr: u64, }
struct NewInstInfo {
offset: u64,
reloc_info: Option<JmpRelocationInfo>,
}
pub(super) fn move_code_to_addr(
ori_insts: &Vec<Instruction>,
dest_addr: u64,
) -> Result<Vec<u8>, HookError> {
if ori_insts[0].ip().abs_diff(dest_addr) < 0x7fff_f000 {
let block = InstructionBlock::new(ori_insts, dest_addr);
let encoded = BlockEncoder::encode(64, block, BlockEncoderOptions::NONE)
.map_err(|_| HookError::MoveCode)?;
Ok(encoded.code_buffer)
} else {
let mut new_inst_info: Vec<NewInstInfo> = vec![];
let mut buf = Cursor::new(Vec::<u8>::with_capacity(100));
for inst in ori_insts {
let cur_pos = buf.stream_position().unwrap();
let off_info = relocate_inst_addr(inst, dest_addr + cur_pos, &mut buf)?;
let reloc_info = if let Some(off_info) = off_info {
let last_inst = ori_insts.last().unwrap();
let (is_jmp_to_new_insts, dest_addr) = if off_info.dest_addr >= ori_insts[0].ip()
&& off_info.dest_addr < last_inst.ip() + last_inst.len() as u64
{
let idx = ori_insts
.iter()
.position(|i| i.ip() == off_info.dest_addr)
.ok_or(HookError::MovingCodeNotSupported)?;
(true, idx as u64)
} else {
(false, off_info.dest_addr)
};
Some(JmpRelocationInfo {
offset_of_addr_to_relocate: cur_pos + off_info.disp_offset,
relative_start_offset: cur_pos + off_info.relative_start_offset,
is_jmp_to_new_insts,
dest_addr,
})
} else {
None
};
new_inst_info.push(NewInstInfo {
offset: cur_pos,
reloc_info,
});
}
let need_relocating_cnt = new_inst_info
.iter()
.filter(|i| i.reloc_info.is_some())
.count();
if need_relocating_cnt != 0 {
let cur_addr = dest_addr + buf.stream_position().unwrap();
let padding_cnt = ((cur_addr + 5 + 7) & !7) - (cur_addr + 5);
let jmp_over_len = padding_cnt + need_relocating_cnt as u64 * 8;
buf.write(&[0xe9])?;
buf.write(&(jmp_over_len as u32).to_le_bytes())?;
buf.write(&vec![0xCCu8; padding_cnt as usize])?;
let mut table_offset = buf.stream_position().unwrap();
for new_inst in &new_inst_info {
if let Some(reloc_info) = &new_inst.reloc_info {
buf.seek(SeekFrom::Start(reloc_info.offset_of_addr_to_relocate))
.unwrap();
let disp = (table_offset - reloc_info.relative_start_offset) as u32;
buf.write(&disp.to_le_bytes()).unwrap();
buf.seek(SeekFrom::Start(table_offset)).unwrap();
if reloc_info.is_jmp_to_new_insts {
let real_dest_addr =
dest_addr + new_inst_info[reloc_info.dest_addr as usize].offset;
buf.write(&real_dest_addr.to_le_bytes())?;
} else {
buf.write(&reloc_info.dest_addr.to_le_bytes())?;
}
table_offset += 8;
}
}
}
Ok(buf.into_inner())
}
}
fn relocate_inst_addr<T: Write>(
inst: &Instruction,
dest_addr: u64,
buf: &mut T,
) -> Result<Option<JmpOffsetInfo>, HookError> {
let mut encoder = Encoder::new(64);
match inst.flow_control() {
FlowControl::UnconditionalBranch => {
buf.write(&[0xff, 0x25, 0, 0, 0, 0])?;
Ok(Some(JmpOffsetInfo {
disp_offset: 2,
relative_start_offset: 6,
dest_addr: inst.near_branch_target(),
}))
}
FlowControl::IndirectBranch if inst.is_ip_rel_memory_operand() => {
buf.write(&[0x48, 0x89, 0x44, 0x24, 0xf0, 0x48, 0xb8])?;
buf.write(&inst.ip_rel_memory_address().to_le_bytes())?;
buf.write(&[0xff, 0x30, 0x48, 0x8b, 0x44, 0x24, 0xf8, 0xc3])?;
Ok(None)
}
FlowControl::ConditionalBranch if inst.is_jcc_short_or_near() => {
let mut new_inst = inst.clone();
new_inst.negate_condition_code();
new_inst.set_near_branch64(dest_addr + 8);
new_inst.as_short_branch();
encoder
.encode(&new_inst, dest_addr)
.map_err(|_| HookError::MoveCode)?;
buf.write(&encoder.take_buffer())?;
buf.write(&[0xff, 0x25, 0, 0, 0, 0])?;
Ok(Some(JmpOffsetInfo {
disp_offset: 4,
relative_start_offset: 8,
dest_addr: inst.near_branch_target(),
}))
}
FlowControl::ConditionalBranch
if inst.is_jcx_short() || inst.is_loop() || inst.is_loopcc() =>
{
let mut new_inst = inst.clone();
new_inst.set_near_branch64(dest_addr + 4);
encoder
.encode(&new_inst, dest_addr)
.map_err(|_| HookError::MoveCode)?;
buf.write(&encoder.take_buffer())?;
buf.write(&[0xeb, 0x06, 0xff, 0x25, 0, 0, 0, 0])?;
Ok(Some(JmpOffsetInfo {
disp_offset: 6,
relative_start_offset: 10,
dest_addr: inst.near_branch_target(),
}))
}
FlowControl::Call if inst.is_call_near() || inst.is_call_far() => {
buf.write(&[0xff, 0x15, 0, 0, 0, 0])?;
Ok(Some(JmpOffsetInfo {
disp_offset: 2,
relative_start_offset: 6,
dest_addr: inst.near_branch_target(),
}))
}
FlowControl::IndirectCall if inst.is_ip_rel_memory_operand() => {
buf.write(&[0x48, 0x89, 0x44, 0x24, 0xe8, 0x48, 0xb8])?;
buf.write(&inst.ip_rel_memory_address().to_le_bytes())?;
let retn_addr = dest_addr + 0x24;
buf.write(&[0x68])?;
buf.write(&((retn_addr & 0xffffffff) as u32).to_le_bytes())?;
buf.write(&[0xc7, 0x44, 0x24, 0x04])?;
buf.write(&((retn_addr >> 32) as u32).to_le_bytes())?;
buf.write(&[0xff, 0x30, 0x48, 0x8b, 0x44, 0x24, 0xf8, 0xc3])?;
Ok(None)
}
_ if inst.is_ip_rel_memory_operand() => {
if let Register::RSP = inst.op0_register() {
return Err(HookError::MovingCodeNotSupported);
}
let encoded = relocate_ip_rel_memory_inst(inst, dest_addr);
buf.write(&encoded)?;
Ok(None)
}
_ => {
encoder.encode(inst, dest_addr).unwrap();
buf.write(&encoder.take_buffer())?;
Ok(None)
}
}
}
fn relocate_ip_rel_memory_inst(inst: &Instruction, dest_addr: u64) -> Vec<u8> {
if let Mnemonic::Lea = inst.mnemonic() {
let inst = Instruction::with2(
Code::Mov_r64_imm64,
inst.op0_register(),
inst.ip_rel_memory_address(),
)
.unwrap();
let mut encoder = Encoder::new(64);
encoder.encode(&inst, dest_addr).unwrap();
encoder.take_buffer()
} else {
let middle_register = if inst_not_use_rbx(inst) {
Register::RBX
} else if inst_not_use_r8(inst) {
Register::R8
} else {
Register::R9 };
let new_inst1 = Instruction::with2(
Code::Mov_rm64_r64,
MemoryOperand::with_base_displ(Register::RSP, -16),
middle_register,
)
.unwrap();
let new_inst2 = Instruction::with2(
Code::Mov_r64_imm64,
middle_register,
inst.ip_rel_memory_address(),
)
.unwrap();
let mut new_inst3 = inst.clone();
new_inst3.set_memory_base(middle_register);
new_inst3.set_memory_displacement32(0);
new_inst3.set_memory_displ_size(0);
let stack_inc = inst.stack_pointer_increment() as i64;
let new_inst4 = Instruction::with2(
Code::Mov_r64_rm64,
middle_register,
MemoryOperand::with_base_displ(Register::RSP, -16 - stack_inc),
)
.unwrap();
let new_insts = [new_inst1, new_inst2, new_inst3, new_inst4];
let block = InstructionBlock::new(&new_insts, dest_addr);
let encoded = BlockEncoder::encode(64, block, BlockEncoderOptions::NONE).unwrap();
encoded.code_buffer
}
}
fn inst_not_use_rbx(inst: &Instruction) -> bool {
(0..inst.op_count()).map(|i| inst.op_register(i)).all(|r| {
!matches!(r, Register::BL)
&& !matches!(r, Register::BH)
&& !matches!(r, Register::BX)
&& !matches!(r, Register::EBX)
&& !matches!(r, Register::RBX)
})
}
fn inst_not_use_r8(inst: &Instruction) -> bool {
(0..inst.op_count()).map(|i| inst.op_register(i)).all(|r| {
!matches!(r, Register::R8D)
&& !matches!(r, Register::R8L)
&& !matches!(r, Register::R8W)
&& !matches!(r, Register::R8)
})
}