Skip to content

Commit 20adc82

Browse files
committed
[ObjC] Set return type on objc_msgSend calls that send an init message to a known class
1 parent efbde23 commit 20adc82

File tree

4 files changed

+146
-9
lines changed

4 files changed

+146
-9
lines changed

plugins/workflow_objc/src/activities/objc_msg_send_calls.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ fn process_instruction(
107107
};
108108

109109
let mut function_changed = false;
110-
if adjust_call_type::process_call(bv, func, insn, &selector, message_send_type).is_ok() {
110+
if adjust_call_type::process_call(bv, func, ssa, insn, &selector, message_send_type).is_ok() {
111111
function_changed = true;
112112
}
113113

@@ -166,7 +166,7 @@ fn selector_from_call(
166166
return None;
167167
};
168168

169-
let raw_selector = ssa.get_ssa_register_value(&reg.source_reg())?.value as u64;
169+
let raw_selector = ssa.get_ssa_register_value(reg.source_reg())?.value as u64;
170170
if raw_selector == 0 {
171171
return None;
172172
}

plugins/workflow_objc/src/activities/objc_msg_send_calls/adjust_call_type.rs

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,150 @@
11
use binaryninja::{
2+
architecture::CoreRegister,
23
binary_view::{BinaryView, BinaryViewBase as _, BinaryViewExt},
34
confidence::Conf,
45
function::Function,
56
low_level_il::{
6-
function::{Mutable, SSA},
7-
instruction::LowLevelILInstruction,
7+
expression::{
8+
ExpressionHandler as _, LowLevelILExpression, LowLevelILExpressionKind, ValueExpr,
9+
},
10+
function::{LowLevelILFunction, Mutable, SSA},
11+
instruction::{InstructionHandler as _, LowLevelILInstruction, LowLevelILInstructionKind},
12+
operation::{CallSsa, Operation},
13+
LowLevelILSSARegisterKind,
814
},
915
rc::Ref,
1016
types::{FunctionParameter, Type},
17+
variable::PossibleValueSet,
1118
};
19+
use bstr::ByteSlice as _;
1220

1321
use super::MessageSendType;
14-
use crate::{metadata::Selector, workflow::Confidence, Error};
22+
use crate::{activities::util, metadata::Selector, workflow::Confidence, Error};
1523

1624
fn named_type(bv: &BinaryView, name: &str) -> Option<Ref<Type>> {
1725
bv.type_by_name(name)
1826
.map(|t| Type::named_type_from_type(name, &t))
1927
}
2028

29+
// j_ prefixes are for stub functions in the dyld shared cache.
30+
const ALLOC_FUNCTIONS: &[&str] = &[
31+
"_objc_alloc_init",
32+
"_objc_alloc_initWithZone",
33+
"_objc_alloc",
34+
"_objc_allocWithZone",
35+
"_objc_opt_new",
36+
"j__objc_alloc_init",
37+
"j__objc_alloc_initWithZone",
38+
"j__objc_alloc",
39+
"j__objc_allocWithZone",
40+
"j__objc_opt_new",
41+
];
42+
43+
/// Extract parameter expressions from a call, handling the SeparateParamListSsa wrapper.
44+
fn call_param_exprs<'a>(
45+
call_op: &'a Operation<'a, Mutable, SSA, CallSsa>,
46+
) -> Option<Vec<LowLevelILExpression<'a, Mutable, SSA, ValueExpr>>> {
47+
let LowLevelILExpressionKind::CallParamSsa(params) = &call_op.param_expr().kind() else {
48+
return None;
49+
};
50+
51+
let param_exprs = params.param_exprs();
52+
Some(
53+
if let Some(LowLevelILExpressionKind::SeparateParamListSsa(inner)) =
54+
param_exprs.first().map(|e| e.kind())
55+
{
56+
inner.param_exprs()
57+
} else {
58+
param_exprs
59+
},
60+
)
61+
}
62+
63+
/// Follow an SSA register back through register-to-register copies to find the
64+
/// instruction that originally defined its value.
65+
fn source_def_for_register<'a>(
66+
ssa: &'a LowLevelILFunction<Mutable, SSA>,
67+
reg: LowLevelILSSARegisterKind<CoreRegister>,
68+
) -> Option<LowLevelILInstruction<'a, Mutable, SSA>> {
69+
let mut def = ssa.get_ssa_register_definition(reg)?;
70+
while let LowLevelILInstructionKind::SetRegSsa(set_reg) = def.kind() {
71+
let LowLevelILExpressionKind::RegSsa(src_reg) = set_reg.source_expr().kind() else {
72+
break;
73+
};
74+
def = ssa.get_ssa_register_definition(src_reg.source_reg())?;
75+
}
76+
Some(def)
77+
}
78+
79+
/// For init-family selectors on a normal message send, try to determine the return type
80+
/// by tracing the receiver register back to an alloc call and resolving the class.
81+
fn return_type_for_init_receiver(
82+
bv: &BinaryView,
83+
func: &Function,
84+
ssa: &LowLevelILFunction<Mutable, SSA>,
85+
insn: &LowLevelILInstruction<Mutable, SSA>,
86+
selector: &Selector,
87+
message_send_type: MessageSendType,
88+
) -> Option<Ref<Type>> {
89+
if message_send_type != MessageSendType::Normal || !selector.is_init_family() {
90+
return None;
91+
}
92+
93+
let call_op = match insn.kind() {
94+
LowLevelILInstructionKind::CallSsa(op) | LowLevelILInstructionKind::TailCallSsa(op) => op,
95+
_ => return None,
96+
};
97+
98+
let param_exprs = call_param_exprs(&call_op)?;
99+
let LowLevelILExpressionKind::RegSsa(receiver_reg) = param_exprs.first()?.kind() else {
100+
return None;
101+
};
102+
103+
let def = source_def_for_register(ssa, receiver_reg.source_reg())?;
104+
let def_call_op = match def.kind() {
105+
LowLevelILInstructionKind::CallSsa(op) | LowLevelILInstructionKind::TailCallSsa(op) => op,
106+
_ => return None,
107+
};
108+
109+
// Check if the defining call is to an alloc function.
110+
let target_values = def_call_op.target().possible_values();
111+
let call_target = match target_values {
112+
PossibleValueSet::ConstantValue { value }
113+
| PossibleValueSet::ConstantPointerValue { value }
114+
| PossibleValueSet::ImportedAddressValue { value } => value as u64,
115+
_ => return None,
116+
};
117+
118+
let target_name = bv
119+
.symbol_by_address(call_target)?
120+
.raw_name()
121+
.to_string_lossy()
122+
.into_owned();
123+
if !ALLOC_FUNCTIONS.contains(&target_name.as_str()) {
124+
return None;
125+
}
126+
127+
// Get the class from the alloc call's first parameter.
128+
let alloc_params = call_param_exprs(&def_call_op)?;
129+
let LowLevelILExpressionKind::RegSsa(class_reg) = alloc_params.first()?.kind() else {
130+
return None;
131+
};
132+
133+
let class_addr = ssa.get_ssa_register_value(class_reg.source_reg())?.value as u64;
134+
if class_addr == 0 {
135+
return None;
136+
}
137+
138+
let class_symbol_name = bv.symbol_by_address(class_addr)?.full_name();
139+
let class_name = util::class_name_from_symbol_name(class_symbol_name.to_bytes().as_bstr())?;
140+
let class_type = bv.type_by_name(class_name.to_str_lossy())?;
141+
Some(Type::pointer(&func.arch(), &class_type))
142+
}
143+
21144
pub fn process_call(
22145
bv: &BinaryView,
23146
func: &Function,
147+
ssa: &LowLevelILFunction<Mutable, SSA>,
24148
insn: &LowLevelILInstruction<Mutable, SSA>,
25149
selector: &Selector,
26150
message_send_type: MessageSendType,
@@ -39,8 +163,9 @@ pub fn process_call(
39163
};
40164
let sel = named_type(bv, "SEL").unwrap_or_else(|| Type::pointer(&arch, &Type::char()));
41165

42-
// TODO: Infer return type based on receiver type / selector.
43-
let return_type = id.clone();
166+
let return_type =
167+
return_type_for_init_receiver(bv, func, ssa, insn, selector, message_send_type)
168+
.unwrap_or_else(|| id.clone());
44169

45170
let mut params = vec![
46171
FunctionParameter::new(receiver_type, receiver_name.to_string(), None),

plugins/workflow_objc/src/activities/super_init.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ fn return_type_for_super_init(call: &util::Call, view: &BinaryView) -> Option<Re
3939
util::match_constant_pointer_or_load_of_constant_pointer(&call.call.params[1])?;
4040
let selector = Selector::from_address(view, selector_addr).ok()?;
4141

42-
// TODO: This will match `initialize` and `initiate` which are not init methods.
43-
if !selector.name.starts_with("init") {
42+
if !selector.is_init_family() {
4443
return None;
4544
}
4645

plugins/workflow_objc/src/metadata/selector.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ impl Selector {
2323
Ok(Selector { name, addr })
2424
}
2525

26+
/// Returns true if this selector belongs to the `init` method family.
27+
///
28+
/// Per the ObjC ARC spec, a selector is in the init family if it starts with
29+
/// "init" and the next character is either uppercase or absent (e.g. `init`,
30+
/// `initWithFrame:`, but NOT `initialize` or `initials`).
31+
pub fn is_init_family(&self) -> bool {
32+
if let Some(rest) = self.name.strip_prefix("init") {
33+
rest.is_empty() || rest.starts_with(|c: char| c.is_ascii_uppercase() || c == ':')
34+
} else {
35+
false
36+
}
37+
}
38+
2639
pub fn argument_labels(&self) -> Vec<String> {
2740
if !self.name.contains(':') {
2841
return vec![];

0 commit comments

Comments
 (0)