diff options
35 files changed, 3112 insertions, 122 deletions
diff --git a/basicblock.cpp b/basicblock.cpp index 67856d88..a56512fe 100644 --- a/basicblock.cpp +++ b/basicblock.cpp @@ -592,6 +592,12 @@ bool BasicBlock::IsMediumLevelILBlock() const } +bool BasicBlock::IsHighLevelILBlock() const +{ + return BNIsHighLevelILBasicBlock(m_object); +} + + Ref<LowLevelILFunction> BasicBlock::GetLowLevelILFunction() const { BNLowLevelILFunction* func = BNGetBasicBlockLowLevelILFunction(m_object); diff --git a/binaryninjaapi.h b/binaryninjaapi.h index 92e6b397..709675f7 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -4073,7 +4073,9 @@ namespace BinaryNinja { Ref<BasicBlock> block; DisassemblyTextLine contents; - static LinearDisassemblyLine FromAPIObject(BNLinearDisassemblyLine* line); + BNLinearDisassemblyLine GetAPIObject() const; + static LinearDisassemblyLine FromAPIObject(const BNLinearDisassemblyLine* line); + static void FreeAPIObject(BNLinearDisassemblyLine* line); }; class NamedTypeReference; @@ -10414,17 +10416,23 @@ namespace BinaryNinja { */ bool IsILBlock() const; + /*! Whether the basic block contains Low Level IL + + \return Whether the basic block contains Low Level IL + */ + bool IsLowLevelILBlock() const; + /*! Whether the basic block contains Medium Level IL \return Whether the basic block contains Medium Level IL */ - bool IsLowLevelILBlock() const; + bool IsMediumLevelILBlock() const; /*! Whether the basic block contains High Level IL \return Whether the basic block contains High Level IL */ - bool IsMediumLevelILBlock() const; + bool IsHighLevelILBlock() const; /*! Get the Low Level IL Function for this basic block @@ -11443,6 +11451,12 @@ namespace BinaryNinja { */ Ref<FlowGraphNode> GetNode(size_t i); + /*! Get the total number of nodes in the graph + + \return Node count + */ + size_t GetNodeCount() const; + /*! Whether the FlowGraph has any nodes added \return Whether the FlowGraph has any nodes added @@ -11451,12 +11465,29 @@ namespace BinaryNinja { /*! Add a node to this flowgraph + \note After the graph has completed layout, this function has no effect. + \param node Node to be added. \return Index of the node */ size_t AddNode(FlowGraphNode* node); + /*! Replace an existing node in the graph with a new node. + Any existing edges referencing the old node will be updated to point to + the new node. + \note After the graph has completed layout, this function has no effect. + + \param i Index of the node to replace + \param newNode New node with which to replace the old node + */ + void ReplaceNode(size_t i, FlowGraphNode* newNode); + + /*! Clear all the nodes in the graph + + \note After the graph has completed layout, this function has no effect. + */ + void ClearNodes(); /*! Flow graph width @@ -11548,6 +11579,26 @@ namespace BinaryNinja { void SetOption(BNFlowGraphOption option, bool value = true); bool IsOptionSet(BNFlowGraphOption option); + + /*! Get the list of Render Layers which will be applied to this Flow Graph, + after it calls PopulateNodes. + + \return List of Render Layers + */ + std::vector<class RenderLayer*> GetRenderLayers() const; + + /*! Add a Render Layer to be applied to this Flow Graph. Note that layers will + be applied in the order in which they are added. + + \param layer Render Layer to add + */ + void AddRenderLayer(class RenderLayer* layer); + + /*! Remove a Render Layer from being applied to this Flow Graph + + \param layer Render Layer to remove + */ + void RemoveRenderLayer(class RenderLayer* layer); }; /*! @@ -17101,6 +17152,26 @@ namespace BinaryNinja { Ref<LinearViewCursor> Duplicate(); + /*! Get the list of Render Layers which will be applied to this cursor, at the + end of calls to GetLines. + + \return List of Render Layers + */ + std::vector<class RenderLayer*> GetRenderLayers() const; + + /*! Add a Render Layer to be applied to this cursor. Note that layers will + be applied in the order in which they are added. + + \param layer Render Layer to add + */ + void AddRenderLayer(class RenderLayer* layer); + + /*! Remove a Render Layer from being applied to this cursor + + \param layer Render Layer to remove + */ + void RemoveRenderLayer(class RenderLayer* layer); + static int Compare(LinearViewCursor* a, LinearViewCursor* b); }; @@ -19007,6 +19078,244 @@ namespace BinaryNinja { static void AddNamesForOuterStructureMembers( BinaryView* data, Type* type, const HighLevelILInstruction& var, std::vector<std::string>& nameList); }; + + /*! RenderLayer is a plugin class that allows you to customize the presentation of + Linear and Graph view output, adding, changing, or removing lines before they are + presented in the UI. + */ + class RenderLayer: public StaticCoreRefCountObject<BNRenderLayer> + { + std::string m_nameForRegister; + static std::unordered_map<BNRenderLayer*, RenderLayer*> g_registeredInstances; + + protected: + explicit RenderLayer(const std::string& name); + RenderLayer(BNRenderLayer* layer); + virtual ~RenderLayer() = default; + static void ApplyToFlowGraphCallback(void* ctxt, BNFlowGraph* graph); + static void ApplyToLinearViewObjectCallback( + void* ctxt, + BNLinearViewObject* obj, + BNLinearViewObject* prev, + BNLinearViewObject* next, + BNLinearDisassemblyLine* inLines, + size_t inLineCount, + BNLinearDisassemblyLine** outLines, + size_t* outLineCount + ); + static void FreeLinesCallback(void* ctxt, BNLinearDisassemblyLine* lines, size_t count); + + public: + /*! Register a custom Render Layer. + + \param layer Render Layer to register + \param enableState Whether the layer should be enabled by default + */ + static void Register(RenderLayer* layer, BNRenderLayerDefaultEnableState enableState = DisabledByDefaultRenderLayerDefaultEnableState); + + /*! Get the list of all currently registered Render Layers. + + \return List of Render Layers + */ + static std::vector<Ref<RenderLayer>> GetList(); + + /*! Look up a Render Layer by its name + + \param name Name of Render Layer + \return Render Layer, if it exists. Otherwise, nullptr. + */ + static Ref<RenderLayer> GetByName(const std::string& name); + + /*! Get the name of a Render Layer + + \return Render Layer's name + */ + std::string GetName() const; + + /*! Get whether the Render Layer is enabled by default + + \return Default enable state + */ + BNRenderLayerDefaultEnableState GetDefaultEnableState() const; + + /*! Apply this Render Layer to a single Basic Block of Disassembly lines. + Subclasses should modify the input `lines` list to make modifications to + the presentation of the block. + + \note This function will only handle Disassembly lines, and not any ILs. + + \param block Basic Block containing those lines + \param lines Lines of text for the block, to be modified by this function + */ + virtual void ApplyToDisassemblyBlock( + Ref<BasicBlock> block, + std::vector<DisassemblyTextLine>& lines + ) + { + (void)block; + (void)lines; + } + + /*! Apply this Render Layer to a single Basic Block of Low Level IL lines. + Subclasses should modify the input `lines` list to make modifications to + the presentation of the block. + + \note This function will only handle Lifted IL/LLIL/LLIL(SSA) lines. + You can use the block's `function_graph_type` property to determine which is being handled. + + \param block Basic Block containing those lines + \param lines Lines of text for the block, to be modified by this function + */ + virtual void ApplyToLowLevelILBlock( + Ref<BasicBlock> block, + std::vector<DisassemblyTextLine>& lines + ) + { + (void)block; + (void)lines; + } + + /*! Apply this Render Layer to a single Basic Block of Medium Level IL lines. + Subclasses should modify the input `lines` list to make modifications to + the presentation of the block. + + \note This function will only handle MLIL/MLIL(SSA)/Mapped MLIL/Mapped MLIL(SSA) lines. + You can use the block's `function_graph_type` property to determine which is being handled. + + \param block Basic Block containing those lines + \param lines Lines of text for the block, to be modified by this function + */ + virtual void ApplyToMediumLevelILBlock( + Ref<BasicBlock> block, + std::vector<DisassemblyTextLine>& lines + ) + { + (void)block; + (void)lines; + } + + /*! Apply this Render Layer to a single Basic Block of High Level IL lines. + Subclasses should modify the input `lines` list to make modifications to + the presentation of the block. + + \note This function will only handle HLIL/HLIL(SSA)/Language Representation lines. + You can use the block's `function_graph_type` property to determine which is being handled. + + \warning This function will NOT apply to High Level IL bodies as displayed + in Linear View! Those are handled by `ApplyToHighLevelILBody` instead as they + do not have a Basic Block associated with them. + + \param block Basic Block containing those lines + \param lines Lines of text for the block, to be modified by this function + */ + virtual void ApplyToHighLevelILBlock( + Ref<BasicBlock> block, + std::vector<DisassemblyTextLine>& lines + ) + { + (void)block; + (void)lines; + } + + /*! Apply this Render Layer to the entire body of a High Level IL function. + Subclasses should modify the input `lines` list to make modifications to + the presentation of the function. + + \warning This function only applies to Linear View, and not to Graph View! + If you want to handle Graph View too, you will need to use `ApplyToHighLevelILBlock` + and handle the lines one block at a time. + + \param function Function containing those lines + \param lines Lines of text for the function, to be modified by this function + */ + virtual void ApplyToHighLevelILBody( + Ref<Function> function, + std::vector<LinearDisassemblyLine>& lines + ) + { + (void)function; + (void)lines; + } + + /*! Apply to lines generated by Linear View that are not part of a function. + It is up to your implementation to figure out which type of Linear View Object + lines these are, and what to do with them. + + \param obj Linear View Object being rendered + \param prev Linear View Object located directly above this one + \param next Linear View Object located directly below this one + \param lines Lines rendered by `obj`, to be modified by this function + */ + virtual void ApplyToMiscLinearLines( + Ref<LinearViewObject> obj, + Ref<LinearViewObject> prev, + Ref<LinearViewObject> next, + std::vector<LinearDisassemblyLine>& lines + ) + { + (void)obj; + (void)prev; + (void)next; + (void)lines; + } + + /*! Apply to lines generated by a Basic Block, of any type. If not overridden, this + function will call the appropriate ApplyToXLevelILBlock function. + + \param block Basic Block containing those lines + \param lines Lines of text for the block, to be modified by this function + */ + virtual void ApplyToBlock( + Ref<BasicBlock> block, + std::vector<DisassemblyTextLine>& lines + ); + + /*! Apply this Render Layer to a Flow Graph, potentially modifying its nodes, + their edges, their lines, and their lines' content. + + \note If you override this function, you will need to call the parent + implementation if you want to use the higher level ApplyToXLevelILBlock + functionality. + + \param graph Graph to modify + */ + virtual void ApplyToFlowGraph(Ref<FlowGraph> graph); + + /*! Apply this Render Layer to the lines produced by a LinearViewObject for rendering + in Linear View, potentially modifying the lines and their contents. + + \note If you override this function, you will need to call the parent + implementation if you want to use the higher level ApplyToXLevelILBlock + functionality. + + \param obj Linear View Object being rendered + \param prev Linear View Object located directly above this one + \param next Linear View Object located directly below this one + \param lines Lines originally rendered by the Linear View Object + \return Updated set of lines to display in Linear View + */ + virtual void ApplyToLinearViewObject( + Ref<LinearViewObject> obj, + Ref<LinearViewObject> prev, + Ref<LinearViewObject> next, + std::vector<LinearDisassemblyLine>& lines + ); + }; + + class CoreRenderLayer: public RenderLayer + { + public: + CoreRenderLayer(BNRenderLayer* layer); + virtual ~CoreRenderLayer() = default; + + virtual void ApplyToFlowGraph(Ref<FlowGraph> graph) override; + virtual void ApplyToLinearViewObject( + Ref<LinearViewObject> obj, + Ref<LinearViewObject> prev, + Ref<LinearViewObject> next, + std::vector<LinearDisassemblyLine>& lines + ) override; + }; } // namespace BinaryNinja diff --git a/binaryninjacore.h b/binaryninjacore.h index bba1c786..73bff230 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -37,7 +37,7 @@ // Current ABI version for linking to the core. This is incremented any time // there are changes to the API that affect linking, including new functions, // new types, or modifications to existing functions or types. -#define BN_CURRENT_CORE_ABI_VERSION 89 +#define BN_CURRENT_CORE_ABI_VERSION 90 // Minimum ABI version that is supported for loading of plugins. Plugins that // are linked to an ABI version less than this will not be able to load and @@ -303,6 +303,7 @@ extern "C" typedef struct BNFirmwareNinja BNFirmwareNinja; typedef struct BNFirmwareNinjaReferenceNode BNFirmwareNinjaReferenceNode; typedef struct BNLineFormatter BNLineFormatter; + typedef struct BNRenderLayer BNRenderLayer; //! Console log levels typedef enum BNLogLevel @@ -3562,6 +3563,29 @@ extern "C" void (*freeLines)(void* ctxt, BNDisassemblyTextLine* lines, size_t count); } BNCustomLineFormatter; + typedef struct BNRenderLayerCallbacks + { + void* context; + void (*applyToFlowGraph)(void* ctxt, BNFlowGraph* graph); + void (*applyToLinearViewObject)( + void* ctxt, + BNLinearViewObject* obj, + BNLinearViewObject* prev, + BNLinearViewObject* next, + BNLinearDisassemblyLine* inLines, + size_t inLineCount, + BNLinearDisassemblyLine** outLines, + size_t* outLineCount + ); + void (*freeLines)(void* ctxt, BNLinearDisassemblyLine* lines, size_t count); + } BNRenderLayerCallbacks; + + typedef enum BNRenderLayerDefaultEnableState + { + DisabledByDefaultRenderLayerDefaultEnableState, + EnabledByDefaultRenderLayerDefaultEnableState, + AlwaysEnabledRenderLayerDefaultEnableState, + } BNRenderLayerDefaultEnableState; BINARYNINJACOREAPI char* BNAllocString(const char* contents); BINARYNINJACOREAPI char* BNAllocStringWithLength(const char* contents, size_t len); @@ -5127,6 +5151,10 @@ extern "C" BINARYNINJACOREAPI BNLinearDisassemblyLine* BNGetLinearViewCursorLines(BNLinearViewCursor* cursor, size_t* count); BINARYNINJACOREAPI int BNCompareLinearViewCursors(BNLinearViewCursor* a, BNLinearViewCursor* b); + BINARYNINJACOREAPI BNRenderLayer** BNGetLinearViewCursorRenderLayers(BNLinearViewCursor* cursor, size_t* count); + BINARYNINJACOREAPI void BNAddLinearViewCursorRenderLayer(BNLinearViewCursor* cursor, BNRenderLayer* layer); + BINARYNINJACOREAPI void BNRemoveLinearViewCursorRenderLayer(BNLinearViewCursor* cursor, BNRenderLayer* layer); + BINARYNINJACOREAPI void BNDefineDataVariable(BNBinaryView* view, uint64_t addr, BNTypeWithConfidence* type); BINARYNINJACOREAPI void BNDefineUserDataVariable(BNBinaryView* view, uint64_t addr, BNTypeWithConfidence* type); BINARYNINJACOREAPI void BNUndefineDataVariable(BNBinaryView* view, uint64_t addr, bool blacklist); @@ -5550,14 +5578,21 @@ extern "C" BINARYNINJACOREAPI BNFlowGraphNode** BNGetFlowGraphNodesInRegion( BNFlowGraph* graph, int left, int top, int right, int bottom, size_t* count); BINARYNINJACOREAPI void BNFreeFlowGraphNodeList(BNFlowGraphNode** nodes, size_t count); + BINARYNINJACOREAPI size_t BNGetFlowGraphNodeCount(BNFlowGraph* graph); BINARYNINJACOREAPI bool BNFlowGraphHasNodes(BNFlowGraph* graph); BINARYNINJACOREAPI size_t BNAddFlowGraphNode(BNFlowGraph* graph, BNFlowGraphNode* node); + BINARYNINJACOREAPI void BNReplaceFlowGraphNode(BNFlowGraph* graph, size_t i, BNFlowGraphNode* newNode); + BINARYNINJACOREAPI void BNClearFlowGraphNodes(BNFlowGraph* graph); BINARYNINJACOREAPI int BNGetFlowGraphWidth(BNFlowGraph* graph); BINARYNINJACOREAPI int BNGetFlowGraphHeight(BNFlowGraph* graph); BINARYNINJACOREAPI void BNFlowGraphSetWidth(BNFlowGraph* graph, int width); BINARYNINJACOREAPI void BNFlowGraphSetHeight(BNFlowGraph* graph, int height); + BINARYNINJACOREAPI BNRenderLayer** BNGetFlowGraphRenderLayers(BNFlowGraph* graph, size_t* count); + BINARYNINJACOREAPI void BNAddFlowGraphRenderLayer(BNFlowGraph* graph, BNRenderLayer* layer); + BINARYNINJACOREAPI void BNRemoveFlowGraphRenderLayer(BNFlowGraph* graph, BNRenderLayer* layer); + BINARYNINJACOREAPI BNFlowGraphNode* BNCreateFlowGraphNode(BNFlowGraph* graph); BINARYNINJACOREAPI BNFlowGraphNode* BNNewFlowGraphNodeReference(BNFlowGraphNode* node); BINARYNINJACOREAPI void BNFreeFlowGraphNode(BNFlowGraphNode* node); @@ -8108,6 +8143,26 @@ extern "C" BNDisassemblySettings* settings, BNLanguageRepresentationFunction* func); BINARYNINJACOREAPI void BNFreeLineFormatterSettings(BNLineFormatterSettings* settings); + // Render Layers + BINARYNINJACOREAPI BNRenderLayer* BNRegisterRenderLayer(const char* name, BNRenderLayerCallbacks* callbacks, BNRenderLayerDefaultEnableState enableState); + BINARYNINJACOREAPI BNRenderLayer** BNGetRenderLayerList(size_t* count); + BINARYNINJACOREAPI void BNFreeRenderLayerList(BNRenderLayer** renderLayers); + BINARYNINJACOREAPI BNRenderLayer* BNGetRenderLayerByName(const char* name); + BINARYNINJACOREAPI char* BNGetRenderLayerName(BNRenderLayer* renderLayer); + BINARYNINJACOREAPI BNRenderLayerDefaultEnableState BNGetRenderLayerDefaultEnableState(BNRenderLayer* renderLayer); + + BINARYNINJACOREAPI void BNApplyRenderLayerToFlowGraph(BNRenderLayer* renderLayer, BNFlowGraph* graph); + BINARYNINJACOREAPI void BNApplyRenderLayerToLinearViewObject( + BNRenderLayer* renderLayer, + BNLinearViewObject* obj, + BNLinearViewObject* prev, + BNLinearViewObject* next, + BNLinearDisassemblyLine* inLines, + size_t inLineCount, + BNLinearDisassemblyLine** outLines, + size_t* outLineCount + ); + #ifdef __cplusplus } #endif @@ -65,6 +65,86 @@ namespace BinaryNinja //endregion + //region Generic API Objects + + /*! Helper class to determine if a type is "API-able" aka has the following interface: + + struct Foo + { + BNFoo GetAPIObject() const; + static Foo FromAPIObject(const BNFoo* obj); + static void FreeAPIObject(BNFoo* obj); + }; + + If you get weird compiler errors around here, make sure you've implemented + the above interface correctly (with the `const`s too!). + */ + template< + typename T, + // Grab the type for TAPI from the return type of GetAPIObject() + // Store into template argument for easier lookup + typename TAPI_ = decltype(std::declval<T>().GetAPIObject()) + > + // Subtype of bool_constant to allow std::enable_if usage + struct APIAble : std::bool_constant< + // Make sure T::FromAPIObject(TAPI*) actually works + std::is_invocable_v<decltype(T::FromAPIObject), const TAPI_*> + // Make sure T::FromAPIObject(TAPI*) returns T + && std::is_same_v<T, decltype(T::FromAPIObject(std::declval<const TAPI_*>()))> + // Make sure T::FreeAPIObject(TAPI*) actually works + && std::is_invocable_v<decltype(T::FreeAPIObject), TAPI_*> + > + { + // For reference by users of APIAble + typedef TAPI_ TAPI; + }; + + template<typename T, typename _ = std::enable_if_t<APIAble<T>::value, void>> + void AllocAPIObjectList(const std::vector<T>& objects, typename APIAble<T>::TAPI BN_API_PTR** output, size_t* count) + { + *count = objects.size(); + *output = new typename APIAble<T>::TAPI[objects.size()]; + + size_t i = 0; + for (const auto& o: objects) + { + (*output)[i] = o.GetAPIObject(); + i ++; + } + } + + template<typename T, typename _ = std::enable_if_t<APIAble<T>::value, void>> + typename APIAble<T>::TAPI BN_API_PTR* AllocAPIObjectList(const std::vector<T>& objects, size_t* count) + { + typename APIAble<T>::TAPI* result; + AllocAPIObjectList(objects, &result, count); + return result; + } + + template<typename T, typename _ = std::enable_if_t<APIAble<T>::value, void>> + std::vector<T> ParseAPIObjectList(const typename APIAble<T>::TAPI* objects, size_t count) + { + std::vector<T> result; + result.reserve(count); + for (size_t i = 0; i < count; i ++) + { + result.push_back(T::FromAPIObject(&objects[i])); + } + return result; + } + + template<typename T, typename _ = std::enable_if_t<APIAble<T>::value, void>> + void FreeAPIObjectList(typename APIAble<T>::TAPI BN_API_PTR* objects, size_t count) + { + for (size_t i = 0; i < count; i ++) + { + T::FreeAPIObject(&objects[i]); + } + delete[] objects; + } + + //endregion + //------------------------------------------------------------------------------------ //region Try/Catch Helpers diff --git a/flowgraph.cpp b/flowgraph.cpp index 257764c9..b28b7ed9 100644 --- a/flowgraph.cpp +++ b/flowgraph.cpp @@ -256,6 +256,12 @@ Ref<FlowGraphNode> FlowGraph::GetNode(size_t i) } +size_t FlowGraph::GetNodeCount() const +{ + return BNGetFlowGraphNodeCount(m_object); +} + + bool FlowGraph::HasNodes() const { return BNFlowGraphHasNodes(m_object); @@ -269,6 +275,18 @@ size_t FlowGraph::AddNode(FlowGraphNode* node) } +void FlowGraph::ReplaceNode(size_t i, FlowGraphNode* newNode) +{ + BNReplaceFlowGraphNode(m_object, i, newNode->GetObject()); +} + + +void FlowGraph::ClearNodes() +{ + BNClearFlowGraphNodes(m_object); +} + + int FlowGraph::GetWidth() const { return BNGetFlowGraphWidth(m_object); @@ -419,6 +437,32 @@ bool FlowGraph::IsOptionSet(BNFlowGraphOption option) } +std::vector<RenderLayer*> FlowGraph::GetRenderLayers() const +{ + size_t count = 0; + BNRenderLayer** layers = BNGetFlowGraphRenderLayers(m_object, &count); + std::vector<RenderLayer*> result; + for (size_t i = 0; i < count; i ++) + { + result.push_back(new CoreRenderLayer(layers[i])); + } + BNFreeRenderLayerList(layers); + return result; +} + + +void FlowGraph::AddRenderLayer(RenderLayer* layer) +{ + BNAddFlowGraphRenderLayer(m_object, layer->GetObject()); +} + + +void FlowGraph::RemoveRenderLayer(RenderLayer* layer) +{ + BNRemoveFlowGraphRenderLayer(m_object, layer->GetObject()); +} + + CoreFlowGraph::CoreFlowGraph(BNFlowGraph* graph) : FlowGraph(graph) { m_queryMode = BNFlowGraphUpdateQueryMode(GetObject()); diff --git a/linearviewcursor.cpp b/linearviewcursor.cpp index 1ba252f7..50c6228a 100644 --- a/linearviewcursor.cpp +++ b/linearviewcursor.cpp @@ -211,6 +211,32 @@ Ref<LinearViewCursor> LinearViewCursor::Duplicate() } +std::vector<RenderLayer*> LinearViewCursor::GetRenderLayers() const +{ + size_t count = 0; + BNRenderLayer** layers = BNGetLinearViewCursorRenderLayers(m_object, &count); + std::vector<RenderLayer*> result; + for (size_t i = 0; i < count; i ++) + { + result.push_back(new CoreRenderLayer(layers[i])); + } + BNFreeRenderLayerList(layers); + return result; +} + + +void LinearViewCursor::AddRenderLayer(RenderLayer* layer) +{ + BNAddLinearViewCursorRenderLayer(m_object, layer->GetObject()); +} + + +void LinearViewCursor::RemoveRenderLayer(RenderLayer* layer) +{ + BNRemoveLinearViewCursorRenderLayer(m_object, layer->GetObject()); +} + + int LinearViewCursor::Compare(LinearViewCursor* a, LinearViewCursor* b) { return BNCompareLinearViewCursors(a->GetObject(), b->GetObject()); diff --git a/linearviewobject.cpp b/linearviewobject.cpp index 4f00adb0..7cd08fcc 100644 --- a/linearviewobject.cpp +++ b/linearviewobject.cpp @@ -24,7 +24,27 @@ using namespace std; using namespace BinaryNinja; -LinearDisassemblyLine LinearDisassemblyLine::FromAPIObject(BNLinearDisassemblyLine* line) +BNLinearDisassemblyLine LinearDisassemblyLine::GetAPIObject() const +{ + BNLinearDisassemblyLine result; + result.type = this->type; + result.function = this->function ? BNNewFunctionReference(this->function->GetObject()) : nullptr; + result.block = this->block ? BNNewBasicBlockReference(this->block->GetObject()) : nullptr; + result.contents.addr = this->contents.addr; + result.contents.instrIndex = this->contents.instrIndex; + result.contents.highlight = this->contents.highlight; + result.contents.count = this->contents.tokens.size(); + result.contents.tokens = InstructionTextToken::CreateInstructionTextTokenList(this->contents.tokens); + result.contents.tags = Tag::CreateTagList(this->contents.tags, &result.contents.tagCount); + result.contents.typeInfo.hasTypeInfo = this->contents.typeInfo.hasTypeInfo; + result.contents.typeInfo.parentType = this->contents.typeInfo.parentType ? BNNewTypeReference(this->contents.typeInfo.parentType->GetObject()) : nullptr; + result.contents.typeInfo.fieldIndex = this->contents.typeInfo.fieldIndex; + result.contents.typeInfo.offset = this->contents.typeInfo.offset; + return result; +} + + +LinearDisassemblyLine LinearDisassemblyLine::FromAPIObject(const BNLinearDisassemblyLine* line) { LinearDisassemblyLine result; result.type = line->type; @@ -45,6 +65,19 @@ LinearDisassemblyLine LinearDisassemblyLine::FromAPIObject(BNLinearDisassemblyLi } +void LinearDisassemblyLine::FreeAPIObject(BNLinearDisassemblyLine* line) +{ + if (line->function) + BNFreeFunction(line->function); + if (line->block) + BNFreeBasicBlock(line->block); + InstructionTextToken::FreeInstructionTextTokenList(line->contents.tokens, line->contents.count); + Tag::FreeTagList(line->contents.tags, line->contents.tagCount); + if (line->contents.typeInfo.parentType) + BNFreeType(line->contents.typeInfo.parentType); +} + + LinearViewObjectIdentifier::LinearViewObjectIdentifier() : type(SingleLinearViewObject), start(0), end(0) {} diff --git a/plugins/stack_render_layer/CMakeLists.txt b/plugins/stack_render_layer/CMakeLists.txt new file mode 100644 index 00000000..3133e63d --- /dev/null +++ b/plugins/stack_render_layer/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(stack_render_layer) + +file(GLOB SOURCES + *.cpp + *.c + *.h) + +if(DEMO) + add_library(${PROJECT_NAME} STATIC ${SOURCES}) +else() + add_library(${PROJECT_NAME} SHARED ${SOURCES}) +endif() + +if(NOT BN_INTERNAL_BUILD) + # Out-of-tree build + find_path( + BN_API_PATH + NAMES binaryninjaapi.h + HINTS ../../.. binaryninjaapi $ENV{BN_API_PATH} + REQUIRED + ) + add_subdirectory(${BN_API_PATH} api) +endif() + +target_link_libraries(${PROJECT_NAME} binaryninjaapi) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 17 + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD_REQUIRED ON + C_STANDARD 99 + C_STANDARD_REQUIRED ON + C_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON) + +if(BN_INTERNAL_BUILD) + plugin_rpath(${PROJECT_NAME}) + set_target_properties(${PROJECT_NAME} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR} + RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}) +else() + bn_install_plugin(${PROJECT_NAME}) +endif() diff --git a/plugins/stack_render_layer/plugin.cpp b/plugins/stack_render_layer/plugin.cpp new file mode 100644 index 00000000..b32ee470 --- /dev/null +++ b/plugins/stack_render_layer/plugin.cpp @@ -0,0 +1,124 @@ + +#include <binaryninjaapi.h> +#include <thread> + +using namespace BinaryNinja; + + +class StackRenderLayer: public RenderLayer +{ +public: + StackRenderLayer(): RenderLayer("Annotate Stack Offset") {} + + void ApplyToLines( + Ref<BasicBlock> block, + std::vector<DisassemblyTextLine>& lines + ) + { + for (auto& line: lines) + { + // Skip blank lines (block separators) + if (line.tokens.empty()) + { + continue; + } + + // Insert tokens after the address separator + int64_t sep = -1; + for (int64_t i = 0; i < line.tokens.size(); i ++) + { + if (line.tokens[i].type == AddressSeparatorToken) + { + sep = i; + break; + } + } + // Don't annotate lines which don't have an address separator + // (these are usually annotations like { Does not return } + if (sep == -1) + { + continue; + } + + // Grab stack offset value from function + auto stackOffset = block->GetFunction()->GetRegisterValueAtInstruction( + block->GetArchitecture(), + line.addr, + block->GetArchitecture()->GetStackPointerRegister() + ); + auto stackOffsetAfter = block->GetFunction()->GetRegisterValueAfterInstruction( + block->GetArchitecture(), + line.addr, + block->GetArchitecture()->GetStackPointerRegister() + ); + if (stackOffset.state == StackFrameOffset) + { + // Stack pointer is resolved to an offset: show the offset + // (but negative because that is how other tools do it) + line.tokens.emplace( + line.tokens.begin() + sep + 1, + IntegerToken, + fmt::format("{:4x}", -stackOffset.value), + -stackOffset.value + ); + } + else + { + // Stack pointer is not resolved, show ?? + line.tokens.emplace( + line.tokens.begin() + sep + 1, + IntegerToken, + " ??", + 0 + ); + } + // And put a spacer after the offset token + if (stackOffset != stackOffsetAfter) + { + line.tokens.emplace( + line.tokens.begin() + sep + 2, + TextToken, + "* " + ); + } + else + { + line.tokens.emplace( + line.tokens.begin() + sep + 2, + TextToken, + " " + ); + } + } + } + + virtual void ApplyToDisassemblyBlock( + Ref<BasicBlock> block, + std::vector<DisassemblyTextLine>& lines + ) override + { + // Break this out into a helper so we don't have to write it twice + ApplyToLines(block, lines); + } + + virtual void ApplyToLowLevelILBlock( + Ref<BasicBlock> block, + std::vector<DisassemblyTextLine>& lines + ) override + { + // Break this out into a helper so we don't have to write it twice + ApplyToLines(block, lines); + } +}; + + +extern "C" { + BN_DECLARE_CORE_ABI_VERSION + + BINARYNINJAPLUGIN bool CorePluginInit() + { + static StackRenderLayer* layer = new StackRenderLayer(); + RenderLayer::Register(layer, DisabledByDefaultRenderLayerDefaultEnableState); + return true; + } +}
\ No newline at end of file diff --git a/python/__init__.py b/python/__init__.py index 1cdc541e..db4e8e69 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -81,6 +81,7 @@ from .undo import * from .fileaccessor import * from .languagerepresentation import * from .lineformatter import * +from .renderlayer import * # We import each of these by name to prevent conflicts between # log.py and the function 'log' which we don't import below from .log import ( diff --git a/python/basicblock.py b/python/basicblock.py index 6c03fe32..a8dc85a8 100644 --- a/python/basicblock.py +++ b/python/basicblock.py @@ -23,6 +23,7 @@ from dataclasses import dataclass from typing import Generator, Optional, List, Tuple # Binary Ninja components +import binaryninja from . import _binaryninjacore as core from .enums import BranchType, HighlightStandardColor from . import binaryview @@ -88,6 +89,33 @@ class BasicBlock: if core is not None: core.BNFreeBasicBlock(self.handle) + @classmethod + def _from_core_block(cls, block: core.BNBasicBlockHandle) -> Optional['BasicBlock']: + """From a BNBasicBlockHandle, get a BasicBlock or one of the IL subclasses (takes ref)""" + func_handle = core.BNGetBasicBlockFunction(block) + if not func_handle: + core.BNFreeBasicBlock(block) + return None + + view = binaryview.BinaryView(handle=core.BNGetFunctionData(func_handle)) + func = _function.Function(view, func_handle) + + if core.BNIsLowLevelILBasicBlock(block): + return binaryninja.lowlevelil.LowLevelILBasicBlock( + block, binaryninja.lowlevelil.LowLevelILFunction(func.arch, core.BNGetBasicBlockLowLevelILFunction(block), func), + view + ) + elif core.BNIsMediumLevelILBasicBlock(block): + mlil_func = binaryninja.mediumlevelil.MediumLevelILFunction( + func.arch, core.BNGetBasicBlockMediumLevelILFunction(block), func + ) + return binaryninja.mediumlevelil.MediumLevelILBasicBlock(block, mlil_func, view) + elif core.BNIsHighLevelILBasicBlock(block): + hlil_func = binaryninja.highlevelil.HighLevelILFunction(func.arch, core.BNGetBasicBlockHighLevelILFunction(block), func) + return binaryninja.highlevelil.HighLevelILBasicBlock(block, hlil_func, view) + else: + return BasicBlock(block, view) + def __repr__(self): arch = self.arch if arch: @@ -211,6 +239,78 @@ class BasicBlock: return self._func @property + def il_function(self) -> Optional['_function.ILFunctionType']: + """IL Function of which this block is a part, if the block is part of an IL Function.""" + func = self.function + if func is None: + return None + il_type = self.function_graph_type + if il_type == _function.FunctionGraphType.NormalFunctionGraph: + return None + elif il_type == _function.FunctionGraphType.LowLevelILFunctionGraph: + return func.low_level_il + elif il_type == _function.FunctionGraphType.LiftedILFunctionGraph: + return func.lifted_il + elif il_type == _function.FunctionGraphType.LowLevelILSSAFormFunctionGraph: + return func.low_level_il.ssa_form + elif il_type == _function.FunctionGraphType.MediumLevelILFunctionGraph: + return func.medium_level_il + elif il_type == _function.FunctionGraphType.MediumLevelILSSAFormFunctionGraph: + return func.medium_level_il.ssa_form + elif il_type == _function.FunctionGraphType.MappedMediumLevelILFunctionGraph: + return func.mapped_medium_level_il + elif il_type == _function.FunctionGraphType.MappedMediumLevelILSSAFormFunctionGraph: + return func.mapped_medium_level_il.ssa_form + elif il_type == _function.FunctionGraphType.HighLevelILFunctionGraph: + return func.high_level_il + elif il_type == _function.FunctionGraphType.HighLevelILSSAFormFunctionGraph: + return func.high_level_il.ssa_form + elif il_type == _function.FunctionGraphType.HighLevelLanguageRepresentationFunctionGraph: + return func.high_level_il + else: + return None + + @property + def il_function_if_available(self) -> Optional['_function.ILFunctionType']: + """IL Function of which this block is a part, if the block is part of an IL Function, and if the function has generated IL already.""" + func = self.function + if func is None: + return None + il_type = self.function_graph_type + if il_type == _function.FunctionGraphType.NormalFunctionGraph: + return None + elif il_type == _function.FunctionGraphType.LowLevelILFunctionGraph: + return func.llil_if_available + elif il_type == _function.FunctionGraphType.LiftedILFunctionGraph: + return func.lifted_il_if_available + elif il_type == _function.FunctionGraphType.LowLevelILSSAFormFunctionGraph: + if func.llil_if_available is None: + return None + return func.llil_if_available.ssa_form + elif il_type == _function.FunctionGraphType.MediumLevelILFunctionGraph: + return func.mlil_if_available + elif il_type == _function.FunctionGraphType.MediumLevelILSSAFormFunctionGraph: + if func.mlil_if_available is None: + return None + return func.mlil_if_available.ssa_form + elif il_type == _function.FunctionGraphType.MappedMediumLevelILFunctionGraph: + return func.mmlil_if_available + elif il_type == _function.FunctionGraphType.MappedMediumLevelILSSAFormFunctionGraph: + if func.mmlil_if_available is None: + return None + return func.mmlil_if_available.ssa_form + elif il_type == _function.FunctionGraphType.HighLevelILFunctionGraph: + return func.hlil_if_available + elif il_type == _function.FunctionGraphType.HighLevelILSSAFormFunctionGraph: + if func.hlil_if_available is None: + return None + return func.hlil_if_available.ssa_form + elif il_type == _function.FunctionGraphType.HighLevelLanguageRepresentationFunctionGraph: + return func.hlil_if_available + else: + return None + + @property def view(self) -> Optional['binaryview.BinaryView']: """BinaryView that contains the basic block (read-only)""" if self._view is not None: @@ -426,6 +526,11 @@ class BasicBlock: self.set_user_highlight(value) @property + def function_graph_type(self) -> '_function.FunctionGraphType': + """Type of function graph from which this block represents instructions""" + return _function.FunctionGraphType(core.BNGetBasicBlockFunctionGraphType(self.handle)) + + @property def is_il(self) -> bool: """Whether the basic block contains IL""" return core.BNIsILBasicBlock(self.handle) diff --git a/python/binaryview.py b/python/binaryview.py index 73ddfd39..c1a57765 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -9011,20 +9011,9 @@ to a the type "tagRECT" found in the typelibrary "winX64common" def _LinearDisassemblyLine_convertor( self, lines: core.BNLinearDisassemblyLineHandle ) -> 'lineardisassembly.LinearDisassemblyLine': - func = None - block = None - line = lines[0] - if line.function: - func = _function.Function(self, core.BNNewFunctionReference(line.function)) - if line.block: - block_handle = core.BNNewBasicBlockReference(line.block) - assert block_handle is not None, "core.BNNewBasicBlockReference returned None" - block = basicblock.BasicBlock(block_handle, self) - color = highlight.HighlightColor._from_core_struct(line.contents.highlight) - addr = line.contents.addr - tokens = _function.InstructionTextToken._from_core_struct(line.contents.tokens, line.contents.count) - contents = _function.DisassemblyTextLine(tokens, addr, color=color) - return lineardisassembly.LinearDisassemblyLine(line.type, func, block, contents) + line = lineardisassembly.LinearDisassemblyLine._from_core_struct(lines[0]) + core.BNFreeLinearDisassemblyLines(lines, 1) + return line def find_all_text( self, start: int, end: int, text: str, settings: Optional[_function.DisassemblySettings] = None, diff --git a/python/examples/args_render_layer.py b/python/examples/args_render_layer.py new file mode 100644 index 00000000..bac0889e --- /dev/null +++ b/python/examples/args_render_layer.py @@ -0,0 +1,213 @@ +import functools +from typing import List, Mapping, Tuple, Iterator + +from binaryninja import DisassemblyTextLine, LowLevelILInstruction, LowLevelILOperation, \ + TypeClass, DisassemblyTextRenderer, MediumLevelILFunction, \ + MediumLevelILCallSsa, MediumLevelILVarSsa, MediumLevelILConstBase, \ + MediumLevelILInstruction, MediumLevelILTailcallSsa, MediumLevelILOperation, \ + MediumLevelILVarPhi, log_debug, RenderLayer, BasicBlock, InstructionTextTokenType, \ + RenderLayerDefaultEnableState + +""" +Render Layer that shows you where the arguments to calls are set, for Disasm/LLIL. +- Adds "Argument '<arg>' for call at <callsite>" comments to lines that set up call args +- Adds "Call at <callsite>" comments to the call sites + +========================================================================================= + +But how do you determine the argument to a call? +What seems like it has worked: +- You can't determine that an instruction is a parameter, you have to go from the call to its parameters +- Since trying to look up the call for an instruction is impossible, instead go through every call at once for a function (and memoize it) +- LLIL is useless for looking this up, since it has no types and the call parameters often include a list of every register +- Using MLIL, we can find all of the parameters as MLIL instructions, but we need to map them to LLIL so we can use them in the LLIL/Disasm display +- How do we map them? Turns out that's rather inconvenient: + - Register arguments are generally pretty easy because they are just MLIL vars + - Stack arguments somehow also work out generally, the .llil on the MLIL points to the push() + - Constants are a mess and I just use the MLIL's address (this is often incorrect) + - Flags are completely unhandled for now + - Phis are handled by just looking up every var they use... probably not proper but sort of works +- This fails in a couple of scenarios though, notably __builtin_xxxxxx() functions + - Which instruction specifies the length of a group of `mov qword [rbx+8], rax {0}` calls? I think it just picks one? + - The `rep` instructions could actually have these params resolved (they use real registers) but in practice this doesn't work + - Thunks are unhandled +""" + + +@functools.lru_cache(maxsize=64) +def get_param_sites(mlil: MediumLevelILFunction) -> Mapping[LowLevelILInstruction, List[Tuple[MediumLevelILInstruction, int]]]: + """ + For a given function, find all LLIL instructions that are parameters to a call, + and return a mapping for each instruction with all the calls that it maps to, + their corresponding MLIL call instruction, and which numbered parameter they are + in the call. + + :param mlil: MLIL function to search + :return: Map of param sites as described above + """ + call_sites = {} + mlil = mlil.ssa_form + + # As a function to handle call and tailcall identically + def collect_call_params(call_site, dest, params): + def_sites = [] + for i, param in enumerate(params): + llil = param.llil + if llil is not None: + def_sites.append((param, llil)) + continue + + match param: + case MediumLevelILVarSsa(src=var_src): + def_site = mlil.get_ssa_var_definition(var_src) + if def_site is not None and def_site.llil is not None: + def_sites.append((i, def_site.llil)) + continue + # Handle phis by just looking up the def sites of all their sources + match def_site: + case MediumLevelILVarPhi(src=phis): + for phi in phis: + phi_def = mlil.get_ssa_var_definition(phi) + if phi_def is not None and phi_def.llil is not None: + def_sites.append((i, phi_def.llil)) + case MediumLevelILConstBase(): + # This is wrong, but it works (sometimes) + # Oh god, have I just quoted php.net + def_site_idx = mlil.llil.get_instruction_start(param.address) + if def_site_idx is not None: + def_sites.append((i, mlil.llil[def_site_idx].ssa_form)) + continue + + if len(def_sites) == 0: + log_debug(f"Could not find def site for param {i} in call at {call_site.address:#x}") + + call_sites[call_site] = def_sites + + for instr in mlil.instructions: + match instr: + case MediumLevelILCallSsa(dest=dest, params=params) as call_site: + collect_call_params(call_site, dest, params) + case MediumLevelILTailcallSsa(dest=dest, params=params) as call_site: + collect_call_params(call_site, dest, params) + + # Inverse args + all_def_sites = {} + for call_site, params in call_sites.items(): + for (param_idx, llil) in params: + if llil not in all_def_sites: + all_def_sites[llil] = [] + else: + print(f"got two at {llil.instr_index} @ {llil.address:#x} -> {call_site.address:#x}") + all_def_sites[llil].append((call_site, param_idx)) + + return all_def_sites + + +def get_llil_arg(llil: LowLevelILInstruction) -> Iterator[Tuple[str, MediumLevelILInstruction]]: + args = get_param_sites(llil.function.mlil) + + if llil.ssa_form in args: + for call_site, param_idx in args[llil.ssa_form]: + target_type = call_site.function.get_expr_type(call_site.dest.expr_index) + + # Try getting the param name from the call's type + if target_type is not None: + if target_type.type_class == TypeClass.PointerTypeClass: + target_type = target_type.target + if target_type.type_class == TypeClass.FunctionTypeClass: + target_params = target_type.parameters + if param_idx < len(target_params): + param_name = target_params[param_idx].name + if param_name == '': + param_name = f"arg{param_idx+1}" + yield param_name, call_site + continue + + # Some calls have extra params that aren't reflected in their type + yield f"arg{param_idx+1}", call_site + return + + +def apply_to_lines(lines, get_instr, renderer): + # So we don't process lines twice since we're iterating over a list as we modify it + skip_lines = [] + + # Tailcalls that don't return incorrectly mark the { Does not return } line as a call + ignore_calls = set() + + for i, line in enumerate(lines): + if len(line.tokens) == 0: + continue + if i in skip_lines: + continue + + llil_instr = get_instr(line) + if llil_instr is not None: + new_lines = [] + for (arg, call) in get_llil_arg(llil_instr): + if call.operation == MediumLevelILOperation.MLIL_TAILCALL_SSA: + comment = f"Argument '{arg}' for tailcall at {call.address:#x}" + else: + comment = f"Argument '{arg}' for call at {call.address:#x}" + renderer.wrap_comment(new_lines, line, comment, False, " ", "") + for j, token in enumerate(line.tokens): + if token.type == InstructionTextTokenType.AddressSeparatorToken: + line.tokens = line.tokens[:j] + break + + # Annotate calls too so we can see them easily next to their args + if llil_instr.address == line.address and llil_instr.address not in ignore_calls: + if llil_instr.operation in [ + LowLevelILOperation.LLIL_CALL, + LowLevelILOperation.LLIL_CALL_SSA, + LowLevelILOperation.LLIL_TAILCALL, + LowLevelILOperation.LLIL_TAILCALL_SSA + ]: + ignore_calls.add(llil_instr.address) + if llil_instr.operation in [ + LowLevelILOperation.LLIL_TAILCALL, + LowLevelILOperation.LLIL_TAILCALL_SSA + ]: + comment = f"Tailcall at {llil_instr.address:#x}" + else: + comment = f"Call at {llil_instr.address:#x}" + # Creating comments is a bit unwieldy at the moment + renderer.wrap_comment(new_lines, line, comment, False, " ", "") + for j, token in enumerate(line.tokens): + if token.type == InstructionTextTokenType.AddressSeparatorToken: + line.tokens = line.tokens[:j] + break + + # If any of our lines changed, swap out the existing lines with the new ones + if len(new_lines) > 0: + lines.pop(i) + for j, new_line in enumerate(new_lines): + lines.insert(i + j, new_line) + skip_lines.append(i + j) + return lines + + +class ArgumentsRenderLayer(RenderLayer): + name = "Annotate Call Parameters" + default_enable_state = RenderLayerDefaultEnableState.EnabledByDefaultRenderLayerDefaultEnableState + + def apply_to_disassembly_block( + self, + block: BasicBlock, + lines: List['DisassemblyTextLine'] + ): + # Break this out into a helper so we don't have to write it twice + renderer = DisassemblyTextRenderer(block.function) + return apply_to_lines(lines, lambda line: block.function.get_llil_at(line.address), renderer) + + def apply_to_low_level_il_block( + self, + block: BasicBlock, + lines: List['DisassemblyTextLine'] + ): + # Break this out into a helper so we don't have to write it twice + renderer = DisassemblyTextRenderer(block.function) + return apply_to_lines(lines, lambda line: line.il_instruction, renderer) + + +ArgumentsRenderLayer.register() diff --git a/python/examples/follow_reg_render_layer.py b/python/examples/follow_reg_render_layer.py new file mode 100644 index 00000000..36e66576 --- /dev/null +++ b/python/examples/follow_reg_render_layer.py @@ -0,0 +1,209 @@ +from typing import List + +from PySide6.QtCore import QSettings +from binaryninja import DisassemblyTextLine, LowLevelILOperation, DisassemblyTextRenderer, \ + MediumLevelILOperation, \ + RenderLayer, BasicBlock, InstructionTextTokenType, RenderLayerDefaultEnableState, \ + PluginCommand, BinaryView, interaction, InstructionTextToken, RegisterValueType + + +""" +Render layer that adds annotations to follow the value of a register, for Disasm/LLIL. +- Adds "<reg> after: <value>" comments to every line that modifies your register +- Adds "<reg> at block entry: <value>" comments to every block +- Adds "<reg> at block exit: <value>" comments to every block +You can switch registers with the plugin command "Set Followed Register" +""" + + +class FollowRegRenderLayer(RenderLayer): + name = "Follow Register" + default_enable_state = RenderLayerDefaultEnableState.EnabledByDefaultRenderLayerDefaultEnableState + + def __init__(self, handle=None): + super().__init__(handle) + self.followed_reg = QSettings().value("layer/followed_reg", None) + + def apply_to_lines( + self, + block: BasicBlock, + lines: List['DisassemblyTextLine'], + ): + if self.followed_reg is None: + return lines + if self.followed_reg not in block.arch.regs: + return lines + + func = block.function + arch = block.arch + + # Give our tokens a unique value so they don't highlight all the other comments + token_value = arch.get_reg_index(self.followed_reg) ^ 0x12345678 + + # =============================================================================== + # Render the "<reg> after" comments + + for i, line in enumerate(lines): + # Ignore non-disassembly lines + if not any(token.type == InstructionTextTokenType.AddressSeparatorToken for token in line.tokens): + continue + + # If the register has changed, we should render the update + written = False + for w in func.get_regs_written_by(line.address, arch): + # In case this instruction only modifies a sub register of our target + # Or in the case that we wanted to follow a sub register + if arch.regs[w].full_width_reg == arch.regs[self.followed_reg].full_width_reg: + written = True + break + + if written: + # Add comment tokens to existing line + line.tokens.extend([ + InstructionTextToken(InstructionTextTokenType.CommentToken, ' // ', token_value), + InstructionTextToken(InstructionTextTokenType.CommentToken, self.followed_reg, token_value), + InstructionTextToken(InstructionTextTokenType.CommentToken, ' after: ', token_value), + ]) + + after = func.get_reg_value_after(line.address, self.followed_reg, arch) + if after.type == RegisterValueType.UndeterminedValue: + if line.il_instruction is not None: + instr = line.il_instruction + else: + instr_start = func.low_level_il.get_instruction_start(line.address, arch) + instr = func.low_level_il[instr_start] + if instr is not None: + after_possible = instr.get_possible_reg_values_after(self.followed_reg) + line.tokens.append( + InstructionTextToken(InstructionTextTokenType.CommentToken, str(after_possible), token_value) + ) + else: + line.tokens.append( + InstructionTextToken(InstructionTextTokenType.CommentToken, str(after), token_value) + ) + else: + line.tokens.append( + InstructionTextToken(InstructionTextTokenType.CommentToken, str(after), token_value) + ) + + block_start = block.start + block_end = block.end + if block.is_low_level_il: + block_start = func.llil[block.start].address + block_end = func.llil[block.end - 1].address + + # =============================================================================== + # Render the before-block "<reg> at block entry" comment + + start_before = func.get_reg_value_at(block_start, self.followed_reg, arch) + line = [ + InstructionTextToken(InstructionTextTokenType.CommentToken, '// ', token_value), + InstructionTextToken(InstructionTextTokenType.CommentToken, self.followed_reg, token_value), + InstructionTextToken(InstructionTextTokenType.CommentToken, ' at block entry: ', token_value), + ] + + # Sometimes the first line is blank and we want to insert after it + first_line = 0 + if len(lines[0].tokens) == 0: + first_line = 1 + if start_before.type == RegisterValueType.UndeterminedValue: + if lines[first_line].il_instruction is not None: + instr = lines[first_line].il_instruction + else: + instr_start = func.low_level_il.get_instruction_start(block_start, arch) + instr = func.low_level_il[instr_start] + if instr is not None: + start_before_possible = instr.get_possible_reg_values(self.followed_reg) + line.append( + InstructionTextToken(InstructionTextTokenType.CommentToken, str(start_before_possible), token_value) + ) + else: + line.append( + InstructionTextToken(InstructionTextTokenType.CommentToken, str(start_before), token_value) + ) + else: + line.append( + InstructionTextToken(InstructionTextTokenType.CommentToken, str(start_before), token_value) + ) + + lines.insert(first_line, DisassemblyTextLine(line, block_start)) + + # =============================================================================== + # Render the after-block "<reg> at block exit" comment + + end_after = func.get_reg_value_after(block_end, self.followed_reg, arch) + line = [ + InstructionTextToken(InstructionTextTokenType.CommentToken, '// ', token_value), + InstructionTextToken(InstructionTextTokenType.CommentToken, self.followed_reg, token_value), + InstructionTextToken(InstructionTextTokenType.CommentToken, ' at block exit: ', token_value), + ] + + # Sometimes the last line is blank and we want to insert before it + if len(lines) > 1 and len(lines[-1].tokens) == 0: + last_line = len(lines) - 1 + else: + last_line = len(lines) + if end_after.type == RegisterValueType.UndeterminedValue: + if lines[last_line - 1].il_instruction is not None: + instr = lines[last_line - 1].il_instruction + else: + instr_start = func.low_level_il.get_instruction_start(block_end, arch) + instr = func.low_level_il[instr_start] + if instr is not None: + end_after_possible = instr.get_possible_reg_values_after(self.followed_reg) + line.append( + InstructionTextToken(InstructionTextTokenType.CommentToken, str(end_after_possible), token_value) + ) + else: + line.append( + InstructionTextToken(InstructionTextTokenType.CommentToken, str(end_after), token_value) + ) + else: + line.append( + InstructionTextToken(InstructionTextTokenType.CommentToken, str(end_after), token_value) + ) + + lines.insert(last_line, DisassemblyTextLine(line, block_end)) + + return lines + + def apply_to_disassembly_block( + self, + block: BasicBlock, + lines: List['DisassemblyTextLine'] + ): + # Break this out into a helper so we don't have to write it twice + return self.apply_to_lines(block, lines) + + def apply_to_low_level_il_block( + self, + block: BasicBlock, + lines: List['DisassemblyTextLine'] + ): + # Break this out into a helper so we don't have to write it twice + return self.apply_to_lines(block, lines) + + +def set_follow_reg(bv: BinaryView): + if bv.platform is not None: + regs = list(bv.platform.arch.regs.keys()) + idx = interaction.get_large_choice_input("Choose", "Choose Followed Register", regs) + + # Save choice both in QSettings and on the RenderLayer object instance + layer = RenderLayer[FollowRegRenderLayer.name] + if idx is None: + layer.followed_reg = None + QSettings().remove("layer/followed_reg") + else: + layer.followed_reg = regs[idx] + QSettings().setValue("layer/followed_reg", regs[idx]) + + # Trigger view refresh (gross) + for func in bv.get_functions_containing(bv.offset): + func.reanalyze() + + +FollowRegRenderLayer.register() + +# Using a command for this is kinda janky but currently the only option +PluginCommand.register("Set Followed Register", "Choose which register to follow for the Follow Register Render Layer", set_follow_reg) diff --git a/python/flowgraph.py b/python/flowgraph.py index e661fff8..1f5040c2 100644 --- a/python/flowgraph.py +++ b/python/flowgraph.py @@ -156,30 +156,7 @@ class FlowGraphNode: block = core.BNGetFlowGraphBasicBlock(self.handle) if not block: return None - func_handle = core.BNGetBasicBlockFunction(block) - if not func_handle: - core.BNFreeBasicBlock(block) - return None - - view = binaryview.BinaryView(handle=core.BNGetFunctionData(func_handle)) - func = function.Function(view, func_handle) - - if core.BNIsLowLevelILBasicBlock(block): - block = lowlevelil.LowLevelILBasicBlock( - block, lowlevelil.LowLevelILFunction(func.arch, core.BNGetBasicBlockLowLevelILFunction(block), func), - view - ) - elif core.BNIsMediumLevelILBasicBlock(block): - mlil_func = mediumlevelil.MediumLevelILFunction( - func.arch, core.BNGetBasicBlockMediumLevelILFunction(block), func - ) - block = mediumlevelil.MediumLevelILBasicBlock(block, mlil_func, view) - elif core.BNIsHighLevelILBasicBlock(block): - hlil_func = highlevelil.HighLevelILFunction(func.arch, core.BNGetBasicBlockHighLevelILFunction(block), func) - block = highlevelil.HighLevelILBasicBlock(block, hlil_func, view) - else: - block = basicblock.BasicBlock(block, view) - return block + return basicblock.BasicBlock._from_core_block(block) @basic_block.setter def basic_block(self, block): @@ -613,6 +590,11 @@ class FlowGraph: return result @property + def node_count(self) -> int: + """Number of nodes in graph (read-only)""" + return core.BNGetFlowGraphNodeCount(self.handle) + + @property def has_nodes(self): """Whether the flow graph has at least one node (read-only)""" return core.BNFlowGraphHasNodes(self.handle) @@ -835,12 +817,35 @@ class FlowGraph: """ ``append`` adds a node to a flow graph. + .. note:: After the graph has completed layout, this function has no effect. + :param FlowGraphNode node: Node to add :return: Index of node :rtype: int """ return core.BNAddFlowGraphNode(self.handle, node.handle) + def replace(self, index, node): + """ + ``replace`` replaces an existing node in the graph with a new node. + Any existing edges referencing the old node will be updated to point to + the new node. + + .. note:: After the graph has completed layout, this function has no effect. + + :param index: Index of the node to replace + :param node: New node with which to replace the old node + """ + core.BNReplaceFlowGraphNode(self.handle, index, node.handle) + + def clear(self): + """ + ``clear`` clears all the nodes in the graph + + .. note:: After the graph has completed layout, this function has no effect. + """ + core.BNClearFlowGraphNodes(self.handle) + def show(self, title): """ ``show`` displays the graph in a new tab in the UI. @@ -869,6 +874,50 @@ class FlowGraph: def is_option_set(self, option): return core.BNIsFlowGraphOptionSet(self.handle, option) + @property + def render_layers(self) -> List['binaryninja.RenderLayer']: + """ + Get the list of Render Layers which will be applied to this Flow Graph, + after it calls populate_nodes. + :return: List of Render Layers + """ + count = ctypes.c_size_t(0) + layers = core.BNGetFlowGraphRenderLayers(self.handle, count) + assert layers is not None, "core.BNGetFlowGraphRenderLayers returned None" + + try: + result = [] + for i in range(0, count.value): + result.append(binaryninja.RenderLayer(handle=layers[i])) + + return result + finally: + core.BNFreeRenderLayerList(layers) + + @render_layers.setter + def render_layers(self, render_layers: List['binaryninja.RenderLayer']): + for layer in self.render_layers: + self.remove_render_layer(layer) + for layer in render_layers: + self.add_render_layer(layer) + + def add_render_layer(self, layer: 'binaryninja.RenderLayer'): + """ + Add a Render Layer to be applied to this Flow Graph. Note that layers will + be applied in the order in which they are added. + + :param layer: Render Layer to add + """ + core.BNAddFlowGraphRenderLayer(self.handle, layer.handle) + + def remove_render_layer(self, layer: 'binaryninja.RenderLayer'): + """ + Remove a Render Layer from being applied to this Flow Graph + + :param layer: Render Layer to remove + """ + core.BNRemoveFlowGraphRenderLayer(self.handle, layer.handle) + class CoreFlowGraph(FlowGraph): def __init__(self, handle): diff --git a/python/function.py b/python/function.py index 80f0a37c..14cc5e43 100644 --- a/python/function.py +++ b/python/function.py @@ -3335,15 +3335,29 @@ class AdvancedFunctionAnalysisDataRequestor: @dataclass +class DisassemblyTextLineTypeInfo: + parent_type: Optional['types.Type'] + field_index: int + offset: int + + +@dataclass class DisassemblyTextLine: tokens: List['InstructionTextToken'] highlight: '_highlight.HighlightColor' address: Optional[int] il_instruction: Optional[ILInstructionType] + tags: List['binaryview.Tag'] + type_info: Optional[DisassemblyTextLineTypeInfo] def __init__( - self, tokens: List['InstructionTextToken'], address: Optional[int] = None, il_instr: Optional[ILInstructionType] = None, - color: Optional[Union['_highlight.HighlightColor', HighlightStandardColor]] = None + self, + tokens: List['InstructionTextToken'], + address: Optional[int] = None, + il_instr: Optional[ILInstructionType] = None, + color: Optional[Union['_highlight.HighlightColor', HighlightStandardColor]] = None, + tags: Optional[List['binaryview.Tag']] = None, + type_info: Optional[DisassemblyTextLineTypeInfo] = None, ): self.address = address self.tokens = tokens @@ -3358,6 +3372,10 @@ class DisassemblyTextLine: self.highlight = _highlight.HighlightColor(color) else: self.highlight = color + if tags is None: + tags = [] + self.tags = tags + self.type_info = type_info def __str__(self): return "".join(map(str, self.tokens)) @@ -3413,6 +3431,64 @@ class DisassemblyTextLine: self._find_address_and_indentation_tokens(collect_tokens) return result + @classmethod + def _from_core_struct(cls, struct: core.BNDisassemblyTextLine, il_func: Optional['ILFunctionType'] = None): + il_instr = None + if il_func is not None and struct.instrIndex < len(il_func): + try: + il_instr = il_func[struct.instrIndex] + except: + il_instr = None + tokens = InstructionTextToken._from_core_struct(struct.tokens, struct.count) + + tags = [] + for i in range(struct.tagCount): + tags.append(binaryview.Tag(handle=core.BNNewTagReference(struct.tags[i]))) + + type_info = None + if struct.typeInfo.hasTypeInfo: + parent_type = None + if struct.typeInfo.parentType: + parent_type = types.Type.create(core.BNNewTypeReference(struct.typeInfo.parentType)) + type_info = DisassemblyTextLineTypeInfo( + parent_type=parent_type, + field_index=struct.typeInfo.fieldIndex, + offset=struct.typeInfo.offset + ) + + return DisassemblyTextLine( + tokens, + struct.addr, + il_instr, + _highlight.HighlightColor._from_core_struct(struct.highlight), + tags, + type_info + ) + + def _to_core_struct(self) -> core.BNDisassemblyTextLine: + result = core.BNDisassemblyTextLine() + result.addr = self.address + if self.il_instruction is not None: + result.instrIndex = self.il_instruction.instr_index + else: + result.instrIndex = 0xffffffffffffffff + result.tokens = InstructionTextToken._get_core_struct(self.tokens) + result.count = len(self.tokens) + result.highlight = self.highlight._to_core_struct() + result.tagCount = len(self.tags) + result.tags = (ctypes.POINTER(core.BNTag) * len(self.tags))() + for i, tag in enumerate(self.tags): + result.tags[i] = tag.handle + if self.type_info is None: + result.typeInfo.hasTypeInfo = False + else: + result.typeInfo.hasTypeInfo = True + if self.type_info.parent_type is not None: + result.typeInfo.parentType = self.type_info.parent_type.handle + result.typeInfo.fieldIndex = self.type_info.field_index + result.typeInfo.offset = self.type_info.offset + return result + class DisassemblyTextRenderer: def __init__( @@ -3463,7 +3539,7 @@ class DisassemblyTextRenderer: def basic_block(self) -> Optional['basicblock.BasicBlock']: result = core.BNGetDisassemblyTextRendererBasicBlock(self.handle) if result: - return basicblock.BasicBlock(handle=result) + return basicblock.BasicBlock._from_core_block(handle=result) return None @basic_block.setter diff --git a/python/lineardisassembly.py b/python/lineardisassembly.py index 40a0d6c5..8c4b0eb5 100644 --- a/python/lineardisassembly.py +++ b/python/lineardisassembly.py @@ -27,7 +27,7 @@ from . import highlight from . import function as _function from . import basicblock from . import binaryview -from .enums import LinearViewObjectIdentifierType +from .enums import (LinearDisassemblyLineType, LinearViewObjectIdentifierType) class LinearDisassemblyLine: @@ -43,6 +43,41 @@ class LinearDisassemblyLine: def __str__(self): return str(self.contents) + @classmethod + def _from_core_struct(cls, struct: core.BNLinearDisassemblyLine, obj: Optional['LinearViewObject'] = None) -> 'LinearDisassemblyLine': + function = None + if struct.function: + function = _function.Function(handle=core.BNNewFunctionReference(struct.function)) + block = None + if struct.block: + block = basicblock.BasicBlock._from_core_block(core.BNNewBasicBlockReference(struct.block)) + il_func = None + if block is not None: + il_func = block.il_function + if obj is not None and function is not None: + # Hack: HLIL bodies don't have basic blocks + if obj.identifier.name == "HLIL Function Body": + il_func = function.hlil + elif obj.identifier.name == "HLIL SSA Function Body": + il_func = function.hlil.ssa_form + elif obj.identifier.name == "Language Representation Function Body": + il_func = function.hlil + + contents = _function.DisassemblyTextLine._from_core_struct(struct.contents, il_func) + return LinearDisassemblyLine(LinearDisassemblyLineType(struct.type), function, block, contents) + + def _to_core_struct(self) -> core.BNLinearDisassemblyLine: + result = core.BNLinearDisassemblyLine() + result.type = self.type + result.function = None + if self.function is not None: + result.function = self.function.handle + result.block = None + if self.block is not None: + result.block = self.block.handle + result.contents = _function.DisassemblyTextLine._to_core_struct(self.contents) + return result + class LinearViewObjectIdentifier: def __init__(self, name, start=None, end=None): @@ -247,7 +282,7 @@ class LinearViewObject: count = ctypes.c_ulonglong(0) return LinearViewCursor._make_lines( - core.BNGetLinearViewObjectLines(self.handle, prev_obj, next_obj, count), count.value + core.BNGetLinearViewObjectLines(self.handle, prev_obj, next_obj, count), count.value, self ) def ordering_index_for_child(self, child): @@ -602,34 +637,21 @@ class LinearViewCursor: return core.BNLinearViewCursorNext(self.handle) @staticmethod - def _make_lines(lines, count: int) -> List['LinearDisassemblyLine']: + def _make_lines(lines, count: int, object) -> List['LinearDisassemblyLine']: assert lines is not None, "core returned None for LinearDisassembly lines" try: result = [] for i in range(0, count): - func = None - block = None - if lines[i].function: - func = _function.Function(None, core.BNNewFunctionReference(lines[i].function)) - if lines[i].block: - core_block = core.BNNewBasicBlockReference(lines[i].block) - assert core_block is not None, "core.BNNewBasicBlockReference returned None" - block = basicblock.BasicBlock(core_block, None) - color = highlight.HighlightColor._from_core_struct(lines[i].contents.highlight) - addr = lines[i].contents.addr - tokens = _function.InstructionTextToken._from_core_struct( - lines[i].contents.tokens, lines[i].contents.count - ) - contents = _function.DisassemblyTextLine(tokens, addr, color=color) - result.append(LinearDisassemblyLine(lines[i].type, func, block, contents)) + result.append(LinearDisassemblyLine._from_core_struct(lines[i], obj=object)) return result finally: core.BNFreeLinearDisassemblyLines(lines, count) @property def lines(self): + current = self.current_object count = ctypes.c_ulonglong(0) - return LinearViewCursor._make_lines(core.BNGetLinearViewCursorLines(self.handle, count), count.value) + return LinearViewCursor._make_lines(core.BNGetLinearViewCursorLines(self.handle, count), count.value, current) def duplicate(self): return LinearViewCursor(None, handle=core.BNDuplicateLinearViewCursor(self.handle)) @@ -637,3 +659,48 @@ class LinearViewCursor: @staticmethod def compare(a, b): return core.BNCompareLinearViewCursors(a.handle, b.handle) + + @property + def render_layers(self) -> List['binaryninja.RenderLayer']: + """ + Get the list of Render Layers which will be applied to this cursor, at the + end of calls to lines(). + + :return: List of Render Layers + """ + count = ctypes.c_size_t(0) + layers = core.BNGetLinearViewCursorRenderLayers(self.handle, count) + assert layers is not None, "core.BNGetLinearViewCursorRenderLayers returned None" + + try: + result = [] + for i in range(0, count.value): + result.append(binaryninja.RenderLayer(handle=layers[i])) + + return result + finally: + core.BNFreeRenderLayerList(layers) + + @render_layers.setter + def render_layers(self, render_layers: List['binaryninja.RenderLayer']): + for layer in self.render_layers: + self.remove_render_layer(layer) + for layer in render_layers: + self.add_render_layer(layer) + + def add_render_layer(self, layer: 'binaryninja.RenderLayer'): + """ + Add a Render Layer to be applied to this cursor. Note that layers will + be applied in the order in which they are added. + + :param layer: Render Layer to add + """ + core.BNAddLinearViewCursorRenderLayer(self.handle, layer.handle) + + def remove_render_layer(self, layer: 'binaryninja.RenderLayer'): + """ + Remove a Render Layer from being applied to this cursor + + :param layer: Render Layer to remove + """ + core.BNRemoveLinearViewCursorRenderLayer(self.handle, layer.handle) diff --git a/python/renderlayer.py b/python/renderlayer.py new file mode 100644 index 00000000..6f5984e0 --- /dev/null +++ b/python/renderlayer.py @@ -0,0 +1,450 @@ +# Copyright (c) 2015-2024 Vector 35 Inc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import ctypes +import traceback + +# Binary Ninja components +import binaryninja +from . import _binaryninjacore as core, LinearDisassemblyLine +from .enums import LinearDisassemblyLineType, RenderLayerDefaultEnableState +from . import binaryview +from . import types +from .log import log_error +from typing import Iterable, List, Optional, Union, Tuple + + +class _RenderLayerMetaclass(type): + def __iter__(self): + binaryninja._init_plugins() + count = ctypes.c_ulonglong() + instances = core.BNGetRenderLayerList(count) + try: + for i in range(0, count.value): + yield self._handle_to_instance(instances[i]) + finally: + core.BNFreeRenderLayerList(instances) + + def __getitem__(self, value): + binaryninja._init_plugins() + handle = core.BNGetRenderLayerByName(str(value)) + if handle is None: + raise KeyError(f"'{value}' is not a valid RenderLayer") + return self._handle_to_instance(handle) + + def _handle_to_instance(self, handle): + handle_ptr = ctypes.cast(handle, ctypes.c_void_p) + if handle_ptr.value in RenderLayer._registered_instances: + return RenderLayer._registered_instances[handle_ptr.value] + return CoreRenderLayer(handle) + + +class RenderLayer(metaclass=_RenderLayerMetaclass): + """ + RenderLayer is a plugin class that allows you to customize the presentation of + Linear and Graph view output, adding, changing, or removing lines before they are + presented in the UI. + """ + + name = None + default_enable_state = RenderLayerDefaultEnableState.DisabledByDefaultRenderLayerDefaultEnableState + _registered_instances = {} + _pending_lines = {} + + def __init__(self, handle=None): + if handle is not None: + self.handle = core.handle_of_type(handle, core.BNRenderLayer) + self.__dict__["name"] = core.BNGetRenderLayerName(handle) + self.__dict__["default_enable_state"] = core.BNGetRenderLayerDefaultEnableState(handle) + else: + self.handle = None + + @classmethod + def register(cls): + """ + Register a custom Render Layer. + """ + layer = cls() + + assert layer.__class__.name is not None + assert layer.handle is None + + layer._cb = core.BNRenderLayerCallbacks() + layer._cb.context = 0 + layer._cb.applyToFlowGraph = layer._cb.applyToFlowGraph.__class__(layer._apply_to_flow_graph) + layer._cb.applyToLinearViewObject = layer._cb.applyToLinearViewObject.__class__(layer._apply_to_linear_view_object) + layer._cb.freeLines = layer._cb.freeLines.__class__(layer._free_lines) + layer.handle = core.BNRegisterRenderLayer(layer.__class__.name, layer._cb, layer.default_enable_state) + handle_ptr = ctypes.cast(layer.handle, ctypes.c_void_p) + cls._registered_instances[handle_ptr.value] = layer + + def __eq__(self, other): + if not isinstance(other, RenderLayer): + return False + return self.name == other.name + + def __str__(self): + return f'<RenderLayer: {self.name}>' + + def __repr__(self): + return f'<RenderLayer: {self.name}>' + + def _apply_to_flow_graph(self, ctxt, graph): + try: + self.apply_to_flow_graph(binaryninja.FlowGraph(handle=core.BNNewFlowGraphReference(graph))) + except: + log_error(traceback.format_exc()) + + def _apply_to_linear_view_object(self, ctxt, obj, prev, next, in_lines, in_line_count, out_lines, out_line_count): + try: + obj_obj = binaryninja.LinearViewObject(core.BNNewLinearViewObjectReference(obj)) + prev_obj = binaryninja.LinearViewObject(core.BNNewLinearViewObjectReference(prev)) if prev else None + next_obj = binaryninja.LinearViewObject(core.BNNewLinearViewObjectReference(next)) if next else None + + lines = [] + for i in range(in_line_count): + lines.append(LinearDisassemblyLine._from_core_struct(in_lines[i], obj=obj_obj)) + + lines = self.apply_to_linear_view_object(obj_obj, prev_obj, next_obj, lines) + + out_line_count[0] = len(lines) + out_lines_buf = (core.BNLinearDisassemblyLine * len(lines))() + for i, r in enumerate(lines): + out_lines_buf[i] = r._to_core_struct() + out_lines_ptr = ctypes.cast(out_lines_buf, ctypes.c_void_p) + out_lines[0] = out_lines_buf + self._pending_lines[out_lines_ptr.value] = (out_lines_ptr.value, out_lines_buf) + except: + log_error(traceback.format_exc()) + out_lines[0] = None + out_line_count[0] = 0 + + def _free_lines(self, ctxt, lines, count): + try: + buf = ctypes.cast(lines, ctypes.c_void_p) + if buf.value is not None: + if buf.value not in self._pending_lines: + raise ValueError("freeing lines list that wasn't allocated") + del self._pending_lines[buf.value] + except: + log_error(traceback.format_exc()) + + def apply_to_disassembly_block( + self, + block: 'binaryninja.BasicBlock', + lines: List['binaryninja.DisassemblyTextLine'] + ): + """ + Apply this Render Layer to a single Basic Block of Disassembly lines. + Subclasses should modify the input `lines` list to make modifications to + the presentation of the block. + + .. note:: This function will only handle Disassembly lines, and not any ILs. + + :param block: Basic Block containing those lines + :param lines: Original lines of text for the block + :return: Modified list of lines + """ + return lines + + def apply_to_low_level_il_block( + self, + block: 'binaryninja.LowLevelILBasicBlock', + lines: List['binaryninja.DisassemblyTextLine'] + ): + """ + Apply this Render Layer to a single Basic Block of Low Level IL lines. + Subclasses should modify the input `lines` list to make modifications to + the presentation of the block. + + .. note:: This function will only handle Lifted IL/LLIL/LLIL(SSA) lines. \ + You can use the block's `function_graph_type` property to determine which is being handled. + + :param block: Basic Block containing those lines + :param lines: Original lines of text for the block + :return: Modified list of lines + """ + return lines + + def apply_to_medium_level_il_block( + self, + block: 'binaryninja.MediumLevelILBasicBlock', + lines: List['binaryninja.DisassemblyTextLine'] + ): + """ + Apply this Render Layer to a single Basic Block of Medium Level IL lines. + Subclasses should modify the input `lines` list to make modifications to + the presentation of the block. + + .. note:: This function will only handle MLIL/MLIL(SSA)/Mapped MLIL/Mapped MLIL(SSA) lines. \ + You can use the block's `function_graph_type` property to determine which is being handled. + + :param block: Basic Block containing those lines + :param lines: Original lines of text for the block + :return: Modified list of lines + """ + return lines + + def apply_to_high_level_il_block( + self, + block: 'binaryninja.HighLevelILBasicBlock', + lines: List['binaryninja.DisassemblyTextLine'] + ): + """ + Apply this Render Layer to a single Basic Block of High Level IL lines. + Subclasses should modify the input `lines` list to make modifications to + the presentation of the block. + + .. note:: This function will only handle HLIL/HLIL(SSA)/Language Representation lines. \ + You can use the block's `function_graph_type` property to determine which is being handled. + + .. warning:: This function will NOT apply to High Level IL bodies as displayed \ + in Linear View! Those are handled by `apply_to_high_level_il_body` instead as they \ + do not have a Basic Block associated with them. + + :param block: Basic Block containing those lines + :param lines: Original lines of text for the block + :return: Modified list of lines + """ + return lines + + def apply_to_high_level_il_body( + self, + function: 'binaryninja.Function', + lines: List['binaryninja.LinearDisassemblyLine'] + ): + """ + Apply this Render Layer to the entire body of a High Level IL function. + Subclasses should modify the input `lines` list to make modifications to + the presentation of the function. + + .. warning:: This function only applies to Linear View, and not to Graph View! \ + If you want to handle Graph View too, you will need to use `apply_to_high_level_il_block` \ + and handle the lines one block at a time. + + :param function: Function containing those lines + :param lines: Original lines of text for the function + :return: Modified list of lines + """ + return lines + + def apply_to_misc_linear_lines( + self, + obj: 'binaryninja.LinearViewObject', + prev: Optional['binaryninja.LinearViewObject'], + next: Optional['binaryninja.LinearViewObject'], + lines: List['binaryninja.LinearDisassemblyLine'] + ): + """ + Apply to lines generated by Linear View that are not part of a function. + It is up to your implementation to figure out which type of Linear View Object + lines these are, and what to do with them. + + :param obj: Linear View Object being rendered + :param prev: Linear View Object located directly above this one + :param next: Linear View Object located directly below this one + :param lines: Original lines rendered by `obj` + :return: Modified list of lines + """ + return lines + + def apply_to_block( + self, + block: 'binaryninja.BasicBlock', + lines: List['binaryninja.DisassemblyTextLine'], + ): + """ + Apply to lines generated by a Basic Block, of any type. If not overridden, this + function will call the appropriate apply_to_X_level_il_block function. + + :param block: Basic Block containing those lines + :param lines: Original lines of text for the block + :return: Modified list of lines + """ + if not block.is_il: + return self.apply_to_disassembly_block(block, lines) + elif block.is_low_level_il: + return self.apply_to_low_level_il_block(block, lines) + elif block.is_medium_level_il: + return self.apply_to_medium_level_il_block(block, lines) + elif block.is_high_level_il: + return self.apply_to_high_level_il_block(block, lines) + else: + # ??? + return lines + + def apply_to_flow_graph(self, graph: 'binaryninja.FlowGraph') -> None: + """ + Apply this Render Layer to a Flow Graph, potentially modifying its nodes, + their edges, their lines, and their lines' content. + + .. note:: If you override this function, you will need to call the ``super()`` \ + implementation if you want to use the higher level ``apply_to_X_level_il_block`` \ + functionality. + + :param graph: Graph to modify + """ + pass + for i, node in enumerate(graph.nodes): + lines = node.lines + if node.basic_block is not None and isinstance(node.basic_block, binaryninja.BasicBlock): + lines = self.apply_to_block(node.basic_block, lines) + node.lines = lines + + def apply_to_linear_view_object( + self, + obj: 'binaryninja.LinearViewObject', + prev: Optional['binaryninja.LinearViewObject'], + next: Optional['binaryninja.LinearViewObject'], + lines: List['binaryninja.LinearDisassemblyLine'] + ) -> List['binaryninja.LinearDisassemblyLine']: + """ + Apply this Render Layer to the lines produced by a LinearViewObject for rendering + in Linear View, potentially modifying the lines and their contents. + + .. note:: If you override this function, you will need to call the ``super()`` \ + implementation if you want to use the higher level ``apply_to_X_level_il_block`` \ + functionality. + + :param obj: Linear View Object being rendered + :param prev: Linear View Object located directly above this one + :param next: Linear View Object located directly below this one + :param lines: Original lines rendered by the Linear View Object + :return: Modified list of lines to display in Linear View + """ + # Hack: HLIL bodies don't have basic blocks + if len(lines) > 0 and obj.identifier.name in [ + "HLIL Function Body", + "HLIL SSA Function Body", + "Language Representation Function Body" + ]: + return self.apply_to_high_level_il_body(lines[0].function, lines) + + block_lines = [] + final_lines = [] + last_block = None + + def finish_block(): + nonlocal block_lines + nonlocal final_lines + if len(block_lines) > 0: + if last_block is not None: + # Convert linear lines to disassembly lines for the apply() + # and then convert back for linear view + new_block_lines = [] + disasm_lines = [] + misc_lines = [] + + def process_disasm(): + nonlocal disasm_lines + + if len(disasm_lines) > 0: + disasm_lines = self.apply_to_block(last_block, disasm_lines) + func = block_lines[0].function + block = block_lines[0].block + for block_line in disasm_lines: + new_block_lines.append( + LinearDisassemblyLine( + LinearDisassemblyLineType.CodeDisassemblyLineType, + func, + block, + block_line + ) + ) + disasm_lines = [] + + def process_misc(): + nonlocal misc_lines + nonlocal new_block_lines + + if len(misc_lines) > 0: + misc_lines = self.apply_to_misc_linear_lines(obj, prev, next, misc_lines) + new_block_lines += misc_lines + misc_lines = [] + + for block_line in block_lines: + # Lines in the block get sent to process_disasm, anything else goes + # to process_misc so we preserve line information + if block_line.type == LinearDisassemblyLineType.CodeDisassemblyLineType: + process_misc() + disasm_lines.append(block_line.contents) + else: + process_disasm() + misc_lines.append(block_line) + + # At the end, zero or one of these has lines in it + process_misc() + process_disasm() + block_lines = new_block_lines + else: + block_lines = self.apply_to_misc_linear_lines(obj, prev, next, block_lines) + final_lines += block_lines + block_lines = [] + + for line in lines: + # Assume we've finished a block when the line's block changes + if line.block != last_block: + finish_block() + block_lines.append(line) + last_block = line.block + + # And we've finished a block when we're done with every line + finish_block() + return final_lines + + +class CoreRenderLayer(RenderLayer): + + def apply_to_flow_graph(self, graph: 'binaryninja.FlowGraph') -> None: + core.BNApplyRenderLayerToFlowGraph(self.handle, graph.handle) + + def apply_to_linear_view_object( + self, + obj: 'binaryninja.LinearViewObject', + prev: Optional['binaryninja.LinearViewObject'], + next: Optional['binaryninja.LinearViewObject'], + lines: List['binaryninja.LinearDisassemblyLine'] + ) -> List['binaryninja.LinearDisassemblyLine']: + + in_lines_buf = (core.BNLinearDisassemblyLine * len(lines))() + for i, r in enumerate(lines): + in_lines_buf[i] = r._to_core_struct() + + out_lines = ctypes.POINTER(core.BNLinearDisassemblyLine)() + out_line_count = ctypes.c_size_t(0) + + core.BNApplyRenderLayerToLinearViewObject( + self.handle, + obj.handle, + prev.handle if prev is not None else None, + next.handle if next is not None else None, + in_lines_buf, + len(lines), + out_lines, + out_line_count + ) + + result = [] + for i in range(out_line_count.value): + result.append(binaryninja.LinearDisassemblyLine._from_core_struct(out_lines[i], obj=obj)) + + core.BNFreeLinearDisassemblyLines(out_lines, out_line_count.value) + + return result diff --git a/renderlayer.cpp b/renderlayer.cpp new file mode 100644 index 00000000..fb89be53 --- /dev/null +++ b/renderlayer.cpp @@ -0,0 +1,329 @@ +// Copyright (c) 2015-2024 Vector 35 Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "binaryninjaapi.h" +#include "ffi.h" + +using namespace BinaryNinja; +using namespace std; + +std::unordered_map<BNRenderLayer*, RenderLayer*> RenderLayer::g_registeredInstances; + + +RenderLayer::RenderLayer(const std::string& name): m_nameForRegister(name) +{ + +} + + +RenderLayer::RenderLayer(BNRenderLayer* layer) +{ + m_object = layer; +} + + +void RenderLayer::ApplyToFlowGraphCallback(void* ctxt, BNFlowGraph* graph) +{ + RenderLayer* layer = (RenderLayer*)ctxt; + layer->ApplyToFlowGraph(new CoreFlowGraph(BNNewFlowGraphReference(graph))); +} + + +void RenderLayer::ApplyToLinearViewObjectCallback( + void* ctxt, + BNLinearViewObject* obj, + BNLinearViewObject* prev, + BNLinearViewObject* next, + BNLinearDisassemblyLine* inLines, + size_t inLineCount, + BNLinearDisassemblyLine** outLines, + size_t* outLineCount +) +{ + RenderLayer* layer = (RenderLayer*)ctxt; + vector<LinearDisassemblyLine> lines = ParseAPIObjectList<LinearDisassemblyLine>(inLines, inLineCount); + + layer->ApplyToLinearViewObject( + new LinearViewObject(BNNewLinearViewObjectReference(obj)), + prev ? new LinearViewObject(BNNewLinearViewObjectReference(prev)) : nullptr, + next ? new LinearViewObject(BNNewLinearViewObjectReference(next)) : nullptr, + lines + ); + + AllocAPIObjectList<LinearDisassemblyLine>(lines, outLines, outLineCount); +} + + +void RenderLayer::FreeLinesCallback(void* ctxt, BNLinearDisassemblyLine* lines, size_t count) +{ + FreeAPIObjectList<LinearDisassemblyLine>(lines, count); +} + + +void RenderLayer::Register(RenderLayer* layer, BNRenderLayerDefaultEnableState enableState) +{ + BNRenderLayerCallbacks cb; + cb.context = (void*)layer; + cb.applyToFlowGraph = ApplyToFlowGraphCallback; + cb.applyToLinearViewObject = ApplyToLinearViewObjectCallback; + cb.freeLines = FreeLinesCallback; + layer->m_object = BNRegisterRenderLayer(layer->m_nameForRegister.c_str(), &cb, enableState); + g_registeredInstances[layer->m_object] = layer; +} + + +std::vector<Ref<RenderLayer>> RenderLayer::GetList() +{ + size_t count; + BNRenderLayer** list = BNGetRenderLayerList(&count); + vector<Ref<RenderLayer>> result; + for (size_t i = 0; i < count; i ++) + { + if (auto reg = g_registeredInstances.find(list[i]); reg != g_registeredInstances.end()) + { + result.push_back(reg->second); + } + else + { + result.push_back(new CoreRenderLayer(list[i])); + } + } + BNFreeRenderLayerList(list); + return result; +} + + +Ref<RenderLayer> RenderLayer::GetByName(const std::string& name) +{ + BNRenderLayer* result = BNGetRenderLayerByName(name.c_str()); + if (!result) + return nullptr; + if (auto reg = g_registeredInstances.find(result); reg != g_registeredInstances.end()) + { + return reg->second; + } + return new CoreRenderLayer(result); +} + + +BNRenderLayerDefaultEnableState RenderLayer::GetDefaultEnableState() const +{ + return BNGetRenderLayerDefaultEnableState(m_object); +} + + +std::string RenderLayer::GetName() const +{ + char* name = BNGetRenderLayerName(m_object); + std::string value = name; + BNFreeString(name); + return value; +} + + +void RenderLayer::ApplyToBlock( + Ref<BasicBlock> block, + std::vector<DisassemblyTextLine>& lines +) +{ + if (!block->IsILBlock()) + { + ApplyToDisassemblyBlock(block, lines); + } + else if (block->IsLowLevelILBlock()) + { + ApplyToLowLevelILBlock(block, lines); + } + else if (block->IsMediumLevelILBlock()) + { + ApplyToMediumLevelILBlock(block, lines); + } + else if (block->IsHighLevelILBlock()) + { + ApplyToHighLevelILBlock(block, lines); + } +} + + +void RenderLayer::ApplyToFlowGraph(Ref<FlowGraph> graph) +{ + for (auto node: graph->GetNodes()) + { + auto lines = node->GetLines(); + if (node->GetBasicBlock()) + { + ApplyToBlock(node->GetBasicBlock(), lines); + } + node->SetLines(lines); + } +} + + +void RenderLayer::ApplyToLinearViewObject( + Ref<LinearViewObject> obj, + Ref<LinearViewObject> prev, + Ref<LinearViewObject> next, + std::vector<LinearDisassemblyLine>& lines +) +{ + // Hack: HLIL bodies don't have basic blocks + if (!lines.empty() && + (obj->GetIdentifier().name == "HLIL Function Body" + || obj->GetIdentifier().name == "HLIL SSA Function Body" + || obj->GetIdentifier().name == "Language Representation Function Body")) + { + ApplyToHighLevelILBody(lines[0].function, lines); + return; + } + + std::vector<LinearDisassemblyLine> blockLines; + std::vector<LinearDisassemblyLine> finalLines; + Ref<BasicBlock> lastBlock; + + auto finishBlock = [&]() + { + if (!blockLines.empty()) + { + if (lastBlock) + { + // Convert linear lines to disassembly lines for the apply() + // and then convert back for linear view + std::vector<LinearDisassemblyLine> newBlockLines; + std::vector<DisassemblyTextLine> disasmLines; + std::vector<LinearDisassemblyLine> miscLines; + + auto processDisasm = [&]() + { + if (!disasmLines.empty()) + { + ApplyToBlock(lastBlock, disasmLines); + Ref<Function> func = blockLines[0].function; + Ref<BasicBlock> block = blockLines[0].block; + for (auto& blockLine: disasmLines) + { + LinearDisassemblyLine newLine; + newLine.type = CodeDisassemblyLineType; + newLine.function = func; + newLine.block = block; + newLine.contents = blockLine; + newBlockLines.push_back(newLine); + } + disasmLines.clear(); + } + }; + + auto processMisc = [&]() + { + if (!miscLines.empty()) + { + ApplyToMiscLinearLines(obj, prev, next, miscLines); + std::move( + miscLines.begin(), + miscLines.end(), + std::back_inserter(newBlockLines) + ); + miscLines.clear(); + } + }; + + for (auto& blockLine: blockLines) + { + // Lines in the block get sent to processDisasm, anything else goes + // to processMisc so we preserve line information + if (blockLine.type == CodeDisassemblyLineType) + { + processMisc(); + disasmLines.push_back(blockLine.contents); + } + else + { + processDisasm(); + miscLines.push_back(blockLine); + } + } + // At the end, zero or one of these has lines in it + processMisc(); + processDisasm(); + blockLines = newBlockLines; + } + else + { + ApplyToMiscLinearLines(obj, prev, next, blockLines); + } + } + std::move(blockLines.begin(), blockLines.end(), std::back_inserter(finalLines)); + }; + + for (auto& line: lines) + { + // Assume we've finished a block when the line's block changes + if (line.block != lastBlock) + { + finishBlock(); + } + blockLines.push_back(line); + lastBlock = line.block; + } + // And we've finished a block when we're done with every line + finishBlock(); + + lines = finalLines; +} + + +CoreRenderLayer::CoreRenderLayer(BNRenderLayer* layer): RenderLayer(layer) +{ +} + + +void CoreRenderLayer::ApplyToFlowGraph(Ref<FlowGraph> graph) +{ + BNApplyRenderLayerToFlowGraph(m_object, graph->GetObject()); +} + + +void CoreRenderLayer::ApplyToLinearViewObject( + Ref<LinearViewObject> obj, + Ref<LinearViewObject> prev, + Ref<LinearViewObject> next, + std::vector<LinearDisassemblyLine>& lines +) +{ + BNLinearDisassemblyLine* inLines; + size_t inLineCount; + AllocAPIObjectList<LinearDisassemblyLine>(lines, &inLines, &inLineCount); + + BNLinearDisassemblyLine* outLines; + size_t outLineCount; + + BNApplyRenderLayerToLinearViewObject( + m_object, + obj->GetObject(), + prev ? prev->GetObject() : nullptr, + next ? next->GetObject() : nullptr, + inLines, + inLineCount, + &outLines, + &outLineCount + ); + + lines = ParseAPIObjectList<LinearDisassemblyLine>(outLines, outLineCount); + FreeAPIObjectList<LinearDisassemblyLine>(inLines, inLineCount); +} diff --git a/rust/src/basic_block.rs b/rust/src/basic_block.rs index b880d28f..8846a90b 100644 --- a/rust/src/basic_block.rs +++ b/rust/src/basic_block.rs @@ -19,6 +19,7 @@ use crate::BranchType; use binaryninjacore_sys::*; use std::fmt; use std::fmt::Debug; +use std::hash::{Hash, Hasher}; enum EdgeDirection { Incoming, @@ -97,7 +98,14 @@ pub trait BlockContext: Clone + Sync + Send + Sized { fn iter(&self, block: &BasicBlock<Self>) -> Self::Iter; } -#[derive(PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)] +pub enum BasicBlockType { + Native, + LowLevelIL, + MediumLevelIL, + HighLevelIL, +} + pub struct BasicBlock<C: BlockContext> { pub(crate) handle: *mut BNBasicBlock, context: C, @@ -127,6 +135,19 @@ impl<C: BlockContext> BasicBlock<C> { } } + pub fn block_type(&self) -> BasicBlockType { + if unsafe { !BNIsILBasicBlock(self.handle) } { + BasicBlockType::Native + } else if unsafe { BNIsLowLevelILBasicBlock(self.handle) } { + BasicBlockType::LowLevelIL + } else if unsafe { BNIsMediumLevelILBasicBlock(self.handle) } { + BasicBlockType::MediumLevelIL + } else { + // We checked all other IL levels, so this is safe. + BasicBlockType::HighLevelIL + } + } + pub fn iter(&self) -> C::Iter { self.context.iter(self) } @@ -194,7 +215,7 @@ impl<C: BlockContext> BasicBlock<C> { if block.is_null() { return None; } - Some(Ref::new(BasicBlock::from_raw(block, self.context.clone()))) + Some(BasicBlock::ref_from_raw(block, self.context.clone())) } } @@ -237,6 +258,24 @@ impl<C: BlockContext> BasicBlock<C> { // TODO iterated dominance frontier } +impl<C: BlockContext> Hash for BasicBlock<C> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.function().hash(state); + self.block_type().hash(state); + state.write_usize(self.index()); + } +} + +impl<C: BlockContext> PartialEq for BasicBlock<C> { + fn eq(&self, other: &Self) -> bool { + self.function() == other.function() + && self.index() == other.index() + && self.block_type() == other.block_type() + } +} + +impl<C: BlockContext> Eq for BasicBlock<C> {} + impl<C: BlockContext> IntoIterator for &BasicBlock<C> { type Item = C::Instruction; type IntoIter = C::Iter; diff --git a/rust/src/binary_view.rs b/rust/src/binary_view.rs index 43e913d5..af4709e7 100644 --- a/rust/src/binary_view.rs +++ b/rust/src/binary_view.rs @@ -1209,8 +1209,8 @@ pub trait BinaryViewExt: BinaryViewBase { /// Retrieves a list of the next disassembly lines. /// - /// `get_next_linear_disassembly_lines` retrieves an [Array] over [LinearDisassemblyLine] objects for the - /// next disassembly lines, and updates the [LinearViewCursor] passed in. This function can be called + /// Retrieves an [`Array`] over [`LinearDisassemblyLine`] objects for the + /// next disassembly lines, and updates the [`LinearViewCursor`] passed in. This function can be called /// repeatedly to get more lines of linear disassembly. /// /// # Arguments diff --git a/rust/src/collaboration/sync.rs b/rust/src/collaboration/sync.rs index 14e18856..1c11b8f0 100644 --- a/rust/src/collaboration/sync.rs +++ b/rust/src/collaboration/sync.rs @@ -701,7 +701,7 @@ pub fn is_type_archive_snapshot_ignored<S: BnStrCompatible>( pub fn download_type_archive<S: BnStrCompatible>( file: &RemoteFile, location: S, -) -> Result<Option<TypeArchive>, ()> { +) -> Result<Option<Ref<TypeArchive>>, ()> { download_type_archive_with_progress(file, location, NoProgressCallback) } @@ -711,7 +711,7 @@ pub fn download_type_archive_with_progress<S: BnStrCompatible, F: ProgressCallba file: &RemoteFile, location: S, mut progress: F, -) -> Result<Option<TypeArchive>, ()> { +) -> Result<Option<Ref<TypeArchive>>, ()> { let mut value = std::ptr::null_mut(); let db_path = location.into_bytes_with_nul(); let success = unsafe { @@ -724,7 +724,7 @@ pub fn download_type_archive_with_progress<S: BnStrCompatible, F: ProgressCallba ) }; success - .then(|| NonNull::new(value).map(|handle| unsafe { TypeArchive::from_raw(handle) })) + .then(|| NonNull::new(value).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) })) .ok_or(()) } diff --git a/rust/src/disassembly.rs b/rust/src/disassembly.rs index 99938e5f..5f9ed559 100644 --- a/rust/src/disassembly.rs +++ b/rust/src/disassembly.rs @@ -30,9 +30,10 @@ pub type DisassemblyOption = BNDisassemblyOption; pub type InstructionTextTokenType = BNInstructionTextTokenType; pub type StringType = BNStringType; -#[derive(Clone, PartialEq, Debug, Default)] +#[derive(Clone, PartialEq, Debug, Default, Eq)] pub struct DisassemblyTextLine { pub address: u64, + // TODO: This is not always available. pub instruction_index: usize, pub tokens: Vec<InstructionTextToken>, pub highlight: HighlightColor, @@ -234,7 +235,7 @@ impl DisassemblyTextLineTypeInfo { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct InstructionTextToken { pub address: u64, pub text: String, @@ -882,6 +883,8 @@ impl From<InstructionTextTokenKind> for BNInstructionTextTokenType { } } +impl Eq for InstructionTextTokenKind {} + #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum InstructionTextTokenContext { Normal, diff --git a/rust/src/flowgraph.rs b/rust/src/flowgraph.rs index ead984f9..f5d4ed80 100644 --- a/rust/src/flowgraph.rs +++ b/rust/src/flowgraph.rs @@ -14,12 +14,15 @@ //! Interfaces for creating and displaying pretty CFGs in Binary Ninja. -use binaryninjacore_sys::*; - use crate::disassembly::DisassemblyTextLine; +use binaryninjacore_sys::*; +use std::slice; use crate::rc::*; +use crate::basic_block::{BasicBlock, BlockContext}; +use crate::function::HighlightColor; +use crate::render_layer::CoreRenderLayer; use std::marker::PhantomData; pub type BranchType = BNBranchType; @@ -37,14 +40,61 @@ impl FlowGraph { Self { handle: raw } } + pub(crate) unsafe fn ref_from_raw(raw: *mut BNFlowGraph) -> Ref<Self> { + Ref::new(Self { handle: raw }) + } + pub fn new() -> Ref<Self> { - unsafe { Ref::new(FlowGraph::from_raw(BNCreateFlowGraph())) } + unsafe { FlowGraph::ref_from_raw(BNCreateFlowGraph()) } + } + + pub fn nodes<'a>(&self) -> Vec<Ref<FlowGraphNode<'a>>> { + let mut count: usize = 0; + let nodes_ptr = unsafe { BNGetFlowGraphNodes(self.handle, &mut count as *mut usize) }; + + let nodes = unsafe { slice::from_raw_parts_mut(nodes_ptr, count) }; + + let mut result = vec![]; + result.reserve(count); + + for i in 0..count { + result.push(unsafe { RefCountable::inc_ref(&FlowGraphNode::from_raw(nodes[i])) }); + } + + unsafe { BNFreeFlowGraphNodeList(nodes_ptr, count) }; + + result + } + + pub fn get_node<'a>(&self, i: usize) -> Option<Ref<FlowGraphNode<'a>>> { + let node_ptr = unsafe { BNGetFlowGraphNode(self.handle, i) }; + if node_ptr.is_null() { + None + } else { + Some(unsafe { Ref::new(FlowGraphNode::from_raw(node_ptr)) }) + } + } + + pub fn get_node_count(&self) -> usize { + unsafe { BNGetFlowGraphNodeCount(self.handle) } + } + + pub fn has_nodes(&self) -> bool { + unsafe { BNFlowGraphHasNodes(self.handle) } } pub fn append(&self, node: &FlowGraphNode) -> usize { unsafe { BNAddFlowGraphNode(self.handle, node.handle) } } + pub fn replace(&self, index: usize, node: &FlowGraphNode) { + unsafe { BNReplaceFlowGraphNode(self.handle, index, node.handle) } + } + + pub fn clear(&self) { + unsafe { BNClearFlowGraphNodes(self.handle) } + } + pub fn set_option(&self, option: FlowGraphOption, value: bool) { unsafe { BNSetFlowGraphOption(self.handle, option, value) } } @@ -52,6 +102,27 @@ impl FlowGraph { pub fn is_option_set(&self, option: FlowGraphOption) -> bool { unsafe { BNIsFlowGraphOptionSet(self.handle, option) } } + + /// A list of the currently applied [`CoreRenderLayer`]'s + pub fn render_layers(&self) -> Array<CoreRenderLayer> { + let mut count: usize = 0; + unsafe { + let handles = BNGetFlowGraphRenderLayers(self.handle, &mut count); + Array::new(handles, count, ()) + } + } + + /// Add a Render Layer to be applied to this [`FlowGraph`]. + /// + /// NOTE: Layers will be applied in the order in which they are added. + pub fn add_render_layer(&self, layer: &CoreRenderLayer) { + unsafe { BNAddFlowGraphRenderLayer(self.handle, layer.handle.as_ptr()) }; + } + + /// Remove a Render Layer from being applied to this [`FlowGraph`]. + pub fn remove_render_layer(&self, layer: &CoreRenderLayer) { + unsafe { BNRemoveFlowGraphRenderLayer(self.handle, layer.handle.as_ptr()) }; + } } unsafe impl RefCountable for FlowGraph { @@ -88,8 +159,37 @@ impl<'a> FlowGraphNode<'a> { } } - pub fn new(graph: &FlowGraph) -> Self { - unsafe { FlowGraphNode::from_raw(BNCreateFlowGraphNode(graph.handle)) } + pub(crate) unsafe fn ref_from_raw(raw: *mut BNFlowGraphNode) -> Ref<Self> { + Ref::new(Self { + handle: raw, + _data: PhantomData, + }) + } + + pub fn new(graph: &FlowGraph) -> Ref<Self> { + unsafe { FlowGraphNode::ref_from_raw(BNCreateFlowGraphNode(graph.handle)) } + } + + pub fn basic_block<C: BlockContext>(&self, context: C) -> Option<Ref<BasicBlock<C>>> { + let block_ptr = unsafe { BNGetFlowGraphBasicBlock(self.handle) }; + if block_ptr.is_null() { + return None; + } + Some(unsafe { BasicBlock::ref_from_raw(block_ptr, context) }) + } + + pub fn set_basic_block<C: BlockContext>(&self, block: Option<&BasicBlock<C>>) { + match block { + Some(block) => unsafe { BNSetFlowGraphBasicBlock(self.handle, block.handle) }, + None => unsafe { BNSetFlowGraphBasicBlock(self.handle, std::ptr::null_mut()) }, + } + } + + pub fn lines(&self) -> Array<DisassemblyTextLine> { + let mut count = 0; + let result = unsafe { BNGetFlowGraphNodeLines(self.handle, &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } } pub fn set_lines(&self, lines: impl IntoIterator<Item = DisassemblyTextLine>) { @@ -106,6 +206,30 @@ impl<'a> FlowGraphNode<'a> { } } + /// Returns the graph position of the node in X, Y form. + pub fn position(&self) -> (i32, i32) { + let pos_x = unsafe { BNGetFlowGraphNodeX(self.handle) }; + let pos_y = unsafe { BNGetFlowGraphNodeY(self.handle) }; + (pos_x, pos_y) + } + + /// Sets the graph position of the node. + pub fn set_position(&self, x: i32, y: i32) { + unsafe { BNFlowGraphNodeSetX(self.handle, x) }; + unsafe { BNFlowGraphNodeSetX(self.handle, y) }; + } + + pub fn highlight_color(&self) -> HighlightColor { + let raw = unsafe { BNGetFlowGraphNodeHighlight(self.handle) }; + HighlightColor::from(raw) + } + + pub fn set_highlight_color(&self, highlight: HighlightColor) { + unsafe { BNSetFlowGraphNodeHighlight(self.handle, highlight.into()) }; + } + + // TODO: Add getters and setters for edges + pub fn add_outgoing_edge( &self, type_: BranchType, diff --git a/rust/src/function.rs b/rust/src/function.rs index d4eb1d53..63ebc82e 100644 --- a/rust/src/function.rs +++ b/rust/src/function.rs @@ -128,7 +128,7 @@ impl Iterator for NativeBlockIter { } } -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct NativeBlock { _priv: (), } @@ -2346,7 +2346,7 @@ impl Function { /// Flow graph of unresolved stack adjustments pub fn unresolved_stack_adjustment_graph(&self) -> Option<Ref<FlowGraph>> { let graph = unsafe { BNGetUnresolvedStackAdjustmentGraph(self.handle) }; - (!graph.is_null()).then(|| unsafe { Ref::new(FlowGraph::from_raw(graph)) }) + (!graph.is_null()).then(|| unsafe { FlowGraph::ref_from_raw(graph) }) } pub fn create_graph( @@ -2358,7 +2358,7 @@ impl Function { let raw_view_type = FunctionViewType::into_raw(view_type); let result = unsafe { BNCreateFunctionGraph(self.handle, raw_view_type, settings_raw) }; FunctionViewType::free_raw(raw_view_type); - unsafe { Ref::new(FlowGraph::from_raw(result)) } + unsafe { FlowGraph::ref_from_raw(result) } } pub fn parent_components(&self) -> Array<Component> { diff --git a/rust/src/lib.rs b/rust/src/lib.rs index eb89ff0c..0cb0484e 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -68,6 +68,7 @@ pub mod project; pub mod rc; pub mod references; pub mod relocation; +pub mod render_layer; pub mod section; pub mod segment; pub mod settings; diff --git a/rust/src/linear_view.rs b/rust/src/linear_view.rs index dc3ca597..6d2347b3 100644 --- a/rust/src/linear_view.rs +++ b/rust/src/linear_view.rs @@ -18,26 +18,37 @@ use binaryninjacore_sys::*; use crate::binary_view::BinaryView; use crate::disassembly::{DisassemblySettings, DisassemblyTextLine}; -use crate::function::Function; +use crate::function::{Function, NativeBlock}; +use crate::basic_block::BasicBlock; use crate::rc::*; +use crate::render_layer::CoreRenderLayer; +use crate::string::{raw_to_string, BnString}; use std::ops::Deref; -use std::mem; - pub type LinearDisassemblyLineType = BNLinearDisassemblyLineType; +pub type LinearViewObjectIdentifierType = BNLinearViewObjectIdentifierType; -// TODO: Rename to LinearView? pub struct LinearViewObject { pub(crate) handle: *mut BNLinearViewObject, } impl LinearViewObject { + pub(crate) unsafe fn from_raw(handle: *mut BNLinearViewObject) -> Self { + debug_assert!(!handle.is_null()); + Self { handle } + } + pub(crate) unsafe fn ref_from_raw(handle: *mut BNLinearViewObject) -> Ref<Self> { debug_assert!(!handle.is_null()); Ref::new(Self { handle }) } + pub fn identifier(&self) -> LinearViewObjectIdentifier { + let raw = unsafe { BNGetLinearViewObjectIdentifier(self.handle) }; + LinearViewObjectIdentifier::from_owned_raw(raw) + } + pub fn data_only(view: &BinaryView, settings: &DisassemblySettings) -> Ref<Self> { unsafe { let handle = BNCreateLinearViewDataOnly(view.handle, settings.handle); @@ -214,6 +225,45 @@ impl ToOwned for LinearViewObject { unsafe impl Send for LinearViewObject {} unsafe impl Sync for LinearViewObject {} +#[derive(Clone, PartialEq, Debug)] +pub struct LinearViewObjectIdentifier { + pub name: String, + pub ty: LinearViewObjectIdentifierType, + pub start: u64, + pub end: u64, +} + +impl LinearViewObjectIdentifier { + pub fn from_raw(value: &BNLinearViewObjectIdentifier) -> Self { + Self { + name: raw_to_string(value.name).unwrap(), + ty: value.type_, + start: value.start, + end: value.end, + } + } + + pub fn from_owned_raw(value: BNLinearViewObjectIdentifier) -> Self { + let owned = Self::from_raw(&value); + Self::free_raw(value); + owned + } + + pub fn into_raw(value: Self) -> BNLinearViewObjectIdentifier { + let bn_name = BnString::new(value.name); + BNLinearViewObjectIdentifier { + name: BnString::into_raw(bn_name), + type_: value.ty, + start: value.start, + end: value.end, + } + } + + pub fn free_raw(value: BNLinearViewObjectIdentifier) { + let _ = unsafe { BnString::from_raw(value.name) }; + } +} + #[derive(Eq)] pub struct LinearViewCursor { pub(crate) handle: *mut BNLinearViewCursor, @@ -252,15 +302,15 @@ impl LinearViewCursor { !(self.before_begin() || self.after_end()) } - pub fn seek_to_start(&self) { + pub fn seek_to_start(&mut self) { unsafe { BNSeekLinearViewCursorToBegin(self.handle) } } - pub fn seek_to_end(&self) { + pub fn seek_to_end(&mut self) { unsafe { BNSeekLinearViewCursorToEnd(self.handle) } } - pub fn seek_to_address(&self, address: u64) { + pub fn seek_to_address(&mut self, address: u64) { unsafe { BNSeekLinearViewCursorToAddress(self.handle, address) } } @@ -275,15 +325,15 @@ impl LinearViewCursor { unsafe { BNGetLinearViewCursorOrderingIndexTotal(self.handle) } } - pub fn seek_to_ordering_index(&self, idx: u64) { + pub fn seek_to_ordering_index(&mut self, idx: u64) { unsafe { BNSeekLinearViewCursorToAddress(self.handle, idx) } } - pub fn previous(&self) -> bool { + pub fn previous(&mut self) -> bool { unsafe { BNLinearViewCursorPrevious(self.handle) } } - pub fn next(&self) -> bool { + pub fn next(&mut self) -> bool { unsafe { BNLinearViewCursorNext(self.handle) } } @@ -294,6 +344,27 @@ impl LinearViewCursor { Array::new(handles, count, ()) } } + + /// A list of the currently applied [`CoreRenderLayer`]'s + pub fn render_layers(&self) -> Array<CoreRenderLayer> { + let mut count: usize = 0; + unsafe { + let handles = BNGetLinearViewCursorRenderLayers(self.handle, &mut count); + Array::new(handles, count, ()) + } + } + + /// Add a Render Layer to be applied to this [`LinearViewCursor`]. + /// + /// NOTE: Layers will be applied in the order in which they are added. + pub fn add_render_layer(&self, layer: &CoreRenderLayer) { + unsafe { BNAddLinearViewCursorRenderLayer(self.handle, layer.handle.as_ptr()) }; + } + + /// Remove a Render Layer from being applied to this [`LinearViewCursor`]. + pub fn remove_render_layer(&self, layer: &CoreRenderLayer) { + unsafe { BNRemoveLinearViewCursorRenderLayer(self.handle, layer.handle.as_ptr()) }; + } } impl PartialEq for LinearViewCursor { @@ -341,55 +412,81 @@ impl ToOwned for LinearViewCursor { unsafe impl Send for LinearViewCursor {} unsafe impl Sync for LinearViewCursor {} +#[derive(Clone, PartialEq, Debug, Eq)] pub struct LinearDisassemblyLine { - t: LinearDisassemblyLineType, - - // These will be cleaned up by BNFreeLinearDisassemblyLines, so we - // don't drop them in the relevant deconstructors. - // TODO: This is insane! - function: mem::ManuallyDrop<Ref<Function>>, - contents: mem::ManuallyDrop<DisassemblyTextLine>, + pub ty: LinearDisassemblyLineType, + pub function: Option<Ref<Function>>, + pub basic_block: Option<Ref<BasicBlock<NativeBlock>>>, + pub contents: DisassemblyTextLine, } impl LinearDisassemblyLine { - pub(crate) unsafe fn from_raw(raw: &BNLinearDisassemblyLine) -> Self { - let linetype = raw.type_; - // TODO: We must remove this behavior. - let function = mem::ManuallyDrop::new(Function::ref_from_raw(raw.function)); - let contents = mem::ManuallyDrop::new(DisassemblyTextLine::from_raw(&raw.contents)); + pub(crate) unsafe fn from_raw(value: &BNLinearDisassemblyLine) -> Self { + let function = if !value.function.is_null() { + Some(unsafe { Function::from_raw(value.function).to_owned() }) + } else { + None + }; + let basic_block = if !value.block.is_null() { + Some(unsafe { BasicBlock::from_raw(value.block, NativeBlock::new()).to_owned() }) + } else { + None + }; Self { - t: linetype, + ty: value.type_, function, - contents, + basic_block, + contents: DisassemblyTextLine::from_raw(&value.contents), } } - pub fn function(&self) -> &Function { - self.function.as_ref() + #[allow(unused)] + pub(crate) unsafe fn from_owned_raw(value: BNLinearDisassemblyLine) -> Self { + let owned = Self::from_raw(&value); + Self::free_raw(value); + owned + } + + pub(crate) fn into_raw(value: Self) -> BNLinearDisassemblyLine { + let function_ptr = value + .function + .map(|f| unsafe { Ref::into_raw(f) }.handle) + .unwrap_or(std::ptr::null_mut()); + let block_ptr = value + .basic_block + .map(|b| unsafe { Ref::into_raw(b) }.handle) + .unwrap_or(std::ptr::null_mut()); + BNLinearDisassemblyLine { + type_: value.ty, + function: function_ptr, + block: block_ptr, + contents: DisassemblyTextLine::into_raw(value.contents), + } } - pub fn line_type(&self) -> LinearDisassemblyLineType { - self.t + pub(crate) fn free_raw(value: BNLinearDisassemblyLine) { + let _ = unsafe { Function::ref_from_raw(value.function) }; + DisassemblyTextLine::free_raw(value.contents); } } impl Deref for LinearDisassemblyLine { type Target = DisassemblyTextLine; fn deref(&self) -> &Self::Target { - self.contents.deref() + &self.contents } } impl std::fmt::Display for LinearDisassemblyLine { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.deref()) + write!(f, "{}", self.contents) } } impl CoreArrayProvider for LinearDisassemblyLine { type Raw = BNLinearDisassemblyLine; type Context = (); - type Wrapped<'a> = Guard<'a, LinearDisassemblyLine>; + type Wrapped<'a> = LinearDisassemblyLine; } unsafe impl CoreArrayProviderInner for LinearDisassemblyLine { @@ -397,8 +494,7 @@ unsafe impl CoreArrayProviderInner for LinearDisassemblyLine { BNFreeLinearDisassemblyLines(raw, count); } - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { - // TODO: Cant remove this guard until we remove those manual drops... INSANE! - Guard::new(Self::from_raw(raw), context) + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::from_raw(raw) } } diff --git a/rust/src/low_level_il.rs b/rust/src/low_level_il.rs index 5df7252b..750bcfd1 100644 --- a/rust/src/low_level_il.rs +++ b/rust/src/low_level_il.rs @@ -24,7 +24,7 @@ use crate::architecture::Register as ArchReg; use crate::architecture::{Architecture, RegisterId}; use crate::function::Location; -mod block; +pub mod block; pub mod expression; pub mod function; pub mod instruction; diff --git a/rust/src/medium_level_il/function.rs b/rust/src/medium_level_il/function.rs index a9802005..8f91c731 100644 --- a/rust/src/medium_level_il/function.rs +++ b/rust/src/medium_level_il/function.rs @@ -548,10 +548,10 @@ impl MediumLevelILFunction { unsafe { BNGetMediumLevelILSSAVarValue(self.handle, &raw_var, ssa_variable.version) }.into() } - pub fn create_graph(&self, settings: Option<DisassemblySettings>) -> FlowGraph { + pub fn create_graph(&self, settings: Option<DisassemblySettings>) -> Ref<FlowGraph> { let settings = settings.map(|x| x.handle).unwrap_or(std::ptr::null_mut()); let graph = unsafe { BNCreateMediumLevelILFunctionGraph(self.handle, settings) }; - unsafe { FlowGraph::from_raw(graph) } + unsafe { FlowGraph::ref_from_raw(graph) } } /// This gets just the MLIL variables - you may be interested in the union diff --git a/rust/src/render_layer.rs b/rust/src/render_layer.rs new file mode 100644 index 00000000..fa617fe7 --- /dev/null +++ b/rust/src/render_layer.rs @@ -0,0 +1,410 @@ +//! Customize the presentation of Linear and Graph view output. + +use crate::basic_block::{BasicBlock, BasicBlockType}; +use crate::disassembly::DisassemblyTextLine; +use crate::flowgraph::FlowGraph; +use crate::function::{Function, NativeBlock}; +use crate::linear_view::{LinearDisassemblyLine, LinearDisassemblyLineType, LinearViewObject}; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner}; +use crate::string::BnStrCompatible; +use binaryninjacore_sys::*; +use std::ffi::{c_char, c_void}; +use std::ptr::NonNull; + +pub type RenderLayerDefaultEnableState = BNRenderLayerDefaultEnableState; + +/// Register a [`RenderLayer`] with the API. +pub fn register_render_layer<S: BnStrCompatible, T: RenderLayer>( + name: S, + render_layer: T, + enable_state: RenderLayerDefaultEnableState, +) -> (&'static mut T, CoreRenderLayer) { + let render_layer = Box::leak(Box::new(render_layer)); + let mut callback = BNRenderLayerCallbacks { + context: render_layer as *mut _ as *mut c_void, + applyToFlowGraph: Some(cb_apply_to_flow_graph::<T>), + applyToLinearViewObject: Some(cb_apply_to_linear_view_object::<T>), + freeLines: Some(cb_free_lines), + }; + let result = unsafe { + BNRegisterRenderLayer( + name.into_bytes_with_nul().as_ref().as_ptr() as *const _, + &mut callback, + enable_state, + ) + }; + let core = CoreRenderLayer::from_raw(NonNull::new(result).unwrap()); + (render_layer, core) +} + +pub trait RenderLayer: Sized { + /// Apply this Render Layer to a Flow Graph. + fn apply_to_flow_graph(&self, graph: &mut FlowGraph) { + for node in graph.nodes() { + if let Some(block) = node.basic_block(NativeBlock::new()) { + let new_lines = self.apply_to_block(&block, node.lines().to_vec()); + node.set_lines(new_lines); + } + } + } + + /// Apply this Render Layer to the lines produced by a LinearViewObject for rendering in Linear View. + fn apply_to_linear_object( + &self, + object: &mut LinearViewObject, + _prev_object: Option<&mut LinearViewObject>, + _next_object: Option<&mut LinearViewObject>, + lines: Vec<LinearDisassemblyLine>, + ) -> Vec<LinearDisassemblyLine> { + let text_to_lines = + |function: &Function, block: &BasicBlock<NativeBlock>, text: DisassemblyTextLine| { + LinearDisassemblyLine { + ty: LinearDisassemblyLineType::CodeDisassemblyLineType, + function: Some(function.to_owned()), + basic_block: Some(block.to_owned()), + contents: text, + } + }; + + // Hack: HLIL bodies don't have basic blocks. + let obj_ident = object.identifier(); + if !lines.is_empty() + && (obj_ident.name.starts_with("HLIL") || obj_ident.name.starts_with("Language")) + { + // Apply to HLIL body. + let function = lines[0] + .function + .to_owned() + .expect("HLIL body has no function"); + return self.apply_to_hlil_body(&function, lines); + } + + // Collect the "line blocks". + // Line blocks are contiguous lines with the same backing basic block (or lack thereof). + // Line blocks also group by line type. + let mut line_blocks: Vec<Vec<LinearDisassemblyLine>> = Vec::new(); + for line in lines { + let Some(last_block) = line_blocks.last_mut() else { + // No last block, create the first block. + line_blocks.push(vec![line]); + continue; + }; + + let Some(last_line) = last_block.last() else { + // No last line, create the first line. + last_block.push(line); + continue; + }; + + // TODO: If we want to allow a block with multiple line types we need to specifically check + // TODO: If the last line type was Code, if it is and the last line is not we make a new block. + if last_line.basic_block == line.basic_block && last_line.ty == line.ty { + // Same basic block and line type, this is a part of the same line block. + last_block.push(line); + } else { + // Not the same line block, create a new block. + line_blocks.push(vec![line]); + } + } + + line_blocks + .into_iter() + .filter_map(|line_block| { + let probe_line = line_block.first()?; + Some((probe_line.ty, probe_line.basic_block.to_owned(), line_block)) + }) + .map(|(line_ty, basic_block, lines)| { + match line_ty { + LinearDisassemblyLineType::CodeDisassemblyLineType => { + // Dealing with code lines. + let block = basic_block.expect("Code line has no basic block"); + let function = block.function(); + let text_lines = lines.into_iter().map(|line| line.contents).collect(); + let new_text_lines = self.apply_to_block(&block, text_lines); + let new_lines = new_text_lines + .into_iter() + .map(|line| text_to_lines(&function, &block, line)) + .collect(); + new_lines + } + _ => { + // Dealing with misc lines. + self.apply_to_misc_lines( + object, + _prev_object.as_deref(), + _next_object.as_deref(), + lines, + ) + } + } + }) + .flatten() + .collect() + } + + /// Apply this Render Layer to a single Basic Block of Disassembly lines. + /// + /// Modify the lines to change the presentation of the block. + fn apply_to_disassembly_block( + &self, + _block: &BasicBlock<NativeBlock>, + lines: Vec<DisassemblyTextLine>, + ) -> Vec<DisassemblyTextLine> { + lines + } + + /// Apply this Render Layer to a single Basic Block of Low Level IL lines. + /// + /// Modify the lines to change the presentation of the block. + fn apply_to_llil_block( + &self, + _block: &BasicBlock<NativeBlock>, + lines: Vec<DisassemblyTextLine>, + ) -> Vec<DisassemblyTextLine> { + lines + } + + /// Apply this Render Layer to a single Basic Block of Medium Level IL lines. + /// + /// Modify the lines to change the presentation of the block. + fn apply_to_mlil_block( + &self, + _block: &BasicBlock<NativeBlock>, + lines: Vec<DisassemblyTextLine>, + ) -> Vec<DisassemblyTextLine> { + lines + } + + /// Apply this Render Layer to a single Basic Block of High Level IL lines. + /// + /// Modify the lines to change the presentation of the block. + /// + /// This function will NOT apply to High Level IL bodies as displayed in Linear View! + /// Those are handled by [`RenderLayer::apply_to_hlil_body`] instead as they do not + /// have a [`BasicBlock`] associated with them. + fn apply_to_hlil_block( + &self, + _block: &BasicBlock<NativeBlock>, + lines: Vec<DisassemblyTextLine>, + ) -> Vec<DisassemblyTextLine> { + lines + } + + /// Apply this Render Layer to the entire body of a High Level IL function. + /// + /// Modify the lines to change the presentation of the block. + /// + /// This function only applies to Linear View, and not to Graph View! If you want to + /// handle Graph View too, you will need to use [`RenderLayer::apply_to_hlil_block`] and handle + /// the lines one block at a time. + fn apply_to_hlil_body( + &self, + _function: &Function, + lines: Vec<LinearDisassemblyLine>, + ) -> Vec<LinearDisassemblyLine> { + lines + } + + // TODO: We might want to just go ahead and pass the line type. + /// Apply to lines generated by Linear View that are not part of a function. + /// + /// Modify the lines to change the presentation of the block. + fn apply_to_misc_lines( + &self, + _object: &mut LinearViewObject, + _prev_object: Option<&LinearViewObject>, + _next_object: Option<&LinearViewObject>, + lines: Vec<LinearDisassemblyLine>, + ) -> Vec<LinearDisassemblyLine> { + lines + } + + /// Apply this Render Layer to all IL blocks and disassembly blocks. + /// + /// If not implemented this will handle calling the view specific apply functions: + /// + /// - [`RenderLayer::apply_to_disassembly_block`] + /// - [`RenderLayer::apply_to_llil_block`] + /// - [`RenderLayer::apply_to_mlil_block`] + /// - [`RenderLayer::apply_to_hlil_block`] + /// + /// Modify the lines to change the presentation of the block. + fn apply_to_block( + &self, + block: &BasicBlock<NativeBlock>, + lines: Vec<DisassemblyTextLine>, + ) -> Vec<DisassemblyTextLine> { + match block.block_type() { + BasicBlockType::Native => self.apply_to_disassembly_block(block, lines), + BasicBlockType::LowLevelIL => self.apply_to_llil_block(block, lines), + BasicBlockType::MediumLevelIL => self.apply_to_mlil_block(block, lines), + BasicBlockType::HighLevelIL => self.apply_to_hlil_block(block, lines), + } + } +} + +#[repr(transparent)] +pub struct CoreRenderLayer { + pub(crate) handle: NonNull<BNRenderLayer>, +} + +impl CoreRenderLayer { + pub fn from_raw(handle: NonNull<BNRenderLayer>) -> Self { + Self { handle } + } + + pub fn render_layers() -> Array<CoreRenderLayer> { + let mut count = 0; + let result = unsafe { BNGetRenderLayerList(&mut count) }; + unsafe { Array::new(result, count, ()) } + } + + pub fn render_layer_by_name<S: BnStrCompatible>(name: S) -> Option<CoreRenderLayer> { + let name_raw = name.into_bytes_with_nul(); + let result = unsafe { BNGetRenderLayerByName(name_raw.as_ref().as_ptr() as *const c_char) }; + NonNull::new(result).map(|x| Self::from_raw(x)) + } + + pub fn default_enable_state(&self) -> RenderLayerDefaultEnableState { + unsafe { BNGetRenderLayerDefaultEnableState(self.handle.as_ptr()) } + } + + pub fn apply_to_flow_graph(&self, graph: &FlowGraph) { + unsafe { BNApplyRenderLayerToFlowGraph(self.handle.as_ptr(), graph.handle) } + } + + pub fn apply_to_linear_view_object( + &self, + object: &LinearViewObject, + prev_object: Option<&LinearViewObject>, + next_object: Option<&LinearViewObject>, + lines: Vec<LinearDisassemblyLine>, + ) -> Vec<LinearDisassemblyLine> { + let mut lines_raw: Vec<_> = lines + .into_iter() + // NOTE: Freed after the core call + .map(LinearDisassemblyLine::into_raw) + .collect(); + + let prev_object_ptr = prev_object + .map(|o| o.handle) + .unwrap_or(std::ptr::null_mut()); + let next_object_ptr = next_object + .map(|o| o.handle) + .unwrap_or(std::ptr::null_mut()); + + let mut new_lines = std::ptr::null_mut(); + let mut new_line_count = 0; + + unsafe { + BNApplyRenderLayerToLinearViewObject( + self.handle.as_ptr(), + object.handle, + prev_object_ptr, + next_object_ptr, + lines_raw.as_mut_ptr(), + lines_raw.len(), + &mut new_lines, + &mut new_line_count, + ) + }; + + for line in lines_raw { + LinearDisassemblyLine::free_raw(line); + } + + let raw: Array<LinearDisassemblyLine> = + unsafe { Array::new(new_lines, new_line_count, ()) }; + raw.to_vec() + } +} + +impl CoreArrayProvider for CoreRenderLayer { + type Raw = *mut BNRenderLayer; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for CoreRenderLayer { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeRenderLayerList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + // TODO: Because handle is a NonNull we should prob make Self::Raw that as well... + let handle = NonNull::new(*raw).unwrap(); + CoreRenderLayer::from_raw(handle) + } +} + +unsafe extern "C" fn cb_apply_to_flow_graph<T: RenderLayer>( + ctxt: *mut c_void, + graph: *mut BNFlowGraph, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + // SAFETY: We do not own the flowgraph, do not take it as Ref. + let mut flow_graph = FlowGraph::from_raw(graph); + ctxt.apply_to_flow_graph(&mut flow_graph); +} + +unsafe extern "C" fn cb_apply_to_linear_view_object<T: RenderLayer>( + ctxt: *mut c_void, + object: *mut BNLinearViewObject, + prev: *mut BNLinearViewObject, + next: *mut BNLinearViewObject, + in_lines: *mut BNLinearDisassemblyLine, + in_line_count: usize, + out_lines: *mut *mut BNLinearDisassemblyLine, + out_line_count: *mut usize, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + // SAFETY: We do not own the flowgraph, do not take it as Ref. + let mut object = LinearViewObject::from_raw(object); + let mut prev_object = if !prev.is_null() { + Some(LinearViewObject::from_raw(prev)) + } else { + None + }; + let mut next_object = if !next.is_null() { + Some(LinearViewObject::from_raw(next)) + } else { + None + }; + + let raw_lines = std::slice::from_raw_parts(in_lines, in_line_count); + // NOTE: The caller is owned of the inLines. + let lines: Vec<_> = raw_lines + .iter() + .map(|line| LinearDisassemblyLine::from_raw(line)) + .collect(); + + let new_lines = ctxt.apply_to_linear_object( + &mut object, + prev_object.as_mut(), + next_object.as_mut(), + lines, + ); + + unsafe { + *out_line_count = new_lines.len(); + let boxed_new_lines: Box<[_]> = new_lines + .into_iter() + // NOTE: Freed by cb_free_lines + .map(LinearDisassemblyLine::into_raw) + .collect(); + // NOTE: Dropped by cb_free_lines + *out_lines = Box::leak(boxed_new_lines).as_mut_ptr(); + } +} + +unsafe extern "C" fn cb_free_lines( + _ctxt: *mut c_void, + lines: *mut BNLinearDisassemblyLine, + line_count: usize, +) { + let lines_ptr = std::ptr::slice_from_raw_parts_mut(lines, line_count); + let boxed_lines = Box::from_raw(lines_ptr); + for line in boxed_lines { + LinearDisassemblyLine::free_raw(line); + } +} diff --git a/rust/src/workflow.rs b/rust/src/workflow.rs index 68cd3e18..e95beb48 100644 --- a/rust/src/workflow.rs +++ b/rust/src/workflow.rs @@ -565,7 +565,7 @@ impl Workflow { &self, activity: A, sequential: Option<bool>, - ) -> Option<FlowGraph> { + ) -> Option<Ref<FlowGraph>> { let sequential = sequential.unwrap_or(false); let activity_name = activity.into_bytes_with_nul(); let graph = unsafe { @@ -578,7 +578,7 @@ impl Workflow { if graph.is_null() { return None; } - Some(unsafe { FlowGraph::from_raw(graph) }) + Some(unsafe { FlowGraph::ref_from_raw(graph) }) } /// Not yet implemented. diff --git a/rust/tests/render_layer.rs b/rust/tests/render_layer.rs new file mode 100644 index 00000000..6c914564 --- /dev/null +++ b/rust/tests/render_layer.rs @@ -0,0 +1,96 @@ +use binaryninja::basic_block::BasicBlock; +use binaryninja::disassembly::{DisassemblyOption, DisassemblySettings, DisassemblyTextLine}; +use binaryninja::function::NativeBlock; +use binaryninja::headless::Session; +use binaryninja::linear_view::LinearViewObject; +use binaryninja::render_layer::{register_render_layer, CoreRenderLayer, RenderLayer}; +use rstest::{fixture, rstest}; +use std::path::PathBuf; + +#[fixture] +#[once] +fn session() -> Session { + Session::new().expect("Failed to initialize session") +} + +#[rstest] +fn test_render_layer_register(_session: &Session) { + struct EmptyRenderLayer; + impl RenderLayer for EmptyRenderLayer {} + register_render_layer("Test Render Layer", EmptyRenderLayer); + CoreRenderLayer::render_layer_by_name("Test Render Layer").expect("Failed to get render layer"); +} + +#[rstest] +fn test_render_layer_linear_view(_session: &Session) { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + + struct NopRenderLayer; + impl RenderLayer for NopRenderLayer { + fn apply_to_disassembly_block( + &self, + _block: &BasicBlock<NativeBlock>, + lines: Vec<DisassemblyTextLine>, + ) -> Vec<DisassemblyTextLine> { + println!("Nothing added to disassembly block"); + lines + } + } + let (_, nop_render_layer) = register_render_layer("Nop Render Layer", NopRenderLayer); + + // Create linear view object stuff + let settings = DisassemblySettings::new(); + settings.set_option(DisassemblyOption::ShowAddress, false); + settings.set_option(DisassemblyOption::WaitForIL, true); + settings.set_option(DisassemblyOption::IndentHLILBody, false); + settings.set_option(DisassemblyOption::ShowCollapseIndicators, false); + settings.set_option(DisassemblyOption::ShowFunctionHeader, false); + + let linear_view = LinearViewObject::disassembly(&view, &settings); + let mut cursor = linear_view.create_cursor(); + // Seek to the start of the function `__crt_strtox::is_overflow_condition<uint64_t>` + cursor.seek_to_address(0x26240); + let current_object = cursor.current_object(); + let current_lines = cursor.lines().to_vec(); + + let new_lines = nop_render_layer.apply_to_linear_view_object( + ¤t_object, + None, + None, + current_lines.clone(), + ); + + // These should 100% be in the same order. If not that is a bug. + + for (i, (current_line, new_line)) in current_lines.iter().zip(new_lines.iter()).enumerate() { + if current_line != new_line { + assert_eq!(current_line, new_line, "Line mismatch at index {}", i); + } + } + + struct AddRenderLayer; + impl RenderLayer for AddRenderLayer { + fn apply_to_disassembly_block( + &self, + _block: &BasicBlock<NativeBlock>, + mut lines: Vec<DisassemblyTextLine>, + ) -> Vec<DisassemblyTextLine> { + println!("Adding to disassembly block"); + lines.push(DisassemblyTextLine::from("heyyyyy")); + lines + } + } + let (_, adding_render_layer) = register_render_layer("Add Render Layer", AddRenderLayer); + + // Calling lines() again should now have the render layer applied. + cursor.add_render_layer(&adding_render_layer); + let new_current_lines = cursor.lines().to_vec(); + // Assert that new_current_lines is one longer than current_lines (we added a line) + assert_eq!(new_current_lines.len(), current_lines.len() + 1); + + // Remove the render layer and make sure that the line is no longer present. + cursor.remove_render_layer(&adding_render_layer); + let new_new_current_lines = cursor.lines().to_vec(); + assert_eq!(new_new_current_lines.len(), current_lines.len()); +} diff --git a/ui/disassemblyview.h b/ui/disassemblyview.h index c7595f96..7c97576d 100644 --- a/ui/disassemblyview.h +++ b/ui/disassemblyview.h @@ -81,6 +81,9 @@ class BINARYNINJAUIAPI DisassemblyView : public FlowGraphWidget void setDisplayedFileName(); void setAddressBaseOffset(bool toHere); + void toggleRenderLayer(const std::string& layer); + FlowGraphRef applyRenderLayers(FlowGraphRef graph); + virtual DisassemblySettingsRef getDisassemblySettings() override; virtual void setDisassemblySettings(DisassemblySettingsRef settings) override; @@ -89,6 +92,7 @@ class BINARYNINJAUIAPI DisassemblyView : public FlowGraphWidget virtual void onHighlightChanged(const HighlightTokenState& highlight) override; static void registerActions(); + virtual void bindDynamicActions() override; private: class DisassemblyViewOptionsWidget : public MenuHelper @@ -138,6 +142,7 @@ class BINARYNINJAUIAPI DisassemblyView : public FlowGraphWidget BNDisassemblyCallParameterHints m_callParamHints; DisassemblyContainer* m_container; SettingsRef m_settings; + std::set<std::string> m_layers; private Q_SLOTS: void viewInHexEditor(); diff --git a/ui/flowgraphwidget.h b/ui/flowgraphwidget.h index 858b8126..3ef1b111 100644 --- a/ui/flowgraphwidget.h +++ b/ui/flowgraphwidget.h @@ -182,7 +182,7 @@ class BINARYNINJAUIAPI FlowGraphWidget : virtual void contextMenuEvent(QContextMenuEvent*) override; void bindActions(); - void bindDynamicActions(); + virtual void bindDynamicActions(); void navigateToAddress(uint64_t addr); void navigateToGotoLabel(uint64_t label); diff --git a/ui/linearview.h b/ui/linearview.h index 888425e7..d328fd7c 100644 --- a/ui/linearview.h +++ b/ui/linearview.h @@ -242,6 +242,8 @@ class BINARYNINJAUIAPI LinearView : public QAbstractScrollArea, public View, pub QWidget* m_dataButtonContainer = nullptr; QHBoxLayout* m_dataButtonLayout = nullptr; + std::set<std::string> m_layers; + void setTopToAddress(uint64_t addr); void setTopToOrderingIndex(uint64_t idx); void refreshLines(size_t lineOffset = 0, bool refreshUIContext = true); @@ -499,6 +501,9 @@ public: void setDisplayedFileName(); void setAddressBaseOffset(bool toHere); + void toggleRenderLayer(const std::string& layer); + BinaryNinja::Ref<BinaryNinja::LinearViewCursor> applyRenderLayers(BinaryNinja::Ref<BinaryNinja::LinearViewCursor> cursor); + virtual bool goToReference(FunctionRef func, uint64_t source, uint64_t target) override; QFont getFont() override { return m_render.getFont(); } |
