mirror of https://github.com/ConsenSys/mythril
blockchainethereumsmart-contractssoliditysecurityprogram-analysissecurity-analysissymbolic-execution
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
186 lines
7.6 KiB
186 lines
7.6 KiB
7 years ago
|
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
|
||
|
+ }
|