Skip to content

Commit e0a9ee9

Browse files
committed
[ObjC] Propagate types from objc_alloc_init and friends
1 parent 1e85844 commit e0a9ee9

5 files changed

Lines changed: 246 additions & 143 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use binaryninja::{
2+
binary_view::{BinaryView, BinaryViewExt as _},
3+
medium_level_il::MediumLevelILLiftedInstruction,
4+
rc::Ref,
5+
types::Type,
6+
workflow::AnalysisContext,
7+
};
8+
use bstr::ByteSlice;
9+
10+
use super::util;
11+
use crate::{error::ILLevel, metadata::GlobalState, workflow::Confidence, Error};
12+
13+
// j_ prefixes are for stub functions in the dyld shared cache.
14+
// The prefix is added by Binary Ninja's shared cache workflow.
15+
const ALLOC_INIT_FUNCTIONS: &[&[u8]] = &[
16+
b"_objc_alloc_init",
17+
b"_objc_alloc_initWithZone",
18+
b"_objc_alloc",
19+
b"_objc_allocWithZone",
20+
b"_objc_opt_new",
21+
b"j__objc_alloc_init",
22+
b"j__objc_alloc_initWithZone",
23+
b"j__objc_alloc",
24+
b"j__objc_allocWithZone",
25+
b"j__objc_opt_new",
26+
];
27+
28+
fn return_type_for_alloc_call(call: &util::Call<'_>, view: &BinaryView) -> Option<Ref<Type>> {
29+
if call.call.params.is_empty() {
30+
return None;
31+
}
32+
33+
let class_addr =
34+
util::match_constant_pointer_or_load_of_constant_pointer(&call.call.params[0])?;
35+
let class_symbol_name = view.symbol_by_address(class_addr)?.full_name();
36+
let class_name = util::class_name_from_symbol_name(class_symbol_name.to_bytes().as_bstr())?;
37+
38+
let class_type = view.type_by_name(class_name.to_str_lossy())?;
39+
Some(Type::pointer(&call.target.arch(), &class_type))
40+
}
41+
42+
fn process_instruction(instr: &MediumLevelILLiftedInstruction, view: &BinaryView) -> Option<()> {
43+
let call = util::match_call_to_function_named(instr, view, ALLOC_INIT_FUNCTIONS)?;
44+
45+
util::adjust_return_type_of_call(
46+
&call,
47+
return_type_for_alloc_call(&call, view)?.as_ref(),
48+
Confidence::AllocInit as u8,
49+
);
50+
51+
Some(())
52+
}
53+
54+
pub fn process(ac: &AnalysisContext) -> Result<(), Error> {
55+
let bv = ac.view();
56+
if GlobalState::should_ignore_view(&bv) {
57+
return Ok(());
58+
}
59+
60+
let mlil = ac.mlil_function().ok_or(Error::MissingIL {
61+
level: ILLevel::Medium,
62+
func_start: ac.function().start(),
63+
})?;
64+
let mlil_ssa = mlil.ssa_form();
65+
66+
for block in &mlil_ssa.basic_blocks() {
67+
for instr in block.iter() {
68+
process_instruction(&instr.lift(), &bv);
69+
}
70+
}
71+
72+
Ok(())
73+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
pub mod alloc_init;
12
pub mod inline_stubs;
23
pub mod objc_msg_send_calls;
34
pub mod remove_memory_management;
45
pub mod super_init;
6+
pub(crate) mod util;

plugins/workflow_objc/src/activities/super_init.rs

Lines changed: 15 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
use binaryninja::{
2-
binary_view::{BinaryView, BinaryViewBase, BinaryViewExt as _},
3-
confidence::Conf,
4-
function::Function,
2+
binary_view::{BinaryView, BinaryViewBase as _, BinaryViewExt as _},
53
medium_level_il::{
6-
operation::{
7-
Constant, LiftedCallSsa, LiftedLoadSsa, LiftedSetVarSsa, LiftedSetVarSsaField, Var,
8-
VarSsa,
9-
},
10-
MediumLevelILFunction, MediumLevelILLiftedInstruction, MediumLevelILLiftedInstructionKind,
4+
operation::{Constant, LiftedSetVarSsa, LiftedSetVarSsaField, Var, VarSsa},
5+
MediumLevelILLiftedInstruction, MediumLevelILLiftedInstructionKind,
116
},
127
rc::Ref,
138
types::Type,
14-
variable::{RegisterValueType, SSAVariable},
159
workflow::AnalysisContext,
1610
};
17-
use bstr::{BStr, ByteSlice};
11+
use bstr::ByteSlice;
1812

13+
use super::util;
1914
use crate::{
2015
error::ILLevel,
2116
metadata::{GlobalState, Selector},
@@ -32,110 +27,16 @@ const OBJC_MSG_SEND_SUPER_FUNCTIONS: &[&[u8]] = &[
3227
b"j__objc_msgSendSuper",
3328
];
3429

35-
fn ssa_variable_value_or_load_of_constant_pointer(
36-
function: &MediumLevelILFunction,
37-
var: &SSAVariable,
38-
) -> Option<u64> {
39-
let value = function.ssa_variable_value(var);
40-
match value.state {
41-
RegisterValueType::ConstantPointerValue => return Some(value.value as u64),
42-
RegisterValueType::UndeterminedValue => {}
43-
_ => return None,
44-
}
45-
46-
let def = function.ssa_variable_definition(var)?;
47-
let MediumLevelILLiftedInstructionKind::SetVarSsa(set_var) = def.lift().kind else {
48-
return None;
49-
};
50-
51-
let MediumLevelILLiftedInstructionKind::LoadSsa(LiftedLoadSsa { src, .. }) = set_var.src.kind
52-
else {
53-
return None;
54-
};
55-
56-
match src.kind {
57-
MediumLevelILLiftedInstructionKind::ConstPtr(Constant { constant }) => Some(constant),
58-
_ => None,
59-
}
60-
}
61-
62-
/// If `instr` is a constant pointer or is a variable whose value is loaded from a constant pointer,
63-
/// return that pointer address.
64-
fn match_constant_pointer_or_load_of_constant_pointer(
65-
instr: &MediumLevelILLiftedInstruction,
66-
) -> Option<u64> {
67-
match instr.kind {
68-
MediumLevelILLiftedInstructionKind::ConstPtr(Constant { constant }) => Some(constant),
69-
MediumLevelILLiftedInstructionKind::VarSsa(var) => {
70-
ssa_variable_value_or_load_of_constant_pointer(&instr.function, &var.src)
71-
}
72-
_ => None,
73-
}
74-
}
75-
76-
#[allow(clippy::struct_field_names)]
77-
struct Call<'a> {
78-
pub instr: &'a MediumLevelILLiftedInstruction,
79-
pub call: &'a LiftedCallSsa,
80-
pub target: Ref<Function>,
81-
}
82-
83-
/// Returns a `Call` if `instr` is a call or tail call to a function whose name appears in `function_names`
84-
fn match_call_to_function_named<'a>(
85-
instr: &'a MediumLevelILLiftedInstruction,
86-
view: &'a BinaryView,
87-
function_names: &'a [&[u8]],
88-
) -> Option<Call<'a>> {
89-
let (MediumLevelILLiftedInstructionKind::TailcallSsa(ref call)
90-
| MediumLevelILLiftedInstructionKind::CallSsa(ref call)) = instr.kind
91-
else {
92-
return None;
93-
};
94-
95-
let MediumLevelILLiftedInstructionKind::ConstPtr(Constant {
96-
constant: call_target,
97-
}) = call.dest.kind
98-
else {
99-
return None;
100-
};
101-
102-
let target_function = view.function_at(&instr.function.function().platform(), call_target)?;
103-
let function_name = target_function.symbol().full_name();
104-
if !function_names.contains(&function_name.to_bytes()) {
105-
return None;
106-
}
107-
108-
Some(Call {
109-
instr,
110-
call,
111-
target: target_function,
112-
})
113-
}
114-
115-
fn class_name_from_symbol_name(symbol_name: &BStr) -> Option<&BStr> {
116-
// The symbol name for the `objc_class_t` can have different names depending
117-
// on factors such as being local or external, and whether the reference
118-
// is from the shared cache or a standalone Mach-O file.
119-
Some(if symbol_name.starts_with(b"cls_") {
120-
&symbol_name[4..]
121-
} else if symbol_name.starts_with(b"clsRef_") {
122-
&symbol_name[7..]
123-
} else if symbol_name.starts_with(b"_OBJC_CLASS_$_") {
124-
&symbol_name[14..]
125-
} else {
126-
return None;
127-
})
128-
}
129-
13030
/// Detect the return type for a call to `objc_msgSendSuper2` where the selector is in the `init` family.
13131
/// Returns `None` if selector is not in the `init` family or the return type cannot be determined.
132-
fn return_type_for_super_init(call: &Call, view: &BinaryView) -> Option<Ref<Type>> {
32+
fn return_type_for_super_init(call: &util::Call, view: &BinaryView) -> Option<Ref<Type>> {
13333
// Expecting to see at least `objc_super` and a selector.
13434
if call.call.params.len() < 2 {
13535
return None;
13636
}
13737

138-
let selector_addr = match_constant_pointer_or_load_of_constant_pointer(&call.call.params[1])?;
38+
let selector_addr =
39+
util::match_constant_pointer_or_load_of_constant_pointer(&call.call.params[1])?;
13940
let selector = Selector::from_address(view, selector_addr).ok()?;
14041

14142
// TODO: This will match `initialize` and `initiate` which are not init methods.
@@ -238,7 +139,7 @@ fn return_type_for_super_init(call: &Call, view: &BinaryView) -> Option<Ref<Type
238139

239140
let super_class_symbol_name = super_class_symbol.full_name();
240141
let Some(class_name) =
241-
class_name_from_symbol_name(super_class_symbol_name.to_bytes().as_bstr())
142+
util::class_name_from_symbol_name(super_class_symbol_name.to_bytes().as_bstr())
242143
else {
243144
tracing::debug!(
244145
"Unable to extract class name from symbol name: {super_class_symbol_name:?}"
@@ -254,42 +155,14 @@ fn return_type_for_super_init(call: &Call, view: &BinaryView) -> Option<Ref<Type
254155
Some(Type::pointer(&call.target.arch(), &class_type))
255156
}
256157

257-
/// Adjust the return type of the call represented by `call`.
258-
fn adjust_return_type_of_call(call: &Call<'_>, return_type: &Type) {
259-
let function = call.instr.function.function();
260-
261-
// We're changing only the return type, so preserve other aspects of any existing call type adjustment.
262-
let target_function_type = if let Some(existing_call_type_adjustment) =
263-
function.call_type_adjustment(call.instr.address, None)
264-
{
265-
existing_call_type_adjustment.contents
266-
} else {
267-
call.target.function_type()
268-
};
269-
270-
// There's nothing to do if the return type is already correct
271-
if let Some(conf) = target_function_type.return_value() {
272-
if &*conf.contents == return_type {
273-
return;
274-
}
275-
}
276-
277-
let adjusted_call_type = target_function_type
278-
.to_builder()
279-
.set_child_type(return_type)
280-
.finalize();
281-
282-
function.set_auto_call_type_adjustment(
283-
call.instr.address,
284-
Conf::new(&*adjusted_call_type, Confidence::SuperInit as u8),
285-
None,
286-
);
287-
}
288-
289158
fn process_instruction(instr: &MediumLevelILLiftedInstruction, view: &BinaryView) -> Option<()> {
290-
let call = match_call_to_function_named(instr, view, OBJC_MSG_SEND_SUPER_FUNCTIONS)?;
159+
let call = util::match_call_to_function_named(instr, view, OBJC_MSG_SEND_SUPER_FUNCTIONS)?;
291160

292-
adjust_return_type_of_call(&call, return_type_for_super_init(&call, view)?.as_ref());
161+
util::adjust_return_type_of_call(
162+
&call,
163+
return_type_for_super_init(&call, view)?.as_ref(),
164+
Confidence::SuperInit as u8,
165+
);
293166
Some(())
294167
}
295168

0 commit comments

Comments
 (0)