diff options
Diffstat (limited to 'lang/c/pseudoobjc.cpp')
| -rw-r--r-- | lang/c/pseudoobjc.cpp | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/lang/c/pseudoobjc.cpp b/lang/c/pseudoobjc.cpp new file mode 100644 index 00000000..eccf10f1 --- /dev/null +++ b/lang/c/pseudoobjc.cpp @@ -0,0 +1,421 @@ +#include "pseudoobjc.h" + +#include "binaryninjaapi.h" +#include "highlevelilinstruction.h" +#include <optional> +#include <string> +#include <vector> + +using namespace BinaryNinja; + +namespace { + +bool ParameterIsString(const HighLevelILInstruction& expr) +{ + if (expr.operation != HLIL_CONST_PTR) + return false; + + auto exprType = expr.GetType(); + if (!exprType || exprType->GetClass() != PointerTypeClass) + return false; + + if (auto child = exprType->GetChildType(); child) + { + child = child->IsArray() ? child->GetChildType() : child; + return child->IsInteger() && child->IsSigned() && child->GetWidth() == 1; + } + return false; +} + +struct SelectorReference +{ + std::string name; + uint64_t address; +}; + +std::optional<SelectorReference> GetSelectorFromParameter( + const HighLevelILInstruction& expr, const Function& function) +{ + if (expr.operation != HLIL_CONST_PTR) + return std::nullopt; + + if (!ParameterIsString(expr)) + return std::nullopt; + + uint64_t constant = expr.GetConstant<HLIL_CONST_PTR>(); + std::string string; + auto stringType = function.GetView()->CheckForStringAnnotationType(constant, string, true, true, 1); + + if (!stringType || (stringType != AsciiString && stringType != Utf8String)) + return std::nullopt; + + return SelectorReference {string, constant}; +} + +void SplitSelector(const std::string& selector, std::vector<std::string>& tokens) +{ + std::stringstream ss(selector); + std::string token; + while (std::getline(ss, token, ':')) + tokens.push_back(token); +} + +struct RuntimeCall +{ + enum Type + { + MessageSend, + MessageSendSuper, + Alloc, + AllocInit, + New, + Retain, + Release, + Autorelease, + RetainAutorelease, + Class, + }; + + Type type; + uint64_t address; +}; + +constexpr std::array RUNTIME_CALLS = { + std::make_pair("_objc_alloc_init", RuntimeCall::AllocInit), + std::make_pair("_objc_alloc", RuntimeCall::Alloc), + std::make_pair("_objc_autorelease", RuntimeCall::Autorelease), + std::make_pair("_objc_autoreleaseReturnValue", RuntimeCall::Autorelease), + std::make_pair("_objc_msgSend", RuntimeCall::MessageSend), + std::make_pair("_objc_msgSendSuper", RuntimeCall::MessageSendSuper), + std::make_pair("_objc_msgSendSuper2", RuntimeCall::MessageSendSuper), + std::make_pair("_objc_opt_class", RuntimeCall::Class), + std::make_pair("_objc_opt_new", RuntimeCall::New), + std::make_pair("_objc_release", RuntimeCall::Release), + std::make_pair("_objc_retain", RuntimeCall::Retain), + std::make_pair("_objc_retainAutoreleasedReturnValue", RuntimeCall::Retain), + std::make_pair("_objc_retainAutoreleaseReturnValue", RuntimeCall::RetainAutorelease), + std::make_pair("_objc_retainBlock", RuntimeCall::Retain), + std::make_pair("j__objc_alloc_init", RuntimeCall::AllocInit), + std::make_pair("j__objc_alloc", RuntimeCall::Alloc), + std::make_pair("j__objc_autorelease", RuntimeCall::Autorelease), + std::make_pair("j__objc_autoreleaseReturnValue", RuntimeCall::Autorelease), + std::make_pair("j__objc_msgSend", RuntimeCall::MessageSend), + std::make_pair("j__objc_msgSendSuper", RuntimeCall::MessageSendSuper), + std::make_pair("j__objc_msgSendSuper2", RuntimeCall::MessageSendSuper), + std::make_pair("j__objc_opt_class", RuntimeCall::Class), + std::make_pair("j__objc_opt_new", RuntimeCall::New), + std::make_pair("j__objc_release", RuntimeCall::Release), + std::make_pair("j__objc_retain", RuntimeCall::Retain), + std::make_pair("j__objc_retainAutoreleasedReturnValue", RuntimeCall::Retain), + std::make_pair("j__objc_retainAutoreleaseReturnValue", RuntimeCall::RetainAutorelease), + std::make_pair("j__objc_retainBlock", RuntimeCall::Retain), +}; + +std::optional<RuntimeCall> DetectObjCRuntimeCall(const HighLevelILInstruction& callTarget, + const std::vector<HighLevelILInstruction>& parameterExprs, const Function& function) +{ + uint64_t constant = 0; + Ref<Symbol> symbol; + + switch (callTarget.operation) + { + case HLIL_CONST_PTR: + { + constant = callTarget.GetConstant<HLIL_CONST_PTR>(); + symbol = function.GetView()->GetSymbolByAddress(constant); + break; + } + case HLIL_IMPORT: + { + constant = callTarget.GetConstant<HLIL_IMPORT>(); + auto importAddressSymbol = function.GetView()->GetSymbolByAddress(constant); + if (!importAddressSymbol) + return std::nullopt; + + const auto symbolType = importAddressSymbol->GetType(); + if (symbolType != ImportedDataSymbol && symbolType != ImportAddressSymbol) + return std::nullopt; + + symbol = Symbol::ImportedFunctionFromImportAddressSymbol(importAddressSymbol, constant); + } + default: + break; + } + + if (!symbol) + return std::nullopt; + + const auto symbolShortName = symbol->GetShortName(); + auto it = std::find_if(RUNTIME_CALLS.begin(), RUNTIME_CALLS.end(), [&](const auto& pair) { + return pair.first == symbolShortName; + }); + if (it == RUNTIME_CALLS.end()) + return std::nullopt; + + return RuntimeCall {it->second, constant}; +} + +} // unnamed namespace + +PseudoObjCFunction::PseudoObjCFunction(LanguageRepresentationFunctionType* type, Architecture* arch, Function* owner, + HighLevelILFunction* highLevelILFunction) : PseudoCFunction(type, arch, owner, highLevelILFunction) +{} + +void PseudoObjCFunction::GetExpr_CALL_OR_TAILCALL(const BinaryNinja::HighLevelILInstruction& instr, + BinaryNinja::HighLevelILTokenEmitter& tokens, BinaryNinja::DisassemblySettings* settings, + BNOperatorPrecedence precedence, bool statement) +{ + const auto destExpr = instr.GetDestExpr(); + const auto parameterExprs = instr.GetParameterExprs(); + + auto objCRuntimeCall = DetectObjCRuntimeCall(destExpr, parameterExprs, *GetFunction()); + if (!objCRuntimeCall) + return PseudoCFunction::GetExpr_CALL_OR_TAILCALL(instr, tokens, settings, precedence, statement); + + std::vector<std::string_view> runtimeCallTokens; + switch (objCRuntimeCall->type) + { + case RuntimeCall::MessageSend: + case RuntimeCall::MessageSendSuper: + if (GetExpr_ObjCMsgSend(destExpr, tokens, settings, parameterExprs)) + { + if (statement) + tokens.AppendSemicolon(); + return; + } + break; + case RuntimeCall::Alloc: + runtimeCallTokens = {"alloc"}; + break; + case RuntimeCall::AllocInit: + runtimeCallTokens = {"alloc", "init"}; + break; + case RuntimeCall::New: + runtimeCallTokens = {"new"}; + break; + case RuntimeCall::Retain: + runtimeCallTokens = {"retain"}; + break; + case RuntimeCall::Release: + runtimeCallTokens = {"release"}; + break; + case RuntimeCall::Autorelease: + runtimeCallTokens = {"autorelease"}; + break; + case RuntimeCall::RetainAutorelease: + runtimeCallTokens = {"retain", "autorelease"}; + break; + case RuntimeCall::Class: + runtimeCallTokens = {"class"}; + break; + default: + break; + } + + if (runtimeCallTokens.size() + && GetExpr_GenericObjCRuntimeCall( + objCRuntimeCall->address, instr, tokens, settings, parameterExprs, runtimeCallTokens)) + { + if (statement) + tokens.AppendSemicolon(); + return; + } + + return PseudoCFunction::GetExpr_CALL_OR_TAILCALL(instr, tokens, settings, precedence, statement); +} + +bool PseudoObjCFunction::GetExpr_ObjCMsgSend(const HighLevelILInstruction& instr, HighLevelILTokenEmitter& tokens, + DisassemblySettings* settings, const std::vector<HighLevelILInstruction>& parameterExprs) +{ + if (parameterExprs.size() < 2) + return false; + + auto maybeSelector = GetSelectorFromParameter(parameterExprs[1], *GetFunction()); + if (!maybeSelector) + return false; + + auto [selector, selectorAddress] = maybeSelector.value(); + std::vector<std::string> selectorTokens {2}; + SplitSelector(selector, selectorTokens); + + tokens.AppendOpenBracket(); + + GetExprText(parameterExprs[0], tokens, settings); + + for (size_t index = 2; index < parameterExprs.size(); index++) + { + const auto& parameterExpr = parameterExprs[index]; + tokens.Append(TextToken, " "); + if (index < selectorTokens.size()) + { + tokens.Append( + DataSymbolToken, StringReferenceTokenContext, selectorTokens[index], instr.address, selectorAddress); + tokens.Append(TextToken, ":"); + } + else + { + tokens.Append(TextToken, ", "); + } + GetExprText(parameterExpr, tokens, settings); + } + if (selectorTokens.size() > parameterExprs.size()) + { + tokens.Append(TextToken, " "); + for (size_t index = parameterExprs.size(); index < selectorTokens.size(); index++) + { + tokens.Append( + DataSymbolToken, StringReferenceTokenContext, selectorTokens[index], instr.address, selectorAddress); + if (index != selectorTokens.size() - 1 || selector.back() == ':') + tokens.Append(TextToken, ":"); + } + } + tokens.AppendCloseBracket(); + return true; +} + +bool PseudoObjCFunction::GetExpr_GenericObjCRuntimeCall(uint64_t address, const HighLevelILInstruction& instr, + HighLevelILTokenEmitter& tokens, DisassemblySettings* settings, + const std::vector<HighLevelILInstruction>& parameterExprs, const std::vector<std::string_view>& selectorTokens) +{ + if (parameterExprs.size() < 1) + return false; + + for (auto _ : selectorTokens) + tokens.AppendOpenBracket(); + + GetExprText(parameterExprs[0], tokens, settings); + for (auto& token : selectorTokens) + { + tokens.Append(TextToken, " "); + tokens.Append(CodeSymbolToken, StringReferenceTokenContext, std::string(token), instr.address, address); + tokens.AppendCloseBracket(); + } + return true; +} + +void PseudoObjCFunction::GetExpr_CONST_PTR(const BinaryNinja::HighLevelILInstruction& instr, + BinaryNinja::HighLevelILTokenEmitter& tokens, BinaryNinja::DisassemblySettings* settings, + BNOperatorPrecedence precedence, bool statement) +{ + uint64_t constant = instr.GetConstant<HLIL_CONST_PTR>(); + auto symbol = GetFunction()->GetView()->GetSymbolByAddress(constant); + if (!symbol) + return PseudoCFunction::GetExpr_CONST_PTR(instr, tokens, settings, precedence, statement); + + auto shortName = symbol->GetShortName(); + + // Match class references based only on the symbol name as the class metadata may be imported + // from a different image. + if (shortName.rfind("_OBJC_CLASS_$_", 0) == 0 || shortName.rfind("cls_", 0) == 0) + { + if (GetExpr_OBJC_CLASS(*symbol, constant, instr, tokens, settings, precedence, statement)) + return; + } + + DataVariable variable {}; + auto hasVariable = GetFunction()->GetView()->GetDataVariableAtAddress(constant, variable); + if (!hasVariable) + return PseudoCFunction::GetExpr_CONST_PTR(instr, tokens, settings, precedence, statement); + + auto type = variable.type->IsNamedTypeRefer() ? + GetFunction()->GetView()->GetTypeByRef(variable.type->GetNamedTypeReference()) : + variable.type.GetValue(); + + if (type->GetClass() != StructureTypeClass) + return PseudoCFunction::GetExpr_CONST_PTR(instr, tokens, settings, precedence, statement); + + auto structureName = type->GetStructureName().GetString(); + if (structureName == "__NSConstantString") + { + if (GetExpr_NSConstantString(type, constant, instr, tokens, settings, precedence, statement)) + return; + } + + PseudoCFunction::GetExpr_CONST_PTR(instr, tokens, settings, precedence, statement); +} + +bool PseudoObjCFunction::GetExpr_OBJC_CLASS(const Symbol& symbol, uint64_t constant, + const BinaryNinja::HighLevelILInstruction& instr, BinaryNinja::HighLevelILTokenEmitter& tokens, + BinaryNinja::DisassemblySettings* settings, BNOperatorPrecedence precedence, bool statement) +{ + auto shortName = symbol.GetShortName(); + std::string className; + if (shortName.rfind("_OBJC_CLASS_$_", 0) == 0) + className = shortName.substr(14); + else if (shortName.rfind("cls_", 0) == 0) + className = shortName.substr(4); + + if (className.empty()) + return false; + + tokens.Append(DataSymbolToken, ConstDataTokenContext, className, instr.address, constant); + if (statement) + tokens.AppendSemicolon(); + + return true; +} + +bool PseudoObjCFunction::GetExpr_NSConstantString(Ref<Type> type, uint64_t constant, + const BinaryNinja::HighLevelILInstruction& instr, BinaryNinja::HighLevelILTokenEmitter& tokens, + BinaryNinja::DisassemblySettings* settings, BNOperatorPrecedence precedence, bool statement) +{ + StructureMember dataMember; + bool hasDataField = type->GetStructure()->GetMemberByName("data", dataMember); + if (!hasDataField) + return false; + + uint64_t dataPointer = 0; + if (!GetFunction()->GetView()->Read( + &dataPointer, constant + dataMember.offset, GetFunction()->GetView()->GetAddressSize())) + return false; + + std::string stringValue; + if (!GetFunction()->GetView()->CheckForStringAnnotationType(dataPointer, stringValue, true, true, 1)) + return false; + + // TODO: Ideally this'd be part of the same token as the quotes for the string literal. + // Sometimes the view ends up wrapping between the @ and the quote. + tokens.Append(TextToken, "@"); + tokens.AppendConstantTextToken( + instr, dataPointer, GetFunction()->GetView()->GetAddressSize(), settings, MemberAndFunctionOperatorPrecedence); + if (statement) + tokens.AppendSemicolon(); + + return true; +} + +void PseudoObjCFunction::GetExpr_IMPORT(const BinaryNinja::HighLevelILInstruction& instr, + BinaryNinja::HighLevelILTokenEmitter& tokens, BinaryNinja::DisassemblySettings* settings, + BNOperatorPrecedence precedence, bool statement) +{ + const auto constant = instr.GetConstant<HLIL_IMPORT>(); + auto symbol = GetHighLevelILFunction()->GetFunction()->GetView()->GetSymbolByAddress(constant); + const auto symbolType = symbol->GetType(); + + if (symbol && (symbolType == ImportedDataSymbol || symbolType == ImportAddressSymbol)) + { + symbol = Symbol::ImportedFunctionFromImportAddressSymbol(symbol, constant); + const auto symbolShortName = symbol->GetShortName(); + if (symbolShortName.rfind("_OBJC_CLASS_$_", 0) == 0) + { + tokens.Append(IndirectImportToken, ConstDataTokenContext, symbolShortName.substr(14), instr.address, + constant); + if (statement) + tokens.AppendSemicolon(); + return; + } + tokens.Append(IndirectImportToken, NoTokenContext, symbolShortName, instr.address, constant, instr.size, instr.sourceOperand); + return; + } + + PseudoCFunction::GetExpr_IMPORT(instr, tokens, settings, precedence, statement); +} + + +PseudoObjCFunctionType::PseudoObjCFunctionType() : PseudoCFunctionType("Pseudo Objective-C") {} + +Ref<LanguageRepresentationFunction> PseudoObjCFunctionType::Create( + Architecture* arch, Function* owner, HighLevelILFunction* highLevelILFunction) +{ + return new PseudoObjCFunction(this, arch, owner, highLevelILFunction); +} |
