Skip to content

Commit a20a8b8

Browse files
committed
Detect calls that the Objective-C workflow has rewritten directly to an implementation
These are detected by their function names having the characteristic `-[ClassName methodName:]` format. Calls to functions with this naming pattern that accept a selector as a second argument are assumed to be `objc_msgSend` calls that the Objective-C workflow rewrote. These calls are formatted identically to other `objc_msgSend` calls with the exception that the selector tokens reference the address of the call target rather than the selector string, so double-clicking on them takes you to the fixed destination of the rewritten call.
1 parent 31d3a4d commit a20a8b8

File tree

2 files changed

+73
-35
lines changed

2 files changed

+73
-35
lines changed

lang/c/pseudoobjc.cpp

+72-34
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,43 @@ void SplitSelector(const std::string& selector, std::vector<std::string>& tokens
6060
tokens.push_back(token);
6161
}
6262

63+
std::optional<std::pair<uint64_t, Ref<Symbol>>> GetCallTargetInfo(const HighLevelILInstruction& callTarget,
64+
const std::vector<HighLevelILInstruction>& parameterExprs, const Function& function)
65+
{
66+
uint64_t constant = 0;
67+
Ref<Symbol> symbol;
68+
69+
switch (callTarget.operation)
70+
{
71+
case HLIL_CONST_PTR:
72+
{
73+
constant = callTarget.GetConstant<HLIL_CONST_PTR>();
74+
symbol = function.GetView()->GetSymbolByAddress(constant);
75+
break;
76+
}
77+
case HLIL_IMPORT:
78+
{
79+
constant = callTarget.GetConstant<HLIL_IMPORT>();
80+
auto importAddressSymbol = function.GetView()->GetSymbolByAddress(constant);
81+
if (!importAddressSymbol)
82+
return std::nullopt;
83+
84+
const auto symbolType = importAddressSymbol->GetType();
85+
if (symbolType != ImportedDataSymbol && symbolType != ImportAddressSymbol)
86+
return std::nullopt;
87+
88+
symbol = Symbol::ImportedFunctionFromImportAddressSymbol(importAddressSymbol, constant);
89+
}
90+
default:
91+
break;
92+
}
93+
94+
if (!symbol)
95+
return std::nullopt;
96+
97+
return std::make_pair(constant, symbol);
98+
}
99+
63100
struct RuntimeCall
64101
{
65102
enum Type
@@ -78,6 +115,7 @@ struct RuntimeCall
78115

79116
Type type;
80117
uint64_t address;
118+
bool isRewritten = false;
81119
};
82120

83121
constexpr std::array RUNTIME_CALLS = {
@@ -114,36 +152,10 @@ constexpr std::array RUNTIME_CALLS = {
114152
std::optional<RuntimeCall> DetectObjCRuntimeCall(const HighLevelILInstruction& callTarget,
115153
const std::vector<HighLevelILInstruction>& parameterExprs, const Function& function)
116154
{
117-
uint64_t constant = 0;
118-
Ref<Symbol> symbol;
119-
120-
switch (callTarget.operation)
121-
{
122-
case HLIL_CONST_PTR:
123-
{
124-
constant = callTarget.GetConstant<HLIL_CONST_PTR>();
125-
symbol = function.GetView()->GetSymbolByAddress(constant);
126-
break;
127-
}
128-
case HLIL_IMPORT:
129-
{
130-
constant = callTarget.GetConstant<HLIL_IMPORT>();
131-
auto importAddressSymbol = function.GetView()->GetSymbolByAddress(constant);
132-
if (!importAddressSymbol)
133-
return std::nullopt;
134-
135-
const auto symbolType = importAddressSymbol->GetType();
136-
if (symbolType != ImportedDataSymbol && symbolType != ImportAddressSymbol)
137-
return std::nullopt;
138-
139-
symbol = Symbol::ImportedFunctionFromImportAddressSymbol(importAddressSymbol, constant);
140-
}
141-
default:
142-
break;
143-
}
144-
145-
if (!symbol)
155+
auto callTargetInfo = GetCallTargetInfo(callTarget, parameterExprs, function);
156+
if (!callTargetInfo)
146157
return std::nullopt;
158+
auto [constant, symbol] = callTargetInfo.value();
147159

148160
const auto symbolShortName = symbol->GetShortName();
149161
auto it = std::find_if(RUNTIME_CALLS.begin(), RUNTIME_CALLS.end(), [&](const auto& pair) {
@@ -155,6 +167,26 @@ std::optional<RuntimeCall> DetectObjCRuntimeCall(const HighLevelILInstruction& c
155167
return RuntimeCall {it->second, constant};
156168
}
157169

170+
std::optional<RuntimeCall> DetectRewrittenDirectObjCMethodCall(const HighLevelILInstruction& callTarget,
171+
const std::vector<HighLevelILInstruction>& parameterExprs, const Function& function)
172+
{
173+
auto callTargetInfo = GetCallTargetInfo(callTarget, parameterExprs, function);
174+
if (!callTargetInfo)
175+
return std::nullopt;
176+
auto [constant, symbol] = callTargetInfo.value();
177+
178+
const auto symbolShortName = symbol->GetShortName();
179+
if (symbolShortName.length() < 6)
180+
return std::nullopt;
181+
182+
// Look for the pattern -[ClassName methodName:] or +[ClassName methodName:]
183+
if ((symbolShortName[0] != '-' && symbolShortName[0] != '+') || symbolShortName[1] != '['
184+
|| symbolShortName.back() != ']' || symbolShortName.find(' ') == std::string::npos)
185+
return std::nullopt;
186+
187+
return RuntimeCall {RuntimeCall::MessageSend, constant, true};
188+
}
189+
158190
} // unnamed namespace
159191

160192
PseudoObjCFunction::PseudoObjCFunction(LanguageRepresentationFunctionType* type, Architecture* arch, Function* owner,
@@ -169,6 +201,9 @@ void PseudoObjCFunction::GetExpr_CALL_OR_TAILCALL(const BinaryNinja::HighLevelIL
169201
const auto parameterExprs = instr.GetParameterExprs();
170202

171203
auto objCRuntimeCall = DetectObjCRuntimeCall(destExpr, parameterExprs, *GetFunction());
204+
if (!objCRuntimeCall)
205+
objCRuntimeCall = DetectRewrittenDirectObjCMethodCall(destExpr, parameterExprs, *GetFunction());
206+
172207
if (!objCRuntimeCall)
173208
return PseudoCFunction::GetExpr_CALL_OR_TAILCALL(instr, tokens, settings, precedence, statement);
174209

@@ -177,7 +212,7 @@ void PseudoObjCFunction::GetExpr_CALL_OR_TAILCALL(const BinaryNinja::HighLevelIL
177212
{
178213
case RuntimeCall::MessageSend:
179214
case RuntimeCall::MessageSendSuper:
180-
if (GetExpr_ObjCMsgSend(destExpr, tokens, settings, parameterExprs))
215+
if (GetExpr_ObjCMsgSend(objCRuntimeCall->address, objCRuntimeCall->isRewritten, destExpr, tokens, settings, parameterExprs))
181216
{
182217
if (statement)
183218
tokens.AppendSemicolon();
@@ -224,8 +259,9 @@ void PseudoObjCFunction::GetExpr_CALL_OR_TAILCALL(const BinaryNinja::HighLevelIL
224259
return PseudoCFunction::GetExpr_CALL_OR_TAILCALL(instr, tokens, settings, precedence, statement);
225260
}
226261

227-
bool PseudoObjCFunction::GetExpr_ObjCMsgSend(const HighLevelILInstruction& instr, HighLevelILTokenEmitter& tokens,
228-
DisassemblySettings* settings, const std::vector<HighLevelILInstruction>& parameterExprs)
262+
bool PseudoObjCFunction::GetExpr_ObjCMsgSend(uint64_t msgSendAddress, bool isRewritten,
263+
const HighLevelILInstruction& instr, HighLevelILTokenEmitter& tokens, DisassemblySettings* settings,
264+
const std::vector<HighLevelILInstruction>& parameterExprs)
229265
{
230266
if (parameterExprs.size() < 2)
231267
return false;
@@ -238,6 +274,8 @@ bool PseudoObjCFunction::GetExpr_ObjCMsgSend(const HighLevelILInstruction& instr
238274
std::vector<std::string> selectorTokens {2};
239275
SplitSelector(selector, selectorTokens);
240276

277+
uint64_t referencedAddress = isRewritten ? msgSendAddress : selectorAddress;
278+
241279
tokens.AppendOpenBracket();
242280

243281
GetExprText(parameterExprs[0], tokens, settings);
@@ -249,7 +287,7 @@ bool PseudoObjCFunction::GetExpr_ObjCMsgSend(const HighLevelILInstruction& instr
249287
if (index < selectorTokens.size())
250288
{
251289
tokens.Append(
252-
DataSymbolToken, StringReferenceTokenContext, selectorTokens[index], instr.address, selectorAddress);
290+
DataSymbolToken, StringReferenceTokenContext, selectorTokens[index], instr.address, referencedAddress);
253291
tokens.Append(TextToken, ":");
254292
}
255293
else
@@ -264,7 +302,7 @@ bool PseudoObjCFunction::GetExpr_ObjCMsgSend(const HighLevelILInstruction& instr
264302
for (size_t index = parameterExprs.size(); index < selectorTokens.size(); index++)
265303
{
266304
tokens.Append(
267-
DataSymbolToken, StringReferenceTokenContext, selectorTokens[index], instr.address, selectorAddress);
305+
DataSymbolToken, StringReferenceTokenContext, selectorTokens[index], instr.address, referencedAddress);
268306
if (index != selectorTokens.size() - 1 || selector.back() == ':')
269307
tokens.Append(TextToken, ":");
270308
}

lang/c/pseudoobjc.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class PseudoObjCFunction : public PseudoCFunction
2222
BNOperatorPrecedence precedence, bool statement) override;
2323

2424
private:
25-
bool GetExpr_ObjCMsgSend(const BinaryNinja::HighLevelILInstruction& expr,
25+
bool GetExpr_ObjCMsgSend(uint64_t msgSendAddress, bool isRewritten, const BinaryNinja::HighLevelILInstruction& expr,
2626
BinaryNinja::HighLevelILTokenEmitter& tokens, BinaryNinja::DisassemblySettings* settings,
2727
const std::vector<BinaryNinja::HighLevelILInstruction>& parameterExprs);
2828
bool GetExpr_GenericObjCRuntimeCall(uint64_t address, const BinaryNinja::HighLevelILInstruction& expr,

0 commit comments

Comments
 (0)