Skip to content

Commit d93c80f

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

File tree

2 files changed

+132
-7
lines changed

2 files changed

+132
-7
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.name.starts_with("init") {
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),

0 commit comments

Comments
 (0)