# from binaryninja import * import os import webbrowser import time import sys from pathlib import Path from urllib.request import pathname2url from binaryninja.interaction import get_save_filename_input, show_message_box, TextLineField, ChoiceField, SaveFileNameField, get_form_input from binaryninja.settings import Settings from binaryninja.enums import MessageBoxButtonSet, MessageBoxIcon, MessageBoxButtonResult, InstructionTextTokenType, BranchType, DisassemblyOption, FunctionGraphType, ThemeColor from binaryninja.function import DisassemblySettings from binaryninja.plugin import PluginCommand from binaryninjaui import getThemeColor, getTokenColor, UIContext colors = { 'green': [162, 217, 175], 'red': [222, 143, 151], 'blue': [128, 198, 233], 'cyan': [142, 230, 237], 'lightCyan': [176, 221, 228], 'orange': [237, 189, 129], 'yellow': [237, 223, 179], 'magenta': [218, 196, 209], 'none': [74, 74, 74], 'disabled': [144, 144, 144] } escape_table = {"'": "'", ">": ">", "<": "<", '"': """, ' ': " "} def escape(toescape): # handle extended unicode toescape = toescape.encode('ascii', 'xmlcharrefreplace') # still escape the basics return ''.join(escape_table.get(chr(i), chr(i)) for i in toescape) def save_svg(bv, function): sym = bv.get_symbol_at(function.start) if sym: offset = sym.name else: offset = "%x" % function.start path = Path(os.path.dirname(bv.file.filename)) origname = os.path.basename(bv.file.filename) filename = path / f'binaryninja-{origname}-{offset}.html' functionChoice = TextLineField("Blank to accept default") # TODO: implement linear disassembly settings and output modeChoices = ["Graph"] modeChoiceField = ChoiceField("Mode", modeChoices) if Settings().get_bool('ui.debugMode'): formChoices = [ "Assembly", "Lifted IL", "LLIL", "LLIL SSA", "Mapped Medium", "Mapped Medium SSA", "MLIL", "MLIL SSA", "HLIL", "HLIL SSA" ] formChoiceField = ChoiceField("Form", formChoices) else: formChoices = ["Assembly", "LLIL", "MLIL", "HLIL"] formChoiceField = ChoiceField("Form", formChoices) showOpcodes = ChoiceField("Show Opcodes", ["Yes", "No"]) showAddresses = ChoiceField("Show Addresses", ["Yes", "No"]) saveFileChoices = SaveFileNameField("Output file", 'HTML files (*.html)', str(filename)) if not get_form_input([ f'Current Function: {offset}', functionChoice, formChoiceField, modeChoiceField, showOpcodes, showAddresses, saveFileChoices ], "SVG Export") or saveFileChoices.result is None: return if saveFileChoices.result == '': outputfile = filename else: outputfile = saveFileChoices.result content = render_svg( function, offset, modeChoices[modeChoiceField.result], formChoices[formChoiceField.result], showOpcodes.result == 0, showAddresses.result == 0, origname ) output = open(outputfile, 'w') output.write(content) output.close() result = show_message_box( "Open SVG", "Would you like to view the exported SVG?", buttons=MessageBoxButtonSet.YesNoButtonSet, icon=MessageBoxIcon.QuestionIcon ) if result == MessageBoxButtonResult.YesButton: # might need more testing, latest py3 on windows seems.... broken with these APIs relative to other platforms if sys.platform == 'win32': webbrowser.open(outputfile) else: webbrowser.open('file://' + str(outputfile)) def instruction_data_flow(function, address): # TODO: Extract data flow information length = function.view.get_instruction_length(address) func_bytes = function.view.read(address, length) hex = func_bytes.hex() padded = ' '.join([hex[i:i + 2] for i in range(0, len(hex), 2)]) return f'Opcode: {padded}' def rgbStr(tokenType): '''Given a token string name, look up the theme color for it and return as rbg(x,y,z) str''' try: color = eval(f'getThemeColor(ThemeColor.{tokenType})') except: color = None if (not color): try: ctx = UIContext.activeContext() view_frame = ctx.getCurrentViewFrame() color = eval(f'getTokenColor(view_frame, InstructionTextTokenType.{tokenType})') except: return 'rgb(224, 224, 224)' r = color.getRgb()[0] g = color.getRgb()[1] b = color.getRgb()[2] return f"rgb({r}, {g}, {b})" def render_svg(function, offset, mode, form, showOpcodes, showAddresses, origname): settings = DisassemblySettings() if showOpcodes: settings.set_option(DisassemblyOption.ShowOpcode, True) if showAddresses: settings.set_option(DisassemblyOption.ShowAddress, True) if form == "LLIL": graph_type = FunctionGraphType.LowLevelILFunctionGraph elif form == "LLIL SSA": graph_type = FunctionGraphType.LowLevelILSSAFormFunctionGraph elif form == "Lifted IL": graph_type = FunctionGraphType.LiftedILFunctionGraph elif form == "Mapped Medium": graph_type = FunctionGraphType.MappedMediumLevelILFunctionGraph elif form == "Mapped Medium SSA": graph_type = FunctionGraphType.MappedMediumLevelILSSAFormFunctionGraph elif form == "MLIL": graph_type = FunctionGraphType.MediumLevelILFunctionGraph elif form == "MLIL SSA": graph_type = FunctionGraphType.MediumLevelILSSAFormFunctionGraph elif form == "HLIL": graph_type = FunctionGraphType.HighLevelILFunctionGraph elif form == "HLIL SSA": graph_type = FunctionGraphType.HighLevelILSSAFormFunctionGraph else: graph_type = FunctionGraphType.NormalFunctionGraph graph = function.create_graph(graph_type=graph_type, settings=settings) graph.layout_and_wait() heightconst = 15 ratio = 0.48 widthconst = heightconst * ratio output = f''' Function Graph 0 ''' edges = '' for i, block in enumerate(graph): # Calculate basic block location and coordinates x = ((block.x) * widthconst) y = ((block.y) * heightconst) width = ((block.width) * widthconst) height = ((block.height) * heightconst) # Render block output += f' \n' output += f' Basic Block {i}\n' rgb = colors['none'] try: bb = block.basic_block if hasattr(bb.highlight, 'color'): color_code = bb.highlight.color color_str = bb.highlight._standard_color_to_str(color_code) if color_str in colors: rgb = colors[color_str] else: rgb = [bb.highlight.red, bb.highlight.green, bb.highlight.blue] except: pass output += f' \n' # Render instructions, unfortunately tspans don't allow copying/pasting more # than one line at a time, need SVG 1.2 textarea tags for that it looks like output += f' \n' for i, line in enumerate(block.lines): output += f' ' hover = instruction_data_flow(function, line.address) output += f'{hover}' for token in line.tokens: # TODO: add hover for hex, function, and reg tokens output += f'{escape(token.text)}' output += '\n' output += ' \n' output += ' \n' # Edges are rendered in a seperate chunk so they have priority over the # basic blocks or else they'd render below them for edge in block.outgoing_edges: points = "" x, y = edge.points[0] points += str(x * widthconst) + "," + str(y * heightconst + 12) + " " for x, y in edge.points[1:-1]: points += str(x * widthconst) + "," + str(y * heightconst) + " " x, y = edge.points[-1] points += str(x * widthconst) + "," + str(y * heightconst + 0) + " " edgeType=BranchType(edge.type).name if edge.back_edge: edges += f' \n' else: edges += f' \n' output += ' ' + edges + '\n' output += ' \n' output += '\n' timestring=time.strftime("%c") output += f'

This CFG generated by Binary Ninja from {origname} on {timestring} showing {offset} as {form}.

' output += '' return output PluginCommand.register_for_function("Export to SVG", "Exports an SVG of the current function", save_svg)