diff options
Diffstat (limited to 'python/examples/export_svg.py')
| -rwxr-xr-x | python/examples/export_svg.py | 218 |
1 files changed, 117 insertions, 101 deletions
diff --git a/python/examples/export_svg.py b/python/examples/export_svg.py index 5bb27824..673aa59c 100755 --- a/python/examples/export_svg.py +++ b/python/examples/export_svg.py @@ -3,78 +3,83 @@ import os import webbrowser import time try: - from urllib import pathname2url # Python 2.x + from urllib import pathname2url # Python 2.x except: - from urllib.request import pathname2url # Python 3.x + from urllib.request import pathname2url # Python 3.x from binaryninja.interaction import get_save_filename_input, show_message_box from binaryninja.enums import MessageBoxButtonSet, MessageBoxIcon, MessageBoxButtonResult, InstructionTextTokenType, BranchType 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]} +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]} escape_table = { - "'": "'", - ">": ">", - "<": "<", - '"': """, - ' ': " " + "'": "'", + ">": ">", + "<": "<", + '"': """, + ' ': " " } def escape(toescape): - toescape = toescape.decode('utf-8').encode('ascii', 'xmlcharrefreplace') # handle extended unicode - return ''.join(escape_table.get(i, i) for i in toescape) # still escape the basics + # 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): - address = hex(function.start).replace('L', '') - path = os.path.dirname(bv.file.filename) - origname = os.path.basename(bv.file.filename) - filename = os.path.join(path, 'binaryninja-{filename}-{function}.html'.format(filename=origname, function=address)) - outputfile = get_save_filename_input('File name for export_svg', 'HTML files (*.html)', filename) - if outputfile is None: - return - content = render_svg(function, 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: - url = 'file:{}'.format(pathname2url(outputfile)) - webbrowser.open(url) + address = hex(function.start).replace('L', '') + path = os.path.dirname(bv.file.filename) + origname = os.path.basename(bv.file.filename) + filename = os.path.join( + path, 'binaryninja-{filename}-{function}.html'.format(filename=origname, function=address)) + outputfile = get_save_filename_input( + 'File name for export_svg', 'HTML files (*.html)', filename) + if outputfile is None: + return + content = render_svg(function, 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: + url = 'file:{}'.format(pathname2url(outputfile)) + webbrowser.open(url) def instruction_data_flow(function, address): - ''' TODO: Extract data flow information ''' - length = binaryninja.function.view.get_instruction_length(address) - bytes = binaryninja.function.view.read(address, length) - hex = 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) + hex = func_bytes.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, origname): - graph = binaryninja.function.create_graph() - graph.layout_and_wait() - heightconst = 15 - ratio = 0.48 - widthconst = heightconst * ratio + graph = function.create_graph() + 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); body { background-color: rgb(42, 42, 42); - color: rgb(220, 220, 220); - font-family: "Source Code Pro", "Lucida Console", "Consolas", monospace; + color: rgb(220, 220, 220); + font-family: "Source Code Pro", "Lucida Console", "Consolas", monospace; } - a, a:visited { - color: rgb(200, 200, 200); - font-weight: bold; - } + a, a:visited { + color: rgb(200, 200, 200); + font-weight: bold; + } svg { background-color: rgb(42, 42, 42); display: block; @@ -108,7 +113,7 @@ def render_svg(function, origname): fill: currentColor; } text { - font-family: "Source Code Pro", "Lucida Console", "Consolas", monospace; + font-family: "Source Code Pro", "Lucida Console", "Consolas", monospace; font-size: 9pt; fill: rgb(224, 224, 224); } @@ -140,7 +145,7 @@ def render_svg(function, origname): <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" /> @@ -156,69 +161,80 @@ def render_svg(function, origname): </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.blocks): + 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 - color_code = bb.highlight.color - color_str = bb.highlight._standard_color_to_str(color_code) - if color_str in colors: - rgb = colors[color_str] - 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 + color_code = bb.highlight.color + color_str = bb.highlight._standard_color_to_str(color_code) + if color_str in colors: + rgb = colors[color_str] + 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}.</p>'.format(filename = origname, timestring = time.strftime("%c")) - output += '</html>' - return output + output += '<p>This CFG generated by <a href="https://binary.ninja/">Binary Ninja</a> from {filename} on {timestring}.</p>'.format( + filename=origname, timestring=time.strftime("%c")) + 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) |
