summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugins/workflow_objc/src/activities/objc_msg_send_calls.rs4
-rw-r--r--plugins/workflow_objc/src/activities/objc_msg_send_calls/adjust_call_type.rs135
-rw-r--r--plugins/workflow_objc/src/activities/super_init.rs3
-rw-r--r--plugins/workflow_objc/src/metadata/selector.rs13
4 files changed, 146 insertions, 9 deletions
diff --git a/plugins/workflow_objc/src/activities/objc_msg_send_calls.rs b/plugins/workflow_objc/src/activities/objc_msg_send_calls.rs
index 3a772e79..157bb19c 100644
--- a/plugins/workflow_objc/src/activities/objc_msg_send_calls.rs
+++ b/plugins/workflow_objc/src/activities/objc_msg_send_calls.rs
@@ -107,7 +107,7 @@ fn process_instruction(
};
let mut function_changed = false;
- if adjust_call_type::process_call(bv, func, insn, &selector, message_send_type).is_ok() {
+ if adjust_call_type::process_call(bv, func, ssa, insn, &selector, message_send_type).is_ok() {
function_changed = true;
}
@@ -166,7 +166,7 @@ fn selector_from_call(
return None;
};
- let raw_selector = ssa.get_ssa_register_value(&reg.source_reg())?.value as u64;
+ let raw_selector = ssa.get_ssa_register_value(reg.source_reg())?.value as u64;
if raw_selector == 0 {
return None;
}
diff --git a/plugins/workflow_objc/src/activities/objc_msg_send_calls/adjust_call_type.rs b/plugins/workflow_objc/src/activities/objc_msg_send_calls/adjust_call_type.rs
index d9cb1343..07e83ba7 100644
--- a/plugins/workflow_objc/src/activities/objc_msg_send_calls/adjust_call_type.rs
+++ b/plugins/workflow_objc/src/activities/objc_msg_send_calls/adjust_call_type.rs
@@ -1,26 +1,150 @@
use binaryninja::{
+ architecture::CoreRegister,
binary_view::{BinaryView, BinaryViewBase as _, BinaryViewExt},
confidence::Conf,
function::Function,
low_level_il::{
- function::{Mutable, SSA},
- instruction::LowLevelILInstruction,
+ expression::{
+ ExpressionHandler as _, LowLevelILExpression, LowLevelILExpressionKind, ValueExpr,
+ },
+ function::{LowLevelILFunction, Mutable, SSA},
+ instruction::{InstructionHandler as _, LowLevelILInstruction, LowLevelILInstructionKind},
+ operation::{CallSsa, Operation},
+ LowLevelILSSARegisterKind,
},
rc::Ref,
types::{FunctionParameter, Type},
+ variable::PossibleValueSet,
};
+use bstr::ByteSlice as _;
use super::MessageSendType;
-use crate::{metadata::Selector, workflow::Confidence, Error};
+use crate::{activities::util, metadata::Selector, workflow::Confidence, Error};
fn named_type(bv: &BinaryView, name: &str) -> Option<Ref<Type>> {
bv.type_by_name(name)
.map(|t| Type::named_type_from_type(name, &t))
}
+// j_ prefixes are for stub functions in the dyld shared cache.
+const ALLOC_FUNCTIONS: &[&str] = &[
+ "_objc_alloc_init",
+ "_objc_alloc_initWithZone",
+ "_objc_alloc",
+ "_objc_allocWithZone",
+ "_objc_opt_new",
+ "j__objc_alloc_init",
+ "j__objc_alloc_initWithZone",
+ "j__objc_alloc",
+ "j__objc_allocWithZone",
+ "j__objc_opt_new",
+];
+
+/// Extract parameter expressions from a call, handling the SeparateParamListSsa wrapper.
+fn call_param_exprs<'a>(
+ call_op: &'a Operation<'a, Mutable, SSA, CallSsa>,
+) -> Option<Vec<LowLevelILExpression<'a, Mutable, SSA, ValueExpr>>> {
+ let LowLevelILExpressionKind::CallParamSsa(params) = &call_op.param_expr().kind() else {
+ return None;
+ };
+
+ let param_exprs = params.param_exprs();
+ Some(
+ if let Some(LowLevelILExpressionKind::SeparateParamListSsa(inner)) =
+ param_exprs.first().map(|e| e.kind())
+ {
+ inner.param_exprs()
+ } else {
+ param_exprs
+ },
+ )
+}
+
+/// Follow an SSA register back through register-to-register copies to find the
+/// instruction that originally defined its value.
+fn source_def_for_register<'a>(
+ ssa: &'a LowLevelILFunction<Mutable, SSA>,
+ reg: LowLevelILSSARegisterKind<CoreRegister>,
+) -> Option<LowLevelILInstruction<'a, Mutable, SSA>> {
+ let mut def = ssa.get_ssa_register_definition(reg)?;
+ while let LowLevelILInstructionKind::SetRegSsa(set_reg) = def.kind() {
+ let LowLevelILExpressionKind::RegSsa(src_reg) = set_reg.source_expr().kind() else {
+ break;
+ };
+ def = ssa.get_ssa_register_definition(src_reg.source_reg())?;
+ }
+ Some(def)
+}
+
+/// For init-family selectors on a normal message send, try to determine the return type
+/// by tracing the receiver register back to an alloc call and resolving the class.
+fn return_type_for_init_receiver(
+ bv: &BinaryView,
+ func: &Function,
+ ssa: &LowLevelILFunction<Mutable, SSA>,
+ insn: &LowLevelILInstruction<Mutable, SSA>,
+ selector: &Selector,
+ message_send_type: MessageSendType,
+) -> Option<Ref<Type>> {
+ if message_send_type != MessageSendType::Normal || !selector.is_init_family() {
+ return None;
+ }
+
+ let call_op = match insn.kind() {
+ LowLevelILInstructionKind::CallSsa(op) | LowLevelILInstructionKind::TailCallSsa(op) => op,
+ _ => return None,
+ };
+
+ let param_exprs = call_param_exprs(&call_op)?;
+ let LowLevelILExpressionKind::RegSsa(receiver_reg) = param_exprs.first()?.kind() else {
+ return None;
+ };
+
+ let def = source_def_for_register(ssa, receiver_reg.source_reg())?;
+ let def_call_op = match def.kind() {
+ LowLevelILInstructionKind::CallSsa(op) | LowLevelILInstructionKind::TailCallSsa(op) => op,
+ _ => return None,
+ };
+
+ // Check if the defining call is to an alloc function.
+ let target_values = def_call_op.target().possible_values();
+ let call_target = match target_values {
+ PossibleValueSet::ConstantValue { value }
+ | PossibleValueSet::ConstantPointerValue { value }
+ | PossibleValueSet::ImportedAddressValue { value } => value as u64,
+ _ => return None,
+ };
+
+ let target_name = bv
+ .symbol_by_address(call_target)?
+ .raw_name()
+ .to_string_lossy()
+ .into_owned();
+ if !ALLOC_FUNCTIONS.contains(&target_name.as_str()) {
+ return None;
+ }
+
+ // Get the class from the alloc call's first parameter.
+ let alloc_params = call_param_exprs(&def_call_op)?;
+ let LowLevelILExpressionKind::RegSsa(class_reg) = alloc_params.first()?.kind() else {
+ return None;
+ };
+
+ let class_addr = ssa.get_ssa_register_value(class_reg.source_reg())?.value as u64;
+ if class_addr == 0 {
+ return None;
+ }
+
+ let class_symbol_name = bv.symbol_by_address(class_addr)?.full_name();
+ let class_name = util::class_name_from_symbol_name(class_symbol_name.to_bytes().as_bstr())?;
+ let class_type = bv.type_by_name(class_name.to_str_lossy())?;
+ Some(Type::pointer(&func.arch(), &class_type))
+}
+
pub fn process_call(
bv: &BinaryView,
func: &Function,
+ ssa: &LowLevelILFunction<Mutable, SSA>,
insn: &LowLevelILInstruction<Mutable, SSA>,
selector: &Selector,
message_send_type: MessageSendType,
@@ -39,8 +163,9 @@ pub fn process_call(
};
let sel = named_type(bv, "SEL").unwrap_or_else(|| Type::pointer(&arch, &Type::char()));
- // TODO: Infer return type based on receiver type / selector.
- let return_type = id.clone();
+ let return_type =
+ return_type_for_init_receiver(bv, func, ssa, insn, selector, message_send_type)
+ .unwrap_or_else(|| id.clone());
let mut params = vec![
FunctionParameter::new(receiver_type, receiver_name.to_string(), None),
diff --git a/plugins/workflow_objc/src/activities/super_init.rs b/plugins/workflow_objc/src/activities/super_init.rs
index 81b9a453..7b6bb3f7 100644
--- a/plugins/workflow_objc/src/activities/super_init.rs
+++ b/plugins/workflow_objc/src/activities/super_init.rs
@@ -39,8 +39,7 @@ fn return_type_for_super_init(call: &util::Call, view: &BinaryView) -> Option<Re
util::match_constant_pointer_or_load_of_constant_pointer(&call.call.params[1])?;
let selector = Selector::from_address(view, selector_addr).ok()?;
- // TODO: This will match `initialize` and `initiate` which are not init methods.
- if !selector.name.starts_with("init") {
+ if !selector.is_init_family() {
return None;
}
diff --git a/plugins/workflow_objc/src/metadata/selector.rs b/plugins/workflow_objc/src/metadata/selector.rs
index a48be14d..de2e883e 100644
--- a/plugins/workflow_objc/src/metadata/selector.rs
+++ b/plugins/workflow_objc/src/metadata/selector.rs
@@ -23,6 +23,19 @@ impl Selector {
Ok(Selector { name, addr })
}
+ /// Returns true if this selector belongs to the `init` method family.
+ ///
+ /// Per the ObjC ARC spec, a selector is in the init family if it starts with
+ /// "init" and the next character is either uppercase or absent (e.g. `init`,
+ /// `initWithFrame:`, but NOT `initialize` or `initials`).
+ pub fn is_init_family(&self) -> bool {
+ if let Some(rest) = self.name.strip_prefix("init") {
+ rest.is_empty() || rest.starts_with(|c: char| c.is_ascii_uppercase() || c == ':')
+ } else {
+ false
+ }
+ }
+
pub fn argument_labels(&self) -> Vec<String> {
if !self.name.contains(':') {
return vec![];