From 429c0c21c4232ff433074c7f5745f9a1ab09563f Mon Sep 17 00:00:00 2001 From: unknowntrojan <14975032+unknowntrojan@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:24:21 +0200 Subject: initial commit --- .gitignore | 3 + Cargo.toml | 18 ++ src/lib.rs | 685 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 706 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b89935 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +/.vscode \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..17131e5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "binja_sigmaker" +version = "0.1.0" +authors = ["unknowntrojan"] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +binaryninja = { git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev" } +iced-x86 = "1.17.0" + +log = "0.4.17" + +findpattern = { git = "https://github.com/unknowntrojan/findpattern", features = ["parallel"] } + +clipboard = "0.5.0" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..06b7f7a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,685 @@ +#![feature(is_some_and, let_chains, core_intrinsics, iter_array_chunks)] +use std::ffi::CString; +use std::ops::Range; +use std::time::SystemTime; + +use binaryninja::architecture::Architecture; + +use binaryninja::binaryninjacore_sys::{ + BNBinaryView, BNCreateSettings, BNFreeRelocationRanges, BNFreeSettings, BNGetRelocationRanges, + BNSettings, BNSettingsGetBool, BNSettingsGetUInt64, BNSettingsRegisterGroup, + BNSettingsRegisterSetting, +}; +use binaryninja::binaryview::{BinaryView, BinaryViewBase, BinaryViewExt}; +use binaryninja::command::{self, AddressCommand, Command}; +use clipboard::ClipboardProvider; +use findpattern::{OwnedPattern, Pattern}; +use iced_x86::Code::{DeclareByte, DeclareDword, DeclareQword, DeclareWord}; +use iced_x86::{ConstantOffsets, FlowControl, Formatter, Instruction, NasmFormatter, OpKind}; + +struct RustSigMakerCommand; +struct IDASigMakerCommand; +struct CStrSigMakerCommand; +struct SigFinderCommand; + +struct RustPattern<'a>(Pattern<'a>); +struct IDAPattern<'a>(Pattern<'a>); +struct CStrPattern<'a>(Pattern<'a>); + +const MAX_INSTRUCTION_LENGTH: usize = 15; + +impl<'a> core::fmt::Display for RustPattern<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (i, x) in self.0.iter().enumerate() { + match x { + Some(x) => write!(f, "{:#04X}", x)?, + None => write!(f, "_")?, + } + + if i + 1 != self.0.len() { + write!(f, ", ")?; + } + } + + Ok(()) + } +} + +impl<'a> core::fmt::Display for IDAPattern<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (i, x) in self.0.iter().enumerate() { + match x { + Some(x) => write!(f, "{:02X}", x)?, + None => write!(f, "??")?, + } + + if i + 1 != self.0.len() { + write!(f, " ")?; + } + } + + Ok(()) + } +} + +impl<'a> core::fmt::Display for CStrPattern<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "\"")?; + + for x in self.0.iter() { + match x { + Some(x) => write!(f, "\\x{:02X}", x)?, + None => write!(f, "?")?, + } + } + + write!(f, "\" \"")?; + + for x in self.0.iter() { + match x { + Some(_) => write!(f, "x")?, + None => write!(f, "?")?, + } + } + + write!(f, "\"")?; + + Ok(()) + } +} + +fn get_relocation_ranges(bv: &BinaryView) -> Vec> { + let mut count = 0usize; + let ptr = unsafe { + BNGetRelocationRanges( + *std::mem::transmute::<_, *mut *mut BNBinaryView>(bv), + &mut count as *mut usize, + ) + }; + + let ranges = unsafe { std::slice::from_raw_parts(ptr, count) }; + + let ret = ranges + .iter() + .map(|range| Range { + start: range.start, + end: range.end, + }) + .collect::>(); + + unsafe { BNFreeRelocationRanges(ptr) }; + + ret +} +fn find_patterns(bv: &BinaryView, pattern: Pattern) -> Vec { + if pattern.len() == 0 { + return Vec::new(); + } + + bv.segments() + .into_iter() + .filter(|segment| segment.contains_code()) + .map(|segment| { + let range = segment.address_range(); + let len = range.end.checked_sub(range.start); + + let Some(len) = len else { + return Vec::new(); + }; + + if len == 0 { + return Vec::new(); + } + + let mut data = vec![0u8; len as usize]; + + bv.read(&mut data, range.start); + + findpattern::find_patterns_par(&data, &pattern) + .into_iter() + .map(|x| x + range.start as usize) + .collect() + }) + .flatten() + .collect() +} + +fn get_instruction_pattern( + bv: &BinaryView, + start_addr: usize, + instr: &Instruction, + offsets: &ConstantOffsets, + buf: &[u8], + relocations: &[Range], + include_operands: bool, +) -> Option { + let mut pattern = buf.into_iter().map(|x| Some(*x)).collect::(); + + #[allow(unused_parens)] + if instr.is_invalid() + || matches!( + instr.code(), + (DeclareByte | DeclareWord | DeclareDword | DeclareQword) + ) + { + log::warn!("invalid instruction encountered!"); + return None; + } + + #[allow(unused_parens)] + let is_branch = matches!( + instr.flow_control(), + (FlowControl::Call + | FlowControl::ConditionalBranch + | FlowControl::IndirectBranch + | FlowControl::IndirectCall + | FlowControl::UnconditionalBranch) + ); + + if offsets.has_displacement() { + for x in offsets.displacement_offset() + ..offsets.displacement_offset() + offsets.displacement_size() + { + pattern[x] = None; + } + } + + if !include_operands && offsets.has_immediate() { + let branch_target = instr + .op_kinds() + .filter_map(|kind| match kind { + OpKind::FarBranch16 => Some(instr.far_branch16() as u64 + bv.start() - 0x10000000), + OpKind::FarBranch32 => Some(instr.far_branch32() as u64 + bv.start() - 0x10000000), + OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64 => { + Some(instr.near_branch_target()) + } + _ => None, + }) + .nth(0); + + if is_branch && branch_target.is_some_and(|branch_target| bv.offset_valid(branch_target)) { + for x in + offsets.immediate_offset()..offsets.immediate_offset() + offsets.immediate_size() + { + pattern[x] = None; + } + } + } + + if !include_operands && offsets.has_immediate2() { + let branch_target = instr + .op_kinds() + .filter_map(|kind| match kind { + OpKind::FarBranch16 => Some(instr.far_branch16() as u64 + bv.start() - 0x10000000), + OpKind::FarBranch32 => Some(instr.far_branch32() as u64 + bv.start() - 0x10000000), + OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64 => { + Some(instr.near_branch_target()) + } + _ => None, + }) + .nth(0); + + if is_branch && branch_target.is_some_and(|branch_target| bv.offset_valid(branch_target)) { + for x in + offsets.immediate_offset2()..offsets.immediate_offset2() + offsets.immediate_size2() + { + pattern[x] = None; + } + } + } + + for relocation in relocations { + if (start_addr..(start_addr + instr.len())).contains(&(relocation.start as usize)) { + let start_offset = relocation.start as usize - start_addr; + let end_offset = relocation.end as usize - start_addr; + for x in start_offset..end_offset { + pattern[x] = None; + } + } + } + + Some(pattern) +} + +fn is_pattern_unique(bv: &BinaryView, pattern: Pattern) -> bool { + find_patterns(bv, pattern).len() == 1 +} + +fn create_pattern_internal( + bv: &BinaryView, + addr: usize, + include_operands: bool, +) -> Option { + log::info!("creating pattern for address {:#04X}", addr); + let time = SystemTime::now(); + + let mut formatter = NasmFormatter::new(); + formatter.options_mut().set_rip_relative_addresses(true); + + let relocations = get_relocation_ranges(bv); + + let mut current_offset = 0usize; + let mut current_buffer = vec![0u8; MAX_INSTRUCTION_LENGTH]; + let mut current_pattern = OwnedPattern::default(); + let mut pattern_unique = false; + let max_size = get_maximum_signature_size(bv) as usize; + + while !pattern_unique { + if current_offset >= max_size { + log::warn!("pattern isn't unique even at maximum size. aborting."); + return None; + } + + let Some(start_segment) = bv.segment_at(addr as u64) else { + log::warn!("unable to query instruction segment"); + return None; + }; + + let Some(end_segment) = bv.segment_at((addr + current_offset) as u64) else { + log::warn!("unable to query instruction segment"); + return None; + }; + + if !start_segment.contains_code() + || !start_segment.readable() + || !start_segment.executable() + || !start_segment + .address_range() + .contains(&((addr + current_offset) as u64)) + || !start_segment + .address_range() + .contains(&((addr + current_offset + MAX_INSTRUCTION_LENGTH) as u64)) + || start_segment.address_range().start != end_segment.address_range().start + { + log::warn!("pattern is not unique yet, but continuing would access invalid memory."); + return None; + } + + current_buffer.copy_from_slice(&[0u8; MAX_INSTRUCTION_LENGTH]); + bv.read(&mut current_buffer, (addr + current_offset) as u64); + + let mut decoder = iced_x86::Decoder::new( + if let Some(arch) = bv.default_arch() { + (arch.address_size() * 8) as u32 + } else { + 64 + }, + ¤t_buffer, + 0, + ); + decoder.set_ip((addr + current_offset) as u64); + + let instr = decoder.decode(); + let offsets = decoder.get_constant_offsets(&instr); + let instr_bytes = ¤t_buffer[0..instr.len()]; + + let mut instr_string = String::new(); + formatter.format(&instr, &mut instr_string); + + let Some(instr_pattern) = get_instruction_pattern(bv, addr + current_offset, &instr, &offsets, instr_bytes, &relocations, include_operands) else { + log::warn!("unable to get instruction pattern. instruction misaligned?"); + return None; + }; + + #[cfg(debug_assertions)] + log::info!("{}: {}", instr_string, RustPattern(&instr_pattern)); + + current_pattern.extend(&instr_pattern); + + current_offset += instr.len(); + pattern_unique = is_pattern_unique(bv, ¤t_pattern); + } + + while let Some(x) = current_pattern.last() && x.is_none() { + current_pattern.pop(); + } + + log::info!( + "found pattern in {}ms", + SystemTime::now().duration_since(time).unwrap().as_millis() + ); + + Some(current_pattern) +} + +fn create_pattern(bv: &BinaryView, addr: usize) -> Option { + let include_operands = get_include_operands(bv); + let pattern = create_pattern_internal(bv, addr, include_operands); + + if !include_operands && pattern.is_none() { + log::warn!("unable to find a unique pattern that didn't include operands. trying again with operands!"); + create_pattern_internal(bv, addr, true) + } else { + pattern + } +} + +fn is_valid(bv: &BinaryView, range: Range) -> bool { + // range is nonzero + if bv.segment_at(range.start).is_some_and(|x| { + x.contains_code() + && x.address_range().contains(&range.start) + && x.address_range().contains(&range.end) + }) && bv.segment_at(range.end).is_some_and(|x| { + x.contains_code() + && x.address_range().contains(&range.start) + && x.address_range().contains(&range.end) + }) { + true + } else { + false + } +} + +fn emit_result(contents: String) { + log::info!("{}", &contents); + if let Err(e) = set_clipboard_contents(contents) { + log::error!("unable to copy to clipboard: {}", e); + } +} + +fn set_clipboard_contents(contents: String) -> Result<(), Box> { + let mut ctx: clipboard::ClipboardContext = clipboard::ClipboardProvider::new()?; + + ctx.set_contents(contents)?; + + Ok(()) +} + +fn get_clipboard_contents() -> Result> { + let mut ctx: clipboard::ClipboardContext = clipboard::ClipboardProvider::new()?; + + ctx.get_contents() +} + +fn get_maximum_signature_size(bv: &BinaryView) -> u64 { + let schema_id = CString::new("default").unwrap(); + let key = CString::new("coolsigmaker.maximum_size").unwrap(); + let settings = unsafe { BNCreateSettings(schema_id.as_ptr()) }; + + let ret = unsafe { + BNSettingsGetUInt64( + settings, + key.as_ptr(), + *std::mem::transmute::<_, *mut *mut BNBinaryView>(bv), + std::ptr::null_mut(), + ) + }; + + unsafe { BNFreeSettings(settings) }; + + ret +} + +fn get_include_operands(bv: &BinaryView) -> bool { + let schema_id = CString::new("default").unwrap(); + let key = CString::new("coolsigmaker.include_operands").unwrap(); + let settings = unsafe { BNCreateSettings(schema_id.as_ptr()) }; + + let ret = unsafe { + BNSettingsGetBool( + settings, + key.as_ptr(), + *std::mem::transmute::<_, *mut *mut BNBinaryView>(bv), + std::ptr::null_mut(), + ) + }; + + unsafe { BNFreeSettings(settings) }; + + ret +} + +fn register_settings() { + fn register_setting( + settings: *mut BNSettings, + name: &str, + title: &str, + description: &str, + typ: &str, + default: T, + ) where + T: core::fmt::Display, + { + let name = CString::new(name).unwrap(); + + let default = if typ == "string" { + format!("\"{}\"", default) + } else { + format!("{}", default) + }; + + let properties = format!( + r#" + {{ + "title": "{}", + "type": "{}", + "default": {}, + "description": "{}" + }} + "#, + title, typ, default, description + ); + + let properties = CString::new(properties).unwrap(); + + unsafe { BNSettingsRegisterSetting(settings, name.as_ptr(), properties.as_ptr()) }; + } + + let schema_id = CString::new("default").unwrap(); + let group = CString::new("coolsigmaker").unwrap(); + let group_fancy = CString::new("CoolSigMaker").unwrap(); + let settings = unsafe { BNCreateSettings(schema_id.as_ptr()) }; + + unsafe { BNSettingsRegisterGroup(settings, group.as_ptr(), group_fancy.as_ptr()) }; + + register_setting::(settings, "coolsigmaker.include_operands", "Include Operands", "Include immediate operands that aren't memory-relative or relocated when creating signatures. This results in smaller, but potentially more fragile, signatures. If no unique signature can be generated without operands, we fall back to including them.", "boolean", true); + register_setting::( + settings, + "coolsigmaker.maximum_size", + "Maximum Signature Size", + "The maximum size the signature will accumulate before giving up.", + "number", + 64, + ); + + unsafe { BNFreeSettings(settings) }; +} + +impl AddressCommand for RustSigMakerCommand { + fn action(&self, bv: &BinaryView, addr: u64) { + if let Some(pattern) = create_pattern(bv, addr as _) { + emit_result(format!("{}", RustPattern(&pattern))); + } else { + log::error!("unable to create pattern!"); + } + } + + fn valid(&self, bv: &BinaryView, addr: u64) -> bool { + is_valid( + bv, + Range { + start: addr, + end: addr + MAX_INSTRUCTION_LENGTH as u64, + }, + ) + } +} + +impl AddressCommand for IDASigMakerCommand { + fn action(&self, bv: &BinaryView, addr: u64) { + if let Some(pattern) = create_pattern(bv, addr as _) { + emit_result(format!("{}", IDAPattern(&pattern))); + } else { + log::error!("unable to create pattern!"); + } + } + + fn valid(&self, bv: &BinaryView, addr: u64) -> bool { + is_valid( + bv, + Range { + start: addr, + end: addr + MAX_INSTRUCTION_LENGTH as u64, + }, + ) + } +} + +impl AddressCommand for CStrSigMakerCommand { + fn action(&self, bv: &BinaryView, addr: u64) { + if let Some(pattern) = create_pattern(bv, addr as _) { + emit_result(format!("{}", CStrPattern(&pattern))); + } else { + log::error!("unable to create pattern!"); + } + } + + fn valid(&self, bv: &BinaryView, addr: u64) -> bool { + is_valid( + bv, + Range { + start: addr, + end: addr + MAX_INSTRUCTION_LENGTH as u64, + }, + ) + } +} + +impl Command for SigFinderCommand { + fn action(&self, bv: &BinaryView) { + // please don't look a this code. its absolutely disgusting. i hate working with string so much. + + let Ok(mut sig) = get_clipboard_contents() else { + log::error!("unable to get signature from clipboard!"); + return; + }; + + sig = sig.replace("\n", ""); + sig = sig.replace("\r", ""); + sig = sig.replace("\t", ""); + sig = sig.trim().to_string(); + + let mut pattern = OwnedPattern::new(); + + if sig.contains("\"") { + let parts = sig.split(" ").collect::>(); + + if parts.len() != 2 { + log::error!("unable to parse pattern!"); + return; + } + + let first_part = parts[0].trim().replace("\"", ""); + + let first_part = first_part + .split("\\x") + .into_iter() + .filter_map(|x| if x == "" { None } else { Some(x.split("?")) }) + .flatten() + .collect::>(); + + let second_part = parts[1].trim().replace("\"", ""); + + for (byte, mask) in first_part.iter().zip(second_part.chars()) { + match mask { + 'x' => { + let Ok(byte) = u8::from_str_radix(byte, 16) else { + log::error!("unable to parse pattern!"); + return; + }; + pattern.push(Some(byte)); + } + '?' => { + pattern.push(None); + } + _ => { + log::error!("invalid char encountered!"); + } + } + } + } else { + sig = sig.replace("_", "??"); + sig = sig.replace("0x", ""); + sig = sig.replace(", ", ""); + sig = sig.replace(" ", ""); + + for byte in sig.chars().array_chunks::<2>() { + if byte == ['?', '?'] { + pattern.push(None); + } else { + let Ok(byte) = u8::from_str_radix( + &format!("{}{}", byte[0], byte[1]), + 16, + ) else { + log::error!("unable to parse pattern!"); + return; + }; + + pattern.push(Some(byte)); + } + } + } + + for occurrence in find_patterns(bv, &pattern) { + log::info!("found signature at {:#04X}", occurrence); + } + + log::info!("scan finished."); + } + + fn valid(&self, _bv: &BinaryView) -> bool { + true + } +} + +#[no_mangle] +pub extern "C" fn CorePluginInit() -> bool { + binaryninja::logger::init(log::LevelFilter::Info).unwrap(); + + // external_logger::init().unwrap(); + log::info!("say hello to the little ninja in your binja"); + + // #[cfg(debug_assertions)] + std::panic::set_hook(Box::new(|info| { + let string = format!( + "{}\n{:#?}\n{}", + info, + info, + std::backtrace::Backtrace::force_capture() + ); + + // #[cfg(debug_assertions)] + let _ = std::fs::write("C:\\log.txt", &string); + + log::info!("{}", &string); + })); + + register_settings(); + + command::register_for_address( + "CoolSigMaker\\Create Signature from Address: Rust (0xE9, _, _, _, _)", + "Creates a Rust-style signature from the currently selected address", + RustSigMakerCommand {}, + ); + + command::register_for_address( + "CoolSigMaker\\Create Signature from Address: IDA (E9 ?? ?? ?? ??)", + "Creates an IDA-style signature from the currently selected address", + IDASigMakerCommand {}, + ); + + command::register_for_address( + "CoolSigMaker\\Create Signature from Address: CStr (\"/xE9/x00/x00/x00/x00\" \"x????\")", + "Creates a CStr-style signature from the currently selected address", + CStrSigMakerCommand {}, + ); + + command::register( + "CoolSigMaker\\Find Signature", + "Finds a signature in the binary.", + SigFinderCommand {}, + ); + + true +} -- cgit v1.3.1