From 0a53bff300f5b1f214ceb79a14f48e2532be07e8 Mon Sep 17 00:00:00 2001 From: Brandon Miller Date: Thu, 22 Oct 2026 15:55:41 -0400 Subject: [PATCH] Branch type and target override API --- architecture.cpp | 228 ++++++++++++++++++++ binaryninjaapi.h | 82 +++++++ binaryninjacore.h | 84 ++++++++ defaultarch.cpp | 309 ++++++++++++++++++++++++--- function.cpp | 42 ++++ python/architecture.py | 257 ++++++++++++++++++++++ python/function.py | 69 ++++++ rust/src/architecture.rs | 260 ++++++++++++++++++++++ rust/src/architecture/basic_block.rs | 57 ++++- rust/src/architecture/branches.rs | 87 ++++++++ rust/src/architecture/instruction.rs | 1 + rust/src/function.rs | 91 +++++++- rust/src/low_level_il.rs | 3 + 13 files changed, 1523 insertions(+), 47 deletions(-) diff --git a/architecture.cpp b/architecture.cpp index c185d0c131..c30ec233e2 100644 --- a/architecture.cpp +++ b/architecture.cpp @@ -278,6 +278,29 @@ const std::set& BasicBlockAnalysisContext::GetIndirectNoReturnCalls } +const std::map>& BasicBlockAnalysisContext::GetBranchOverrides() +{ + if (!m_branchOverrides) + { + auto& branchOverrides = m_branchOverrides.emplace(); + for (size_t i = 0; i < m_context->branchOverrideCount; i++) + { + ArchAndAddr location(new CoreArchitecture(m_context->branchOverrides[i].arch), + m_context->branchOverrides[i].address); + BranchOverride replacement {m_context->branchOverrides[i].replacementBranchType}; + if (m_context->branchOverrides[i].hasReplacementTarget) + replacement.target = m_context->branchOverrides[i].replacementTarget; + if (m_context->branchOverrides[i].replacementTargetArch) + replacement.targetArch = + new CoreArchitecture(m_context->branchOverrides[i].replacementTargetArch); + branchOverrides[location][m_context->branchOverrides[i].originalBranchType] = replacement; + } + } + + return *m_branchOverrides; +} + + std::map& BasicBlockAnalysisContext::GetContextualReturns() { if (!m_contextualReturns) @@ -340,6 +363,15 @@ std::map& BasicBlockAnalysisContext::GetInlinedUnresol } +std::set& BasicBlockAnalysisContext::GetValidBranchOverrideLocations() +{ + if (!m_validBranchOverrideLocations) + m_validBranchOverrideLocations.emplace(); + + return *m_validBranchOverrideLocations; +} + + bool BasicBlockAnalysisContext::SetFunctionArchContextRaw(void* p) { if (m_context->functionArchContext) @@ -460,6 +492,24 @@ void BasicBlockAnalysisContext::Finalize() delete[] locations; } + if (m_validBranchOverrideLocations) + { + auto& validLocations = *m_validBranchOverrideLocations; + BNArchitectureAndAddress* locations = new BNArchitectureAndAddress[validLocations.size()]; + + size_t i = 0; + for (auto& location : validLocations) + { + locations[i].arch = location.arch->GetObject(); + locations[i].address = location.address; + i++; + } + + BNAnalyzeBasicBlocksContextSetValidBranchOverrideLocations( + m_context, locations, validLocations.size()); + delete[] locations; + } + if (m_contextualReturns) { auto& contextualReturns = *m_contextualReturns; @@ -553,6 +603,18 @@ FunctionLifterContext::FunctionLifterContext(LowLevelILFunction* func, BNFunctio m_userIndirectBranches[src].insert(dest); } + for (size_t i = 0; i < context->branchOverrideCount; i++) + { + ArchAndAddr location(new CoreArchitecture(context->branchOverrides[i].arch), + context->branchOverrides[i].address); + BranchOverride replacement {context->branchOverrides[i].replacementBranchType}; + if (context->branchOverrides[i].hasReplacementTarget) + replacement.target = context->branchOverrides[i].replacementTarget; + if (context->branchOverrides[i].replacementTargetArch) + replacement.targetArch = new CoreArchitecture(context->branchOverrides[i].replacementTargetArch); + m_branchOverrides[location][context->branchOverrides[i].originalBranchType] = replacement; + } + for (size_t i = 0; i < context->inlinedCallsCount; i++) { m_inlinedCalls.insert(context->inlinedCalls[i]); @@ -598,6 +660,114 @@ std::map>& FunctionLifterContext::GetAutoIndi } +const std::map>& FunctionLifterContext::GetBranchOverrides() const +{ + return m_branchOverrides; +} + + +PreparedLiftedBranchOverride::PreparedLiftedBranchOverride(BNPreparedLiftedBranchOverride* object) : m_object(object) +{} + + +PreparedLiftedBranchOverride::~PreparedLiftedBranchOverride() +{ + BNFreePreparedLiftedBranchOverride(m_object); +} + + +bool PreparedLiftedBranchOverride::SuppressesInstruction() const +{ + return BNPreparedLiftedBranchOverrideSuppressesInstruction(m_object); +} + + +Ref PreparedLiftedBranchOverride::GetSource() +{ + if (!m_source) + m_source = new LowLevelILFunction(BNGetPreparedLiftedBranchOverrideSource(m_object)); + return m_source; +} + + +bool PreparedLiftedBranchOverride::Apply(uint64_t continuationAddress) +{ + return BNApplyPreparedLiftedBranchOverride(m_object, continuationAddress); +} + + +struct FunctionLifterContext::LiftedBranchOverrideInputs +{ + vector branches; + vector overrides; + vector indirectTargets; + BNLiftedBranchOverrideInfo info{}; +}; + + +FunctionLifterContext::LiftedBranchOverrideInputs FunctionLifterContext::GetLiftedBranchOverrideInputs( + const ArchAndAddr& location, const vector& branches) const +{ + LiftedBranchOverrideInputs result; + auto& rawBranches = result.branches; + auto& rawOverrides = result.overrides; + auto& indirectTargets = result.indirectTargets; + auto it = m_branchOverrides.find(location); + if (!location.arch || (it == m_branchOverrides.end())) + return result; + + rawBranches.reserve(branches.size()); + for (const auto& branch : branches) + rawBranches.push_back({branch.type, branch.target, branch.arch ? branch.arch->GetObject() : nullptr}); + + rawOverrides.reserve(it->second.size()); + for (const auto& [originalType, value] : it->second) + { + rawOverrides.push_back({location.arch->GetObject(), location.address, originalType, value.type, + value.target.has_value(), value.targetArch ? value.targetArch->GetObject() : nullptr, + value.target.value_or(0)}); + } + + const set* targets = nullptr; + if (auto user = m_userIndirectBranches.find(location); user != m_userIndirectBranches.end()) + targets = &user->second; + else if (auto automatic = m_autoIndirectBranches.find(location); automatic != m_autoIndirectBranches.end()) + targets = &automatic->second; + if (targets) + { + indirectTargets.reserve(targets->size()); + for (const auto& target : *targets) + indirectTargets.push_back({target.arch->GetObject(), target.address}); + } + + result.info = {location.arch->GetObject(), location.address, 0, + rawBranches.data(), rawBranches.size(), rawOverrides.data(), rawOverrides.size(), + m_noReturnCalls.count(location) != 0, indirectTargets.data(), indirectTargets.size()}; + return result; +} + + +unique_ptr FunctionLifterContext::PrepareLiftedBranchOverrides( + LowLevelILFunction& dest, BasicBlock* block, const ArchAndAddr& location, + const vector& branches) const +{ + auto inputs = GetLiftedBranchOverrideInputs(location, branches); + if (!inputs.info.arch) + return nullptr; + auto result = BNPrepareLiftedBranchOverrides(dest.GetObject(), block ? block->GetObject() : nullptr, &inputs.info); + return result ? make_unique(result) : nullptr; +} + + +bool FunctionLifterContext::ApplyLiftedBranchOverrides(LowLevelILFunction& dest, LowLevelILFunction& source, + const ArchAndAddr& location, uint64_t continuationAddress, const vector& branches) const +{ + auto inputs = GetLiftedBranchOverrideInputs(location, branches); + inputs.info.continuationAddress = continuationAddress; + return inputs.info.arch && BNApplyLiftedBranchOverrides(dest.GetObject(), source.GetObject(), &inputs.info); +} + + Ref& FunctionLifterContext::GetLogger() { return m_logger; @@ -775,6 +945,23 @@ bool Architecture::GetInstructionInfoCallback( } +size_t Architecture::GetBranchTypesWithContextCallback(void* ctxt, BNFunction* function, uint64_t addr, + BNOverridableBranchInfo* branches, size_t maxBranches, void* functionArchContext) +{ + CallbackRef arch(ctxt); + Ref func(new Function(BNNewFunctionReference(function))); + auto result = arch->GetBranchTypesWithContext(func, addr, functionArchContext); + size_t count = std::min(result.size(), maxBranches); + for (size_t i = 0; i < count; i++) + { + branches[i].type = result[i].type; + branches[i].target = result[i].target; + branches[i].arch = result[i].arch ? result[i].arch->GetObject() : nullptr; + } + return count; +} + + bool Architecture::GetInstructionTextCallback( void* ctxt, const uint8_t* data, uint64_t addr, size_t* len, BNInstructionTextToken** result, size_t* count) { @@ -1351,6 +1538,7 @@ void Architecture::Register(Architecture* arch) callbacks.getOpcodeDisplayLength = GetOpcodeDisplayLengthCallback; callbacks.getAssociatedArchitectureByAddress = GetAssociatedArchitectureByAddressCallback; callbacks.getInstructionInfo = GetInstructionInfoCallback; + callbacks.getBranchTypesWithContext = GetBranchTypesWithContextCallback; callbacks.getInstructionText = GetInstructionTextCallback; callbacks.getInstructionTextWithContext = GetInstructionTextWithContextCallback; callbacks.freeInstructionText = FreeInstructionTextCallback; @@ -1487,6 +1675,23 @@ size_t Architecture::GetOpcodeDisplayLength() const } +std::vector Architecture::GetBranchTypesWithContext(Function* function, uint64_t addr, void*) +{ + size_t count = 0; + BNOverridableBranchInfo* branches = BNArchitectureDefaultGetBranchTypesWithContext( + m_object, function->GetObject(), addr, &count); + std::vector result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + { + result.push_back({branches[i].type, branches[i].target, + branches[i].arch ? new CoreArchitecture(branches[i].arch) : nullptr}); + } + BNFreeOverridableBranchInfoList(branches); + return result; +} + + Ref Architecture::GetAssociatedArchitectureByAddress(uint64_t&) { return this; @@ -2076,6 +2281,22 @@ bool CoreArchitecture::GetInstructionInfo(const uint8_t* data, uint64_t addr, si } +std::vector CoreArchitecture::GetBranchTypesWithContext(Function* function, uint64_t addr, void*) +{ + size_t count = 0; + BNOverridableBranchInfo* branches = BNGetArchitectureBranchTypesWithContext(m_object, function->GetObject(), addr, &count); + std::vector result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + { + result.push_back({branches[i].type, branches[i].target, + branches[i].arch ? new CoreArchitecture(branches[i].arch) : nullptr}); + } + BNFreeOverridableBranchInfoList(branches); + return result; +} + + bool CoreArchitecture::GetInstructionText( const uint8_t* data, uint64_t addr, size_t& len, std::vector& result) { @@ -2641,6 +2862,13 @@ bool ArchitectureExtension::GetInstructionInfo( } +std::vector ArchitectureExtension::GetBranchTypesWithContext( + Function* function, uint64_t addr, void* functionArchContext) +{ + return m_base->GetBranchTypesWithContext(function, addr, functionArchContext); +} + + bool ArchitectureExtension::GetInstructionText( const uint8_t* data, uint64_t addr, size_t& len, vector& result) { diff --git a/binaryninjaapi.h b/binaryninjaapi.h index fea2ec6f16..4ab10a79f7 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -9795,6 +9795,20 @@ namespace BinaryNinja { void AddBranch(BNBranchType type, uint64_t target = 0, Architecture* arch = nullptr, uint8_t delaySlots = 0); }; + struct OverridableBranchInfo + { + BNBranchType type; + uint64_t target; + Ref arch; + }; + + struct BranchOverride + { + BNBranchType type; + std::optional target; + Ref targetArch; + }; + struct NameAndType { std::string name; @@ -9835,6 +9849,7 @@ namespace BinaryNinja { // in std::optional>> m_indirectBranches; std::optional> m_indirectNoReturnCalls; + std::optional>> m_branchOverrides; // in/out std::optional> m_contextualReturns; @@ -9844,6 +9859,7 @@ namespace BinaryNinja { std::optional> m_directNoReturnCalls; std::optional> m_haltedDisassemblyAddresses; std::optional> m_inlinedUnresolvedIndirectBranches; + std::optional> m_validBranchOverrideLocations; Ref m_lifterInstructionData; @@ -9864,6 +9880,7 @@ namespace BinaryNinja { const std::map> GetIndirectBranches(); const std::set& GetIndirectNoReturnCalls(); + const std::map>& GetBranchOverrides(); std::map& GetContextualReturns(); @@ -9871,6 +9888,7 @@ namespace BinaryNinja { std::set& GetDirectNoReturnCalls(); std::set& GetHaltedDisassemblyAddresses(); std::map& GetInlinedUnresolvedIndirectBranches(); + std::set& GetValidBranchOverrideLocations(); bool SetFunctionArchContextRaw(void* p); void* GetFunctionArchContextRaw() const { return m_context->functionArchContext; } @@ -9897,6 +9915,24 @@ namespace BinaryNinja { void Finalize(); }; + // A prepared override owns its inputs and staging IL. Use only while lifting. + // The architecture chooses the instruction group and its continuation address. + class PreparedLiftedBranchOverride + { + BNPreparedLiftedBranchOverride* m_object; + Ref m_source; + + public: + explicit PreparedLiftedBranchOverride(BNPreparedLiftedBranchOverride* object); + ~PreparedLiftedBranchOverride(); + PreparedLiftedBranchOverride(const PreparedLiftedBranchOverride&) = delete; + PreparedLiftedBranchOverride& operator=(const PreparedLiftedBranchOverride&) = delete; + bool SuppressesInstruction() const; + Ref GetSource(); + // A successful application consumes the prepared override; a second call returns false. + bool Apply(uint64_t continuationAddress); + }; + class FunctionLifterContext { Ref m_function; @@ -9909,10 +9945,14 @@ namespace BinaryNinja { std::map m_inlinedRemapping; std::map> m_userIndirectBranches; std::map> m_autoIndirectBranches; + std::map> m_branchOverrides; std::set m_inlinedCalls; bool* m_containsInlinedFunctions; void* m_functionArchContext; Ref m_lifterInstructionData; + struct LiftedBranchOverrideInputs; + LiftedBranchOverrideInputs GetLiftedBranchOverrideInputs( + const ArchAndAddr& location, const std::vector& branches) const; public: BNFunctionLifterContext* m_context; @@ -9926,6 +9966,18 @@ namespace BinaryNinja { std::map& GetInlinedRemapping(); std::map>& GetUserIndirectBranches(); std::map>& GetAutoIndirectBranches(); + const std::map>& GetBranchOverrides() const; + // Returns null for no matching overrides or invalid inputs, without changing dest. + // The result owns a snapshot of the inputs; no staging IL is allocated until GetSource. + // See BNPrepareLiftedBranchOverrides and BNApplyLiftedBranchOverrides for requirements. + std::unique_ptr PrepareLiftedBranchOverrides(LowLevelILFunction& dest, + BasicBlock* block, const ArchAndAddr& location, const std::vector& branches) const; + // Append an isolated, unfinalized instruction group using this context's overrides. + // See BNApplyLiftedBranchOverrides for label, temporary-register, and ownership requirements. + // False leaves dest untouched, including when no original branch has an override. + bool ApplyLiftedBranchOverrides(LowLevelILFunction& dest, LowLevelILFunction& source, + const ArchAndAddr& location, uint64_t continuationAddress, + const std::vector& branches) const; std::set& GetInlinedCalls(); void SetContainsInlinedFunctions(bool value); void* GetFunctionArchContextRaw() const { return m_functionArchContext; } @@ -9968,6 +10020,8 @@ namespace BinaryNinja { static BNArchitecture* GetAssociatedArchitectureByAddressCallback(void* ctxt, uint64_t* addr); static bool GetInstructionInfoCallback( void* ctxt, const uint8_t* data, uint64_t addr, size_t maxLen, BNInstructionInfo* result); + static size_t GetBranchTypesWithContextCallback(void* ctxt, BNFunction* function, uint64_t addr, + BNOverridableBranchInfo* branches, size_t maxBranches, void* functionArchContext); static bool GetInstructionTextCallback(void* ctxt, const uint8_t* data, uint64_t addr, size_t* len, BNInstructionTextToken** result, size_t* count); static bool GetInstructionTextWithContextCallback(void* ctxt, const uint8_t* data, uint64_t addr, size_t* len, @@ -10158,6 +10212,12 @@ namespace BinaryNinja { \return Whether instruction info was successfully retrieved. */ virtual bool GetInstructionInfo(const uint8_t* data, uint64_t addr, size_t maxLen, InstructionInfo& result) = 0; + /*! Discover overridable branches using the core default implementation. + + Custom implementations can delegate to this base method without invoking their callback again. + */ + virtual std::vector GetBranchTypesWithContext( + Function* function, uint64_t addr, void* functionArchContext); /*! Retrieves a list of InstructionTextTokens @@ -10641,6 +10701,18 @@ namespace BinaryNinja { { return GetInstructionTextWithContext(data, addr, len, static_cast(context), result); } + + virtual std::vector GetBranchTypesWithContext( + Function* function, uint64_t addr, FnCtxT* context) + { + return Architecture::GetBranchTypesWithContext(function, addr, static_cast(context)); + } + + std::vector GetBranchTypesWithContext( + Function* function, uint64_t addr, void* context) override final + { + return GetBranchTypesWithContext(function, addr, static_cast(context)); + } }; /*! @@ -10662,6 +10734,8 @@ namespace BinaryNinja { virtual Ref GetAssociatedArchitectureByAddress(uint64_t& addr) override; virtual bool GetInstructionInfo( const uint8_t* data, uint64_t addr, size_t maxLen, InstructionInfo& result) override; + virtual std::vector GetBranchTypesWithContext( + Function* function, uint64_t addr, void* functionArchContext) override; virtual bool GetInstructionText( const uint8_t* data, uint64_t addr, size_t& len, std::vector& result) override; virtual bool GetInstructionTextWithContext(const uint8_t* data, uint64_t addr, size_t& len, void* context, @@ -10754,6 +10828,8 @@ namespace BinaryNinja { virtual Ref GetAssociatedArchitectureByAddress(uint64_t& addr) override; virtual bool GetInstructionInfo( const uint8_t* data, uint64_t addr, size_t maxLen, InstructionInfo& result) override; + virtual std::vector GetBranchTypesWithContext( + Function* function, uint64_t addr, void* functionArchContext) override; virtual bool GetInstructionText( const uint8_t* data, uint64_t addr, size_t& len, std::vector& result) override; virtual bool GetInstructionTextWithContext(const uint8_t* data, uint64_t addr, size_t& len, void* context, @@ -13736,6 +13812,12 @@ namespace BinaryNinja { Architecture* sourceArch, uint64_t source, const std::vector& branches); void SetUserIndirectBranches( Architecture* sourceArch, uint64_t source, const std::vector& branches); + void SetUserBranchOverride(Architecture* arch, uint64_t addr, BNBranchType originalBranchType, + BNBranchType replacementBranchType, std::optional replacementTarget = std::nullopt, + Architecture* replacementTargetArch = nullptr); + void ClearUserBranchOverride(Architecture* arch, uint64_t addr, BNBranchType originalBranchType); + std::map GetUserBranchOverrides(Architecture* arch, uint64_t addr); + bool IsValidBranchOverrideLocation(Architecture* arch, uint64_t addr); // Guided Analysis Support void SetGuidedSourceBlocks(const std::vector& addresses); diff --git a/binaryninjacore.h b/binaryninjacore.h index 206d0ac2d1..aeb44f10c0 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -255,6 +255,7 @@ extern "C" typedef struct BNFunction BNFunction; typedef struct BNBasicBlock BNBasicBlock; typedef struct BNLifterInstructionData BNLifterInstructionData; + typedef struct BNPreparedLiftedBranchOverride BNPreparedLiftedBranchOverride; typedef struct BNDownloadProvider BNDownloadProvider; typedef struct BNDownloadInstance BNDownloadInstance; typedef struct BNWebsocketProvider BNWebsocketProvider; @@ -495,6 +496,8 @@ extern "C" SystemCall = 5, IndirectBranch = 6, ExceptionBranch = 7, + // No control transfer. A NOP override suppresses the entire native instruction. + NopBranch = 8, UnresolvedBranch = 127, UserDefinedBranch = 128 }; @@ -1211,6 +1214,9 @@ extern "C" // Function call returns its result onto the caller's stack ILStackReturn = 0x4000, + + // Control flow explicitly selected by a user branch override. + ILBranchOverride = 0x8000, }; BN_ENUM(uint8_t, BNIntrinsicClass) @@ -2046,6 +2052,13 @@ extern "C" BNArchitecture* branchArch[BN_MAX_INSTRUCTION_BRANCHES]; // If null, same architecture as instruction } BNInstructionInfo; + typedef struct BNOverridableBranchInfo + { + BNBranchType type; + uint64_t target; + BNArchitecture* arch; // If null, same architecture as instruction + } BNOverridableBranchInfo; + BN_ENUM(uint32_t, BNLinearSweepAnalysisCapability) { BNLinearSweepCallTargetAnalysis = 1 << 0, @@ -2164,6 +2177,31 @@ extern "C" AlwaysSkipFunctionAnalysis }; + typedef struct BNBranchOverride + { + BNArchitecture* arch; + uint64_t address; + BNBranchType originalBranchType; + BNBranchType replacementBranchType; + bool hasReplacementTarget; + BNArchitecture* replacementTargetArch; + uint64_t replacementTarget; + } BNBranchOverride; + + typedef struct BNLiftedBranchOverrideInfo + { + BNArchitecture* arch; + uint64_t address; + uint64_t continuationAddress; + const BNOverridableBranchInfo* branches; + size_t branchCount; + const BNBranchOverride* overrides; + size_t overrideCount; + bool noReturnCall; + const BNArchitectureAndAddress* indirectTargets; + size_t indirectTargetCount; + } BNLiftedBranchOverrideInfo; + typedef struct BNBasicBlockAnalysisContext { BNFunction* function; @@ -2209,6 +2247,14 @@ extern "C" void* functionArchContext; BNLifterInstructionData* lifterInstructionData; + + // IN + size_t branchOverrideCount; + BNBranchOverride* branchOverrides; + + // OUT + size_t validBranchOverrideLocationCount; + BNArchitectureAndAddress* validBranchOverrideLocations; } BNBasicBlockAnalysisContext; typedef struct BNFunctionLifterContext { @@ -2240,6 +2286,10 @@ extern "C" // OUT bool* containsInlinedFunctions; + + // IN + size_t branchOverrideCount; + BNBranchOverride* branchOverrides; } BNFunctionLifterContext; typedef struct BNCustomArchitecture @@ -2325,6 +2375,9 @@ extern "C" size_t (*getLinearSweepInitialAlignment)(void* ctxt); uint32_t (*getLinearSweepAnalysisCapabilities)(void* ctxt); + + size_t (*getBranchTypesWithContext)(void* ctxt, BNFunction* function, uint64_t addr, + BNOverridableBranchInfo* branches, size_t maxBranches, void* functionArchContext); } BNCustomArchitecture; typedef struct BNCustomPlatform @@ -5852,6 +5905,13 @@ extern "C" BINARYNINJACOREAPI BNArchitecture* BNGetAssociatedArchitectureByAddress(BNArchitecture* arch, uint64_t* addr); BINARYNINJACOREAPI bool BNGetInstructionInfo( BNArchitecture* arch, const uint8_t* data, uint64_t addr, size_t maxLen, BNInstructionInfo* result); + BINARYNINJACOREAPI BNOverridableBranchInfo* BNGetArchitectureBranchTypesWithContext( + BNArchitecture* arch, BNFunction* function, uint64_t addr, size_t* count); + // Run default branch discovery without invoking the architecture's branch-types callback. + // Release the returned list with BNFreeOverridableBranchInfoList. + BINARYNINJACOREAPI BNOverridableBranchInfo* BNArchitectureDefaultGetBranchTypesWithContext( + BNArchitecture* arch, BNFunction* function, uint64_t addr, size_t* count); + BINARYNINJACOREAPI void BNFreeOverridableBranchInfoList(BNOverridableBranchInfo* branches); BINARYNINJACOREAPI bool BNGetInstructionText(BNArchitecture* arch, const uint8_t* data, uint64_t addr, size_t* len, BNInstructionTextToken** result, size_t* count); BINARYNINJACOREAPI bool BNGetInstructionTextWithContext(BNArchitecture* arch, const uint8_t* data, uint64_t addr, size_t* len, @@ -5865,6 +5925,17 @@ extern "C" BNBasicBlockAnalysisContext* context); BINARYNINJACOREAPI bool BNArchitectureSetDefaultLiftFunctionCallback(void *callback); BINARYNINJACOREAPI bool BNArchitectureDefaultLiftFunction(BNLowLevelILFunction* function, BNFunctionLifterContext* context); + BINARYNINJACOREAPI bool BNApplyLiftedBranchOverrides(BNLowLevelILFunction* dest, + BNLowLevelILFunction* source, const BNLiftedBranchOverrideInfo* info); + BINARYNINJACOREAPI BNPreparedLiftedBranchOverride* BNPrepareLiftedBranchOverrides( + BNLowLevelILFunction* dest, BNBasicBlock* block, const BNLiftedBranchOverrideInfo* info); + BINARYNINJACOREAPI void BNFreePreparedLiftedBranchOverride(BNPreparedLiftedBranchOverride* prepared); + BINARYNINJACOREAPI bool BNPreparedLiftedBranchOverrideSuppressesInstruction( + BNPreparedLiftedBranchOverride* prepared); + BINARYNINJACOREAPI BNLowLevelILFunction* BNGetPreparedLiftedBranchOverrideSource( + BNPreparedLiftedBranchOverride* prepared); + BINARYNINJACOREAPI bool BNApplyPreparedLiftedBranchOverride( + BNPreparedLiftedBranchOverride* prepared, uint64_t continuationAddress); BINARYNINJACOREAPI bool BNArchitectureLiftFunction(BNArchitecture* arch, BNLowLevelILFunction* function, BNFunctionLifterContext* context); BINARYNINJACOREAPI void BNArchitectureFreeFunctionArchContext(BNArchitecture* arch, void* context); @@ -6492,6 +6563,17 @@ extern "C" BNArchitectureAndAddress* branches, size_t count); BINARYNINJACOREAPI void BNSetUserIndirectBranches(BNFunction* func, BNArchitecture* sourceArch, uint64_t source, BNArchitectureAndAddress* branches, size_t count); + BINARYNINJACOREAPI void BNSetUserBranchOverride( + BNFunction* func, BNArchitecture* arch, uint64_t addr, BNBranchType originalBranchType, + BNBranchType replacementBranchType, bool hasReplacementTarget, BNArchitecture* replacementTargetArch, + uint64_t replacementTarget); + BINARYNINJACOREAPI void BNClearUserBranchOverride( + BNFunction* func, BNArchitecture* arch, uint64_t addr, BNBranchType originalBranchType); + BINARYNINJACOREAPI BNBranchOverride* BNGetUserBranchOverrides( + BNFunction* func, BNArchitecture* arch, uint64_t addr, size_t* count); + BINARYNINJACOREAPI void BNFreeBranchOverrideList(BNBranchOverride* overrides); + BINARYNINJACOREAPI bool BNIsValidBranchOverrideLocation( + BNFunction* func, BNArchitecture* arch, uint64_t addr); BINARYNINJACOREAPI void BNSetGuidedSourceBlocks(BNFunction* func, BNArchitectureAndAddress* addresses, size_t count); BINARYNINJACOREAPI void BNAddGuidedSourceBlocks(BNFunction* func, BNArchitectureAndAddress* addresses, size_t count); @@ -6583,6 +6665,8 @@ extern "C" BINARYNINJACOREAPI void BNAnalyzeBasicBlocksContextSetContextualFunctionReturns(BNBasicBlockAnalysisContext* abb, BNArchitectureAndAddress* sources, bool* values, size_t count); BINARYNINJACOREAPI void BNAnalyzeBasicBlocksContextSetHaltedDisassemblyAddresses(BNBasicBlockAnalysisContext* abb, BNArchitectureAndAddress* sources, size_t count); BINARYNINJACOREAPI void BNAnalyzeBasicBlocksContextSetInlinedUnresolvedIndirectBranches(BNBasicBlockAnalysisContext* abb, BNArchitectureAndAddress* locations, size_t count); + BINARYNINJACOREAPI void BNAnalyzeBasicBlocksContextSetValidBranchOverrideLocations( + BNBasicBlockAnalysisContext* abb, BNArchitectureAndAddress* locations, size_t count); BINARYNINJACOREAPI BNAnalysisParameters BNGetParametersForAnalysis(BNBinaryView* view); BINARYNINJACOREAPI void BNSetParametersForAnalysis(BNBinaryView* view, BNAnalysisParameters params); diff --git a/defaultarch.cpp b/defaultarch.cpp index edced29e9e..a754dc563d 100644 --- a/defaultarch.cpp +++ b/defaultarch.cpp @@ -79,6 +79,58 @@ static bool IsReturnAddressRegisterJumpOrReturn(LowLevelILInstruction instr, con } +// Returning overrides need a continuation even when another branch ends the block. +static void AddBranchOverrideContinuations(const set& overrideContinuations, BasicBlock* block, + const ArchAndAddr& location, set& seenBlocks, queue& blocksToProcess) +{ + block->SetCanExit(true); + for (auto type : overrideContinuations) + { + if ((type != TrueBranch) && (type != FalseBranch)) + type = UnconditionalBranch; + const auto& edges = block->GetPendingOutgoingEdges(); + if (none_of(edges.begin(), edges.end(), [&](const auto& edge) { + return (edge.type == type) && (edge.target == location.address); + })) + block->AddPendingOutgoingEdge(type, location.address, location.arch, true); + } + if (seenBlocks.insert(location).second) + blocksToProcess.push(location); +} + + +// NOPs and some indirect calls have no ordinary branch metadata. Discover it only +// at a user override site, without changing the instruction decoder's normal behavior. +// A NOP replacement suppresses the whole native instruction, including all branch arms. +static bool ApplyInstructionInfoOverride(Function* function, BasicBlockAnalysisContext& context, + const ArchAndAddr& location, InstructionInfo& info) +{ + const auto& overrides = context.GetBranchOverrides(); + auto entry = overrides.find(location); + if (entry == overrides.end()) + return false; + if (!info.branchCount) + { + for (const auto& branch : location.arch->GetBranchTypesWithContext(function, location.address, + context.GetFunctionArchContextRaw())) + if (entry->second.count(branch.type)) + info.AddBranch(branch.type, branch.target, branch.arch); + } + for (size_t i = 0; i < info.branchCount; i++) + { + auto replacement = entry->second.find(info.branchType[i]); + if ((replacement != entry->second.end()) && (replacement->second.type == NopBranch)) + { + info.branchCount = 0; + info.delaySlots = 0; + info.AddBranch(NopBranch); + return true; + } + } + return false; +} + + void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnalysisContext& context) { auto data = function->GetView(); @@ -93,6 +145,7 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly auto& indirectBranches = context.GetIndirectBranches(); auto& indirectNoReturnCalls = context.GetIndirectNoReturnCalls(); + auto& branchOverrides = context.GetBranchOverrides(); auto& contextualFunctionReturns = context.GetContextualReturns(); @@ -188,6 +241,7 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly // we decrement. the architecture can change this value arbitrarily during callbacks. uint8_t delaySlotCount = 0; bool delayInstructionEndsBlock = false; + set overrideContinuations; // Disassemble the instructions in the block while (true) @@ -312,6 +366,8 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly break; } + bool suppressInstruction = !delaySlotCount && !branchOverrides.empty() + && ApplyInstructionInfoOverride(function, context, location, info); bool endsBlock = false; ArchAndAddr target; map>::const_iterator indirectBranchIter, endIter; @@ -325,17 +381,41 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly // Keep track of where the current 'group' of instructions started. A 'group' // is an instruction and all of its delay slot instructions. instructionGroupStart = location; + overrideContinuations.clear(); // Don't process branches in delay slots for (size_t i = 0; i < info.branchCount; i++) { bool fastPath; + auto branchType = info.branchType[i]; + auto branchTarget = info.branchTarget[i]; + bool hasBranchTarget = (branchType == UnconditionalBranch) || (branchType == TrueBranch) + || (branchType == FalseBranch) || (branchType == CallDestination); + Ref branchTargetArch = + info.branchArch[i] ? new CoreArchitecture(info.branchArch[i]) : nullptr; + bool branchOverridden = false; + if (auto locationOverrides = branchOverrides.find(location); + locationOverrides != branchOverrides.end()) + { + if (auto branchOverride = locationOverrides->second.find(branchType); + branchOverride != locationOverrides->second.end()) + { + branchType = branchOverride->second.type; + branchOverridden = true; + if (branchOverride->second.target) + { + hasBranchTarget = true; + branchTarget = *branchOverride->second.target; + branchTargetArch = branchOverride->second.targetArch; + } + } + } auto handleAsFallback = [&]() { // Undefined type or target, check for targets from analysis and stop disassembling this block endsBlock = true; - if (info.branchType[i] == IndirectBranch) + if (branchType == IndirectBranch) { // Indirect calls need not end the block early. Ref ilFunc = new LowLevelILFunction(location.arch, nullptr); @@ -352,7 +432,8 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly indirectBranchIter = indirectBranches.find(location); endIter = indirectBranches.end(); - if (indirectBranchIter != endIter) + if (indirectBranchIter != endIter && (!branchOverridden + || ((branchType != ExceptionBranch) && (branchType != FunctionReturn)))) { for (auto& branch : indirectBranchIter->second) { @@ -376,11 +457,16 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly } } } - else if (info.branchType[i] == ExceptionBranch) + else if (branchType == ExceptionBranch) { block->SetCanExit(false); } - else if (info.branchType[i] == FunctionReturn && function->CanReturn().GetValue()) + else if (branchOverridden && (branchType == FunctionReturn)) + { + // An explicit return override takes precedence over contextual return detection. + return; + } + else if (branchType == FunctionReturn && function->CanReturn().GetValue()) { // Support for contextual function returns. This is mainly used for ARM/Thumb with 'blx lr'. It's most common for this to be treated // as a function return, however it can also be a function call. For now this transform is described as follows: @@ -409,23 +495,33 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly } }; - switch (info.branchType[i]) + if (info.branchType[i] != SystemCall) + context.GetValidBranchOverrideLocations().insert(location); + + bool returningOverride = branchOverridden + && ((branchType == CallDestination) || (branchType == SystemCall)); + switch (branchType) { case UnconditionalBranch: case TrueBranch: case FalseBranch: + if (!hasBranchTarget) + { + handleAsFallback(); + break; + } // Normal branch, resume disassembly at targets endsBlock = true; // Target of a call instruction, add the function to the analysis - if (data->IsOffsetExternSemantics(info.branchTarget[i])) + if (data->IsOffsetExternSemantics(branchTarget)) { // Deal with direct pointers into the extern section DataVariable dataVar; - if (data->GetDataVariableAtAddress(info.branchTarget[i], dataVar) - && (dataVar.address == info.branchTarget[i]) && dataVar.type.GetValue() + if (data->GetDataVariableAtAddress(branchTarget, dataVar) + && (dataVar.address == branchTarget) && dataVar.type.GetValue() && (dataVar.type->GetClass() == FunctionTypeClass)) { - directRefs[info.branchTarget[i]].emplace(location); + directRefs[branchTarget].emplace(location); if (!dataVar.type->CanReturn()) { directNoReturnCalls.insert(location); @@ -436,12 +532,12 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly break; } - fastPath = fastValidate && (info.branchTarget[i] >= fastStartAddr) && (info.branchTarget[i] <= fastEndAddr); - if (fastPath || (data->IsValidOffset(info.branchTarget[i]) && - data->IsOffsetBackedByFile(info.branchTarget[i]) && - ((!validateExecutable) || data->IsOffsetExecutable(info.branchTarget[i])))) + fastPath = fastValidate && (branchTarget >= fastStartAddr) && (branchTarget <= fastEndAddr); + if (fastPath || (data->IsValidOffset(branchTarget) && + data->IsOffsetBackedByFile(branchTarget) && + ((!validateExecutable) || data->IsOffsetExecutable(branchTarget)))) { - target = ArchAndAddr(info.branchArch[i] ? new CoreArchitecture(info.branchArch[i]) : location.arch, info.branchTarget[i]); + target = ArchAndAddr(branchTargetArch ? branchTargetArch : location.arch, branchTarget); // Check if valid target if (data->ShouldSkipTargetAnalysis(location, function, instrEnd, target)) @@ -451,13 +547,14 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly if (target.arch != funcPlatform->GetArchitecture()) targetPlatform = funcPlatform->GetRelatedPlatform(target.arch); - directRefs[info.branchTarget[i]].insert(location); + directRefs[branchTarget].insert(location); auto otherFunc = function->GetCalleeForAnalysis(targetPlatform, target.address, true); - if (translateTailCalls && targetPlatform && otherFunc && (otherFunc->GetStart() != function->GetStart())) + if (!branchOverridden && translateTailCalls && targetPlatform && otherFunc + && (otherFunc->GetStart() != function->GetStart())) { calledFunctions.insert(otherFunc); - if (info.branchType[i] == UnconditionalBranch) + if (branchType == UnconditionalBranch) { if (!otherFunc->CanReturn() && !otherFunc->IsInlinedDuringAnalysis().GetValue()) { @@ -481,7 +578,7 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly if (isGuidedSourceBlock) guidedSourceBlockTargets.insert(target); - block->AddPendingOutgoingEdge(info.branchType[i], target.address, target.arch); + block->AddPendingOutgoingEdge(branchType, target.address, target.arch); // Add the block to the list of blocks to process if it is not already processed if (seenBlocks.count(target) == 0) { @@ -493,18 +590,27 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly break; case CallDestination: + // Preserve an indirect destination when no replacement address was supplied. + // Its callees and no-return status are resolved by indirect-call analysis. + if (!hasBranchTarget) + { + if (indirectNoReturnCalls.count(location)) + returningOverride = false; + break; + } // Target of a call instruction, add the function to the analysis - if (data->IsOffsetExternSemantics(info.branchTarget[i])) + if (data->IsOffsetExternSemantics(branchTarget)) { // Deal with direct pointers into the extern section DataVariable dataVar; - if (data->GetDataVariableAtAddress(info.branchTarget[i], dataVar) - && (dataVar.address == info.branchTarget[i]) && dataVar.type.GetValue() + if (data->GetDataVariableAtAddress(branchTarget, dataVar) + && (dataVar.address == branchTarget) && dataVar.type.GetValue() && (dataVar.type->GetClass() == FunctionTypeClass)) { - directRefs[info.branchTarget[i]].emplace(location); + directRefs[branchTarget].emplace(location); if (!dataVar.type->CanReturn()) { + returningOverride = false; directNoReturnCalls.insert(location); endsBlock = true; block->SetCanExit(false); @@ -515,11 +621,11 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly break; } - fastPath = fastValidate && (info.branchTarget[i] >= fastStartAddr) && (info.branchTarget[i] <= fastEndAddr); - if (fastPath || (data->IsValidOffset(info.branchTarget[i]) && data->IsOffsetBackedByFile(info.branchTarget[i]) && - ((!validateExecutable) || data->IsOffsetExecutable(info.branchTarget[i])))) + fastPath = fastValidate && (branchTarget >= fastStartAddr) && (branchTarget <= fastEndAddr); + if (fastPath || (data->IsValidOffset(branchTarget) && data->IsOffsetBackedByFile(branchTarget) && + ((!validateExecutable) || data->IsOffsetExecutable(branchTarget)))) { - target = ArchAndAddr(info.branchArch[i] ? new CoreArchitecture(info.branchArch[i]) : location.arch, info.branchTarget[i]); + target = ArchAndAddr(branchTargetArch ? branchTargetArch : location.arch, branchTarget); if (!fastPath && !data->IsOffsetCodeSemantics(target.address) && data->IsOffsetCodeSemantics(location.address)) { @@ -558,6 +664,7 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly directRefs[target.address].emplace(location); if (!func->CanReturn()) { + returningOverride = false; if (func->IsInlinedDuringAnalysis().GetValue() && func->HasUnresolvedIndirectBranches()) { auto unresolved = func->GetUnresolvedIndirectBranches(); @@ -577,16 +684,19 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly break; case SystemCall: + case NopBranch: break; default: handleAsFallback(); break; } + if (returningOverride) + overrideContinuations.insert(info.branchType[i]); } } - if (indirectNoReturnCalls.count(location)) + if (!suppressInstruction && indirectNoReturnCalls.count(location)) { // Conditional Call Support (Part 1) // Do not halt basic block analysis if this is a conditional call to a function that is 'no return' @@ -607,7 +717,11 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly instrData->Append(block, std::span(opcode, info.length)); if (endsBlock && !info.delaySlots) + { + if (!overrideContinuations.empty()) + AddBranchOverrideContinuations(overrideContinuations, block, location, seenBlocks, blocksToProcess); break; + } // Respect the 'analysis.limits.maxFunctionSize' setting while allowing for overridable behavior as well. // We prefer to allow disassembly when function analysis is disabled, but only up to the maximum size. @@ -629,7 +743,11 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly { delaySlotCount--; if (!delaySlotCount && delayInstructionEndsBlock) + { + if (!overrideContinuations.empty()) + AddBranchOverrideContinuations(overrideContinuations, block, location, seenBlocks, blocksToProcess); break; + } } else { @@ -788,8 +906,118 @@ static void ApplyExternPointerForRelocation( } +namespace +{ + + // Created only for functions with user branch overrides. Ordinary instructions still + // lift directly into the function; temporary IL exists only at matching override sites. + class BranchOverrideLifter + { + LowLevelILFunction* m_function; + FunctionLifterContext& m_context; + const map>& m_overrides; + unique_ptr m_prepared; + + void limitCoalescing(const ArchAndAddr& location, size_t& len) + { + // A lifter must not consume a later override site while lifting an earlier instruction. + for (auto next = m_overrides.upper_bound(location); + next != m_overrides.end() && next->first.arch == location.arch + && (next->first.address - location.address < len); ++next) + { + if (m_function->GetFunction()->IsValidBranchOverrideLocation(location.arch, next->first.address)) + { + len = next->first.address - location.address; + break; + } + } + } + + void limitToInstructionGroup(const ArchAndAddr& location, const uint8_t* opcode, size_t& len) + { + // Preserve delay slots, but exclude following instructions: a returning override + // resumes immediately after this group. + size_t groupLength = 0; + unsigned remaining = 1; + while (remaining && (groupLength < len)) + { + InstructionInfo info; + if (!location.arch->GetInstructionInfo(opcode + groupLength, location.address + groupLength, + len - groupLength, info) || !info.length || (info.length > len - groupLength)) + break; + remaining = groupLength ? remaining - 1 : (IsNopOverride() ? 0 : info.delaySlots); + groupLength += info.length; + } + if (!remaining) + len = groupLength; + } + + public: + BranchOverrideLifter(LowLevelILFunction* function, FunctionLifterContext& context) : + m_function(function), m_context(context), m_overrides(context.GetBranchOverrides()) + {} + + LowLevelILFunction* PrepareInstruction(BasicBlock* block, const ArchAndAddr& location, + const uint8_t* opcode, size_t& len) + { + m_prepared.reset(); + limitCoalescing(location, len); + if (!m_overrides.count(location)) + return m_function; + + auto branches = location.arch->GetBranchTypesWithContext(m_function->GetFunction(), location.address, + m_context.GetFunctionArchContextRaw()); + m_prepared = m_context.PrepareLiftedBranchOverrides(*m_function, block, location, branches); + if (!m_prepared) + return m_function; + + limitToInstructionGroup(location, opcode, len); + return IsNopOverride() ? m_function : m_prepared->GetSource().GetPtr(); + } + + bool IsNopOverride() const { return m_prepared && m_prepared->SuppressesInstruction(); } + + bool LiftInstruction(BasicBlock* block, const ArchAndAddr& location, const uint8_t* opcode, + size_t& len, LowLevelILFunction*& liftTarget) + { + liftTarget = PrepareInstruction(block, location, opcode, len); + if (IsNopOverride()) + return true; + return location.arch->GetInstructionLowLevelIL(opcode, location.address, len, *liftTarget); + } + + bool FinishInstruction(const ArchAndAddr& location, uint64_t continuationAddress, + bool& status, size_t& instrCountAfter) + { + if (!m_prepared) + return true; + + bool applied = m_prepared->Apply(continuationAddress); + m_prepared.reset(); + if (!applied) + { + m_context.GetLogger()->LogWarn("Unable to match branch overrides to lifted control flow at %#" PRIx64 + " (%s); this instruction requires architecture-specific lifting support.", + location.address, location.arch->GetName().c_str()); + return false; + } + + // Lifters can return false for a valid terminator. An overridden call may now + // continue, so use the analyzed block boundary after a successful rewrite. + status = true; + instrCountAfter = m_function->GetInstructionCount(); + return true; + } + }; +} + + bool Architecture::DefaultLiftFunction(LowLevelILFunction* function, FunctionLifterContext& context) { + unique_ptr overrideLifter; + if (!context.GetBranchOverrides().empty()) + overrideLifter = make_unique(function, context); + Ref data = context.GetView(); Ref logger = context.GetLogger(); Ref platform = context.GetPlatform(); @@ -884,22 +1112,28 @@ bool Architecture::DefaultLiftFunction(LowLevelILFunction* function, FunctionLif } size_t instrCountBefore = function->GetInstructionCount(); - bool status = i->GetArchitecture()->GetInstructionLowLevelIL(opcode, addr, len, *function); - size_t instrCountAfter = function->GetInstructionCount(); + auto liftTarget = function; + bool status = overrideLifter ? overrideLifter->LiftInstruction(i, cur, opcode, len, liftTarget) + : i->GetArchitecture()->GetInstructionLowLevelIL(opcode, addr, len, *function); + size_t instrCountAfter = liftTarget->GetInstructionCount(); while (nextRelocation && nextRelocation->GetAddress() >= addr && nextRelocation->GetAddress() < addr + len) { - if (data->IsOffsetExternSemantics(nextRelocation->GetTarget())) + if (data->IsOffsetExternSemantics(nextRelocation->GetTarget()) + && !(overrideLifter && overrideLifter->IsNopOverride())) { int64_t operand = relocationHandler->GetOperandForExternalRelocation( - opcode, addr, len, function, nextRelocation); + opcode, addr, len, liftTarget, nextRelocation); if (operand != BN_NOCOERCE_EXTERN_PTR) { ApplyExternPointerForRelocation( - operand, *function, instrCountBefore, instrCountAfter, nextRelocation, logger); + operand, *liftTarget, liftTarget == function ? instrCountBefore : 0, + instrCountAfter, nextRelocation, logger); } } nextRelocation = data->GetNextRelocation(nextRelocation->GetAddress() + 1, i->GetEnd()); } + if (overrideLifter && !overrideLifter->FinishInstruction(cur, addr + len, status, instrCountAfter)) + return false; // Conditional Call Support (Part 2) // Replace the emitted GOTO with a noreturn expression @@ -908,7 +1142,8 @@ bool Architecture::DefaultLiftFunction(LowLevelILFunction* function, FunctionLif { for (size_t instrIndex = instrCountBefore; instrIndex < (instrCountAfter - 1); instrIndex++) { - if (function->GetInstruction(instrIndex).operation != LLIL_CALL) + auto call = function->GetInstruction(instrIndex); + if ((call.operation != LLIL_CALL) || (call.attributes & ILBranchOverride)) continue; LowLevelILInstruction instr = function->GetInstruction(instrIndex + 1); if (instr.operation == LLIL_GOTO) @@ -922,7 +1157,8 @@ bool Architecture::DefaultLiftFunction(LowLevelILFunction* function, FunctionLif context.CheckForInlinedCall(i, instrCountBefore, instrCountAfter, prevAddr, addr, opcode, len, indirectSource); // Indirect branch information informs when to translate non-standard returns into jumps - if (auto lastInstr = function->GetInstruction(instrCountAfter - 1); (lastInstr.operation == LLIL_RET) + if (auto lastInstr = instrCountAfter ? function->GetInstruction(instrCountAfter - 1) : LowLevelILInstruction(); + instrCountAfter && (lastInstr.operation == LLIL_RET) && !(lastInstr.attributes & ILBranchOverride) && (function->HasIndirectBranches() || !function->GetFunction()->CanReturn().GetValue())) { auto addressSize = platform->GetAddressSize(); @@ -948,7 +1184,8 @@ bool Architecture::DefaultLiftFunction(LowLevelILFunction* function, FunctionLif // 2) By default, contextualFunctionReturns is used to translate this to a LLIL_RET (conservative) // 3) Downstream analysis uses dataflow to validate the return target // 4) If the target is not the ReturnAddressValue, then we avoid the translation to a return and leave the instruction as a call - if (LowLevelILInstruction prevInstr = function->GetInstruction(function->GetInstructionCount() - 1); prevInstr.operation == LLIL_CALL) + if (LowLevelILInstruction prevInstr = function->GetInstruction(function->GetInstructionCount() - 1); + (prevInstr.operation == LLIL_CALL) && !(prevInstr.attributes & ILBranchOverride)) { if (auto itr = contextualReturns.find(ArchAndAddr(i->GetArchitecture(), prevInstr.address)); itr != contextualReturns.end() && itr->second) prevInstr.Replace(function->Return(prevInstr.GetDestExpr().exprIndex, prevInstr)); @@ -1004,6 +1241,8 @@ void FunctionLifterContext::CheckForInlinedCall(BasicBlock* block, size_t instrC if (instrCountAfter > instrCountBefore) { LowLevelILInstruction lastInstr = m_function->GetInstruction(instrCountAfter - 1); + if ((lastInstr.operation == LLIL_JUMP) && (lastInstr.attributes & ILBranchOverride)) + return; if ((lastInstr.operation == LLIL_CALL || lastInstr.operation == LLIL_JUMP) && (lastInstr.GetDestExpr().operation == LLIL_CONST || lastInstr.GetDestExpr().operation == LLIL_CONST_PTR)) { diff --git a/function.cpp b/function.cpp index a061bd2d7a..442793116e 100644 --- a/function.cpp +++ b/function.cpp @@ -1591,6 +1591,48 @@ void Function::SetUserIndirectBranches( } +void Function::SetUserBranchOverride(Architecture* arch, uint64_t addr, BNBranchType originalBranchType, + BNBranchType replacementBranchType, std::optional replacementTarget, Architecture* replacementTargetArch) +{ + BNSetUserBranchOverride(m_object, arch->GetObject(), addr, originalBranchType, replacementBranchType, + replacementTarget.has_value(), replacementTarget && replacementTargetArch ? replacementTargetArch->GetObject() : nullptr, + replacementTarget.value_or(0)); +} + + +void Function::ClearUserBranchOverride(Architecture* arch, uint64_t addr, BNBranchType originalBranchType) +{ + BNClearUserBranchOverride(m_object, arch->GetObject(), addr, originalBranchType); +} + + +std::map Function::GetUserBranchOverrides(Architecture* arch, uint64_t addr) +{ + size_t count; + BNBranchOverride* overrides = BNGetUserBranchOverrides(m_object, arch->GetObject(), addr, &count); + std::map result; + for (size_t i = 0; i < count; i++) + { + BranchOverride replacement {overrides[i].replacementBranchType}; + if (overrides[i].hasReplacementTarget) + { + replacement.target = overrides[i].replacementTarget; + if (overrides[i].replacementTargetArch) + replacement.targetArch = new CoreArchitecture(overrides[i].replacementTargetArch); + } + result[overrides[i].originalBranchType] = replacement; + } + BNFreeBranchOverrideList(overrides); + return result; +} + + +bool Function::IsValidBranchOverrideLocation(Architecture* arch, uint64_t addr) +{ + return BNIsValidBranchOverrideLocation(m_object, arch->GetObject(), addr); +} + + void Function::SetGuidedSourceBlocks(const std::vector& addresses) { BNArchitectureAndAddress* addressList = new BNArchitectureAndAddress[addresses.size()]; diff --git a/python/architecture.py b/python/architecture.py index c038aad1fc..39ab78dfb0 100644 --- a/python/architecture.py +++ b/python/architecture.py @@ -97,6 +97,14 @@ def get(self, block: "basicblock.BasicBlock", addr: int) -> bytes: return ctypes.string_at(ptr, size.value) +@dataclass +class BranchOverride: + """A replacement branch type and optional explicit destination.""" + type: BranchType + target: Optional[int] = None + target_arch: Optional['Architecture'] = None + + @dataclass class BasicBlockAnalysisContext: """Used by ``analyze_basic_blocks`` and contains analysis settings and other contextual information. @@ -111,6 +119,7 @@ class BasicBlockAnalysisContext: # In _indirect_branches: List["variable.IndirectBranchInfo"] _indirect_no_return_calls: Set["function.ArchAndAddr"] + _branch_overrides: Dict["function.ArchAndAddr", Dict[BranchType, BranchOverride]] _analysis_skip_override: core.FunctionAnalysisSkipOverride _guided_analysis_mode: bool _trigger_guided_on_invalid_instruction: bool @@ -126,6 +135,7 @@ class BasicBlockAnalysisContext: _direct_code_references: Dict[int, "function.ArchAndAddr"] _direct_no_return_calls: Set["function.ArchAndAddr"] _halted_disassembly_addresses: Set["function.ArchAndAddr"] + _valid_branch_override_locations: Set["function.ArchAndAddr"] @staticmethod def from_core_struct(bn_bb_context: core.BNBasicBlockAnalysisContext) -> "BasicBlockAnalysisContext": @@ -150,6 +160,21 @@ def from_core_struct(bn_bb_context: core.BNBasicBlockAnalysisContext) -> "BasicB ) indirect_no_return_calls.add(loc) + branch_overrides = {} + for i in range(0, bn_bb_context.branchOverrideCount): + loc = function.ArchAndAddr( + CoreArchitecture._from_cache(bn_bb_context.branchOverrides[i].arch), + bn_bb_context.branchOverrides[i].address, + ) + raw_override = bn_bb_context.branchOverrides[i] + target = raw_override.replacementTarget if raw_override.hasReplacementTarget else None + target_arch = None + if raw_override.replacementTargetArch: + target_arch = CoreArchitecture._from_cache(raw_override.replacementTargetArch) + branch_overrides.setdefault(loc, {})[BranchType(raw_override.originalBranchType)] = BranchOverride( + BranchType(raw_override.replacementBranchType), target, target_arch + ) + contextual_returns = {} for i in range(0, bn_bb_context.contextualFunctionReturnCount): loc = function.ArchAndAddr( @@ -187,6 +212,7 @@ def from_core_struct(bn_bb_context: core.BNBasicBlockAnalysisContext) -> "BasicB _handle=bn_bb_context, _function=function.Function(view, core.BNNewFunctionReference(bn_bb_context.function)), _indirect_branches=indirect_branches, _indirect_no_return_calls=indirect_no_return_calls, + _branch_overrides=branch_overrides, _analysis_skip_override=bn_bb_context.analysisSkipOverride, _guided_analysis_mode=bn_bb_context.guidedAnalysisMode, _trigger_guided_on_invalid_instruction=bn_bb_context.triggerGuidedOnInvalidInstruction, @@ -196,6 +222,7 @@ def from_core_struct(bn_bb_context: core.BNBasicBlockAnalysisContext) -> "BasicB _contextual_returns=contextual_returns, _contextual_returns_dirty=False, _direct_code_references=direct_code_references, _direct_no_return_calls=direct_no_return_calls, _halted_disassembly_addresses=halted_disassembly_addresses, + _valid_branch_override_locations=set(), ) @property @@ -210,6 +237,17 @@ def indirect_no_return_calls(self) -> Set["function.ArchAndAddr"]: return self._indirect_no_return_calls + @property + def branch_overrides(self) -> Dict["function.ArchAndAddr", Dict[BranchType, BranchOverride]]: + """Get the mapping of branch locations to their user-specified branch types.""" + + return self._branch_overrides + + @property + def valid_branch_override_locations(self) -> Set["function.ArchAndAddr"]: + """Locations at which the function supports user branch type overrides.""" + return self._valid_branch_override_locations + @property def analysis_skip_override(self) -> core.FunctionAnalysisSkipOverride: """Get the analysis skip override setting for this context.""" @@ -453,6 +491,14 @@ def finalize(self) -> None: halted_addresses[i].address = loc.addr core.BNAnalyzeBasicBlocksContextSetHaltedDisassemblyAddresses(self._handle, halted_addresses, total) + if self._valid_branch_override_locations: + total = len(self._valid_branch_override_locations) + locations = (core.BNArchitectureAndAddress * total)() + for i, loc in enumerate(self._valid_branch_override_locations): + locations[i].arch = loc.arch.handle + locations[i].address = loc.addr + core.BNAnalyzeBasicBlocksContextSetValidBranchOverrideLocations(self._handle, locations, total) + self._handle.maxSizeReached = ctypes.c_bool(self._max_size_reached) if self._contextual_returns_dirty: total = len(self._contextual_returns) @@ -465,6 +511,53 @@ def finalize(self) -> None: core.BNAnalyzeBasicBlocksContextSetContextualFunctionReturns(self._handle, returns, values, total) +class PreparedLiftedBranchOverride: + """An override prepared for an architecture-defined instruction group. + + Created by :py:meth:`FunctionLifterContext.prepare_lifted_branch_overrides`. + Owns a snapshot of its inputs and retains the destination and source block. Use only + during lifting. The architecture is responsible for selecting the affected instruction + group, including delayed effects, without suppressing unrelated packet instructions. + """ + + def __init__(self, handle: core.BNPreparedLiftedBranchOverrideHandle, arch: 'Architecture'): + self._handle = handle + self._arch = arch + self._source = None + + def __del__(self): + if core is not None: + core.BNFreePreparedLiftedBranchOverride(self._handle) + + @property + def suppresses_instruction(self) -> bool: + """Whether application suppresses the group; no lifting is needed in this case.""" + return core.BNPreparedLiftedBranchOverrideSuppressesInstruction(self._handle) + + @property + def source(self) -> "lowlevelil.LowLevelILFunction": + """Isolated staging IL, allocated on first access with owner, address and source block set. + + Keep native destinations as address expressions and mark all local labels. Existing + temporary identities are preserved; avoid collisions with destination temporaries. + Do not finalize the staging IL. + """ + if self._source is None: + self._source = lowlevelil.LowLevelILFunction( + arch=self._arch, handle=core.BNGetPreparedLiftedBranchOverrideSource(self._handle)) + return self._source + + def apply(self, continuation_address: int) -> bool: + """Append the rewritten group, or one attributed NOP when suppressed. + + The caller supplies the address following the complete instruction group and sets + the destination's indirect-branch state. Application sets its address and source block. + False leaves the destination untouched. A failed rewrite can be retried; a successful + application consumes the override and subsequent calls return False. + """ + return core.BNApplyPreparedLiftedBranchOverride(self._handle, continuation_address) + + @dataclass class FunctionLifterContext: """Used by ``lift_function`` and contains contextual information for function-level lifting @@ -481,6 +574,7 @@ class FunctionLifterContext: _inline_remapping: Dict["function.ArchAndAddr", "function.ArchAndAddr"] _user_indirect_branches: Dict["function.ArchAndAddr", Set["function.ArchAndAddr"]] _auto_indirect_branches: Dict["function.ArchAndAddr", Set["function.ArchAndAddr"]] + _branch_overrides: Dict["function.ArchAndAddr", Dict[BranchType, BranchOverride]] _inlined_calls: Set[int] _function_arch_context_token: int @@ -542,6 +636,21 @@ def from_core_struct( user_indirect_branches[src] = set() user_indirect_branches[src].add(dest) + branch_overrides = {} + for i in range(0, bn_fl_context.branchOverrideCount): + loc = function.ArchAndAddr( + CoreArchitecture._from_cache(bn_fl_context.branchOverrides[i].arch), + bn_fl_context.branchOverrides[i].address, + ) + raw_override = bn_fl_context.branchOverrides[i] + target = raw_override.replacementTarget if raw_override.hasReplacementTarget else None + target_arch = None + if raw_override.replacementTargetArch: + target_arch = CoreArchitecture._from_cache(raw_override.replacementTargetArch) + branch_overrides.setdefault(loc, {})[BranchType(raw_override.originalBranchType)] = BranchOverride( + BranchType(raw_override.replacementBranchType), target, target_arch + ) + inlined_calls = set() for i in range(0, bn_fl_context.inlinedCallsCount): inlined_calls.add(bn_fl_context.inlinedCalls[i]) @@ -552,6 +661,7 @@ def from_core_struct( core.BNNewLowLevelILFunctionReference(func)), _platform=plat, _logger=logger, _blocks=blocks, _contextual_returns=contextual_returns, _inline_remapping=inline_remapping, _user_indirect_branches=user_indirect_branches, _auto_indirect_branches=auto_indirect_branches, + _branch_overrides=branch_overrides, _inlined_calls=inlined_calls, _function_arch_context_token=bn_fl_context.functionArchContext, ) @@ -560,12 +670,101 @@ def prepare_block_translation(self, function, arch, address): core.BNPrepareBlockTranslation(function.handle, arch.handle, address) + def prepare_lifted_branch_overrides( + self, dest: "lowlevelil.LowLevelILFunction", block: Optional["basicblock.BasicBlock"], + location: "function.ArchAndAddr", branches: List["OverridableBranchInfo"] + ) -> Optional[PreparedLiftedBranchOverride]: + """Prepare matching overrides without changing ``dest``. + + Returns None for no matching overrides or invalid inputs, without allocating staging IL. + Otherwise, skip lifting if ``suppresses_instruction`` is true; lift the architecture-defined + group into ``source`` and call ``apply`` with its continuation address. ``dest`` must have + an owning function. ``block``, when supplied, must belong to that function. Inputs are copied. + """ + info = self._get_lifted_branch_override_info(location, branches) + if info is None: + return None + handle = core.BNPrepareLiftedBranchOverrides(dest.handle, block.handle if block else None, info) + return PreparedLiftedBranchOverride(handle, CoreArchitecture._from_cache(info.arch)) if handle else None + + def apply_lifted_branch_overrides( + self, dest: "lowlevelil.LowLevelILFunction", source: "lowlevelil.LowLevelILFunction", + location: "function.ArchAndAddr", continuation_address: int, + branches: List["OverridableBranchInfo"] + ) -> bool: + """Append an isolated, unfinalized instruction group with this context's overrides applied. + + Native targets in ``source`` must be address expressions and its local labels must be marked. + ``dest`` and ``source`` must be distinct, and ``dest`` must have an owning function. Set its + current address, source block, and indirect targets before calling. Temporary-register identities + are preserved; the caller must avoid collisions with existing temporaries. A NOP override + suppresses the entire group, including side effects. ``continuation_address`` follows the whole + group, including delay slots. + + Returns False without changing ``dest`` if no override matches or a replacement is invalid or + ambiguous. ``source`` may gain a fallthrough expression. Call only during lifting. + """ + info = self._get_lifted_branch_override_info(location, branches) + if info is None: + return False + info.continuationAddress = continuation_address + return core.BNApplyLiftedBranchOverrides(dest.handle, source.handle, info) + + def _get_lifted_branch_override_info(self, location, branches): + # Context keys use CoreArchitecture even when a Python architecture passes itself. + location = function.ArchAndAddr(CoreArchitecture._from_cache(location.arch.handle), location.addr) + overrides = self._branch_overrides.get(location) + if not overrides: + return None + raw_branches = (core.BNOverridableBranchInfo * len(branches))() + for i, branch in enumerate(branches): + raw_branches[i].type = branch.type + raw_branches[i].target = branch.target + raw_branches[i].arch = branch.arch.handle if branch.arch else None + raw_overrides = (core.BNBranchOverride * len(overrides))() + for i, (original, value) in enumerate(overrides.items()): + raw_overrides[i].arch = location.arch.handle + raw_overrides[i].address = location.addr + raw_overrides[i].originalBranchType = original + raw_overrides[i].replacementBranchType = value.type + raw_overrides[i].hasReplacementTarget = value.target is not None + raw_overrides[i].replacementTargetArch = value.target_arch.handle if value.target_arch else None + raw_overrides[i].replacementTarget = value.target if value.target is not None else 0 + targets = self._user_indirect_branches.get(location, self._auto_indirect_branches.get(location, set())) + raw_targets = (core.BNArchitectureAndAddress * len(targets))() + for i, target in enumerate(targets): + raw_targets[i].arch = target.arch.handle + raw_targets[i].address = target.addr + no_return_call = any( + self._handle.noReturnCalls[i].address == location.addr + and CoreArchitecture._from_cache(self._handle.noReturnCalls[i].arch) == location.arch + for i in range(self._handle.noReturnCallsCount) + ) + info = core.BNLiftedBranchOverrideInfo() + info.arch = location.arch.handle + info.address = location.addr + info.continuationAddress = 0 + info.branches = raw_branches + info.branchCount = len(raw_branches) + info.overrides = raw_overrides + info.overrideCount = len(raw_overrides) + info.noReturnCall = no_return_call + info.indirectTargets = raw_targets + info.indirectTargetCount = len(raw_targets) + return info + @property def blocks(self) -> List["basicblock.BasicBlock"]: """Get the list of basic blocks in this context""" return self._blocks + @property + def branch_overrides(self) -> Dict["function.ArchAndAddr", Dict[BranchType, BranchOverride]]: + """Get the mapping of branch locations to their user-specified branch types.""" + + return self._branch_overrides + @property def function_arch_context(self) -> Any: """Get the function architecture context""" @@ -643,6 +842,13 @@ def __repr__(self): return f"<{self.type}: {self.target:#x}>" +@dataclass(frozen=True) +class OverridableBranchInfo: + type: BranchType + target: int + arch: Optional['Architecture'] = None + + @dataclass(frozen=False) class InstructionInfo: length: int = 0 @@ -780,6 +986,9 @@ def __init__(self): self._get_associated_arch_by_address ) self._cb.getInstructionInfo = self._cb.getInstructionInfo.__class__(self._get_instruction_info) + self._cb.getBranchTypesWithContext = self._cb.getBranchTypesWithContext.__class__( + self._get_branch_types_with_context + ) self._cb.getInstructionText = self._cb.getInstructionText.__class__(self._get_instruction_text) self._cb.getInstructionTextWithContext = self._cb.getInstructionTextWithContext.__class__( self._get_instruction_text_with_context @@ -1243,6 +1452,21 @@ def _get_instruction_info(self, ctxt, data, addr, max_len, result): log_error_for_exception("Unhandled Python exception in Architecture._get_instruction_info") return False + def _get_branch_types_with_context(self, ctxt, func, addr, branches, max_branches, context_token): + try: + func_obj = function.Function(handle=core.BNNewFunctionReference(func)) + context = self.function_arch_contexts.get(int(context_token or 0), None) + result = self.get_branch_types_with_context(func_obj, addr, context) + count = min(len(result), max_branches) + for i in range(count): + branches[i].type = result[i].type + branches[i].target = result[i].target + branches[i].arch = result[i].arch.handle if result[i].arch else None + return count + except Exception: + log_error_for_exception("Unhandled Python exception in Architecture._get_branch_types_with_context") + return 0 + def _get_instruction_text(self, ctxt, data, addr, length, result, count): try: buf = ctypes.create_string_buffer(length[0]) @@ -2017,6 +2241,25 @@ def get_instruction_info(self, data: bytes, addr: int) -> Optional[InstructionIn """ raise NotImplementedError + def get_branch_types_with_context( + self, func: 'function.Function', addr: int, function_arch_context: Any = None + ) -> List[OverridableBranchInfo]: + """Use core default branch discovery, including a targetless ``NopBranch`` for a NOP. + + Custom implementations can delegate with ``super()`` without invoking their callback again. + """ + count = ctypes.c_ulonglong() + branches = core.BNArchitectureDefaultGetBranchTypesWithContext(self.handle, func.handle, addr, count) + try: + return [ + OverridableBranchInfo( + BranchType(branches[i].type), branches[i].target, + CoreArchitecture._from_cache(branches[i].arch) if branches[i].arch else None) + for i in range(count.value) + ] + finally: + core.BNFreeOverridableBranchInfoList(branches) + def get_instruction_text(self, data: bytes, addr: int) -> Optional[Tuple[List['function.InstructionTextToken'], int]]: """ ``get_instruction_text`` returns a tuple containing a list of decoded InstructionTextToken objects and the bytes used at the given virtual @@ -3072,6 +3315,18 @@ def get_instruction_info(self, data: bytes, addr: int) -> Optional[InstructionIn result.add_branch(BranchType(info.branchType[i]), target, arch) return result + def get_branch_types_with_context( + self, func: 'function.Function', addr: int, function_arch_context: Any = None + ) -> List[OverridableBranchInfo]: + count = ctypes.c_ulonglong() + branches = core.BNGetArchitectureBranchTypesWithContext(self.handle, func.handle, addr, count) + result = [] + for i in range(count.value): + arch = CoreArchitecture._from_cache(branches[i].arch) if branches[i].arch else None + result.append(OverridableBranchInfo(BranchType(branches[i].type), branches[i].target, arch)) + core.BNFreeOverridableBranchInfoList(branches) + return result + def get_instruction_text(self, data: bytes, addr: int) -> Optional[Tuple[List['function.InstructionTextToken'], int]]: """ ``get_instruction_text`` returns a list of InstructionTextToken objects for the instruction at the given virtual @@ -3450,6 +3705,8 @@ def __init__(self, base_arch: 'Architecture'): self._cb.getAssociatedArchitectureByAddress = self._cb.getAssociatedArchitectureByAddress.__class__() if self.get_instruction_info.__code__ == CoreArchitecture.get_instruction_info.__code__: self._cb.getInstructionInfo = self._cb.getInstructionInfo.__class__() + if self.get_branch_types_with_context.__code__ == CoreArchitecture.get_branch_types_with_context.__code__: + self._cb.getBranchTypesWithContext = self._cb.getBranchTypesWithContext.__class__() if self.get_instruction_text.__code__ == CoreArchitecture.get_instruction_text.__code__: self._cb.getInstructionText = self._cb.getInstructionText.__class__() if self.get_instruction_text_with_context.__code__ == CoreArchitecture.get_instruction_text_with_context.__code__: diff --git a/python/function.py b/python/function.py index 94d2de2f0a..76fd0fd495 100644 --- a/python/function.py +++ b/python/function.py @@ -2396,6 +2396,75 @@ def set_user_indirect_branches( branch_list[i].address = branches[i][1] core.BNSetUserIndirectBranches(self.handle, source_arch.handle, source, branch_list, len(branches)) + def set_user_branch_override( + self, address: int, original_branch_type: 'architecture.BranchType', + replacement_branch_type: 'architecture.BranchType', + replacement_target: Optional[int] = None, + replacement_target_arch: Optional['architecture.Architecture'] = None, + arch: Optional['architecture.Architecture'] = None + ) -> None: + """Replace a branch type and optionally its destination at ``address``. + + Calls cannot be replaced with ``SystemCall`` or ``ExceptionBranch``. Such requests + are ignored without changing existing overrides. + + A target in another architecture must belong to this function's platform or an + explicitly related platform. Unsupported targets are ignored without changing existing overrides. + + ``NopBranch`` as the replacement suppresses the entire instruction and replaces + any other overrides at the same address. Setting a non-NOP override removes an + existing NOP override. Replacing a NOP with a jump or call requires an explicit target. + """ + if arch is None: + arch = self.arch + core.BNSetUserBranchOverride( + self.handle, arch.handle, address, original_branch_type, replacement_branch_type, + replacement_target is not None, + replacement_target_arch.handle if replacement_target is not None and replacement_target_arch else None, + replacement_target or 0, + ) + + def clear_user_branch_override( + self, address: int, original_branch_type: 'architecture.BranchType', + arch: Optional['architecture.Architecture'] = None + ) -> None: + """Clear the branch override for ``original_branch_type`` at ``address``.""" + if arch is None: + arch = self.arch + core.BNClearUserBranchOverride(self.handle, arch.handle, address, original_branch_type) + + def get_user_branch_overrides( + self, address: int, arch: Optional['architecture.Architecture'] = None + ) -> Dict['architecture.BranchType', 'architecture.BranchOverride']: + """Get user branch overrides at ``address``, keyed by the original branch type.""" + if arch is None: + arch = self.arch + count = ctypes.c_ulonglong() + overrides = core.BNGetUserBranchOverrides(self.handle, arch.handle, address, count) + try: + result = {} + for i in range(count.value): + raw_override = overrides[i] + target = raw_override.replacementTarget if raw_override.hasReplacementTarget else None + target_arch = None + if raw_override.hasReplacementTarget and raw_override.replacementTargetArch: + target_arch = architecture.CoreArchitecture._from_cache(raw_override.replacementTargetArch) + result[architecture.BranchType(raw_override.originalBranchType)] = architecture.BranchOverride( + architecture.BranchType(raw_override.replacementBranchType), target, target_arch + ) + return result + finally: + if overrides is not None: + core.BNFreeBranchOverrideList(overrides) + + def is_valid_branch_override_location( + self, address: int, arch: Optional['architecture.Architecture'] = None + ) -> bool: + """Whether ``address`` is valid for a user branch type override in this function.""" + if arch is None: + arch = self.arch + return core.BNIsValidBranchOverrideLocation(self.handle, arch.handle, address) + def set_guided_source_blocks( self, addresses: List[Tuple['architecture.Architecture', int]] ) -> None: diff --git a/rust/src/architecture.rs b/rust/src/architecture.rs index c8e6b7f103..b47a1a1d9e 100644 --- a/rust/src/architecture.rs +++ b/rust/src/architecture.rs @@ -65,6 +65,9 @@ pub mod instruction; pub mod intrinsic; pub mod register; +#[cfg(test)] +mod lifted_branch_override_tests; + // Re-export all the submodules to keep from breaking everyone's code. // We split these out just to clarify each part, not necessarily to enforce an extra namespace. pub use basic_block::*; @@ -603,6 +606,17 @@ pub trait ArchitectureWithFunctionContext: Architecture { ) -> Option<(usize, Vec)> { self.instruction_text(data, addr) } + + /// Use the core's default branch discovery, including on-demand NOP detection. + /// Override this for context-dependent branches. + fn branch_types_with_typed_context( + &self, + function: &Function, + addr: u64, + _context: Option<&Self::FunctionArchContext>, + ) -> Vec { + self.as_ref().default_branch_types_with_context(function, addr) + } } pub struct FunctionLifterContext { @@ -616,6 +630,7 @@ pub struct FunctionLifterContext { pub inlined_remapping: HashMap, pub user_indirect_branches: HashMap>, pub auto_indirect_branches: HashMap>, + pub branch_overrides: HashMap>, pub inlined_calls: HashSet, } @@ -628,6 +643,52 @@ unsafe fn lifter_context_slice<'a, T>(ptr: *const T, len: usize) -> &'a [T] { } } +/// Prepared overrides and staging IL for one architecture-defined instruction group. +/// +/// Owns its inputs and retains the destination/source block. Use only during lifting. +/// Dropping this object without applying it leaves the destination untouched. +#[derive(Debug)] +pub struct PreparedLiftedBranchOverride { + handle: NonNull, + arch: CoreArchitecture, +} + +impl PreparedLiftedBranchOverride { + /// Whether the group is suppressed. No lifting or staging IL is needed in this case. + pub fn suppresses_instruction(&self) -> bool { + unsafe { BNPreparedLiftedBranchOverrideSuppressesInstruction(self.handle.as_ptr()) } + } + + /// Get isolated staging IL, allocated on first access with owner, address and source block set. + /// + /// Native destinations must remain address expressions and all local labels must be marked. + /// Temporary identities are preserved; avoid collisions with destination temporaries. + /// Do not finalize this IL. Repeated calls return references to the same function. + pub fn source(&self) -> Ref { + unsafe { + LowLevelILMutableFunction::ref_from_raw_with_arch( + BNGetPreparedLiftedBranchOverrideSource(self.handle.as_ptr()), + Some(self.arch), + ) + } + } + + /// Append the rewritten group, or one attributed NOP for a suppressed group. + /// + /// Set the destination's indirect-branch state before calling. Its address/source block + /// are set by this method. False leaves the destination untouched and permits retrying. + /// Once successfully applied, subsequent calls return false without emitting more IL. + pub fn apply(&mut self, continuation_address: u64) -> bool { + unsafe { BNApplyPreparedLiftedBranchOverride(self.handle.as_ptr(), continuation_address) } + } +} + +impl Drop for PreparedLiftedBranchOverride { + fn drop(&mut self) { + unsafe { BNFreePreparedLiftedBranchOverride(self.handle.as_ptr()) } + } +} + impl FunctionLifterContext { pub unsafe fn from_raw( function: *mut BNLowLevelILFunction, @@ -719,6 +780,29 @@ impl FunctionLifterContext { } } + let mut branch_overrides: HashMap> = + HashMap::new(); + for entry in lifter_context_slice(flc_ref.branchOverrides, flc_ref.branchOverrideCount) { + let target = entry.hasReplacementTarget.then(|| { + let arch = (!entry.replacementTargetArch.is_null()) + .then(|| CoreArchitecture::from_raw(entry.replacementTargetArch)); + Location::new(arch, entry.replacementTarget) + }); + branch_overrides + .entry(Location::new( + Some(CoreArchitecture::from_raw(entry.arch)), + entry.address, + )) + .or_default() + .insert( + entry.originalBranchType, + BranchOverride { + type_: entry.replacementBranchType, + target, + }, + ); + } + let inlined_calls: HashSet = lifter_context_slice(flc_ref.inlinedCalls, flc_ref.inlinedCallsCount) .iter() @@ -739,6 +823,7 @@ impl FunctionLifterContext { inlined_remapping, user_indirect_branches, auto_indirect_branches, + branch_overrides, inlined_calls, } } @@ -754,6 +839,114 @@ impl FunctionLifterContext { } } + /// Prepare overrides for an architecture-defined instruction group without changing `dest`. + /// + /// Returns `None` for no matching overrides or invalid inputs; staging IL is not allocated. + /// Otherwise, skip lifting when `suppresses_instruction()` is true, or lift into `source()`. + /// Apply with the continuation address after the complete group, including delayed effects. + /// The caller must exclude unrelated packet instructions from the group. Inputs are copied. + /// `dest` must have an owning function; `block`, if supplied, must belong to that function. + pub fn prepare_lifted_branch_overrides( + &self, + dest: &LowLevelILMutableFunction, + block: Option<&BasicBlock>, + location: Location, + branches: &[OverridableBranchInfo], + ) -> Option { + self.with_lifted_branch_override_info(dest, location, branches, |info| { + let handle = unsafe { + BNPrepareLiftedBranchOverrides( + dest.handle, + block.map_or(std::ptr::null_mut(), |b| b.handle), + &info, + ) + }; + NonNull::new(handle).map(|handle| PreparedLiftedBranchOverride { + handle, + arch: location.arch.unwrap_or_else(|| dest.arch()), + }) + }) + .flatten() + } + + /// Append an isolated, unfinalized lifted instruction group with this context's overrides. + /// + /// Native destinations in `source` must remain address expressions, and all local labels + /// must be marked. `dest` and `source` must be distinct; `dest` must have an owning function. + /// Set the destination's source block, current address, and indirect targets before calling. + /// Temporary-register identities are preserved, so the caller must avoid collisions with + /// existing temporaries. A NOP override suppresses the entire group, including side effects. + /// `continuation_address` is the address after the complete group (including delay slots). + /// + /// Returns false without changing `dest` if no overrides match or a replacement is invalid + /// or ambiguous. `source` may gain a synthetic fallthrough expression. Call only during lifting. + pub fn apply_lifted_branch_overrides( + &self, + dest: &LowLevelILMutableFunction, + source: &LowLevelILMutableFunction, + location: Location, + continuation_address: u64, + branches: &[OverridableBranchInfo], + ) -> bool { + self.with_lifted_branch_override_info(dest, location, branches, |mut info| { + info.continuationAddress = continuation_address; + unsafe { BNApplyLiftedBranchOverrides(dest.handle, source.handle, &info) } + }) + .unwrap_or(false) + } + + fn with_lifted_branch_override_info( + &self, + dest: &LowLevelILMutableFunction, + location: Location, + branches: &[OverridableBranchInfo], + f: impl FnOnce(BNLiftedBranchOverrideInfo) -> T, + ) -> Option { + let arch = location.arch.unwrap_or_else(|| dest.arch()); + let location = Location::new(Some(arch), location.addr); + let Some(overrides) = self.branch_overrides.get(&location) else { + return None; + }; + let raw_branches: Vec = + branches.iter().copied().map(Into::into).collect(); + let raw_overrides: Vec = overrides + .iter() + .map(|(original, value)| BNBranchOverride { + arch: arch.handle, + address: location.addr, + originalBranchType: *original, + replacementBranchType: value.type_, + hasReplacementTarget: value.target.is_some(), + replacementTargetArch: value + .target + .and_then(|t| t.arch) + .map_or(std::ptr::null_mut(), |a| a.handle), + replacementTarget: value.target.map_or(0, |t| t.addr), + }) + .collect(); + let mut indirect_targets: Vec = Vec::new(); + if let Some(targets) = self + .user_indirect_branches + .get(&location) + .or_else(|| self.auto_indirect_branches.get(&location)) + { + indirect_targets.extend(targets.iter().map(BNArchitectureAndAddress::from)); + } + let info = BNLiftedBranchOverrideInfo { + arch: arch.handle, + address: location.addr, + continuationAddress: 0, + branches: raw_branches.as_ptr(), + branchCount: raw_branches.len(), + overrides: raw_overrides.as_ptr(), + overrideCount: raw_overrides.len(), + noReturnCall: self.no_return_calls.contains(&location), + indirectTargets: indirect_targets.as_ptr(), + indirectTargetCount: indirect_targets.len(), + }; + Some(f(info)) + } + /// The per-function instruction byte store populated during basic block analysis. Read it here /// to avoid reading the view during lifting. pub fn lifter_instruction_data(&self) -> Option { @@ -837,6 +1030,41 @@ impl CoreArchitecture { reg_stack => CoreRegisterStack::new(*self, RegisterStackId::from(reg_stack)), } } + + /// Query the architecture's branch-types callback using the function's analysis context. + pub fn branch_types_with_context( + &self, + function: &Function, + addr: u64, + ) -> Vec { + let mut count = 0; + let branches = unsafe { + BNGetArchitectureBranchTypesWithContext(self.handle, function.handle, addr, &mut count) + }; + let branches = unsafe { Array::::new(branches, count, ()) }; + branches.iter().collect() + } + + /// Use default branch discovery without invoking the architecture's branch-types callback. + /// + /// Custom callbacks can delegate here without recursing into themselves. + pub fn default_branch_types_with_context( + &self, + function: &Function, + addr: u64, + ) -> Vec { + let mut count = 0; + let branches = unsafe { + BNArchitectureDefaultGetBranchTypesWithContext( + self.handle, + function.handle, + addr, + &mut count, + ) + }; + let branches = unsafe { Array::::new(branches, count, ()) }; + branches.iter().collect() + } } unsafe impl Send for CoreArchitecture {} @@ -2820,6 +3048,7 @@ where skipAndReturnValue: Some(cb_skip_and_return_value::), getLinearSweepInitialAlignment: Some(cb_linear_sweep_initial_alignment::), getLinearSweepAnalysisCapabilities: Some(cb_linear_sweep_analysis_capabilities::), + getBranchTypesWithContext: None, }; customize(&mut custom_arch); @@ -2901,10 +3130,41 @@ where true } + unsafe extern "C" fn cb_branch_types_with_context_typed( + ctxt: *mut c_void, + function: *mut BNFunction, + addr: u64, + branches: *mut BNOverridableBranchInfo, + max_branches: usize, + context: *mut c_void, + ) -> usize + where + A: 'static + + ArchitectureWithFunctionContext> + + Send + + Sync, + { + let custom_arch = unsafe { &*(ctxt as *mut A) }; + let function = unsafe { Function::from_raw(function) }; + let typed_context = if context.is_null() { + None + } else { + Some(unsafe { &*(context as *const A::FunctionArchContext) }) + }; + + let result = custom_arch.branch_types_with_typed_context(&function, addr, typed_context); + let count = result.len().min(max_branches); + for (i, branch) in result.into_iter().take(count).enumerate() { + unsafe { branches.add(i).write(branch.into()) }; + } + count + } + register_architecture_impl(name, func, |custom_arch| { custom_arch.freeFunctionArchContext = Some(cb_free_function_arch_context_typed::); custom_arch.getInstructionTextWithContext = Some(cb_get_instruction_text_with_context_typed::); + custom_arch.getBranchTypesWithContext = Some(cb_branch_types_with_context_typed::); }) } diff --git a/rust/src/architecture/basic_block.rs b/rust/src/architecture/basic_block.rs index 4e49c00145..00c9cd97bd 100644 --- a/rust/src/architecture/basic_block.rs +++ b/rust/src/architecture/basic_block.rs @@ -1,4 +1,7 @@ -use crate::architecture::{ArchitectureWithFunctionContext, CoreArchitecture, IndirectBranchInfo}; +use crate::architecture::{ + ArchitectureWithFunctionContext, BranchOverride, BranchType, CoreArchitecture, + IndirectBranchInfo, +}; use crate::basic_block::BasicBlock; use crate::function::{Function, Location, NativeBlock}; use crate::rc::Ref; @@ -13,6 +16,7 @@ pub struct BasicBlockAnalysisContext { // In pub indirect_branches: Vec, pub indirect_no_return_calls: HashSet, + pub branch_overrides: HashMap>, pub analysis_skip_override: BNFunctionAnalysisSkipOverride, pub guided_analysis_mode: bool, pub trigger_guided_on_invalid_instruction: bool, @@ -29,6 +33,7 @@ pub struct BasicBlockAnalysisContext { direct_no_return_calls: HashSet, halted_disassembly_addresses: HashSet, inlined_unresolved_indirect_branches: HashSet, + pub valid_branch_override_locations: HashSet, } /// Per-function store of basic block instruction bytes, populated during basic block analysis and @@ -101,6 +106,31 @@ impl BasicBlockAnalysisContext { .map(Location::from) .collect(); + let raw_branch_overrides: &[BNBranchOverride] = + std::slice::from_raw_parts(ctx_ref.branchOverrides, ctx_ref.branchOverrideCount); + let mut branch_overrides: HashMap> = + HashMap::new(); + for entry in raw_branch_overrides { + let target = entry.hasReplacementTarget.then(|| { + let arch = (!entry.replacementTargetArch.is_null()) + .then(|| CoreArchitecture::from_raw(entry.replacementTargetArch)); + Location::new(arch, entry.replacementTarget) + }); + branch_overrides + .entry(Location::new( + Some(CoreArchitecture::from_raw(entry.arch)), + entry.address, + )) + .or_default() + .insert( + entry.originalBranchType, + BranchOverride { + type_: entry.replacementBranchType, + target, + }, + ); + } + let raw_contextual_return_locs: &[BNArchitectureAndAddress] = unsafe { std::slice::from_raw_parts( ctx_ref.contextualFunctionReturnLocations, @@ -182,6 +212,7 @@ impl BasicBlockAnalysisContext { contextual_returns_dirty: false, indirect_branches, indirect_no_return_calls, + branch_overrides, analysis_skip_override: ctx_ref.analysisSkipOverride, guided_analysis_mode: ctx_ref.guidedAnalysisMode, trigger_guided_on_invalid_instruction: ctx_ref.triggerGuidedOnInvalidInstruction, @@ -194,6 +225,7 @@ impl BasicBlockAnalysisContext { direct_no_return_calls, halted_disassembly_addresses, inlined_unresolved_indirect_branches, + valid_branch_override_locations: HashSet::new(), } } @@ -371,6 +403,21 @@ impl BasicBlockAnalysisContext { } } + fn update_valid_branch_override_locations(&mut self) { + let mut raw_locations: Vec<_> = self + .valid_branch_override_locations + .iter() + .map(BNArchitectureAndAddress::from) + .collect(); + unsafe { + BNAnalyzeBasicBlocksContextSetValidBranchOverrideLocations( + self.handle, + raw_locations.as_mut_ptr(), + raw_locations.len(), + ); + } + } + /// To be called before finalizing the basic block analysis. fn update_contextual_returns(&mut self) { let total = self.contextual_returns.len(); @@ -408,6 +455,10 @@ impl BasicBlockAnalysisContext { self.update_inlined_unresolved_indirect_branches(); } + if !self.valid_branch_override_locations.is_empty() { + self.update_valid_branch_override_locations(); + } + unsafe { (*self.handle).maxSizeReached = self.max_size_reached; } @@ -436,6 +487,10 @@ impl Debug for BasicBlockAnalysisContext { .field("contextual_returns", &self.contextual_returns) .field("direct_code_references", &self.direct_code_references) .field("direct_no_return_calls", &self.direct_no_return_calls) + .field( + "valid_branch_override_locations", + &self.valid_branch_override_locations, + ) .field( "halted_disassembly_addresses", &self.halted_disassembly_addresses, diff --git a/rust/src/architecture/branches.rs b/rust/src/architecture/branches.rs index a6810b345b..84f7fa5d80 100644 --- a/rust/src/architecture/branches.rs +++ b/rust/src/architecture/branches.rs @@ -5,6 +5,68 @@ use binaryninjacore_sys::*; pub use binaryninjacore_sys::BNBranchType as BranchType; +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct BranchOverride { + pub type_: BranchType, + pub target: Option, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(crate) struct UserBranchOverride { + pub original_type: BranchType, + pub replacement: BranchOverride, +} + +impl CoreArrayProvider for UserBranchOverride { + type Raw = BNBranchOverride; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for UserBranchOverride { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + unsafe { BNFreeBranchOverrideList(raw) } + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + let target = raw.hasReplacementTarget.then(|| { + let arch = (!raw.replacementTargetArch.is_null()) + .then(|| unsafe { CoreArchitecture::from_raw(raw.replacementTargetArch) }); + Location::new(arch, raw.replacementTarget) + }); + Self { + original_type: raw.originalBranchType, + replacement: BranchOverride { + type_: raw.replacementBranchType, + target, + }, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct OverridableBranchInfo { + pub type_: BranchType, + pub target: u64, + pub arch: Option, +} + +impl CoreArrayProvider for OverridableBranchInfo { + type Raw = BNOverridableBranchInfo; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for OverridableBranchInfo { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + unsafe { BNFreeOverridableBranchInfoList(raw) } + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self { + (*raw).into() + } +} + #[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum BranchKind { #[default] @@ -17,6 +79,7 @@ pub enum BranchKind { SystemCall, Indirect, Exception, + Nop, UserDefined, } @@ -54,6 +117,28 @@ impl BranchInfo { } } +impl From for OverridableBranchInfo { + fn from(value: BNOverridableBranchInfo) -> Self { + let arch = + (!value.arch.is_null()).then(|| unsafe { CoreArchitecture::from_raw(value.arch) }); + Self { + type_: value.type_, + target: value.target, + arch, + } + } +} + +impl From for BNOverridableBranchInfo { + fn from(value: OverridableBranchInfo) -> Self { + Self { + type_: value.type_, + target: value.target, + arch: value.arch.map_or(std::ptr::null_mut(), |arch| arch.handle), + } + } +} + impl From for BNBranchType { fn from(value: BranchInfo) -> Self { match value.kind { @@ -66,6 +151,7 @@ impl From for BNBranchType { BranchKind::SystemCall => BNBranchType::SystemCall, BranchKind::Indirect => BNBranchType::IndirectBranch, BranchKind::Exception => BNBranchType::ExceptionBranch, + BranchKind::Nop => BNBranchType::NopBranch, BranchKind::UserDefined => BNBranchType::UserDefinedBranch, } } @@ -92,6 +178,7 @@ impl From for BranchType { BranchKind::SystemCall => BranchType::SystemCall, BranchKind::Indirect => BranchType::IndirectBranch, BranchKind::Exception => BranchType::ExceptionBranch, + BranchKind::Nop => BranchType::NopBranch, BranchKind::UserDefined => BranchType::UserDefinedBranch, } } diff --git a/rust/src/architecture/instruction.rs b/rust/src/architecture/instruction.rs index 94cb788b6a..19edd8b077 100644 --- a/rust/src/architecture/instruction.rs +++ b/rust/src/architecture/instruction.rs @@ -54,6 +54,7 @@ impl From for InstructionInfo { BNBranchType::SystemCall => BranchKind::SystemCall, BNBranchType::IndirectBranch => BranchKind::Indirect, BNBranchType::ExceptionBranch => BranchKind::Exception, + BNBranchType::NopBranch => BranchKind::Nop, BNBranchType::UnresolvedBranch => BranchKind::Unresolved, BNBranchType::UserDefinedBranch => BranchKind::UserDefined, }, diff --git a/rust/src/function.rs b/rust/src/function.rs index 5023b958f2..be596d5def 100644 --- a/rust/src/function.rs +++ b/rust/src/function.rs @@ -15,7 +15,10 @@ use binaryninjacore_sys::*; use crate::{ - architecture::{Architecture, CoreArchitecture, CoreRegister, Register}, + architecture::{ + Architecture, BranchOverride, BranchType, CoreArchitecture, CoreRegister, Register, + UserBranchOverride, + }, basic_block::{BasicBlock, BlockContext}, binary_view::BinaryView, calling_convention::CoreCallingConvention, @@ -52,7 +55,7 @@ use crate::variable::{ StackVariableReference, Variable, }; use crate::workflow::Workflow; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::ffi::CStr; use std::fmt::{Debug, Formatter}; use std::ptr::NonNull; @@ -861,15 +864,10 @@ impl Function { let arch = arch.unwrap_or_else(|| self.arch()); let adjust_type: Conf<&Type> = adjust_type.into(); unsafe { - BNSetAutoCallTypeAdjustment( - self.handle, - arch.handle, - addr, - &mut BNTypeWithConfidence { - type_: adjust_type.contents.handle, - confidence: adjust_type.confidence, - }, - ) + BNSetAutoCallTypeAdjustment(self.handle, arch.handle, addr, &mut BNTypeWithConfidence { + type_: adjust_type.contents.handle, + confidence: adjust_type.confidence, + }) } } @@ -1711,6 +1709,77 @@ impl Function { } } + /// Replace a branch type and optionally its destination. + /// + /// Targets in another architecture must belong to the function's platform or an + /// explicitly related platform. Unsupported targets leave existing overrides unchanged. + pub fn set_user_branch_override( + &self, + address: u64, + original_branch_type: BranchType, + replacement_branch_type: BranchType, + replacement_target: Option, + arch: Option, + ) { + let arch = arch.unwrap_or_else(|| self.arch()); + unsafe { + BNSetUserBranchOverride( + self.handle, + arch.handle, + address, + original_branch_type, + replacement_branch_type, + replacement_target.is_some(), + replacement_target + .and_then(|target| target.arch) + .map_or(std::ptr::null_mut(), |arch| arch.handle), + replacement_target.map_or(0, |target| target.addr), + ) + } + } + + pub fn clear_user_branch_override( + &self, + address: u64, + original_branch_type: BranchType, + arch: Option, + ) { + let arch = arch.unwrap_or_else(|| self.arch()); + unsafe { + BNClearUserBranchOverride( + self.handle, + arch.handle, + address, + original_branch_type, + ) + } + } + + pub fn user_branch_overrides( + &self, + address: u64, + arch: Option, + ) -> HashMap { + let arch = arch.unwrap_or_else(|| self.arch()); + let mut count = 0; + let overrides = + unsafe { BNGetUserBranchOverrides(self.handle, arch.handle, address, &mut count) }; + let overrides = unsafe { Array::::new(overrides, count, ()) }; + overrides + .iter() + .map(|entry| (entry.original_type, entry.replacement)) + .collect() + } + + pub fn is_valid_branch_override_location( + &self, + address: u64, + arch: Option, + ) -> bool { + let arch = arch.unwrap_or_else(|| self.arch()); + unsafe { BNIsValidBranchOverrideLocation(self.handle, arch.handle, address) } + } + pub fn set_auto_indirect_branches( &self, source: u64, diff --git a/rust/src/low_level_il.rs b/rust/src/low_level_il.rs index 13c649b232..9402570bf1 100644 --- a/rust/src/low_level_il.rs +++ b/rust/src/low_level_il.rs @@ -333,6 +333,8 @@ pub enum ILInstructionAttribute { HLILEarlyReturnPossible, HLILSwitchRecoveryPossible, ILTransparentCopy, + /// Control flow explicitly selected by a user branch override. + ILBranchOverride, } impl ILInstructionAttribute { @@ -351,6 +353,7 @@ impl ILInstructionAttribute { Self::HLILEarlyReturnPossible => 1024, Self::HLILSwitchRecoveryPossible => 2048, Self::ILTransparentCopy => 4096, + Self::ILBranchOverride => 32768, } } }