Skip to content

Commit 937162d

Browse files
committed
[ObjC] Introduce a new activity to rename objc_msgSend stub functions
This helps for stripped binaries, and in cases such as the macOS 27 shared cache where the symbols are no longer accruate for stub functions since they are coalesced into stub island regions outside of any dylib.
1 parent 0deca0a commit 937162d

4 files changed

Lines changed: 111 additions & 4 deletions

File tree

plugins/workflow_objc/src/activities/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod alloc_init;
22
pub mod inline_stubs;
3+
pub mod name_stubs;
34
pub mod objc_msg_send_calls;
45
pub mod remove_memory_management;
56
pub mod super_init;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use binaryninja::{
2+
low_level_il::instruction::{InstructionHandler as _, LowLevelILInstructionKind},
3+
symbol::{Symbol, SymbolType},
4+
variable::PossibleValueSet,
5+
workflow::AnalysisContext,
6+
};
7+
8+
use crate::{
9+
activities::objc_msg_send_calls::{call_target_type, selector_from_call, MessageSendType},
10+
error::ILLevel,
11+
metadata::GlobalState,
12+
Error,
13+
};
14+
15+
// Reconstruct names for Objective-C selector stubs, e.g. `_objc_msgSend$length`.
16+
// The compiler emits one of these stubs per selector. Each one does nothing but load the selector and tail-call
17+
// `objc_msgSend`.
18+
pub fn process(ac: &AnalysisContext) -> Result<(), Error> {
19+
let view = ac.view();
20+
if GlobalState::should_ignore_view(&view) {
21+
return Ok(());
22+
}
23+
24+
let func = ac.function();
25+
let func_start = func.start();
26+
27+
// Don't override a name the user has assigned to this function.
28+
if let Some(symbol) = view.symbol_by_address(func_start) {
29+
if !symbol.auto_defined() {
30+
return Ok(());
31+
}
32+
}
33+
34+
// Bail out early for functions that do not look like a stub: a single basic block of a few instructions.
35+
const MAX_STUB_SIZE: u64 = 32;
36+
if func.highest_address().saturating_sub(func_start) > MAX_STUB_SIZE || func.basic_blocks().len() != 1 {
37+
return Ok(());
38+
}
39+
40+
let Some(llil) = (unsafe { ac.llil_function() }) else {
41+
return Err(Error::MissingIL {
42+
level: ILLevel::Low,
43+
func_start,
44+
});
45+
};
46+
let Some(ssa) = llil.ssa_form() else {
47+
return Err(Error::MissingSsaForm {
48+
level: ILLevel::Low,
49+
func_start,
50+
});
51+
};
52+
53+
// A selector stub does nothing besides load a selector and tail-call objc_msgSend.
54+
// Reject any function that contains stores or more than one call.
55+
let mut stub_name = None;
56+
for block in ssa.basic_blocks().iter() {
57+
for insn in block.iter() {
58+
let call_op = match insn.kind() {
59+
LowLevelILInstructionKind::TailCallSsa(op) => op,
60+
// A regular call or a memory write means this is more than a pure selector stub.
61+
LowLevelILInstructionKind::CallSsa(_)
62+
| LowLevelILInstructionKind::Store(_)
63+
| LowLevelILInstructionKind::StoreSsa(_) => return Ok(()),
64+
_ => continue,
65+
};
66+
67+
let call_target = match call_op.target().possible_values() {
68+
PossibleValueSet::ConstantValue { value }
69+
| PossibleValueSet::ConstantPointerValue { value }
70+
| PossibleValueSet::ImportedAddressValue { value } => value as u64,
71+
_ => return Ok(()),
72+
};
73+
74+
if call_target_type(&view, call_target) != Some(MessageSendType::Normal) {
75+
return Ok(());
76+
}
77+
let Some(selector) = selector_from_call(&view, &ssa, &call_op) else {
78+
return Ok(());
79+
};
80+
stub_name = Some(format!("_objc_msgSend${}", selector.name));
81+
}
82+
}
83+
84+
let Some(name) = stub_name else {
85+
return Ok(());
86+
};
87+
88+
let symbol = Symbol::builder(SymbolType::Function, &name, func_start).create();
89+
view.define_auto_symbol(&symbol);
90+
91+
Ok(())
92+
}

plugins/workflow_objc/src/activities/objc_msg_send_calls.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,12 @@ fn process_instruction(
119119
}
120120

121121
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
122-
enum MessageSendType {
122+
pub(crate) enum MessageSendType {
123123
Normal,
124124
Super,
125125
}
126126

127-
fn call_target_type(bv: &BinaryView, call_target: u64) -> Option<MessageSendType> {
127+
pub(crate) fn call_target_type(bv: &BinaryView, call_target: u64) -> Option<MessageSendType> {
128128
let name = bv
129129
.symbol_by_address(call_target)
130130
.map(|s| s.raw_name().to_string_lossy().into_owned())?;
@@ -141,7 +141,7 @@ fn call_target_type(bv: &BinaryView, call_target: u64) -> Option<MessageSendType
141141
}
142142
}
143143

144-
fn selector_from_call(
144+
pub(crate) fn selector_from_call(
145145
bv: &BinaryView,
146146
ssa: &LowLevelILFunction<Mutable, SSA>,
147147
call_op: &Operation<Mutable, SSA, CallSsa>,

plugins/workflow_objc/src/workflow.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ pub fn register_activities() -> Result<(), WorkflowRegistrationError> {
3737
run(activities::objc_msg_send_calls::process),
3838
);
3939

40+
let name_stubs_activity = Activity::new_with_action(
41+
activity::Config::action(
42+
"core.function.objectiveC.nameSelectorStubs",
43+
"Obj-C: Rename Message Send Stubs",
44+
"Reconstruct names for Objective-C selector stubs, such as _objc_msgSend$foo, that have no symbol table entry.",
45+
)
46+
.eligibility(
47+
activity::Eligibility::auto().predicate(
48+
activity::ViewType::in_(["Mach-O", "DSCView"]),
49+
)),
50+
run(activities::name_stubs::process),
51+
);
52+
4053
let inline_stubs_activity = Activity::new_with_action(
4154
activity::Config::action(
4255
"core.function.objectiveC.inlineStubs",
@@ -93,7 +106,8 @@ pub fn register_activities() -> Result<(), WorkflowRegistrationError> {
93106
);
94107

95108
workflow
96-
.activity_after(&inline_stubs_activity, "core.function.translateTailCalls")?
109+
.activity_after(&name_stubs_activity, "core.function.translateTailCalls")?
110+
.activity_after(&inline_stubs_activity, &name_stubs_activity.name())?
97111
.activity_after(&objc_msg_send_calls_activity, &inline_stubs_activity.name())?
98112
.activity_before(
99113
&remove_memory_management_activity,

0 commit comments

Comments
 (0)