From 975249d22e10360ed2c4c0bcea151c39d9446b0a Mon Sep 17 00:00:00 2001 From: KyleMiles Date: Thu, 5 Jul 2018 18:28:59 -0400 Subject: Rebased so commit history is correct now --- python/examples/export_svg.py | 224 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100755 python/examples/export_svg.py (limited to 'python/examples/export_svg.py') diff --git a/python/examples/export_svg.py b/python/examples/export_svg.py new file mode 100755 index 00000000..5bb27824 --- /dev/null +++ b/python/examples/export_svg.py @@ -0,0 +1,224 @@ +# from binaryninja import * +import os +import webbrowser +import time +try: + from urllib import pathname2url # Python 2.x +except: + 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]} + +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 + + +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) + + +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) + + +def render_svg(function, origname): + graph = binaryninja.function.create_graph() + graph.layout_and_wait() + heightconst = 15 + ratio = 0.48 + widthconst = heightconst * ratio + + output = ''' + + + + +''' + output += ''' + + + + + + + + + + + + + + + '''.format(width=graph.width * widthconst + 20, height=graph.height * heightconst + 20) + output += ''' + Function Graph 0 + ''' + edges = '' + for i, block in enumerate(graph.blocks): + + # 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 += ' \n'.format(i=i) + output += ' Basic Block {i}\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 += ' \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 + + output += ' \n'.format(x=x, y=y + (i + 1) * heightconst) + for i, line in enumerate(block.lines): + output += ' '.format(x=x + 6, y=y + 6 + (i + 0.7) * heightconst, address=hex(line.address)[:-1]) + hover = instruction_data_flow(function, line.address) + output += '{hover}'.format(hover=hover) + for token in line.tokens: + # TODO: add hover for hex, function, and reg tokens + output += '{text}'.format(text=escape(token.text), tokentype=InstructionTextTokenType(token.type).name) + 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) + " " + if edge.back_edge: + edges += ' \n'.format(type=BranchType(edge.type).name, points=points) + else: + edges += ' \n'.format(type=BranchType(edge.type).name, points=points) + output += ' ' + edges + '\n' + output += ' \n' + output += '' + + output += '

This CFG generated by Binary Ninja from {filename} on {timestring}.

'.format(filename = origname, timestring = time.strftime("%c")) + output += '' + return output + + +PluginCommand.register_for_function("Export to SVG", "Exports an SVG of the current function", save_svg) -- cgit v1.3.1