diff options
| author | KyleMiles <krm504@nyu.edu> | 2022-01-27 22:43:28 -0500 |
|---|---|---|
| committer | KyleMiles <krm504@nyu.edu> | 2022-01-28 00:24:06 -0500 |
| commit | 6812c973c9fa9b4ad642ab81856c05f87bd6fcc4 (patch) | |
| tree | dace4156d03148bcaf02df138ab4e0d93e61bc6f /python/examples/export_svg.py | |
| parent | 519c9db22367f2659d1a54599fab47e6313be06e (diff) | |
Format All Files
Diffstat (limited to 'python/examples/export_svg.py')
| -rwxr-xr-x | python/examples/export_svg.py | 343 |
1 files changed, 177 insertions, 166 deletions
diff --git a/python/examples/export_svg.py b/python/examples/export_svg.py index c96873e1..8166b27a 100755 --- a/python/examples/export_svg.py +++ b/python/examples/export_svg.py @@ -12,119 +12,126 @@ from binaryninja.enums import MessageBoxButtonSet, MessageBoxIcon, MessageBoxBut from binaryninja.function import DisassemblySettings from binaryninja.plugin import PluginCommand -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 = { - "'": "'", - ">": ">", - "<": "<", - '"': """, - ' ': " " +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 - if sys.version_info[0] == 3: - return ''.join(escape_table.get(chr(i), chr(i)) for i in toescape) - else: - return ''.join(escape_table.get(i, i) for i in toescape) + # handle extended unicode + toescape = toescape.encode('ascii', 'xmlcharrefreplace') + # still escape the basics + if sys.version_info[0] == 3: + return ''.join(escape_table.get(chr(i), chr(i)) for i in toescape) + else: + return ''.join(escape_table.get(i, 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' + 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) + 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"]) + 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)) + 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) - if sys.version_info[0] == 3: - hex = func_bytes.hex() - else: - hex = func_bytes.encode('hex') - padded = ' '.join([hex[i:i + 2] for i in range(0, len(hex), 2)]) - return 'Opcode: {bytes}'.format(bytes=padded) + # TODO: Extract data flow information + length = function.view.get_instruction_length(address) + func_bytes = function.view.read(address, length) + if sys.version_info[0] == 3: + hex = func_bytes.hex() + else: + hex = func_bytes.encode('hex') + padded = ' '.join([hex[i:i + 2] for i in range(0, len(hex), 2)]) + return 'Opcode: {bytes}'.format(bytes=padded) 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 + 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 = '''<html> + output = '''<html> <head> <style type="text/css"> @import url(https://fonts.googleapis.com/css?family=Source+Code+Pro); @@ -205,7 +212,7 @@ def render_svg(function, offset, mode, form, showOpcodes, showAddresses, orignam <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script> </head> ''' - output += '''<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}"> + output += '''<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}"> <defs> <marker id="arrow-TrueBranch" class="arrow TrueBranch" viewBox="0 0 10 10" refX="10" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"> <path d="M 0 0 L 10 5 L 0 10 z" /> @@ -221,83 +228,87 @@ def render_svg(function, offset, mode, form, showOpcodes, showAddresses, orignam </marker> </defs> '''.format(width=graph.width * widthconst + 20, height=graph.height * heightconst + 20) - output += ''' <g id="functiongraph0" class="functiongraph"> + output += ''' <g id="functiongraph0" class="functiongraph"> <title>Function Graph 0</title> ''' - edges = '' - for i, block in enumerate(graph): + 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) + # 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 += ' <g id="basicblock{i}">\n'.format(i=i) - output += ' <title>Basic Block {i}</title>\n'.format(i=i) - 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 += ' <rect class="basicblock" x="{x}" y="{y}" fill-opacity="0.4" height="{height}" width="{width}" fill="rgb({r},{g},{b})"/>\n'.format( - x=x, y=y, width=width + 16, height=height + 12, r=rgb[0], g=rgb[1], b=rgb[2]) + # Render block + output += ' <g id="basicblock{i}">\n'.format(i=i) + output += ' <title>Basic Block {i}</title>\n'.format(i=i) + 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 += ' <rect class="basicblock" x="{x}" y="{y}" fill-opacity="0.4" height="{height}" width="{width}" fill="rgb({r},{g},{b})"/>\n'.format( + x=x, y=y, width=width + 16, height=height + 12, r=rgb[0], g=rgb[1], b=rgb[2] + ) - # 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 + # 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 += ' <text x="{x}" y="{y}">\n'.format( - x=x, y=y + (i + 1) * heightconst) - for i, line in enumerate(block.lines): - output += ' <tspan id="instr-{address}" x="{x}" y="{y}">'.format( - x=x + 6, y=y + 6 + (i + 0.7) * heightconst, address=hex(line.address)[:-1]) - hover = instruction_data_flow(function, line.address) - output += '<title>{hover}</title>'.format(hover=hover) - for token in line.tokens: - # TODO: add hover for hex, function, and reg tokens - output += '<tspan class="{tokentype}">{text}</tspan>'.format( - text=escape(token.text), tokentype=InstructionTextTokenType(token.type).name) - output += '</tspan>\n' - output += ' </text>\n' - output += ' </g>\n' + output += ' <text x="{x}" y="{y}">\n'.format(x=x, y=y + (i+1) * heightconst) + for i, line in enumerate(block.lines): + output += ' <tspan id="instr-{address}" x="{x}" y="{y}">'.format( + x=x + 6, y=y + 6 + (i+0.7) * heightconst, address=hex(line.address)[:-1] + ) + hover = instruction_data_flow(function, line.address) + output += '<title>{hover}</title>'.format(hover=hover) + for token in line.tokens: + # TODO: add hover for hex, function, and reg tokens + output += '<tspan class="{tokentype}">{text}</tspan>'.format( + text=escape(token.text), tokentype=InstructionTextTokenType(token.type).name + ) + output += '</tspan>\n' + output += ' </text>\n' + output += ' </g>\n' - # Edges are rendered in a seperate chunk so they have priority over the - # basic blocks or else they'd render below them + # 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) + " " - if edge.back_edge: - edges += ' <polyline class="back_edge {type}" points="{points}" marker-end="url(#arrow-{type})"/>\n'.format( - type=BranchType(edge.type).name, points=points) - else: - edges += ' <polyline class="edge {type}" points="{points}" marker-end="url(#arrow-{type})"/>\n'.format( - type=BranchType(edge.type).name, points=points) - output += ' ' + edges + '\n' - output += ' </g>\n' - output += '</svg>' + 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) + " " + if edge.back_edge: + edges += ' <polyline class="back_edge {type}" points="{points}" marker-end="url(#arrow-{type})"/>\n'.format( + type=BranchType(edge.type).name, points=points + ) + else: + edges += ' <polyline class="edge {type}" points="{points}" marker-end="url(#arrow-{type})"/>\n'.format( + type=BranchType(edge.type).name, points=points + ) + output += ' ' + edges + '\n' + output += ' </g>\n' + output += '</svg>' - output += '<p>This CFG generated by <a href="https://binary.ninja/">Binary Ninja</a> from {filename} on {timestring} showing {function} as {form}.</p>'.format( - filename=origname, timestring=time.strftime("%c"), function=offset, form=form) - output += '</html>' - return output + output += '<p>This CFG generated by <a href="https://binary.ninja/">Binary Ninja</a> from {filename} on {timestring} showing {function} as {form}.</p>'.format( + filename=origname, timestring=time.strftime("%c"), function=offset, form=form + ) + output += '</html>' + return output -PluginCommand.register_for_function( - "Export to SVG", "Exports an SVG of the current function", save_svg) +PluginCommand.register_for_function("Export to SVG", "Exports an SVG of the current function", save_svg) |
