Skip to content

Commit f2c275d

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 f2c275d

4 files changed

Lines changed: 118 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: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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
37+
|| func.basic_blocks().len() != 1
38+
{
39+
return Ok(());
40+
}
41+
42+
let Some(llil) = (unsafe { ac.llil_function() }) else {
43+
return Err(Error::MissingIL {
44+
level: ILLevel::Low,
45+
func_start,
46+
});
47+
};
48+
let Some(ssa) = llil.ssa_form() else {
49+
return Err(Error::MissingSsaForm {
50+
level: ILLevel::Low,
51+
func_start,
52+
});
53+
};
54+
55+
// The tail call terminates the stub's single basic block, so it can only be the last instruction.
56+
let blocks = ssa.basic_blocks();
57+
let Some(block) = blocks.iter().next() else {
58+
return Ok(());
59+
};
60+
let Some(insn) = block.iter().last() else {
61+
return Ok(());
62+
};
63+
let LowLevelILInstructionKind::TailCallSsa(call_op) = insn.kind() else {
64+
return Ok(());
65+
};
66+
67+
// A selector stub does nothing besides load a selector and tail-call objc_msgSend.
68+
// Reject any function that performs a regular call or memory write before the tail call.
69+
if block.iter().any(|insn| {
70+
matches!(
71+
insn.kind(),
72+
LowLevelILInstructionKind::CallSsa(_)
73+
| LowLevelILInstructionKind::Store(_)
74+
| LowLevelILInstructionKind::StoreSsa(_)
75+
)
76+
}) {
77+
return Ok(());
78+
}
79+
80+
let call_target = match call_op.target().possible_values() {
81+
PossibleValueSet::ConstantValue { value }
82+
| PossibleValueSet::ConstantPointerValue { value }
83+
| PossibleValueSet::ImportedAddressValue { value } => value as u64,
84+
_ => return Ok(()),
85+
};
86+
87+
if call_target_type(&view, call_target) != Some(MessageSendType::Normal) {
88+
return Ok(());
89+
}
90+
let Some(selector) = selector_from_call(&view, &ssa, &call_op) else {
91+
return Ok(());
92+
};
93+
94+
let name = format!("_objc_msgSend${}", selector.name);
95+
let symbol = Symbol::builder(SymbolType::Function, &name, func_start).create();
96+
view.define_auto_symbol(&symbol);
97+
98+
Ok(())
99+
}

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)