Add output option for trace explorer GUI

pull/100/head
Bernhard Mueller 7 years ago
parent ef5ea87253
commit 52f633d03c
  1. 8
      README.md
  2. 21
      myth
  3. 185
      mythril-repo-changes.diff
  4. 109
      mythril/analysis/traceexplore.py
  5. 5
      mythril/ether/__init__.py

@ -144,6 +144,14 @@ $ myth -ig ./graph.html -a 0x5c436ff914c458983414019195e0f4ecbef9e6dd --max-dept
~~The "bounce" effect, while awesome (and thus enabled by default), sometimes messes up the graph layout.~~ Try adding the `--enable-physics` flag for a very entertaining "bounce" effect that unfortunately completely destroys usability.
## Statespace JSON for Traceview Explorer
The `-j FILENAME` option dumps the statespace to json in the format that is required by the [Symbolic Trace Explorer GUI](https://github.com/ConsenSys/mythril-trace-explorer).
```bash
$ ./myth -ij ./statespace.json -a 0x5c436ff914c458983414019195e0f4ecbef9e6dd --max-depth 8
```
## Blockchain exploration
If you are planning to do batch operations or use the contract search features, running a [go-ethereum](https://github.com/ethereum/go-ethereum) node is recommended. Start your local node as follows:

21
myth

@ -4,7 +4,7 @@
http://www.github.com/b-mueller/mythril
"""
from json.decoder import JSONDecodeError
import logging
import json
import sys
@ -29,6 +29,7 @@ from mythril.support.loader import DynLoader
from mythril.exceptions import CompilerError, NoContractFoundError
from mythril.analysis.symbolic import SymExecWrapper
from mythril.analysis.callgraph import generate_graph
from mythril.analysis.traceexplore import get_serializable_statespace
from mythril.analysis.security import fire_lasers
from mythril.analysis.report import Report
@ -57,6 +58,7 @@ commands.add_argument('-g', '--graph', help='generate a control flow graph', met
commands.add_argument('-x', '--fire-lasers', action='store_true', help='detect vulnerabilities, use with -c, -a or solidity file(s)')
commands.add_argument('-t', '--truffle', action='store_true', help='analyze a truffle project (run from project dir)')
commands.add_argument('-d', '--disassemble', action='store_true', help='print disassembly')
commands.add_argument('-j', '--statespace-json', help='dumps the statespace json', metavar='OUTPUT_FILE')
inputs = parser.add_argument_group('input arguments')
inputs.add_argument('-c', '--code', help='hex-encoded bytecode string ("6060604052...")', metavar='BYTECODE')
@ -129,7 +131,7 @@ with open(signatures_file) as f:
# Parse cmdline args
if not (args.search or args.init_db or args.hash or args.disassemble or args.graph or args.fire_lasers or args.storage or args.truffle):
if not (args.search or args.init_db or args.hash or args.disassemble or args.graph or args.fire_lasers or args.storage or args.truffle or args.statespace_json):
parser.print_help()
sys.exit()
@ -402,6 +404,21 @@ elif args.graph or args.fire_lasers:
'markdown': report.as_markdown() or "The analysis was completed successfully. No issues were detected."
}
print(outputs[args.outform])
elif args.statespace_json:
if not contracts:
exitWithError(args.outform, "input files do not contain any valid contracts")
if args.dynld:
sym = SymExecWrapper(contracts[0], address, dynloader=DynLoader(eth), max_depth=args.max_depth)
else:
sym = SymExecWrapper(contracts[0], address, max_depth=args.max_depth)
try:
with open(args.statespace_json, "w") as f:
json.dump(get_serializable_statespace(sym), f)
except Exception as e:
exitWithError(args.outform, "Error saving json: " + str(e))
else:
parser.print_help()

@ -0,0 +1,185 @@
diff --git a/README.md b/README.md
index ab3d9fd..357a612 100644
--- a/README.md
+++ b/README.md
@@ -144,6 +144,14 @@ $ myth -ig ./graph.html -a 0x5c436ff914c458983414019195e0f4ecbef9e6dd --max-dept
~~The "bounce" effect, while awesome (and thus enabled by default), sometimes messes up the graph layout.~~ Try adding the `--enable-physics` flag for a very entertaining "bounce" effect that unfortunately completely destroys usability.
+## Statespace JSON for Traceview Explorer
+
+The `-j FILENAME` option dumps the statespace to json in the format that is required by the TraceView Explorer GUI
+
+```bash
+$ ./myth -ij ./statespace.json -a 0x5c436ff914c458983414019195e0f4ecbef9e6dd --max-depth 8
+```
+
## Blockchain exploration
If you are planning to do batch operations or use the contract search features, running a [go-ethereum](https://github.com/ethereum/go-ethereum) node is recommended. Start your local node as follows:
diff --git a/myth b/myth
index b3289bc..08e44d9 100755
--- a/myth
+++ b/myth
@@ -29,6 +29,7 @@ from mythril.support.loader import DynLoader
from mythril.exceptions import CompilerError, NoContractFoundError
from mythril.analysis.symbolic import SymExecWrapper
from mythril.analysis.callgraph import generate_graph
+from mythril.analysis.traceexplore import get_serializable_statespace
from mythril.analysis.security import fire_lasers
from mythril.analysis.report import Report
@@ -57,6 +58,7 @@ commands.add_argument('-g', '--graph', help='generate a control flow graph', met
commands.add_argument('-x', '--fire-lasers', action='store_true', help='detect vulnerabilities, use with -c, -a or solidity file(s)')
commands.add_argument('-t', '--truffle', action='store_true', help='analyze a truffle project (run from project dir)')
commands.add_argument('-d', '--disassemble', action='store_true', help='print disassembly')
+commands.add_argument('-j', '--statespace-json', help='dumps the statespace json', metavar='OUTPUT_FILE')
inputs = parser.add_argument_group('input arguments')
inputs.add_argument('-c', '--code', help='hex-encoded bytecode string ("6060604052...")', metavar='BYTECODE')
@@ -129,7 +131,7 @@ with open(signatures_file) as f:
# Parse cmdline args
-if not (args.search or args.init_db or args.hash or args.disassemble or args.graph or args.fire_lasers or args.storage or args.truffle):
+if not (args.search or args.init_db or args.hash or args.disassemble or args.graph or args.fire_lasers or args.storage or args.truffle or args.statespace_json):
parser.print_help()
sys.exit()
@@ -402,6 +404,21 @@ elif args.graph or args.fire_lasers:
'markdown': report.as_markdown() or "The analysis was completed successfully. No issues were detected."
}
print(outputs[args.outform])
+
+elif args.statespace_json:
+ if not contracts:
+ exitWithError(args.outform, "input files do not contain any valid contracts")
+
+ if args.dynld:
+ sym = SymExecWrapper(contracts[0], address, dynloader=DynLoader(eth), max_depth=args.max_depth)
+ else:
+ sym = SymExecWrapper(contracts[0], address, max_depth=args.max_depth)
+
+ try:
+ with open(args.statespace_json, "w") as f:
+ json.dump(get_serializable_statespace(sym), f)
+ except Exception as e:
+ exitWithError(args.outform, "Error saving json: " + str(e))
else:
parser.print_help()
diff --git a/mythril/analysis/traceexplore.py b/mythril/analysis/traceexplore.py
new file mode 100644
index 0000000..cbe0391
--- /dev/null
+++ b/mythril/analysis/traceexplore.py
@@ -0,0 +1,109 @@
+from z3 import Z3Exception, simplify
+from laser.ethereum.svm import NodeFlags
+import re
+
+colors = [
+ {'border': '#26996f', 'background': '#2f7e5b', 'highlight': {'border': '#fff', 'background': '#28a16f'}},
+ {'border': '#9e42b3', 'background': '#842899', 'highlight': {'border': '#fff', 'background': '#933da6'}},
+ {'border': '#b82323', 'background': '#991d1d', 'highlight': {'border': '#fff', 'background': '#a61f1f'}},
+ {'border': '#4753bf', 'background': '#3b46a1', 'highlight': {'border': '#fff', 'background': '#424db3'}},
+ {'border': '#26996f', 'background': '#2f7e5b', 'highlight': {'border': '#fff', 'background': '#28a16f'}},
+ {'border': '#9e42b3', 'background': '#842899', 'highlight': {'border': '#fff', 'background': '#933da6'}},
+ {'border': '#b82323', 'background': '#991d1d', 'highlight': {'border': '#fff', 'background': '#a61f1f'}},
+ {'border': '#4753bf', 'background': '#3b46a1', 'highlight': {'border': '#fff', 'background': '#424db3'}},
+]
+
+def get_serializable_statespace(statespace):
+
+ nodes = []
+ edges = []
+
+ color_map = {}
+ i = 0
+ for k in statespace.accounts:
+ color_map[statespace.accounts[k].contract_name] = colors[i]
+ i += 1
+
+ for node_key in statespace.nodes:
+
+ node = statespace.nodes[node_key]
+
+ code = node.get_cfg_dict()['code']
+ code = re.sub("([0-9a-f]{8})[0-9a-f]+", lambda m: m.group(1) + "(...)", code)
+
+ if NodeFlags.FUNC_ENTRY in node.flags:
+ code = re.sub("JUMPDEST", node.function_name, code)
+
+ code_split = code.split("\\n")
+
+ truncated_code = code if (len(code_split) < 7) else "\\n".join(code_split[:6]) + "\\n(click to expand +)"
+
+ color = color_map[node.get_cfg_dict()['contract_name']]
+
+ def get_state_accounts(state):
+ state_accounts = []
+ for key in state.accounts:
+ account = state.accounts[key].as_dict()
+ account.pop('code', None)
+ account['balance'] = str(account['balance'])
+
+ storage = {}
+ for storage_key in account['storage']:
+ storage[str(storage_key)] = str(account['storage'][storage_key])
+
+ state_accounts.append({
+ 'address': key,
+ 'storage': storage
+ })
+ return state_accounts
+
+ states = [{'machine': x.mstate.as_dict(), 'accounts': get_state_accounts(x)} for x in node.states]
+
+ for state in states:
+ state['machine']['stack'] = [str(s) for s in state['machine']['stack']]
+ state['machine']['memory'] = [str(m) for m in state['machine']['memory']]
+
+ truncated_code = truncated_code.replace('\\n', '\n')
+ code = code.replace('\\n', '\n')
+
+ s_node = {
+ 'id': str(node_key),
+ 'func': str(node.function_name),
+ 'label': truncated_code,
+ 'code': code,
+ 'truncated': truncated_code,
+ 'states': states,
+ 'color': color,
+ 'instructions': code.split('\n')
+ }
+
+ nodes.append(s_node)
+
+ for edge in statespace.edges:
+
+ if (edge.condition is None):
+ label = ""
+ else:
+
+ try:
+ label = str(simplify(edge.condition)).replace("\n", "")
+ except Z3Exception:
+ 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)
+
+ s_edge = {
+ 'from': str(edge.as_dict()['from']),
+ 'to': str(edge.as_dict()['to']),
+ 'arrows': 'to',
+ 'label': label,
+ 'smooth': { 'type': "cubicBezier" }
+ }
+
+ edges.append(s_edge)
+
+ return {
+ 'edges': edges,
+ 'nodes': nodes
+ }

@ -0,0 +1,109 @@
from z3 import Z3Exception, simplify
from laser.ethereum.svm import NodeFlags
import re
colors = [
{'border': '#26996f', 'background': '#2f7e5b', 'highlight': {'border': '#fff', 'background': '#28a16f'}},
{'border': '#9e42b3', 'background': '#842899', 'highlight': {'border': '#fff', 'background': '#933da6'}},
{'border': '#b82323', 'background': '#991d1d', 'highlight': {'border': '#fff', 'background': '#a61f1f'}},
{'border': '#4753bf', 'background': '#3b46a1', 'highlight': {'border': '#fff', 'background': '#424db3'}},
{'border': '#26996f', 'background': '#2f7e5b', 'highlight': {'border': '#fff', 'background': '#28a16f'}},
{'border': '#9e42b3', 'background': '#842899', 'highlight': {'border': '#fff', 'background': '#933da6'}},
{'border': '#b82323', 'background': '#991d1d', 'highlight': {'border': '#fff', 'background': '#a61f1f'}},
{'border': '#4753bf', 'background': '#3b46a1', 'highlight': {'border': '#fff', 'background': '#424db3'}},
]
def get_serializable_statespace(statespace):
nodes = []
edges = []
color_map = {}
i = 0
for k in statespace.accounts:
color_map[statespace.accounts[k].contract_name] = colors[i]
i += 1
for node_key in statespace.nodes:
node = statespace.nodes[node_key]
code = node.get_cfg_dict()['code']
code = re.sub("([0-9a-f]{8})[0-9a-f]+", lambda m: m.group(1) + "(...)", code)
if NodeFlags.FUNC_ENTRY in node.flags:
code = re.sub("JUMPDEST", node.function_name, code)
code_split = code.split("\\n")
truncated_code = code if (len(code_split) < 7) else "\\n".join(code_split[:6]) + "\\n(click to expand +)"
color = color_map[node.get_cfg_dict()['contract_name']]
def get_state_accounts(state):
state_accounts = []
for key in state.accounts:
account = state.accounts[key].as_dict()
account.pop('code', None)
account['balance'] = str(account['balance'])
storage = {}
for storage_key in account['storage']:
storage[str(storage_key)] = str(account['storage'][storage_key])
state_accounts.append({
'address': key,
'storage': storage
})
return state_accounts
states = [{'machine': x.mstate.as_dict(), 'accounts': get_state_accounts(x)} for x in node.states]
for state in states:
state['machine']['stack'] = [str(s) for s in state['machine']['stack']]
state['machine']['memory'] = [str(m) for m in state['machine']['memory']]
truncated_code = truncated_code.replace('\\n', '\n')
code = code.replace('\\n', '\n')
s_node = {
'id': str(node_key),
'func': str(node.function_name),
'label': truncated_code,
'code': code,
'truncated': truncated_code,
'states': states,
'color': color,
'instructions': code.split('\n')
}
nodes.append(s_node)
for edge in statespace.edges:
if (edge.condition is None):
label = ""
else:
try:
label = str(simplify(edge.condition)).replace("\n", "")
except Z3Exception:
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)
s_edge = {
'from': str(edge.as_dict()['from']),
'to': str(edge.as_dict()['to']),
'arrows': 'to',
'label': label,
'smooth': { 'type': "cubicBezier" }
}
edges.append(s_edge)
return {
'edges': edges,
'nodes': nodes
}

@ -1,5 +0,0 @@
import time
sync_count = 0
start_time = time.time()
print("start_time: " + str(start_time))
Loading…
Cancel
Save