diff --git a/mythril/analysis/callgraph.py b/mythril/analysis/callgraph.py index 48c3d366..fd619c9b 100644 --- a/mythril/analysis/callgraph.py +++ b/mythril/analysis/callgraph.py @@ -1,217 +1,96 @@ -from z3 import Z3Exception, simplify -from laser.ethereum.svm import NodeFlags import re -default_title = '

Mythril / Ethereum LASER Symbolic VM

' - -default_style = ''' - -''' +from jinja2 import Environment, PackageLoader, select_autoescape +from laser.ethereum.svm import NodeFlags +from z3 import Z3Exception, simplify -default_opts = ''' - var options = { - autoResize: true, - height: '100%', - width: '100%', - manipulation: false, - height: '90%', - layout: { - randomSeed: undefined, - improvedLayout:true, - hierarchical: { - enabled:true, - levelSeparation: 450, - nodeSpacing: 200, - treeSpacing: 100, - blockShifting: true, - edgeMinimization: true, - parentCentralization: false, - direction: 'LR', // UD, DU, LR, RL - sortMethod: 'directed' // hubsize, directed +default_opts = { + 'autoResize': True, + 'height': '100%', + 'width': '100%', + 'manipulation': False, + 'height': '90%', + 'layout': { + 'improvedLayout': True, + 'hierarchical': { + 'enabled': True, + 'levelSeparation': 450, + 'nodeSpacing': 200, + 'treeSpacing': 100, + 'blockShifting': True, + 'edgeMinimization': True, + 'parentCentralization': False, + 'direction': 'LR', + 'sortMethod': 'directed' } - }, - nodes:{ - borderWidth: 1, - borderWidthSelected: 2, - chosen: true, - shape: 'box', - font: { - align: 'left', - color: '#FFFFFF', - }, - }, - edges:{ - font: { - color: '#ffffff', - size: 12, // px - face: 'arial', - background: 'none', - strokeWidth: 0, // px - strokeColor: '#ffffff', - align: 'horizontal', - multi: false, - vadjust: 0, - } - }, - - physics:{ - enabled: [ENABLE_PHYSICS], - } - - } -''' - - -phrack_style = ''' - -''' - -phrack_opts = ''' - var options = { - autoResize: true, - height: '100%', - width: '100%', - manipulation: false, - height: '90%', - layout: { - randomSeed: undefined, - improvedLayout:true, - hierarchical: { - enabled:true, - levelSeparation: 450, - nodeSpacing: 200, - treeSpacing: 100, - blockShifting: true, - edgeMinimization: true, - parentCentralization: false, - direction: 'LR', // UD, DU, LR, RL - sortMethod: 'directed' // hubsize, directed + }, + 'nodes': { + 'color': '#000000', + 'borderWidth': 1, + 'borderWidthSelected': 2, + 'chosen': True, + 'shape': 'box', + 'font': {'align': 'left', 'color': '#FFFFFF'}, + }, + 'edges': { + 'font': { + 'color': '#FFFFFF', + 'face': 'arial', + 'background': 'none', + 'strokeWidth': 0, + 'strokeColor': '#ffffff', + 'align': 'horizontal', + 'multi': False, + 'vadjust': 0, } - }, - nodes:{ - color: '#000000', - borderWidth: 1, - borderWidthSelected: 1, - shapeProperties: { - borderDashes: false, // only for borders - borderRadius: 0, // only for box shape - }, - chosen: true, - shape: 'box', - font: { - face: 'courier new', - align: 'left', - color: '#000000', + }, + 'physics': {'enabled': False} +} + +phrack_opts = { + 'nodes': { + 'color': '#000000', + 'borderWidth': 1, + 'borderWidthSelected': 1, + 'shapeProperties': { + 'borderDashes': False, + 'borderRadius': 0, }, - }, - edges:{ - font: { - color: '#000000', - face: 'courier new', - background: 'none', - strokeWidth: 0, // px - strokeColor: '#ffffff', - align: 'horizontal', - multi: false, - vadjust: 0, + 'chosen': True, + 'shape': 'box', + 'font': {'face': 'courier new', 'align': 'left', 'color': '#000000'}, + }, + 'edges': { + 'font': { + 'color': '#000000', + 'face': 'courier new', + 'background': 'none', + 'strokeWidth': 0, + 'strokeColor': '#ffffff', + 'align': 'horizontal', + 'multi': False, + 'vadjust': 0, } - }, - - physics:{ - enabled: [ENABLE_PHYSICS], - } - } -''' - -graph_html = ''' - - - [STYLE] - - - - - - -

Mythril / LASER Symbolic VM

-


- - - -''' - -colors = [ - "{border: '#26996f', background: '#2f7e5b', highlight: {border: '#26996f', background: '#28a16f'}}", - "{border: '#9e42b3', background: '#842899', highlight: {border: '#9e42b3', background: '#933da6'}}", - "{border: '#b82323', background: '#991d1d', highlight: {border: '#b82323', background: '#a61f1f'}}", - "{border: '#4753bf', background: '#3b46a1', highlight: {border: '#4753bf', background: '#424db3'}}", - "{border: '#26996f', background: '#2f7e5b', highlight: {border: '#26996f', background: '#28a16f'}}", - "{border: '#9e42b3', background: '#842899', highlight: {border: '#9e42b3', background: '#933da6'}}", - "{border: '#b82323', background: '#991d1d', highlight: {border: '#b82323', background: '#a61f1f'}}", - "{border: '#4753bf', background: '#3b46a1', highlight: {border: '#4753bf', background: '#424db3'}}", +} + +default_colors = [ + {'border': '#26996f', 'background': '#2f7e5b', 'highlight': {'border': '#26996f', 'background': '#28a16f'}}, + {'border': '#9e42b3', 'background': '#842899', 'highlight': {'border': '#9e42b3', 'background': '#933da6'}}, + {'border': '#b82323', 'background': '#991d1d', 'highlight': {'border': '#b82323', 'background': '#a61f1f'}}, + {'border': '#4753bf', 'background': '#3b46a1', 'highlight': {'border': '#4753bf', 'background': '#424db3'}}, + {'border': '#26996f', 'background': '#2f7e5b', 'highlight': {'border': '#26996f', 'background': '#28a16f'}}, + {'border': '#9e42b3', 'background': '#842899', 'highlight': {'border': '#9e42b3', 'background': '#933da6'}}, + {'border': '#b82323', 'background': '#991d1d', 'highlight': {'border': '#b82323', 'background': '#a61f1f'}}, + {'border': '#4753bf', 'background': '#3b46a1', 'highlight': {'border': '#4753bf', 'background': '#424db3'}}, ] +phrack_color = {'border': '#000000', 'background': '#ffffff', + 'highlight': {'border': '#000000', 'background': '#ffffff'}} -def serialize(statespace, color_map): +def extract_nodes(statespace, color_map): nodes = [] - edges = [] - for node_key in statespace.nodes: node = statespace.nodes[node_key] @@ -223,16 +102,31 @@ def serialize(statespace, color_map): code = re.sub("JUMPDEST", node.function_name, code) code_split = code.split("\\n") + code_split = [x for x in code_split if x] + full_code = '\n'.join(code_split) - truncated_code = code if (len(code_split) < 7) else "\\n".join(code_split[:6]) + "\\n(click to expand +)" + truncated_code = '\n'.join(code_split) if (len(code_split) < 7) else '\n'.join(code_split[:6]) + "\n(click to expand +)" color = color_map[node.get_cfg_dict()['contract_name']] - nodes.append("{id: '" + str(node_key) + "', color: " + color + ", size: 150, 'label': '" + truncated_code + "', 'fullLabel': '" + code + "', 'truncLabel': '" + truncated_code + "', 'isExpanded': false}") + nodes.append({ + 'id': str(node_key), + 'color': color, + 'size': 150, + 'label': truncated_code, + 'fullLabel': full_code, + 'truncLabel': truncated_code, + 'isExpanded': False + }) + return nodes + + +def extract_edges(statespace): + edges = [] for edge in statespace.edges: - if (edge.condition is None): + if edge.condition is None: label = "" else: @@ -242,38 +136,52 @@ def serialize(statespace, color_map): label = str(edge.condition).replace("\n", "") label = re.sub("([^_])([\d]{2}\d+)", lambda m: m.group(1) + hex(int(m.group(2))), label) - code = re.sub("([0-9a-f]{8})[0-9a-f]+", lambda m: m.group(1) + "(...)", code) - - edges.append("{from: '" + str(edge.as_dict()['from']) + "', to: '" + str(edge.as_dict()['to']) + "', 'arrows': 'to', 'label': '" + label + "', 'smooth': {'type': 'cubicBezier'}}") - return "var nodes = [\n" + ",\n".join(nodes) + "\n];\nvar edges = [\n" + ",\n".join(edges) + "\n];" + edges.append({ + 'from': str(edge.as_dict()['from']), + 'to': str(edge.as_dict()['to']), + 'arrows': 'to', + 'label': label, + 'smooth': {'type': 'cubicBezier'} + }) + return edges -def generate_graph(statespace, physics=False, phrackify=False): +def generate_graph(statespace, physics=False, phrackify=False, opts=None): ''' This is some of the the ugliest code in the whole project. At some point someone needs to write a templating system. ''' + if opts is None: + opts = {} + env = Environment( + loader=PackageLoader('mythril.analysis'), + autoescape=select_autoescape(['html', 'xml']) + ) + template = env.get_template('graph.html') + color_map = {} + graph_opts = default_opts if phrackify: - + graph_opts.update(phrack_opts) for k in statespace.accounts: - color_map[statespace.accounts[k].contract_name] = "{border: '#000000', background: '#ffffff', highlight: {border: '#000000', background: '#ffffff'}}" - - html = graph_html.replace("[STYLE]", phrack_style).replace("[OPTS]", phrack_opts) - + color_map[statespace.accounts[k].contract_name] = phrack_color else: - i = 0 - for k in statespace.accounts: - color_map[statespace.accounts[k].contract_name] = colors[i] + color_map[statespace.accounts[k].contract_name] = default_colors[i] i += 1 - html = graph_html.replace("[STYLE]", default_style).replace("[OPTS]", default_opts) + graph_opts.update(opts) + graph_opts['physics']['enabled'] = physics - html = html.replace("[JS]", serialize(statespace, color_map)).replace("[ENABLE_PHYSICS]", str(physics).lower()) + nodes = extract_nodes(statespace, color_map) - return html + return template.render(title="Mythril / Ethereum LASER Symbolic VM", + nodes=extract_nodes(statespace, color_map), + edges=extract_edges(statespace), + phrackify=phrackify, + opts=graph_opts + ) diff --git a/mythril/analysis/templates/graph.html b/mythril/analysis/templates/graph.html new file mode 100644 index 00000000..58b0e000 --- /dev/null +++ b/mythril/analysis/templates/graph.html @@ -0,0 +1,68 @@ + + + + + + {% if not phrackify %} + + {% else %} + + {% endif %} + + + + +

{{ title }}

+


+ + + diff --git a/requirements.txt b/requirements.txt index d517f1f8..d63c7c86 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,3 +15,4 @@ eth-keys>=0.2.0b3 eth-rlp>=0.1.0 eth-tester>=0.1.0b21 coverage +jinja2