// // Created by kat on 8/6/24. // // TODO We could use an LLIL/MLIL workflow to rewrite off-image value-loads // (i.e. MLIL_VAR_LOAD.MLIL_DEREF.MLIL_CONST_PTR) to just read the value out of the cache and replace the load // in stub regions. // // This is a pretty rough workflow and has huge room for improvements all around. #include "SharedCacheWorkflow.h" #include "lowlevelilinstruction.h" #include "mediumlevelilinstruction.h" #include "../api/sharedcacheapi.h" #include "thread" struct GlobalWorkflowState { std::mutex imageLoadMutex; bool autoLoadStubsAndDyldData = true; bool autoLoadObjCStubRequirements = true; }; static std::unordered_map globalWorkflowState; std::mutex globalWorkflowStateMutex; GlobalWorkflowState* GetGlobalWorkflowState(Ref view) { std::unique_lock lock(globalWorkflowStateMutex); if (globalWorkflowState.find(view->GetFile()->GetSessionId()) == globalWorkflowState.end()) { globalWorkflowState[view->GetFile()->GetSessionId()] = new GlobalWorkflowState(); Ref settings = view->GetLoadSettings(VIEW_NAME); bool autoLoadStubsAndDyldData = true; if (settings && settings->Contains("loader.dsc.autoLoadStubsAndDyldData")) { autoLoadStubsAndDyldData = settings->Get("loader.dsc.autoLoadStubsAndDyldData", view); } globalWorkflowState[view->GetFile()->GetSessionId()]->autoLoadStubsAndDyldData = autoLoadStubsAndDyldData; bool autoLoadObjC = true; if (settings && settings->Contains("loader.dsc.autoLoadObjCStubRequirements")) { autoLoadObjC = settings->Get("loader.dsc.autoLoadObjCStubRequirements", view); } globalWorkflowState[view->GetFile()->GetSessionId()]->autoLoadObjCStubRequirements = autoLoadObjC; } return globalWorkflowState[view->GetFile()->GetSessionId()]; } std::vector splitSelector(const std::string& selector) { std::vector components; std::istringstream stream(selector); std::string component; while (std::getline(stream, component, ':')) { if (!component.empty()) { components.push_back(component); } } return components; } std::vector generateArgumentNames(const std::vector& components) { std::vector argumentNames; for (const std::string& component : components) { size_t startPos = component.find_last_of(" "); std::string argumentName = (startPos == std::string::npos) ? component : component.substr(startPos + 1); argumentNames.push_back(argumentName); } return argumentNames; } void SharedCacheWorkflow::ProcessOffImageCall(Ref ctx, Ref func, Ref mssa, const MediumLevelILInstruction dest, ExprId exprIndex, bool applySymbolIfFoundToCurrentFunction) { auto bv = func->GetView(); WorkerPriorityEnqueue([bv=bv, dest=dest, func=func, applySymbolIfFoundToCurrentFunction]() { auto workflowState = GetGlobalWorkflowState(bv); Ref cache = new SharedCacheAPI::SharedCache(bv); if (!cache) return; if (dest.operation != MLIL_CONST_PTR && dest.operation != MLIL_CONST) return; if (workflowState->autoLoadStubsAndDyldData && (cache->GetNameForAddress(dest.GetConstant()).find("dyld_shared_cache_branch_islands") != std::string::npos || cache->GetNameForAddress(dest.GetConstant()).find("::_stubs") != std::string::npos ) ) { if (cache->LoadSectionAtAddress(dest.GetConstant())) { func->Reanalyze(); } } else { if (applySymbolIfFoundToCurrentFunction) cache->FindSymbolAtAddrAndApplyToAddr(dest.GetConstant(), func->GetStart(), false); else cache->FindSymbolAtAddrAndApplyToAddr(dest.GetConstant(), dest.GetConstant(), false); } }); } void SharedCacheWorkflow::FixupStubs(Ref ctx) { try { const auto func = ctx->GetFunction(); const auto arch = func->GetArchitecture(); const auto bv = func->GetView(); auto workflowState = GetGlobalWorkflowState(bv); auto funcStart = func->GetStart(); auto sectionExists = !bv->GetSectionsAt(funcStart).empty(); if (!sectionExists) return; auto section = bv->GetSectionsAt(funcStart)[0]; auto imageName = section->GetName(); // remove everything after :: auto pos = imageName.find("::"); if (pos != std::string::npos) imageName = imageName.substr(0, pos); const auto llil = ctx->GetLowLevelILFunction(); if (!llil) { return; } const auto ssa = llil->GetSSAForm(); if (!ssa) { return; } const auto mlil = ctx->GetMediumLevelILFunction(); if (!mlil) { return; } const auto mssa = mlil->GetSSAForm(); if (!mssa) { return; } // Processor that automatically loads the libObjC image when it encounters a stub (so we can do inlining). if (workflowState->autoLoadObjCStubRequirements && section->GetName().find("__objc_stubs") != std::string::npos) { auto firstInstruction = mlil->GetInstruction(0); if (firstInstruction.operation == MLIL_TAILCALL) { auto dest = firstInstruction.GetDestExpr(); if (dest.operation == MLIL_CONST_PTR) { // We're ready, everything is here func->SetAutoInlinedDuringAnalysis(true); return; } } for (const auto& block : mssa->GetBasicBlocks()) { for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) { auto instr = mssa->GetInstruction(i); // current_il_function.ssa_form.get_ssa_var_value(current_il_instruction.dest.var) if (instr.operation == MLIL_JUMP) { if (instr.GetDestExpr().operation == MLIL_VAR_SSA) { auto dest = instr.GetDestExpr(); // RegisterValue value = mssa->GetSSAVarValue(instr.GetDestExpr().GetSourceSSAVariable()) // ExprId def = mssa->GetSSAVarDefinition(instr.GetDestExpr().GetSourceSSAVariable()); // MLILInstruction defInstr = mssa->GetInstruction(mssa->GetSSAVarDefinition(instr.GetDestExpr().GetSourceSSAVariable())); // targetOffset = mssa->GetInstruction(mssa->GetSSAVarDefinition(instr.GetDestExpr().GetSourceSSAVariable())).GetSourceExpr().GetSourceExpr().GetConstant(); auto value = mssa->GetSSAVarValue(dest.GetSourceSSAVariable()); if (value.state == UndeterminedValue) { bool otherFunctionAlreadyRunning; { otherFunctionAlreadyRunning = !workflowState->imageLoadMutex.try_lock(); if (!otherFunctionAlreadyRunning) workflowState->imageLoadMutex.unlock(); } if (otherFunctionAlreadyRunning) { return; } std::unique_lock lock(workflowState->imageLoadMutex); auto def = mssa->GetSSAVarDefinition(dest.GetSourceSSAVariable()); auto defInstr = mssa->GetInstruction(def); auto targetOffset = defInstr.GetSourceExpr().GetSourceExpr().GetConstant(); if (bv->IsValidOffset(targetOffset)) return; Ref cache = new SharedCacheAPI::SharedCache(bv); if (!cache) return; if (!cache->GetImageNameForAddress(targetOffset).empty()) { cache->LoadImageContainingAddress(targetOffset); } else { cache->LoadSectionAtAddress(targetOffset); } for (const auto §Func : bv->GetAnalysisFunctionList()) { if (section->GetStart() <= sectFunc->GetStart() && sectFunc->GetStart() < section->GetEnd()) { func->Reanalyze(); } } } } else if (instr.GetDestExpr().operation == MLIL_CONST_PTR) { bool otherFunctionAlreadyRunning; { otherFunctionAlreadyRunning = !workflowState->imageLoadMutex.try_lock(); if (!otherFunctionAlreadyRunning) workflowState->imageLoadMutex.unlock(); } if (otherFunctionAlreadyRunning) { return; } std::unique_lock lock(workflowState->imageLoadMutex); auto dest = instr.GetDestExpr(); auto targetOffset = dest.GetConstant(); if (bv->IsValidOffset(targetOffset)) return; Ref cache = new SharedCacheAPI::SharedCache(bv); if (!cache) return; if (!cache->GetImageNameForAddress(targetOffset).empty()) { cache->LoadImageContainingAddress(targetOffset); } else { cache->LoadSectionAtAddress(targetOffset); } for (const auto §Func : bv->GetAnalysisFunctionList()) { if (section->GetStart() <= sectFunc->GetStart() && sectFunc->GetStart() < section->GetEnd()) { func->Reanalyze(); } } } } } } return; } if (section->GetName().find("::_stubs") != std::string::npos // Branch Islands (iOS 16) || section->GetName().find("dyld_shared_cache_branch_islands") != std::string::npos // Branch Islands (iOS 11-?) || section->GetName().find("::__stubs") != std::string::npos // Stubs (non arm64e) || section->GetName().find("::__auth_stubs") != std::string::npos // Stubs (arm64e) ) { auto firstInstruction = mlil->GetInstruction(0); if (firstInstruction.operation == MLIL_TAILCALL) { auto dest = firstInstruction.GetDestExpr(); if (dest.operation == MLIL_CONST_PTR) { if (auto symbol = bv->GetSymbolByAddress(dest.GetConstant())) { auto newSymbol = new Symbol(FunctionSymbol, "j_" + symbol->GetRawName(), func->GetStart()); bv->DefineUserSymbol(newSymbol); } } } else if (firstInstruction.operation == MLIL_JUMP) { auto dest = firstInstruction.GetDestExpr(); if (dest.operation == MLIL_CONST_PTR) { if (!bv->IsValidOffset(dest.GetConstant())) { ProcessOffImageCall(ctx, func, mssa, dest, firstInstruction.GetSSAExprIndex(), true); } } else if (dest.operation == MLIL_LOAD) { if (dest.GetSourceExpr().operation == MLIL_CONST_PTR) { dest = dest.GetSourceExpr(); if (!bv->IsValidOffset(dest.GetConstant())) { ProcessOffImageCall(ctx, func, mssa, dest, firstInstruction.GetSSAExprIndex()); } } } } else { for (const auto& block : mssa->GetBasicBlocks()) { for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) { auto instr = mssa->GetInstruction(i); // current_il_function.ssa_form.get_ssa_var_value(current_il_instruction.dest.var) if (instr.operation == MLIL_JUMP) { if (instr.GetDestExpr().operation == MLIL_VAR_SSA) { auto dest = instr.GetDestExpr(); // RegisterValue value = mssa->GetSSAVarValue(instr.GetDestExpr().GetSourceSSAVariable()) ExprId def = mssa->GetSSAVarDefinition(instr.GetDestExpr().GetSourceSSAVariable()); // MLILInstruction defInstr = mssa->GetInstruction(mssa->GetSSAVarDefinition(instr.GetDestExpr().GetSourceSSAVariable())); // targetOffset = mssa->GetInstruction(mssa->GetSSAVarDefinition(instr.GetDestExpr().GetSourceSSAVariable())).GetSourceExpr().GetSourceExpr().GetConstant(); auto value = mssa->GetSSAVarValue(dest.GetSourceSSAVariable()); if (value.state == UndeterminedValue) { bool otherFunctionAlreadyRunning; { otherFunctionAlreadyRunning = !workflowState->imageLoadMutex.try_lock(); if (!otherFunctionAlreadyRunning) workflowState->imageLoadMutex.unlock(); } if (otherFunctionAlreadyRunning) { return; } std::unique_lock lock( workflowState->imageLoadMutex); auto def = mssa->GetSSAVarDefinition(dest.GetSourceSSAVariable()); auto defInstr = mssa->GetInstruction(def); auto targetOffset = defInstr.GetSourceExpr().GetSourceExpr().GetConstant(); if (bv->IsValidOffset(targetOffset)) return; Ref cache = new SharedCacheAPI::SharedCache(bv); if (!cache) return; if (!cache->GetImageNameForAddress(targetOffset).empty()) { cache->LoadImageContainingAddress(targetOffset); } else { cache->LoadSectionAtAddress(targetOffset); } for (const auto §Func : bv->GetAnalysisFunctionList()) { if (section->GetStart() <= sectFunc->GetStart() && sectFunc->GetStart() < section->GetEnd()) { func->Reanalyze(); } } } } } } } } return; } for (const auto& block : mssa->GetBasicBlocks()) { for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) { auto instr = mssa->GetInstruction(i); if (instr.operation == MLIL_CALL_SSA) { if (instr.GetDestExpr().operation == MLIL_CONST_PTR) { auto dest = instr.GetDestExpr(); if (!bv->IsValidOffset(dest.GetConstant())) { ProcessOffImageCall(ctx, func, mssa, dest, instr.GetSSAExprIndex()); } } } } } } catch (...) {} } static constexpr auto workflowInfo = R"({ "title": "Shared Cache Workflow", "description": "Shared Cache Workflow", "capabilities": [] })"; void fixObjCCallTypes(Ref ctx) { const auto func = ctx->GetFunction(); const auto arch = func->GetArchitecture(); const auto bv = func->GetView(); const auto llil = ctx->GetLowLevelILFunction(); if (!llil) { return; } const auto ssa = llil->GetSSAForm(); if (!ssa) { return; } const auto rewriteIfEligible = [bv, ssa](size_t insnIndex) { auto insn = ssa->GetInstruction(insnIndex); if (insn.operation == LLIL_CALL_SSA) { // Filter out calls that aren't to `objc_msgSend`. auto callExpr = insn.GetDestExpr(); bool isMessageSend = false; if (auto symbol = bv->GetSymbolByAddress(callExpr.GetValue().value)) isMessageSend = symbol->GetRawName() == "_objc_msgSend"; if (!isMessageSend) return; const auto llil = ssa->GetNonSSAForm(); const auto insn = ssa->GetInstruction(insnIndex); const auto params = insn.GetParameterExprs(); // The second parameter passed to the objc_msgSend call is the address of // either the selector reference or the method's name, which in both cases // is dereferenced to retrieve a selector. if (params.size() < 2) return; uint64_t rawSelector = 0; if (params[1].operation == LLIL_REG_SSA) { const auto selectorRegister = params[1].GetSourceSSARegister(); rawSelector = ssa->GetSSARegisterValue(selectorRegister).value; } else if (params[0].operation == LLIL_SEPARATE_PARAM_LIST_SSA) { if (params[0].GetParameterExprs().size() == 0) { return; } const auto selectorRegister = params[0].GetParameterExprs()[1].GetSourceSSARegister(); rawSelector = ssa->GetSSARegisterValue(selectorRegister).value; } if (rawSelector == 0) return; // -- Do callsite override auto reader = BinaryNinja::BinaryReader(bv); reader.Seek(rawSelector); auto selector = reader.ReadCString(500); auto additionalArgumentCount = std::count(selector.begin(), selector.end(), ':'); auto retType = bv->GetTypeByName({ "id" }); if (!retType) retType = BinaryNinja::Type::PointerType(ssa->GetArchitecture(), BinaryNinja::Type::VoidType()); std::vector callTypeParams; auto cc = bv->GetDefaultPlatform()->GetDefaultCallingConvention(); callTypeParams.push_back({"self", retType, true, BinaryNinja::Variable()}); auto selType = bv->GetTypeByName({ "SEL" }); if (!selType) selType = BinaryNinja::Type::PointerType(ssa->GetArchitecture(), BinaryNinja::Type::IntegerType(1, true)); callTypeParams.push_back({"sel", selType, true, BinaryNinja::Variable()}); std::vector selectorComponents = splitSelector(selector); std::vector argumentNames = generateArgumentNames(selectorComponents); for (size_t i = 0; i < additionalArgumentCount; i++) { auto argType = BinaryNinja::Type::IntegerType(bv->GetAddressSize(), true); if (argumentNames.size() > i && !argumentNames[i].empty()) callTypeParams.push_back({argumentNames[i], argType, true, BinaryNinja::Variable()}); else callTypeParams.push_back({"arg" + std::to_string(i), argType, true, BinaryNinja::Variable()}); } auto funcType = BinaryNinja::Type::FunctionType(retType, cc, callTypeParams); ssa->GetFunction()->SetAutoCallTypeAdjustment(ssa->GetFunction()->GetArchitecture(), insn.address, {funcType, BN_DEFAULT_CONFIDENCE}); // -- } }; for (const auto& block : ssa->GetBasicBlocks()) for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) rewriteIfEligible(i); } void SharedCacheWorkflow::Register() { Ref wf = BinaryNinja::Workflow::Instance("core.function.baseAnalysis")->Clone("core.function.dsc"); wf->RegisterActivity(new BinaryNinja::Activity("core.analysis.dscstubs", &SharedCacheWorkflow::FixupStubs)); wf->RegisterActivity(new BinaryNinja::Activity("core.analysis.fixObjCCallTypes", &fixObjCCallTypes)); wf->Insert("core.function.analyzeTailCalls", "core.analysis.fixObjCCallTypes"); wf->Insert("core.function.analyzeTailCalls", "core.analysis.dscstubs"); BinaryNinja::Workflow::RegisterWorkflow(wf, workflowInfo); } extern "C" { void RegisterSharedCacheWorkflow() { SharedCacheWorkflow::Register(); } }