use super::common::*;
use iced_x86::{Decoder, DecoderOptions, Encoder, Instruction, InstructionInfoFactory, Register, FlowControl, ConditionCode};
use nix::{
libc::*,
sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal},
};
use std::{cell::Cell, ffi::c_void, sync::MutexGuard};
#[derive(Clone, Copy, Default)]
struct InstrPosition {
orig_addr: usize,
trunk_addr: usize,
old_len: u8,
new_len: u8,
replace_reg: u8,
replace_data: i64,
}
thread_local! {
static G_CURRENT_REPLACE: Cell<InstrPosition> = const { Cell::new(InstrPosition{orig_addr: 0, trunk_addr: 0,old_len:0, new_len:0, replace_reg: Register::None as u8, replace_data: 0}) };
}
fn disassemble_instruction(ins: &[u8], addr: u64) -> Option<Instruction> {
let mut decoder = Decoder::with_ip(64, ins, addr, DecoderOptions::NONE);
let mut instruction = Instruction::default();
if decoder.can_decode() {
decoder.decode_out(&mut instruction);
Some(instruction)
} else {
None
}
}
extern "C" fn handle_trap_signal(_: i32, _: *mut siginfo_t, ucontext: *mut c_void) {
let ctx = ucontext as *mut ucontext_t;
let rip = unsafe { (*ctx).uc_mcontext.gregs[REG_RIP as usize] as usize };
let orig_addr = rip - 1;
fn set_ip_register(ctx: *mut ucontext_t, new_func_addr: usize) {
unsafe { (*ctx).uc_mcontext.gregs[REG_RIP as usize] = new_func_addr as i64 };
}
fn get_trunk_addr(old_func: usize) -> usize {
*G_TRUNK_ADDR_TABLE.lock().unwrap().get(&old_func).unwrap()
}
if is_current_thread_mocked(orig_addr) {
set_ip_register(ctx, get_new_func_addr(orig_addr));
} else {
let trunk_addr = get_trunk_addr(orig_addr);
set_ip_register(ctx, trunk_addr + 3);
}
}
fn save_old_instruction(ins: &Instruction, current_position: MutexGuard<Cell<usize>>) {
let old_len = ins.len();
let mut replace_reg = Register::None;
let mut new_instruction = *ins;
if ins.is_ip_rel_memory_operand() {
replace_reg = get_replace_register(ins);
new_instruction = make_new_instruction(*ins, replace_reg);
}
let next_ip = ins.ip() as usize + old_len;
let flow = ins.flow_control();
if matches!(flow, FlowControl::Call | FlowControl::UnconditionalBranch) {
let target = ins.near_branch_target() as usize;
let mut body: Vec<u8> = Vec::new();
let mut tail_needed = false;
if flow == FlowControl::Call {
body.extend_from_slice(&[0xFF, 0x15, 0x00, 0x00, 0x00, 0x00]);
body.extend_from_slice(&[0xEB, 0x08]);
body.extend_from_slice(&(target as u64).to_le_bytes());
tail_needed = true;
} else {
body.extend_from_slice(&[0xFF, 0x25, 0x00, 0x00, 0x00, 0x00]);
body.extend_from_slice(&(target as u64).to_le_bytes());
tail_needed = false;
}
let body_len = body.len();
let tail_len = if tail_needed { 6 + 8 } else { 0 }; let total_len = body_len + tail_len;
let mut pos = current_position.get();
if pos + 3 + total_len - *G_CODE_AREA.lock().unwrap().get_mut() >= get_code_area_size() {
panic!("Code area overflow");
}
write_memory(pos, &[old_len as u8]);
pos += 1;
write_memory(pos, &[body_len as u8]);
pos += 1;
write_memory(pos, &[Register::None as u8]);
pos += 1;
write_memory(pos, &body);
pos += body_len;
if tail_needed {
let mut tail: Vec<u8> = Vec::new();
tail.extend_from_slice(&[0xFF, 0x25, 0x00, 0x00, 0x00, 0x00]); tail.extend_from_slice(&(next_ip as u64).to_le_bytes());
write_memory(pos, &tail);
pos += tail.len();
}
current_position.set(pos);
return;
}
if flow == FlowControl::ConditionalBranch {
let target = ins.near_branch_target() as usize;
let inv_jcc_opcode: u8 = match ins.condition_code() {
ConditionCode::o => 0x71, ConditionCode::no => 0x70, ConditionCode::b => 0x73, ConditionCode::ae => 0x72, ConditionCode::e => 0x75, ConditionCode::ne => 0x74, ConditionCode::be => 0x77, ConditionCode::a => 0x76, ConditionCode::s => 0x79, ConditionCode::ns => 0x78, ConditionCode::p => 0x7B, ConditionCode::np => 0x7A, ConditionCode::l => 0x7D, ConditionCode::ge => 0x7C, ConditionCode::le => 0x7F, ConditionCode::g => 0x7E, _ => {
0
}
};
if inv_jcc_opcode != 0 {
let mut body: Vec<u8> = Vec::new();
body.push(inv_jcc_opcode);
body.push(14u8);
body.extend_from_slice(&[0xFF, 0x25, 0x00, 0x00, 0x00, 0x00]);
body.extend_from_slice(&(target as u64).to_le_bytes());
let body_len = body.len();
let tail_len = 6 + 8; let total_len = body_len + tail_len;
let mut pos = current_position.get();
if pos + 3 + total_len - *G_CODE_AREA.lock().unwrap().get_mut() >= get_code_area_size() {
panic!("Code area overflow");
}
write_memory(pos, &[old_len as u8]);
pos += 1;
write_memory(pos, &[body_len as u8]);
pos += 1;
write_memory(pos, &[Register::None as u8]);
pos += 1;
write_memory(pos, &body);
pos += body_len;
let mut tail: Vec<u8> = Vec::new();
tail.extend_from_slice(&[0xFF, 0x25, 0x00, 0x00, 0x00, 0x00]); tail.extend_from_slice(&(next_ip as u64).to_le_bytes());
write_memory(pos, &tail);
pos += tail.len();
current_position.set(pos);
return;
}
}
let mut encoder = Encoder::new(64);
match encoder.encode(&new_instruction, ins.ip()) {
Ok(_enc_len) => {
let mut body: Vec<u8> = Vec::new();
if replace_reg != Register::None {
match replace_reg {
Register::RAX => body.push(0x50),
Register::RCX => body.push(0x51),
Register::RDX => body.push(0x52),
Register::RBX => body.push(0x53),
Register::RSP => body.push(0x54),
Register::RBP => body.push(0x55),
Register::RSI => body.push(0x56),
Register::RDI => body.push(0x57),
Register::R8 => body.extend_from_slice(&[0x41, 0x50]),
Register::R9 => body.extend_from_slice(&[0x41, 0x51]),
Register::R10 => body.extend_from_slice(&[0x41, 0x52]),
Register::R11 => body.extend_from_slice(&[0x41, 0x53]),
Register::R12 => body.extend_from_slice(&[0x41, 0x54]),
Register::R13 => body.extend_from_slice(&[0x41, 0x55]),
Register::R14 => body.extend_from_slice(&[0x41, 0x56]),
Register::R15 => body.extend_from_slice(&[0x41, 0x57]),
_ => {}
}
let imm_bytes = (next_ip as u64).to_le_bytes();
match replace_reg {
Register::RAX => { body.extend_from_slice(&[0x48, 0xB8]); body.extend_from_slice(&imm_bytes); }
Register::RCX => { body.extend_from_slice(&[0x48, 0xB9]); body.extend_from_slice(&imm_bytes); }
Register::RDX => { body.extend_from_slice(&[0x48, 0xBA]); body.extend_from_slice(&imm_bytes); }
Register::RBX => { body.extend_from_slice(&[0x48, 0xBB]); body.extend_from_slice(&imm_bytes); }
Register::RSP => { body.extend_from_slice(&[0x48, 0xBC]); body.extend_from_slice(&imm_bytes); }
Register::RBP => { body.extend_from_slice(&[0x48, 0xBD]); body.extend_from_slice(&imm_bytes); }
Register::RSI => { body.extend_from_slice(&[0x48, 0xBE]); body.extend_from_slice(&imm_bytes); }
Register::RDI => { body.extend_from_slice(&[0x48, 0xBF]); body.extend_from_slice(&imm_bytes); }
Register::R8 => { body.extend_from_slice(&[0x49, 0xB8]); body.extend_from_slice(&imm_bytes); }
Register::R9 => { body.extend_from_slice(&[0x49, 0xB9]); body.extend_from_slice(&imm_bytes); }
Register::R10 => { body.extend_from_slice(&[0x49, 0xBA]); body.extend_from_slice(&imm_bytes); }
Register::R11 => { body.extend_from_slice(&[0x49, 0xBB]); body.extend_from_slice(&imm_bytes); }
Register::R12 => { body.extend_from_slice(&[0x49, 0xBC]); body.extend_from_slice(&imm_bytes); }
Register::R13 => { body.extend_from_slice(&[0x49, 0xBD]); body.extend_from_slice(&imm_bytes); }
Register::R14 => { body.extend_from_slice(&[0x49, 0xBE]); body.extend_from_slice(&imm_bytes); }
Register::R15 => { body.extend_from_slice(&[0x49, 0xBF]); body.extend_from_slice(&imm_bytes); }
_ => {}
}
}
let inst_bytes = encoder.take_buffer();
body.extend_from_slice(&inst_bytes);
if replace_reg != Register::None {
match replace_reg {
Register::RAX => body.push(0x58),
Register::RCX => body.push(0x59),
Register::RDX => body.push(0x5A),
Register::RBX => body.push(0x5B),
Register::RSP => body.push(0x5C),
Register::RBP => body.push(0x5D),
Register::RSI => body.push(0x5E),
Register::RDI => body.push(0x5F),
Register::R8 => body.extend_from_slice(&[0x41, 0x58]),
Register::R9 => body.extend_from_slice(&[0x41, 0x59]),
Register::R10 => body.extend_from_slice(&[0x41, 0x5A]),
Register::R11 => body.extend_from_slice(&[0x41, 0x5B]),
Register::R12 => body.extend_from_slice(&[0x41, 0x5C]),
Register::R13 => body.extend_from_slice(&[0x41, 0x5D]),
Register::R14 => body.extend_from_slice(&[0x41, 0x5E]),
Register::R15 => body.extend_from_slice(&[0x41, 0x5F]),
_ => {}
}
}
let body_len = body.len();
let mut tail: Vec<u8> = Vec::new();
tail.extend_from_slice(&[0xFF, 0x25, 0x00, 0x00, 0x00, 0x00]); tail.extend_from_slice(&(next_ip as u64).to_le_bytes());
let total_len = body_len + tail.len();
let mut pos = current_position.get();
if pos + 3 + total_len - *G_CODE_AREA.lock().unwrap().get_mut() >= get_code_area_size() {
panic!("Code area overflow");
}
write_memory(pos, &[old_len as u8]);
pos += 1;
write_memory(pos, &[body_len as u8]);
pos += 1;
write_memory(pos, &[replace_reg as u8]);
pos += 1;
write_memory(pos, &body);
pos += body_len;
write_memory(pos, &tail);
pos += tail.len();
current_position.set(pos);
}
Err(e) => {
println!("{}", e);
panic!("Failed to encode instruction block");
}
}
}
fn make_new_instruction(ins: Instruction, reg: Register) -> Instruction {
let mut bak_ins = ins;
if bak_ins.memory_base().is_ip() {
bak_ins.set_memory_base(reg);
bak_ins.set_memory_displacement64(
ins.memory_displacement64().overflowing_sub(ins.next_ip()).0,
);
}
bak_ins
}
fn get_replace_register(ins: &Instruction) -> Register {
let candidates = [
Register::RAX,
Register::RCX,
Register::RDX,
Register::RSI,
Register::RDI,
Register::R8,
Register::R9,
Register::R10,
Register::R11,
];
let mut info_factory = InstructionInfoFactory::new();
let info = info_factory.info(ins);
let used: Vec<Register> = info.used_registers().iter().map(|u| u.register()).collect();
*candidates
.iter()
.find(|&&r| !used.contains(&r))
.expect("No available caller-saved register to use for RIP-relative rewrite")
}
fn write_memory(addr: usize, data: &[u8]) {
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr(), addr as *mut u8, data.len());
}
}
fn init_mock() {
G_INIT_FLAG.lock().unwrap().get_or_init(|| {
setup_trap_handler();
alloc_code_area();
});
}
fn setup_trap_handler() {
if let Err(err) = unsafe {
sigaction(
Signal::SIGTRAP,
&SigAction::new(
SigHandler::SigAction(handle_trap_signal),
SaFlags::SA_SIGINFO | SaFlags::SA_ONSTACK,
SigSet::empty(),
),
)
} {
panic!("Failed to set signal handler: {:?}", err);
}
}
impl Mocker {
pub fn mock(old_func: usize, new_func: usize) -> Mocker {
init_mock();
{
let mut addr_table = G_TRUNK_ADDR_TABLE.lock().unwrap();
if addr_table.get(&old_func).is_none() {
let ins_mem = read_memory(old_func, G_REPLACE_LEN).clone();
if let Some(ins) = disassemble_instruction(&ins_mem, old_func as u64) {
let current_position = G_CURRENT_POSITION.lock().unwrap();
addr_table.insert(old_func, current_position.get());
save_old_instruction(&ins, current_position);
set_mem_writable(old_func, 1);
write_memory(old_func, [0xcc].as_slice());
set_mem_rx(old_func, 1);
} else {
panic!("Failed to disassemble instruction at 0x{:x}", old_func);
}
}
}
G_THREAD_REPLACE_TABLE.with(|x| {
let mut x = x.borrow_mut();
if let Some(v) = x.get_mut(&old_func) {
v.push(new_func);
} else {
x.insert(old_func, vec![new_func]);
}
});
Mocker { old_func, new_func }
}
}