summaryrefslogtreecommitdiff
path: root/view/sharedcache/workflow/SharedCacheWorkflow.cpp
diff options
context:
space:
mode:
authorkat <kat@vector35.com>2024-10-23 21:56:29 -0400
committerkat <kat@vector35.com>2024-10-23 21:58:47 -0400
commit3006c68e108a18f9b8782bc937dc9ec7e2cb3b54 (patch)
treef589bb38909349142c09b6215244205aef0a57ac /view/sharedcache/workflow/SharedCacheWorkflow.cpp
parent80fcccec8f686e0e5c89626b60af121a97baf856 (diff)
Initial commit of the alpha dyld_shared_cache view API Plugin.
This is an early release of our DSC processing plugin. We're still hard at work improving this feature. You should be able to just drop in a dyld_shared_cache and use the 'Shared Cache Triage' view to load and analyze images.
Diffstat (limited to 'view/sharedcache/workflow/SharedCacheWorkflow.cpp')
-rw-r--r--view/sharedcache/workflow/SharedCacheWorkflow.cpp496
1 files changed, 496 insertions, 0 deletions
diff --git a/view/sharedcache/workflow/SharedCacheWorkflow.cpp b/view/sharedcache/workflow/SharedCacheWorkflow.cpp
new file mode 100644
index 00000000..4b6afe97
--- /dev/null
+++ b/view/sharedcache/workflow/SharedCacheWorkflow.cpp
@@ -0,0 +1,496 @@
+//
+// 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"
+
+
+std::unordered_map<uint64_t, std::mutex> imageLoadMutex;
+
+
+std::vector<std::string> splitSelector(const std::string& selector) {
+ std::vector<std::string> components;
+ std::istringstream stream(selector);
+ std::string component;
+
+ while (std::getline(stream, component, ':')) {
+ if (!component.empty()) {
+ components.push_back(component);
+ }
+ }
+
+ return components;
+}
+
+std::vector<std::string> generateArgumentNames(const std::vector<std::string>& components) {
+ std::vector<std::string> 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<AnalysisContext> ctx, Ref<Function> func, Ref<MediumLevelILFunction> mssa, const MediumLevelILInstruction dest, ExprId exprIndex, bool applySymbolIfFoundToCurrentFunction)
+{
+ auto bv = func->GetView();
+ WorkerPriorityEnqueue([bv=bv, dest=dest, func=func, applySymbolIfFoundToCurrentFunction]()
+ {
+ SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(bv);
+ Ref<Settings> settings = bv->GetLoadSettings(VIEW_NAME);
+ bool autoLoadStubsAndDyldData = true;
+ if (settings && settings->Contains("loader.dsc.autoLoadStubsAndDyldData"))
+ {
+ autoLoadStubsAndDyldData = settings->Get<bool>("loader.dsc.autoLoadStubsAndDyldData", bv);
+ }
+ if (dest.operation != MLIL_CONST_PTR && dest.operation != MLIL_CONST)
+ return;
+ if (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<AnalysisContext> ctx)
+{
+ try
+ {
+ const auto func = ctx->GetFunction();
+ const auto arch = func->GetArchitecture();
+
+ const auto bv = func->GetView();
+
+ 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;
+ }
+
+ // FIXME optimize
+ Ref<Settings> settings = bv->GetLoadSettings(VIEW_NAME);
+ bool autoLoadObjC = true;
+ if (settings && settings->Contains("loader.dsc.autoLoadObjCStubRequirements"))
+ {
+ autoLoadObjC = settings->Get<bool>("loader.dsc.autoLoadObjCStubRequirements", bv);
+ }
+
+ // Processor that automatically loads the libObjC image when it encounters a stub (so we can do inlining).
+ if (autoLoadObjC && section->GetName().find("__objc_stubs") != std::string::npos)
+ {
+ auto firstInstruction = mlil->GetInstruction(0);
+ if (firstInstruction.operation == MLIL_TAILCALL)
+ {
+ auto dest = firstInstruction.GetDestExpr<MLIL_TAILCALL>();
+ 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<MLIL_JUMP>().operation == MLIL_VAR_SSA)
+ {
+ auto dest = instr.GetDestExpr<MLIL_JUMP>();
+ // 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 = !imageLoadMutex[bv->GetFile()->GetSessionId()].try_lock();
+ if (!otherFunctionAlreadyRunning)
+ imageLoadMutex[bv->GetFile()->GetSessionId()].unlock();
+ }
+ if (otherFunctionAlreadyRunning)
+ {
+ return;
+ }
+
+ std::unique_lock<std::mutex> lock(imageLoadMutex[bv->GetFile()->GetSessionId()]);
+ auto def = mssa->GetSSAVarDefinition(dest.GetSourceSSAVariable());
+ auto defInstr = mssa->GetInstruction(def);
+ auto targetOffset = defInstr.GetSourceExpr().GetSourceExpr().GetConstant();
+ auto sharedCache = SharedCacheAPI::SharedCache(bv);
+ if (!sharedCache.GetImageNameForAddress(targetOffset).empty())
+ {
+ sharedCache.LoadImageContainingAddress(targetOffset);
+ }
+ else
+ {
+ sharedCache.LoadSectionAtAddress(targetOffset);
+ }
+ for (const auto &sectFunc : bv->GetAnalysisFunctionList())
+ {
+ if (section->GetStart() <= sectFunc->GetStart() && sectFunc->GetStart() < section->GetEnd())
+ {
+ func->Reanalyze();
+ }
+ }
+ }
+ }
+
+ else if (instr.GetDestExpr<MLIL_JUMP>().operation == MLIL_CONST_PTR)
+ {
+ bool otherFunctionAlreadyRunning;
+ {
+ otherFunctionAlreadyRunning = !imageLoadMutex[bv->GetFile()->GetSessionId()].try_lock();
+ if (!otherFunctionAlreadyRunning)
+ imageLoadMutex[bv->GetFile()->GetSessionId()].unlock();
+ }
+ if (otherFunctionAlreadyRunning)
+ {
+ return;
+ }
+
+ std::unique_lock<std::mutex> lock(imageLoadMutex[bv->GetFile()->GetSessionId()]);
+ auto dest = instr.GetDestExpr<MLIL_JUMP>();
+ auto targetOffset = dest.GetConstant();
+ auto sharedCache = SharedCacheAPI::SharedCache(bv);
+ if (!sharedCache.GetImageNameForAddress(targetOffset).empty())
+ {
+ sharedCache.LoadImageContainingAddress(targetOffset);
+ }
+ else
+ {
+ sharedCache.LoadSectionAtAddress(targetOffset);
+ }
+ for (const auto &sectFunc : 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<MLIL_TAILCALL>();
+ 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<MLIL_JUMP>();
+ 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<MLIL_JUMP>().operation == MLIL_VAR_SSA)
+ {
+ auto dest = instr.GetDestExpr<MLIL_JUMP>();
+ // 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 =
+ !imageLoadMutex[bv->GetFile()->GetSessionId()].try_lock();
+ if (!otherFunctionAlreadyRunning)
+ imageLoadMutex[bv->GetFile()->GetSessionId()].unlock();
+ }
+ if (otherFunctionAlreadyRunning)
+ {
+ return;
+ }
+
+ std::unique_lock<std::mutex> lock(
+ imageLoadMutex[bv->GetFile()->GetSessionId()]);
+ auto def = mssa->GetSSAVarDefinition(dest.GetSourceSSAVariable());
+ auto defInstr = mssa->GetInstruction(def);
+ auto targetOffset = defInstr.GetSourceExpr().GetSourceExpr().GetConstant();
+ auto sharedCache = SharedCacheAPI::SharedCache(bv);
+ if (!bv->IsValidOffset(targetOffset))
+ {
+ if (!sharedCache.GetImageNameForAddress(targetOffset).empty())
+ {
+ sharedCache.LoadImageContainingAddress(targetOffset);
+ }
+ else
+ {
+ sharedCache.LoadSectionAtAddress(targetOffset);
+ }
+ for (const auto& sectFunc : 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<MLIL_CALL_SSA>().operation == MLIL_CONST_PTR)
+ {
+ auto dest = instr.GetDestExpr<MLIL_CALL_SSA>();
+ 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<AnalysisContext> 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<LLIL_CALL_SSA>();
+ 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<LLIL_CALL_SSA>();
+
+ // 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<LLIL_REG_SSA>();
+ rawSelector = ssa->GetSSARegisterValue(selectorRegister).value;
+ }
+ else if (params[0].operation == LLIL_SEPARATE_PARAM_LIST_SSA)
+ {
+ if (params[0].GetParameterExprs<LLIL_SEPARATE_PARAM_LIST_SSA>().size() == 0)
+ {
+ return;
+ }
+ const auto selectorRegister = params[0].GetParameterExprs<LLIL_SEPARATE_PARAM_LIST_SSA>()[1].GetSourceSSARegister<LLIL_REG_SSA>();
+ 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<BinaryNinja::FunctionParameter> 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<std::string> selectorComponents = splitSelector(selector);
+ std::vector<std::string> 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()
+{
+ const auto wf = BinaryNinja::Workflow::Instance()->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();
+ }
+}