diff options
| author | Rusty Wagner <rusty@vector35.com> | 2018-06-04 14:12:26 -0400 |
|---|---|---|
| committer | Rusty Wagner <rusty@vector35.com> | 2018-06-04 14:12:26 -0400 |
| commit | bfa6fce83383e7be1458a917f8e6dbf71bdab28b (patch) | |
| tree | 58f7d373f6298bbbac70911cd661627b316c011b /python | |
| parent | c09ae6ce7bd652dbb6e1a4f73d5a8a51440ec1ff (diff) | |
Generic flow graph API and report collections
Diffstat (limited to 'python')
| -rw-r--r-- | python/__init__.py | 1 | ||||
| -rw-r--r-- | python/basicblock.py | 12 | ||||
| -rw-r--r-- | python/binaryview.py | 7 | ||||
| -rw-r--r-- | python/flowgraph.py | 465 | ||||
| -rw-r--r-- | python/function.py | 400 | ||||
| -rw-r--r-- | python/highlight.py | 10 | ||||
| -rw-r--r-- | python/interaction.py | 172 |
7 files changed, 685 insertions, 382 deletions
diff --git a/python/__init__.py b/python/__init__.py index 729e4f8a..05ba4c2b 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -53,6 +53,7 @@ from .scriptingprovider import * from .pluginmanager import * from .setting import * from .metadata import * +from .flowgraph import * def shutdown(): diff --git a/python/basicblock.py b/python/basicblock.py index c55e15f0..3f615bba 100644 --- a/python/basicblock.py +++ b/python/basicblock.py @@ -243,14 +243,7 @@ class BasicBlock(object): >>> current_basic_block.highlight <color: blue> """ - color = core.BNGetBasicBlockHighlight(self.handle) - if color.style == HighlightColorStyle.StandardHighlightColor: - return highlight.HighlightColor(color=color.color, alpha=color.alpha) - elif color.style == HighlightColorStyle.MixedHighlightColor: - return highlight.HighlightColor(color=color.color, mix_color=color.mixColor, mix=color.mix, alpha=color.alpha) - elif color.style == HighlightColorStyle.CustomHighlightColor: - return highlight.HighlightColor(red=color.r, green=color.g, blue=color.b, alpha=color.alpha) - return highlight.HighlightColor(color=HighlightStandardColor.NoHighlightColor) + return highlight.HighlightColor._from_core_struct(core.BNGetBasicBlockHighlight(self.handle)) @highlight.setter def highlight(self, value): @@ -341,6 +334,7 @@ class BasicBlock(object): il_instr = self.il_function[lines[i].instrIndex] else: il_instr = None + color = highlight.HighlightColor._from_core_struct(lines[i].highlight) tokens = [] for j in xrange(0, lines[i].count): token_type = InstructionTextTokenType(lines[i].tokens[j].type) @@ -352,7 +346,7 @@ class BasicBlock(object): confidence = lines[i].tokens[j].confidence address = lines[i].tokens[j].address tokens.append(function.InstructionTextToken(token_type, text, value, size, operand, context, address, confidence)) - result.append(function.DisassemblyTextLine(addr, tokens, il_instr)) + result.append(function.DisassemblyTextLine(tokens, addr, il_instr, color)) core.BNFreeDisassemblyTextLines(lines, count.value) return result diff --git a/python/binaryview.py b/python/binaryview.py index 0cf25e70..ede279ad 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -41,6 +41,7 @@ import basicblock import types import lineardisassembly import metadata +import highlight class BinaryDataNotification(object): @@ -2961,6 +2962,7 @@ class BinaryView(object): func = function.Function(self, core.BNNewFunctionReference(lines[i].function)) if lines[i].block: block = basicblock.BasicBlock(self, core.BNNewBasicBlockReference(lines[i].block)) + color = highlight.HighlightColor._from_core_struct(lines[i].contents.highlight) addr = lines[i].contents.addr tokens = [] for j in xrange(0, lines[i].contents.count): @@ -2973,7 +2975,7 @@ class BinaryView(object): confidence = lines[i].contents.tokens[j].confidence address = lines[i].contents.tokens[j].address tokens.append(function.InstructionTextToken(token_type, text, value, size, operand, context, address, confidence)) - contents = function.DisassemblyTextLine(addr, tokens) + contents = function.DisassemblyTextLine(tokens, addr, color = color) result.append(lineardisassembly.LinearDisassemblyLine(lines[i].type, func, block, lines[i].lineOffset, contents)) func = None @@ -3352,6 +3354,9 @@ class BinaryView(object): def show_html_report(self, title, contents, plaintext = ""): core.BNShowHTMLReport(self.handle, title, contents, plaintext) + def show_graph_report(self, title, graph): + core.BNShowHTMLReport(self.handle, title, graph.handle) + def get_address_input(self, prompt, title, current_address = None): if current_address is None: current_address = self.file.offset diff --git a/python/flowgraph.py b/python/flowgraph.py new file mode 100644 index 00000000..1568bc77 --- /dev/null +++ b/python/flowgraph.py @@ -0,0 +1,465 @@ +# Copyright (c) 2018 Vector 35 LLC +# +# 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 threading +import traceback + +# Binary Ninja components +import _binaryninjacore as core +from enums import (BranchType, InstructionTextTokenType, HighlightColorStyle, HighlightStandardColor) +import function +import binaryview +import lowlevelil +import mediumlevelil +import basicblock +import architecture +import log +import interaction +import highlight + + +class FlowGraphEdge(object): + def __init__(self, branch_type, source, target, points, back_edge): + self.type = BranchType(branch_type) + self.source = source + self.target = target + self.points = points + self.back_edge = back_edge + + def __repr__(self): + return "<%s: %s>" % (self.type.name, repr(self.target)) + + +class FlowGraphNode(object): + def __init__(self, graph, handle = None): + if handle is None: + handle = core.BNCreateFlowGraphNode(graph.handle) + self.handle = handle + self.graph = graph + + def __del__(self): + core.BNFreeFlowGraphNode(self.handle) + + def __eq__(self, value): + if not isinstance(value, FlowGraphNode): + return False + return ctypes.addressof(self.handle.contents) == ctypes.addressof(value.handle.contents) + + def __ne__(self, value): + if not isinstance(value, FlowGraphNode): + return True + return ctypes.addressof(self.handle.contents) != ctypes.addressof(value.handle.contents) + + @property + def basic_block(self): + """Basic block associated with this part of the flow graph (read-only)""" + 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(view, block, + lowlevelil.LowLevelILFunction(func.arch, core.BNGetBasicBlockLowLevelILFunction(block), func)) + elif core.BNIsMediumLevelILBasicBlock(block): + block = mediumlevelil.MediumLevelILBasicBlock(view, block, + mediumlevelil.MediumLevelILFunction(func.arch, core.BNGetBasicBlockMediumLevelILFunction(block), func)) + else: + block = basicblock.BasicBlock(view, block) + return block + + @property + def x(self): + """Flow graph block X (read-only)""" + return core.BNGetFlowGraphNodeX(self.handle) + + @property + def y(self): + """Flow graph block Y (read-only)""" + return core.BNGetFlowGraphNodeY(self.handle) + + @property + def width(self): + """Flow graph block width (read-only)""" + return core.BNGetFlowGraphNodeWidth(self.handle) + + @property + def height(self): + """Flow graph block height (read-only)""" + return core.BNGetFlowGraphNodeHeight(self.handle) + + @property + def lines(self): + """Flow graph block list of lines""" + count = ctypes.c_ulonglong() + lines = core.BNGetFlowGraphNodeLines(self.handle, count) + block = self.basic_block + result = [] + for i in xrange(0, count.value): + addr = lines[i].addr + if (lines[i].instrIndex != 0xffffffffffffffff) and (block is not None) and hasattr(block, 'il_function'): + il_instr = block.il_function[lines[i].instrIndex] + else: + il_instr = None + color = highlight.HighlightColor._from_core_struct(lines[i].highlight) + tokens = [] + for j in xrange(0, lines[i].count): + token_type = InstructionTextTokenType(lines[i].tokens[j].type) + text = lines[i].tokens[j].text + value = lines[i].tokens[j].value + size = lines[i].tokens[j].size + operand = lines[i].tokens[j].operand + context = lines[i].tokens[j].context + confidence = lines[i].tokens[j].confidence + address = lines[i].tokens[j].address + tokens.append(function.InstructionTextToken(token_type, text, value, size, operand, context, address, confidence)) + result.append(function.DisassemblyTextLine(tokens, addr, il_instr, color)) + core.BNFreeDisassemblyTextLines(lines, count.value) + return result + + @lines.setter + def lines(self, lines): + if isinstance(lines, str): + lines = lines.split('\n') + line_buf = (core.BNDisassemblyTextLine * len(lines))() + for i in xrange(0, len(lines)): + line = lines[i] + if isinstance(line, str): + line = function.DisassemblyTextLine([function.InstructionTextToken(InstructionTextTokenType.TextToken, line)]) + if not isinstance(line, function.DisassemblyTextLine): + line = function.DisassemblyTextLine(line) + if line.address is None: + if len(line.tokens) > 0: + line_buf[i].addr = line.tokens[0].address + else: + line_buf[i].addr = 0 + else: + line_buf[i].addr = line.address + if line.il_instruction is not None: + line_buf[i].instrIndex = line.il_instruction.instr_index + else: + line_buf[i].instrIndex = 0xffffffffffffffff + color = line.highlight + if not isinstance(color, HighlightStandardColor) and not isinstance(color, highlight.HighlightColor): + raise ValueError("Specified color is not one of HighlightStandardColor, highlight.HighlightColor") + if isinstance(color, HighlightStandardColor): + color = highlight.HighlightColor(color) + line_buf[i].highlight = color._get_core_struct() + line_buf[i].count = len(line.tokens) + line_buf[i].tokens = (core.BNInstructionTextToken * len(line.tokens))() + for j in xrange(0, len(line.tokens)): + line_buf[i].tokens[j].type = line.tokens[j].type + line_buf[i].tokens[j].text = line.tokens[j].text + line_buf[i].tokens[j].value = line.tokens[j].value + line_buf[i].tokens[j].size = line.tokens[j].size + line_buf[i].tokens[j].operand = line.tokens[j].operand + line_buf[i].tokens[j].context = line.tokens[j].context + line_buf[i].tokens[j].confidence = line.tokens[j].confidence + line_buf[i].tokens[j].address = line.tokens[j].address + core.BNSetFlowGraphNodeLines(self.handle, line_buf, len(lines)) + + @property + def outgoing_edges(self): + """Flow graph block list of outgoing edges (read-only)""" + count = ctypes.c_ulonglong() + edges = core.BNGetFlowGraphNodeOutgoingEdges(self.handle, count) + result = [] + for i in xrange(0, count.value): + branch_type = BranchType(edges[i].type) + target = edges[i].target + if target: + target = FlowGraphNode(self.graph, core.BNNewFlowGraphNodeReference(target)) + points = [] + for j in xrange(0, edges[i].pointCount): + points.append((edges[i].points[j].x, edges[i].points[j].y)) + result.append(FlowGraphEdge(branch_type, self, target, points, edges[i].backEdge)) + core.BNFreeFlowGraphNodeOutgoingEdgeList(edges, count.value) + return result + + @property + def highlight(self): + """Gets or sets the highlight color for the node + + :Example: + >>> g = FlowGraph() + >>> node = FlowGraphNode(g) + >>> node.highlight = HighlightStandardColor.BlueHighlightColor + >>> node.highlight + <color: blue> + """ + return highlight.HighlightColor._from_core_struct(core.BNGetFlowGraphNodeHighlight(self.handle)) + + @highlight.setter + def highlight(self, color): + if not isinstance(color, HighlightStandardColor) and not isinstance(color, highlight.HighlightColor): + raise ValueError("Specified color is not one of HighlightStandardColor, highlight.HighlightColor") + if isinstance(color, HighlightStandardColor): + color = highlight.HighlightColor(color) + core.BNSetFlowGraphNodeHighlight(self.handle, color._get_core_struct()) + + def __repr__(self): + block = self.basic_block + if block: + arch = block.arch + if arch: + return "<graph node: %s@%#x-%#x>" % (arch.name, block.start, block.end) + else: + return "<graph node: %#x-%#x>" % (block.start, block.end) + return "<graph node>" + + def __iter__(self): + count = ctypes.c_ulonglong() + lines = core.BNGetFlowGraphNodeLines(self.handle, count) + block = self.basic_block + try: + for i in xrange(0, count.value): + addr = lines[i].addr + if (lines[i].instrIndex != 0xffffffffffffffff) and (block is not None) and hasattr(block, 'il_function'): + il_instr = block.il_function[lines[i].instrIndex] + else: + il_instr = None + tokens = [] + for j in xrange(0, lines[i].count): + token_type = InstructionTextTokenType(lines[i].tokens[j].type) + text = lines[i].tokens[j].text + value = lines[i].tokens[j].value + size = lines[i].tokens[j].size + operand = lines[i].tokens[j].operand + context = lines[i].tokens[j].context + confidence = lines[i].tokens[j].confidence + address = lines[i].tokens[j].address + tokens.append(function.InstructionTextToken(token_type, text, value, size, operand, context, address, confidence)) + yield function.DisassemblyTextLine(tokens, addr, il_instr) + finally: + core.BNFreeDisassemblyTextLines(lines, count.value) + + def add_outgoing_edge(self, edge_type, target): + core.BNAddFlowGraphNodeOutgoingEdge(self.handle, edge_type, target.handle) + + +class FlowGraph(object): + def __init__(self, handle = None): + if handle is None: + handle = core.BNCreateFlowGraph() + self.handle = handle + self._on_complete = None + self._cb = ctypes.CFUNCTYPE(None, ctypes.c_void_p)(self._complete) + + def __del__(self): + self.abort() + core.BNFreeFlowGraph(self.handle) + + def __eq__(self, value): + if not isinstance(value, FlowGraph): + return False + return ctypes.addressof(self.handle.contents) == ctypes.addressof(value.handle.contents) + + def __ne__(self, value): + if not isinstance(value, FlowGraph): + return True + return ctypes.addressof(self.handle.contents) != ctypes.addressof(value.handle.contents) + + @property + def function(self): + """Function for a flow graph""" + func = core.BNGetFunctionForFlowGraph(self.handle) + if func is None: + return None + return function.Function(binaryview.BinaryView(handle = core.BNGetFunctionData(func)), func) + + @function.setter + def function(self, func): + if func is not None: + func = func.handle + core.BNSetFunctionForFlowGraph(self.handle, func) + + @property + def complete(self): + """Whether flow graph layout is complete (read-only)""" + return core.BNIsFlowGraphLayoutComplete(self.handle) + + @property + def nodes(self): + """List of nodes in graph (read-only)""" + count = ctypes.c_ulonglong() + blocks = core.BNGetFlowGraphNodes(self.handle, count) + result = [] + for i in xrange(0, count.value): + result.append(FlowGraphNode(self, core.BNNewFlowGraphNodeReference(blocks[i]))) + core.BNFreeFlowGraphNodeList(blocks, count.value) + return result + + @property + def has_nodes(self): + """Whether the flow graph has at least one node (read-only)""" + return core.BNFlowGraphHasNodes(self.handle) + + @property + def width(self): + """Flow graph width (read-only)""" + return core.BNGetFlowGraphWidth(self.handle) + + @property + def height(self): + """Flow graph height (read-only)""" + return core.BNGetFlowGraphHeight(self.handle) + + @property + def horizontal_block_margin(self): + return core.BNGetHorizontalFlowGraphBlockMargin(self.handle) + + @horizontal_block_margin.setter + def horizontal_block_margin(self, value): + core.BNSetFlowGraphBlockMargins(self.handle, value, self.vertical_block_margin) + + @property + def vertical_block_margin(self): + return core.BNGetVerticalFlowGraphBlockMargin(self.handle) + + @vertical_block_margin.setter + def vertical_block_margin(self, value): + core.BNSetFlowGraphBlockMargins(self.handle, self.horizontal_block_margin, value) + + @property + def is_il(self): + return core.BNIsILFlowGraph(self.handle) + + @property + def is_low_level_il(self): + return core.BNIsLowLevelILFlowGraph(self.handle) + + @property + def is_medium_level_il(self): + return core.BNIsMediumLevelILFlowGraph(self.handle) + + @property + def il_function(self): + if self.is_low_level_il: + il_func = core.BNGetFlowGraphLowLevelILFunction(self.handle) + if not il_func: + return None + function = self.function + if function is None: + return None + return lowlevelil.LowLevelILFunction(function.arch, il_func, function) + if self.is_medium_level_il: + il_func = core.BNGetFlowGraphMediumLevelILFunction(self.handle) + if not il_func: + return None + function = self.function + if function is None: + return None + return mediumlevelil.MediumLevelILFunction(function.arch, il_func, function) + return None + + @il_function.setter + def il_function(self, func): + if isinstance(func, lowlevelil.LowLevelILFunction): + core.BNSetFlowGraphLowLevelILFunction(self.handle, func.handle) + core.BNSetFlowGraphMediumLevelILFunction(self.handle, None) + elif isinstance(func, mediumlevelil.MediumLevelILFunction): + core.BNSetFlowGraphLowLevelILFunction(self.handle, None) + core.BNSetFlowGraphMediumLevelILFunction(self.handle, func.handle) + elif func is None: + core.BNSetFlowGraphLowLevelILFunction(self.handle, None) + core.BNSetFlowGraphMediumLevelILFunction(self.handle, None) + else: + raise TypeError("expected IL function for setting il_function property") + + def __setattr__(self, name, value): + try: + object.__setattr__(self, name, value) + except AttributeError: + raise AttributeError("attribute '%s' is read only" % name) + + def __repr__(self): + function = self.function + if function is None: + return "<flow graph>" + return "<graph of %s>" % repr(function) + + def __iter__(self): + count = ctypes.c_ulonglong() + nodes = core.BNGetFlowGraphNodes(self.handle, count) + try: + for i in xrange(0, count.value): + yield FlowGraphNode(self, core.BNNewFlowGraphNodeReference(nodes[i])) + finally: + core.BNFreeFlowGraphNodeList(nodes, count.value) + + def _complete(self, ctxt): + try: + if self._on_complete is not None: + self._on_complete() + except: + log.log_error(traceback.format_exc()) + + def layout(self): + core.BNStartFlowGraphLayout(self.handle) + + def _wait_complete(self): + self._wait_cond.acquire() + self._wait_cond.notify() + self._wait_cond.release() + + def layout_and_wait(self): + self._wait_cond = threading.Condition() + self.on_complete(self._wait_complete) + self.layout() + + self._wait_cond.acquire() + while not self.complete: + self._wait_cond.wait() + self._wait_cond.release() + + def on_complete(self, callback): + self._on_complete = callback + core.BNSetFlowGraphCompleteCallback(self.handle, None, self._cb) + + def abort(self): + core.BNAbortFlowGraph(self.handle) + + def get_nodes_in_region(self, left, top, right, bottom): + count = ctypes.c_ulonglong() + nodes = core.BNGetFlowGraphNodesInRegion(self.handle, left, top, right, bottom, count) + result = [] + for i in xrange(0, count.value): + result.append(FlowGraphNode(self, core.BNNewFlowGraphNodeReference(nodes[i]))) + core.BNFreeFlowGraphNodeList(nodes, count.value) + return result + + def append(self, node): + return core.BNAddFlowGraphNode(self.handle, node.handle) + + def __getitem__(self, i): + node = core.BNGetFlowGraphNode(self.handle, i) + if node is None: + return None + return FlowGraphNode(self, node) + + def show(self, title): + interaction.show_graph_report(title, self) diff --git a/python/function.py b/python/function.py index 6db44e6d..dca9dda6 100644 --- a/python/function.py +++ b/python/function.py @@ -39,6 +39,7 @@ import mediumlevelil import binaryview import log import callingconvention +import flowgraph class LookupTableEntry(object): @@ -844,6 +845,14 @@ class Function(object): def analysis_skip_override(self, override): core.BNSetFunctionAnalysisSkipOverride(self.handle, override) + @property + def unresolved_stack_adjustment_graph(self): + """Flow graph of unresolved stack adjustments (read-only)""" + graph = core.BNGetUnresolvedStackAdjustmentGraph(self.handle) + if not graph: + return None + return flowgraph.FlowGraph(graph) + def __iter__(self): count = ctypes.c_ulonglong() blocks = core.BNGetFunctionBasicBlockList(self.handle, count) @@ -1108,8 +1117,12 @@ class Function(object): core.BNFreeRegisterList(flags) return result - def create_graph(self): - return FunctionGraph(self._view, core.BNCreateFunctionGraph(self.handle)) + def create_graph(self, graph_type = FunctionGraphType.NormalFunctionGraph, settings = None): + if settings is not None: + settings_obj = settings.handle + else: + settings_obj = None + return flowgraph.FlowGraph(core.BNCreateFunctionGraph(self.handle, graph_type, settings_obj)) def apply_imported_types(self, sym): core.BNApplyImportedTypes(self.handle, sym.handle) @@ -1461,6 +1474,7 @@ class Function(object): result = [] for i in xrange(0, count.value): addr = lines[i].addr + color = highlight.HighlightColor._from_core_struct(lines[i].highlight) tokens = [] for j in xrange(0, lines[i].count): token_type = InstructionTextTokenType(lines[i].tokens[j].type) @@ -1472,7 +1486,7 @@ class Function(object): confidence = lines[i].tokens[j].confidence address = lines[i].tokens[j].address tokens.append(InstructionTextToken(token_type, text, value, size, operand, context, address, confidence)) - result.append(DisassemblyTextLine(addr, tokens)) + result.append(DisassemblyTextLine(tokens, addr, color = color)) core.BNFreeDisassemblyTextLines(lines, count.value) return result @@ -1598,10 +1612,18 @@ class AdvancedFunctionAnalysisDataRequestor(object): class DisassemblyTextLine(object): - def __init__(self, addr, tokens, il_instr = None): - self.address = addr + def __init__(self, tokens, address = None, il_instr = None, color = None): + self.address = address self.tokens = tokens self.il_instruction = il_instr + if color is None: + self.highlight = highlight.HighlightColor() + else: + if not isinstance(color, HighlightStandardColor) and not isinstance(color, highlight.HighlightColor): + raise ValueError("Specified color is not one of HighlightStandardColor, highlight.HighlightColor") + if isinstance(color, HighlightStandardColor): + color = highlight.HighlightColor(color) + self.highlight = color def __str__(self): result = "" @@ -1610,192 +1632,11 @@ class DisassemblyTextLine(object): return result def __repr__(self): + if self.address is None: + return str(self) return "<%#x: %s>" % (self.address, str(self)) -class FunctionGraphEdge(object): - def __init__(self, branch_type, source, target, points, back_edge): - self.type = BranchType(branch_type) - self.source = source - self.target = target - self.points = points - self.back_edge = back_edge - - def __repr__(self): - return "<%s: %s>" % (self.type.name, repr(self.target)) - - -class FunctionGraphBlock(object): - def __init__(self, handle, graph): - self.handle = handle - self.graph = graph - - def __del__(self): - core.BNFreeFunctionGraphBlock(self.handle) - - def __eq__(self, value): - if not isinstance(value, FunctionGraphBlock): - return False - return ctypes.addressof(self.handle.contents) == ctypes.addressof(value.handle.contents) - - def __ne__(self, value): - if not isinstance(value, FunctionGraphBlock): - return True - return ctypes.addressof(self.handle.contents) != ctypes.addressof(value.handle.contents) - - @property - def basic_block(self): - """Basic block associated with this part of the function graph (read-only)""" - block = core.BNGetFunctionGraphBasicBlock(self.handle) - func_handle = core.BNGetBasicBlockFunction(block) - if func_handle is None: - core.BNFreeBasicBlock(block) - return None - - view = binaryview.BinaryView(handle = core.BNGetFunctionData(func_handle)) - func = Function(view, func_handle) - - if core.BNIsLowLevelILBasicBlock(block): - block = lowlevelil.LowLevelILBasicBlock(view, block, - lowlevelil.LowLevelILFunction(func.arch, core.BNGetBasicBlockLowLevelILFunction(block), func)) - elif core.BNIsMediumLevelILBasicBlock(block): - block = mediumlevelil.MediumLevelILBasicBlock(view, block, - mediumlevelil.MediumLevelILFunction(func.arch, core.BNGetBasicBlockMediumLevelILFunction(block), func)) - else: - block = basicblock.BasicBlock(view, block) - return block - - @property - def arch(self): - """Function graph block architecture (read-only)""" - arch = core.BNGetFunctionGraphBlockArchitecture(self.handle) - if arch is None: - return None - return architecture.CoreArchitecture._from_cache(arch) - - @property - def start(self): - """Function graph block start (read-only)""" - return core.BNGetFunctionGraphBlockStart(self.handle) - - @property - def end(self): - """Function graph block end (read-only)""" - return core.BNGetFunctionGraphBlockEnd(self.handle) - - @property - def x(self): - """Function graph block X (read-only)""" - return core.BNGetFunctionGraphBlockX(self.handle) - - @property - def y(self): - """Function graph block Y (read-only)""" - return core.BNGetFunctionGraphBlockY(self.handle) - - @property - def width(self): - """Function graph block width (read-only)""" - return core.BNGetFunctionGraphBlockWidth(self.handle) - - @property - def height(self): - """Function graph block height (read-only)""" - return core.BNGetFunctionGraphBlockHeight(self.handle) - - @property - def lines(self): - """Function graph block list of lines (read-only)""" - count = ctypes.c_ulonglong() - lines = core.BNGetFunctionGraphBlockLines(self.handle, count) - block = self.basic_block - result = [] - for i in xrange(0, count.value): - addr = lines[i].addr - if (lines[i].instrIndex != 0xffffffffffffffff) and hasattr(block, 'il_function'): - il_instr = block.il_function[lines[i].instrIndex] - else: - il_instr = None - tokens = [] - for j in xrange(0, lines[i].count): - token_type = InstructionTextTokenType(lines[i].tokens[j].type) - text = lines[i].tokens[j].text - value = lines[i].tokens[j].value - size = lines[i].tokens[j].size - operand = lines[i].tokens[j].operand - context = lines[i].tokens[j].context - confidence = lines[i].tokens[j].confidence - address = lines[i].tokens[j].address - tokens.append(InstructionTextToken(token_type, text, value, size, operand, context, address, confidence)) - result.append(DisassemblyTextLine(addr, tokens, il_instr)) - core.BNFreeDisassemblyTextLines(lines, count.value) - return result - - @property - def outgoing_edges(self): - """Function graph block list of outgoing edges (read-only)""" - count = ctypes.c_ulonglong() - edges = core.BNGetFunctionGraphBlockOutgoingEdges(self.handle, count) - result = [] - for i in xrange(0, count.value): - branch_type = BranchType(edges[i].type) - target = edges[i].target - if target: - func = core.BNGetBasicBlockFunction(target) - if func is None: - core.BNFreeBasicBlock(target) - target = None - else: - target = basicblock.BasicBlock(binaryview.BinaryView(handle = core.BNGetFunctionData(func)), - core.BNNewBasicBlockReference(target)) - core.BNFreeFunction(func) - points = [] - for j in xrange(0, edges[i].pointCount): - points.append((edges[i].points[j].x, edges[i].points[j].y)) - result.append(FunctionGraphEdge(branch_type, self, target, points, edges[i].backEdge)) - core.BNFreeFunctionGraphBlockOutgoingEdgeList(edges, count.value) - return result - - def __setattr__(self, name, value): - try: - object.__setattr__(self, name, value) - except AttributeError: - raise AttributeError("attribute '%s' is read only" % name) - - def __repr__(self): - arch = self.arch - if arch: - return "<graph block: %s@%#x-%#x>" % (arch.name, self.start, self.end) - else: - return "<graph block: %#x-%#x>" % (self.start, self.end) - - def __iter__(self): - count = ctypes.c_ulonglong() - lines = core.BNGetFunctionGraphBlockLines(self.handle, count) - block = self.basic_block - try: - for i in xrange(0, count.value): - addr = lines[i].addr - if (lines[i].instrIndex != 0xffffffffffffffff) and hasattr(block, 'il_function'): - il_instr = block.il_function[lines[i].instrIndex] - else: - il_instr = None - tokens = [] - for j in xrange(0, lines[i].count): - token_type = InstructionTextTokenType(lines[i].tokens[j].type) - text = lines[i].tokens[j].text - value = lines[i].tokens[j].value - size = lines[i].tokens[j].size - operand = lines[i].tokens[j].operand - context = lines[i].tokens[j].context - confidence = lines[i].tokens[j].confidence - address = lines[i].tokens[j].address - tokens.append(InstructionTextToken(token_type, text, value, size, operand, context, address, confidence)) - yield DisassemblyTextLine(addr, tokens, il_instr) - finally: - core.BNFreeDisassemblyTextLines(lines, count.value) - - class DisassemblySettings(object): def __init__(self, handle = None): if handle is None: @@ -1833,189 +1674,6 @@ class DisassemblySettings(object): core.BNSetDisassemblySettingsOption(self.handle, option, state) -class FunctionGraph(object): - def __init__(self, view, handle): - self.view = view - self.handle = handle - self._on_complete = None - self._cb = ctypes.CFUNCTYPE(None, ctypes.c_void_p)(self._complete) - - def __del__(self): - self.abort() - core.BNFreeFunctionGraph(self.handle) - - def __eq__(self, value): - if not isinstance(value, FunctionGraph): - return False - return ctypes.addressof(self.handle.contents) == ctypes.addressof(value.handle.contents) - - def __ne__(self, value): - if not isinstance(value, FunctionGraph): - return True - return ctypes.addressof(self.handle.contents) != ctypes.addressof(value.handle.contents) - - @property - def function(self): - """Function for a function graph (read-only)""" - func = core.BNGetFunctionForFunctionGraph(self.handle) - if func is None: - return None - return Function(self.view, func) - - @property - def complete(self): - """Whether function graph layout is complete (read-only)""" - return core.BNIsFunctionGraphLayoutComplete(self.handle) - - @property - def type(self): - """Function graph type (read-only)""" - return FunctionGraphType(core.BNGetFunctionGraphType(self.handle)) - - @property - def blocks(self): - """List of basic blocks in function (read-only)""" - count = ctypes.c_ulonglong() - blocks = core.BNGetFunctionGraphBlocks(self.handle, count) - result = [] - for i in xrange(0, count.value): - result.append(FunctionGraphBlock(core.BNNewFunctionGraphBlockReference(blocks[i]), self)) - core.BNFreeFunctionGraphBlockList(blocks, count.value) - return result - - @property - def has_blocks(self): - """Whether the function graph has at least one block (read-only)""" - return core.BNFunctionGraphHasBlocks(self.handle) - - @property - def width(self): - """Function graph width (read-only)""" - return core.BNGetFunctionGraphWidth(self.handle) - - @property - def height(self): - """Function graph height (read-only)""" - return core.BNGetFunctionGraphHeight(self.handle) - - @property - def horizontal_block_margin(self): - return core.BNGetHorizontalFunctionGraphBlockMargin(self.handle) - - @horizontal_block_margin.setter - def horizontal_block_margin(self, value): - core.BNSetFunctionGraphBlockMargins(self.handle, value, self.vertical_block_margin) - - @property - def vertical_block_margin(self): - return core.BNGetVerticalFunctionGraphBlockMargin(self.handle) - - @vertical_block_margin.setter - def vertical_block_margin(self, value): - core.BNSetFunctionGraphBlockMargins(self.handle, self.horizontal_block_margin, value) - - @property - def settings(self): - return DisassemblySettings(core.BNGetFunctionGraphSettings(self.handle)) - - @property - def is_il(self): - return core.BNIsILFunctionGraph(self.handle) - - @property - def is_low_level_il(self): - return core.BNIsLowLevelILFunctionGraph(self.handle) - - @property - def is_medium_level_il(self): - return core.BNIsMediumLevelILFunctionGraph(self.handle) - - @property - def il_function(self): - if self.is_low_level_il: - il_func = core.BNGetFunctionGraphLowLevelILFunction(self.handle) - if not il_func: - return None - return lowlevelil.LowLevelILFunction(self.function.arch, il_func, self.function) - if self.is_medium_level_il: - il_func = core.BNGetFunctionGraphMediumLevelILFunction(self.handle) - if not il_func: - return None - return mediumlevelil.MediumLevelILFunction(self.function.arch, il_func, self.function) - return None - - def __setattr__(self, name, value): - try: - object.__setattr__(self, name, value) - except AttributeError: - raise AttributeError("attribute '%s' is read only" % name) - - def __repr__(self): - return "<graph of %s>" % repr(self.function) - - def __iter__(self): - count = ctypes.c_ulonglong() - blocks = core.BNGetFunctionGraphBlocks(self.handle, count) - try: - for i in xrange(0, count.value): - yield FunctionGraphBlock(core.BNNewFunctionGraphBlockReference(blocks[i]), self) - finally: - core.BNFreeFunctionGraphBlockList(blocks, count.value) - - def _complete(self, ctxt): - try: - if self._on_complete is not None: - self._on_complete() - except: - log.log_error(traceback.format_exc()) - - def layout(self, graph_type = FunctionGraphType.NormalFunctionGraph): - if isinstance(graph_type, str): - graph_type = FunctionGraphType[graph_type] - core.BNStartFunctionGraphLayout(self.handle, graph_type) - - def _wait_complete(self): - self._wait_cond.acquire() - self._wait_cond.notify() - self._wait_cond.release() - - def layout_and_wait(self, graph_type=FunctionGraphType.NormalFunctionGraph): - self._wait_cond = threading.Condition() - self.on_complete(self._wait_complete) - self.layout(graph_type) - - self._wait_cond.acquire() - while not self.complete: - self._wait_cond.wait() - self._wait_cond.release() - - def on_complete(self, callback): - self._on_complete = callback - core.BNSetFunctionGraphCompleteCallback(self.handle, None, self._cb) - - def abort(self): - core.BNAbortFunctionGraph(self.handle) - - def get_blocks_in_region(self, left, top, right, bottom): - count = ctypes.c_ulonglong() - blocks = core.BNGetFunctionGraphBlocksInRegion(self.handle, left, top, right, bottom, count) - result = [] - for i in xrange(0, count.value): - result.append(FunctionGraphBlock(core.BNNewFunctionGraphBlockReference(blocks[i]), self)) - core.BNFreeFunctionGraphBlockList(blocks, count.value) - return result - - def is_option_set(self, option): - if isinstance(option, str): - option = DisassemblyOption[option] - return core.BNIsFunctionGraphOptionSet(self.handle, option) - - def set_option(self, option, state = True): - if isinstance(option, str): - option = DisassemblyOption[option] - core.BNSetFunctionGraphOption(self.handle, option, state) - - class RegisterInfo(object): def __init__(self, full_width_reg, size, offset=0, extend=ImplicitRegisterExtend.NoExtend, index=None): self.full_width_reg = full_width_reg diff --git a/python/highlight.py b/python/highlight.py index 96bc543d..87329202 100644 --- a/python/highlight.py +++ b/python/highlight.py @@ -110,3 +110,13 @@ class HighlightColor(object): result.b = self.blue return result + + @staticmethod + def _from_core_struct(color): + if color.style == HighlightColorStyle.StandardHighlightColor: + return HighlightColor(color=color.color, alpha=color.alpha) + elif color.style == HighlightColorStyle.MixedHighlightColor: + return HighlightColor(color=color.color, mix_color=color.mixColor, mix=color.mix, alpha=color.alpha) + elif color.style == HighlightColorStyle.CustomHighlightColor: + return HighlightColor(red=color.r, green=color.g, blue=color.b, alpha=color.alpha) + return HighlightColor(color=HighlightStandardColor.NoHighlightColor) diff --git a/python/interaction.py b/python/interaction.py index 4f6ed67d..81aeb04f 100644 --- a/python/interaction.py +++ b/python/interaction.py @@ -23,9 +23,10 @@ import traceback # Binary Ninja components import _binaryninjacore as core -from enums import FormInputFieldType, MessageBoxIcon, MessageBoxButtonSet, MessageBoxButtonResult +from enums import FormInputFieldType, MessageBoxIcon, MessageBoxButtonSet, MessageBoxButtonResult, ReportType import binaryview import log +import flowgraph class LabelField(object): @@ -249,6 +250,8 @@ class InteractionHandler(object): self._cb.showPlainTextReport = self._cb.showPlainTextReport.__class__(self._show_plain_text_report) self._cb.showMarkdownReport = self._cb.showMarkdownReport.__class__(self._show_markdown_report) self._cb.showHTMLReport = self._cb.showHTMLReport.__class__(self._show_html_report) + self._cb.showGraphReport = self._cb.showGraphReport.__class__(self._show_graph_report) + self._cb.showReportCollection = self._cb.showReportCollection.__class__(self._show_report_collection) self._cb.getTextLineInput = self._cb.getTextLineInput.__class__(self._get_text_line_input) self._cb.getIntegerInput = self._cb.getIntegerInput.__class__(self._get_int_input) self._cb.getAddressInput = self._cb.getAddressInput.__class__(self._get_address_input) @@ -293,6 +296,22 @@ class InteractionHandler(object): except: log.log_error(traceback.format_exc()) + def _show_graph_report(self, ctxt, view, title, graph): + try: + if view: + view = binaryview.BinaryView(handle = core.BNNewViewReference(view)) + else: + view = None + self.show_graph_report(view, title, flowgraph.FlowGraph(core.BNNewFlowGraphReference(graph))) + except: + log.log_error(traceback.format_exc()) + + def _show_report_collection(self, ctxt, title, reports): + try: + self.show_report_collection(title, ReportCollection(core.BNNewReportCollectionReference(reports))) + except: + log.log_error(traceback.format_exc()) + def _get_text_line_input(self, ctxt, result, prompt, title): try: value = self.get_text_line_input(prompt, title) @@ -426,6 +445,12 @@ class InteractionHandler(object): if len(plaintext) != 0: self.show_plain_text_report(view, title, plaintext) + def show_graph_report(self, view, title, graph): + pass + + def show_report_collection(self, title, reports): + pass + def get_text_line_input(self, prompt, title): return None @@ -461,6 +486,123 @@ class InteractionHandler(object): return MessageBoxButtonResult.CancelButton +class PlainTextReport(object): + def __init__(self, title, contents, view = None): + self.view = view + self.title = title + self.contents = contents + + def __repr__(self): + return "<plain text report: %s>" % self.title + + def __str__(self): + return self.contents + + +class MarkdownReport(object): + def __init__(self, title, contents, plaintext = "", view = None): + self.view = view + self.title = title + self.contents = contents + self.plaintext = plaintext + + def __repr__(self): + return "<markdown report: %s>" % self.title + + def __str__(self): + return self.contents + + +class HTMLReport(object): + def __init__(self, title, contents, plaintext = "", view = None): + self.view = view + self.title = title + self.contents = contents + self.plaintext = plaintext + + def __repr__(self): + return "<html report: %s>" % self.title + + def __str__(self): + return self.contents + + +class FlowGraphReport(object): + def __init__(self, title, graph, view = None): + self.view = view + self.title = title + self.graph = graph + + def __repr__(self): + return "<graph report: %s>" % self.title + + +class ReportCollection(object): + def __init__(self, handle = None): + if handle is None: + self.handle = core.BNCreateReportCollection() + else: + self.handle = handle + + def __len__(self): + return core.BNGetReportCollectionCount(self.handle) + + def _report_from_index(self, i): + report_type = core.BNGetReportType(self.handle, i) + title = core.BNGetReportTitle(self.handle, i) + view = core.BNGetReportView(self.handle, i) + if view: + view = binaryview.BinaryView(handle = view) + else: + view = None + if report_type == ReportType.PlainTextReportType: + contents = core.BNGetReportContents(self.handle, i) + return PlainTextReport(title, contents, view) + elif report_type == ReportType.MarkdownReportType: + contents = core.BNGetReportContents(self.handle, i) + plaintext = core.BNGetReportPlainText(self.handle, i) + return MarkdownReport(title, contents, plaintext, view) + elif report_type == ReportType.HTMLReportType: + contents = core.BNGetReportContents(self.handle, i) + plaintext = core.BNGetReportPlainText(self.handle, i) + return HTMLReport(title, contents, plaintext, view) + elif report_type == ReportType.FlowGraphReportType: + graph = flowgraph.FlowGraph(core.BNGetReportFlowGraph(self.handle, i)) + return FlowGraphReport(title, graph, view) + raise TypeError("invalid report type %s" % repr(report_type)) + + def __getitem__(self, i): + if isinstance(i, slice) or isinstance(i, tuple): + raise IndexError("expected integer report index") + if (i < 0) or (i >= len(self)): + raise IndexError("index out of range") + return self._report_from_index(i) + + def __iter__(self): + count = len(self) + for i in xrange(0, count): + yield self._report_from_index(i) + + def __repr__(self): + return "<reports: %s>" % repr(list(self)) + + def append(self, report): + if report.view is None: + view = None + else: + view = report.view.handle + if isinstance(report, PlainTextReport): + core.BNAddPlainTextReportToCollection(self.handle, view, report.title, report.contents) + elif isinstance(report, MarkdownReport): + core.BNAddMarkdownReportToCollection(self.handle, view, report.title, report.contents, report.plaintext) + elif isinstance(report, HTMLReport): + core.BNAddHTMLReportToCollection(self.handle, view, report.title, report.contents, report.plaintext) + elif isinstance(report, FlowGraphReport): + core.BNAddGraphReportToCollection(self.handle, view, report.title, report.graph.handle) + else: + raise TypeError("expected report object") + + def markdown_to_html(contents): """ ``markdown_to_html`` converts the provided markdown to HTML. @@ -527,6 +669,34 @@ def show_html_report(title, contents, plaintext=""): core.BNShowHTMLReport(None, title, contents, plaintext) +def show_graph_report(title, graph): + """ + ``show_graph_report`` displays a flow graph in UI applications. + + Note: This API function will have no effect outside the UI. + + :param FlowGraph graph: Flow graph to display + :rtype: None + """ + func = graph.function + if func is None: + core.BNShowGraphReport(None, title, graph.handle) + else: + core.BNShowGraphReport(func.view.handle, title, graph.handle) + + +def show_report_collection(title, reports): + """ + ``show_report_collection`` displays mulitple reports in UI applications. + + Note: This API function will have no effect outside the UI. + + :param ReportCollection reports: Reports to display + :rtype: None + """ + core.BNShowReportCollection(title, reports.handle) + + def get_text_line_input(prompt, title): """ ``get_text_line_input`` prompts the user to input a string with the given prompt and title. |
