From 265b2984b0fad7332680ffd5d44cc9d25f9ad457 Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Sun, 2 Feb 2025 22:08:16 -0500 Subject: Move instruction filters out of `basic_block_guid` and make them public Need to do this for exposing to FFI and things like render layers --- plugins/warp/src/lib.rs | 163 +++++++++++++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 65 deletions(-) (limited to 'plugins') diff --git a/plugins/warp/src/lib.rs b/plugins/warp/src/lib.rs index 62af0102..6d147bdc 100644 --- a/plugins/warp/src/lib.rs +++ b/plugins/warp/src/lib.rs @@ -6,7 +6,7 @@ use binaryninja::architecture::{ Architecture, ImplicitRegisterExtend, Register as BNRegister, RegisterInfo, }; use binaryninja::basic_block::BasicBlock as BNBasicBlock; -use binaryninja::binary_view::BinaryViewExt; +use binaryninja::binary_view::{BinaryView, BinaryViewExt}; use binaryninja::confidence::MAX_CONFIDENCE; use binaryninja::function::{Function as BNFunction, NativeBlock}; use binaryninja::low_level_il::expression::{ExpressionHandler, LowLevelILExpressionKind}; @@ -16,6 +16,7 @@ use binaryninja::low_level_il::instruction::{ }; use binaryninja::low_level_il::{LowLevelILRegisterKind, VisitorAction}; use binaryninja::rc::Ref as BNRef; +use std::ops::Range; use std::path::PathBuf; use warp::signature::basic_block::BasicBlockGUID; use warp::signature::function::constraints::FunctionConstraints; @@ -78,15 +79,18 @@ pub fn function_guid( func: &BNFunction, llil: &LowLevelILFunction, ) -> FunctionGUID { + // TODO: We might want to make this configurable, or otherwise _not_ retrieve from the view here. + let relocatable_regions = relocatable_regions(&func.view()); let basic_blocks = sorted_basic_blocks(func); let basic_block_guids = basic_blocks .iter() - .map(|bb| basic_block_guid(bb, llil)) + .map(|bb| basic_block_guid(&relocatable_regions, bb, llil)) .collect::>(); FunctionGUID::from_basic_blocks(&basic_block_guids) } pub fn basic_block_guid( + relocatable_regions: &[Range], basic_block: &BNBasicBlock, llil: &LowLevelILFunction, ) -> BasicBlockGUID { @@ -95,67 +99,6 @@ pub fn basic_block_guid( let arch = func.arch(); let max_instr_len = arch.max_instr_len(); - // NOPs and useless moves are blacklisted to allow for hot-patchable functions. - let is_blacklisted_instr = |instr: &LowLevelILInstruction| { - match instr.kind() { - LowLevelILInstructionKind::Nop(_) => true, - LowLevelILInstructionKind::SetReg(op) => { - match op.source_expr().kind() { - LowLevelILExpressionKind::Reg(source_op) - if op.dest_reg() == source_op.source_reg() => - { - match op.dest_reg() { - LowLevelILRegisterKind::Arch(r) => { - // If this register has no implicit extend then we can safely assume it's a NOP. - // Ex. on x86_64 we don't want to remove `mov edi, edi` as it will zero the upper 32 bits. - // Ex. on x86 we do want to remove `mov edi, edi` as it will not have a side effect like above. - matches!( - r.info().implicit_extend(), - ImplicitRegisterExtend::NoExtend - ) - } - LowLevelILRegisterKind::Temp(_) => false, - } - } - _ => false, - } - } - _ => false, - } - }; - - let is_variant_instr = |instr: &LowLevelILInstruction| { - let is_variant_expr = |expr: &LowLevelILExpressionKind| { - // TODO: Checking the section here is slow, we should gather all section ranges outside of this. - match expr { - LowLevelILExpressionKind::ConstPtr(op) - if !view.sections_at(op.value()).is_empty() => - { - // Constant Pointer must be in a section for it to be relocatable. - // NOTE: We cannot utilize segments here as there will be a zero based segment. - true - } - LowLevelILExpressionKind::ExternPtr(_) => true, - LowLevelILExpressionKind::Const(op) if !view.sections_at(op.value()).is_empty() => { - // Constant value must be in a section for it to be relocatable. - // NOTE: We cannot utilize segments here as there will be a zero based segment. - true - } - _ => false, - } - }; - - // Visit instruction expressions looking for variant expression, [VisitorAction::Halt] means variant. - instr.visit_tree(&mut |expr| { - if is_variant_expr(&expr.kind()) { - // Found a variant expression - VisitorAction::Halt - } else { - VisitorAction::Descend - } - }) == VisitorAction::Halt - }; - let basic_block_range = basic_block.start_index()..basic_block.end_index(); let mut basic_block_bytes = Vec::with_capacity(basic_block_range.count()); for instr_addr in basic_block.into_iter() { @@ -164,8 +107,8 @@ pub fn basic_block_guid( instr_bytes.truncate(instr_info.length); if let Some(instr_llil) = llil.instruction_at(instr_addr) { // If instruction is blacklisted don't include the bytes. - if !is_blacklisted_instr(&instr_llil) { - if is_variant_instr(&instr_llil) { + if !is_blacklisted_instruction(&instr_llil) { + if is_variant_instruction(relocatable_regions, &instr_llil) { // Found a variant instruction, mask off entire instruction. instr_bytes.fill(0); } @@ -179,6 +122,96 @@ pub fn basic_block_guid( BasicBlockGUID::from(basic_block_bytes.as_slice()) } +/// Is the instruction not included in the masked byte sequence? +/// +/// Blacklisted instructions will make an otherwise identical function GUID fail to match. +/// +/// Example: NOPs and useless moves are blacklisted to allow for hot-patchable functions. +pub fn is_blacklisted_instruction( + instr: &LowLevelILInstruction>, +) -> bool { + match instr.kind() { + LowLevelILInstructionKind::Nop(_) => true, + LowLevelILInstructionKind::SetReg(op) => { + match op.source_expr().kind() { + LowLevelILExpressionKind::Reg(source_op) + if op.dest_reg() == source_op.source_reg() => + { + match op.dest_reg() { + LowLevelILRegister::ArchReg(r) => { + // If this register has no implicit extend then we can safely assume it's a NOP. + // Ex. on x86_64 we don't want to remove `mov edi, edi` as it will zero the upper 32 bits. + // Ex. on x86 we do want to remove `mov edi, edi` as it will not have a side effect like above. + matches!(r.info().implicit_extend(), ImplicitRegisterExtend::NoExtend) + } + LowLevelILRegister::Temp(_) => false, + } + } + _ => false, + } + } + _ => false, + } +} + +pub fn is_variant_instruction( + relocatable_regions: &[Range], + instr: &LowLevelILInstruction>, +) -> bool { + let is_variant_expr = |expr: &LowLevelILExpressionKind>| { + match expr { + LowLevelILExpressionKind::ConstPtr(op) + if is_address_relocatable(relocatable_regions, op.value()) => + { + // Constant Pointer must be in a section for it to be relocatable. + // NOTE: We cannot utilize segments here as there will be a zero based segment. + true + } + LowLevelILExpressionKind::Const(op) + if is_address_relocatable(relocatable_regions, op.value()) => + { + // Constant value must be in a section for it to be relocatable. + // NOTE: We cannot utilize segments here as there will be a zero based segment. + true + } + LowLevelILExpressionKind::ExternPtr(_) => true, + _ => false, + } + }; + + // Visit instruction expressions looking for variant expression, [VisitorAction::Halt] means variant. + instr.visit_tree(&mut |expr| { + if is_variant_expr(&expr.kind()) { + // Found a variant expression. + VisitorAction::Halt + } else { + // Keep looking for a variant expression. + VisitorAction::Descend + } + }) == VisitorAction::Halt +} + +/// If the address is inside any of the given ranges we will assume the address to be relocatable. +pub fn is_address_relocatable(relocatable_regions: &[Range], address: u64) -> bool { + relocatable_regions + .iter() + .any(|range| range.contains(&address)) +} + +// TODO: This might need to be configurable, in that case we better remove this function. +/// Get the relocatable regions of the view. +/// +/// Currently, this is all the sections, however this might be refined later. +pub fn relocatable_regions(view: &BinaryView) -> Vec> { + view.sections() + .iter() + .map(|s| Range { + start: s.start(), + end: s.end(), + }) + .collect() +} + #[cfg(test)] mod tests { use crate::cache::cached_function_guid; -- cgit v1.3.1