summaryrefslogtreecommitdiff
path: root/plugins/workflow_objc/src/activities/objc_msg_send_calls.rs
blob: 41fc38c065d64f0db98987adc3bce401cf8fe258 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use binaryninja::{
    binary_view::BinaryView,
    function::Function,
    low_level_il::{
        expression::{ExpressionHandler as _, LowLevelILExpressionKind},
        function::{LowLevelILFunction, Mutable, NonSSA, SSA},
        instruction::{InstructionHandler as _, LowLevelILInstruction, LowLevelILInstructionKind},
        operation::{CallSsa, Operation},
    },
    variable::PossibleValueSet,
    workflow::AnalysisContext,
};

use crate::{
    error::ILLevel,
    metadata::{GlobalState, Selector},
    Error,
};

mod adjust_call_type;
mod rewrite_to_direct_call;

// Apply all transformations that are specific to calls to `objc_msgSend` and `objc_msgSendSuper2`
// At present these are:
// 1. Call type adjustments
// 2. Rewriting to direct calls, if enabled.
pub fn process(ac: &AnalysisContext) -> Result<(), Error> {
    let bv = ac.view();
    if GlobalState::should_ignore_view(&bv) {
        return Ok(());
    }

    let func_start = ac.function().start();
    let Some(llil) = (unsafe { ac.llil_function() }) else {
        return Err(Error::MissingIL {
            level: ILLevel::Low,
            func_start,
        });
    };

    let Some(ssa) = llil.ssa_form() else {
        return Err(Error::MissingSsaForm {
            level: ILLevel::Low,
            func_start,
        });
    };

    let func = ac.function();
    let mut function_changed = false;
    for block in ssa.basic_blocks().iter() {
        for insn in block.iter() {
            match process_instruction(&bv, &func, &llil, &ssa, &insn) {
                Ok(true) => function_changed = true,
                Ok(_) => {}
                Err(err) => {
                    tracing::error!(
                        "Error processing instruction at {:#x}: {}",
                        insn.address(),
                        err
                    );
                    continue;
                }
            }
        }
    }

    if function_changed {
        // Regenerate SSA form after modifications
        llil.generate_ssa_form();
    }
    Ok(())
}

// Process a single instruction, looking for calls to `objc_msgSend` or `objc_msgSendSuper2`
// Returns `Ok(false)` if the instruction is not a relevant call or if the function was not
// modified. Returns `Ok(true)` to indicate that the function was modified.
fn process_instruction(
    bv: &BinaryView,
    func: &Function,
    llil: &LowLevelILFunction<Mutable, NonSSA>,
    ssa: &LowLevelILFunction<Mutable, SSA>,
    insn: &LowLevelILInstruction<Mutable, SSA>,
) -> Result<bool, &'static str> {
    let call_op = match insn.kind() {
        LowLevelILInstructionKind::CallSsa(op) => op,
        LowLevelILInstructionKind::TailCallSsa(op) => op,
        _ => return Ok(false),
    };

    let target = call_op.target();
    let target_values = target.possible_values();

    // Check if the target is a constant pointer to objc_msgSend
    let call_target = match target_values {
        PossibleValueSet::ConstantValue { value }
        | PossibleValueSet::ConstantPointerValue { value }
        | PossibleValueSet::ImportedAddressValue { value } => value as u64,
        _ => return Ok(false),
    };

    let Some(message_send_type) = call_target_type(bv, call_target) else {
        return Ok(false);
    };

    let Some(selector) = selector_from_call(bv, ssa, &call_op) else {
        return Ok(false);
    };

    let mut function_changed = false;
    if adjust_call_type::process_call(bv, func, ssa, insn, &selector, message_send_type).is_ok() {
        function_changed = true;
    }

    if rewrite_to_direct_call::process_call(bv, llil, insn, &selector, message_send_type).is_ok() {
        function_changed = true;
    }

    Ok(function_changed)
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum MessageSendType {
    Normal,
    Super,
}

fn call_target_type(bv: &BinaryView, call_target: u64) -> Option<MessageSendType> {
    let name = bv
        .symbol_by_address(call_target)
        .map(|s| s.raw_name().to_string_lossy().into_owned())?;

    // Strip the `j_` prefix that the shared cache adds to the names of stub functions
    let name = name.strip_prefix("j_").unwrap_or(&name);

    if name == "_objc_msgSend" {
        Some(MessageSendType::Normal)
    } else if name == "_objc_msgSendSuper" || name == "_objc_msgSendSuper2" {
        Some(MessageSendType::Super)
    } else {
        None
    }
}

fn selector_from_call(
    bv: &BinaryView,
    ssa: &LowLevelILFunction<Mutable, SSA>,
    call_op: &Operation<Mutable, SSA, CallSsa>,
) -> Option<Selector> {
    let LowLevelILExpressionKind::CallParamSsa(params) = &call_op.param_expr().kind() else {
        return None;
    };

    let param_exprs = params.param_exprs();
    if param_exprs.is_empty() {
        return None;
    }

    let param_exprs =
        if let LowLevelILExpressionKind::SeparateParamListSsa(params) = &param_exprs[0].kind() {
            params.param_exprs()
        } else {
            param_exprs
        };

    let LowLevelILExpressionKind::RegSsa(reg) = param_exprs.get(1)?.kind() else {
        return None;
    };

    let raw_selector = ssa.get_ssa_register_value(reg.source_reg())?.value as u64;
    if raw_selector == 0 {
        return None;
    }

    Selector::from_address(bv, raw_selector).ok()
}