Merge branch 'master' of github.com:ConsenSys/mythril

pull/396/head
Ubuntu 6 years ago
commit 61c17fe806
  1. 35
      .github/ISSUE_TEMPLATE/analysis-module.md
  2. 68
      .github/ISSUE_TEMPLATE/bug-report.md
  3. 28
      .github/ISSUE_TEMPLATE/bug_report.md
  4. 24
      .github/ISSUE_TEMPLATE/feature-request.md
  5. 14
      mythril/ether/soliditycontract.py
  6. 23
      mythril/interfaces/cli.py
  7. 101
      mythril/mythril.py
  8. 4
      mythril/support/signatures.py
  9. 6
      mythril/support/truffle.py

@ -4,13 +4,34 @@ about: Create an analysis module feature request
--- ---
# Detection issue: Please remove any of the optional sections if they are not applicable.
Name the issue that should be detected using the analysis module
## Description: ## Description
Provide a detailed description of the vulnerabiltity that should be detected
## Link Replace this text with a description of an vulnerability that should be
Provide resources that can help with implementing the analysis module detected by a Mythril analysis module.
## Implementation details:Initial implementation ideas/instruction ## Tests
_This section is optional._
Replace this text with suggestions on how to test the feature,
if it is not obvious. This might require certain Solidity source,
bytecode, or a Truffle project. You can also provide
links to existing code.
## Implementation details
_This section is optional._
If you have thoughts about how to implement the analysis, feel free
replace this text with that.
## Links
_This section is optional._
Replace this text with any links describing the issue or pointing to resources
that can help in implementing the analysis
Thanks for helping!

@ -0,0 +1,68 @@
---
name: Bug report
about: Tell us about Mythril bugs to help us improve
---
_Note: did you notice that there is now a template for requesting new features?_
Please remove any of the optional sections if they are not applicable.
## Description
Replace this text with a clear and concise description of the bug.
## How to Reproduce
Please show both the input you gave and the
output you got in describing how to reproduce the bug:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
or give a complete console log with input and output
```console
$ myth <command-line-options>
==== Exception state ====
Type: ...
Contract: ...
Function name: ...
...
$
```
If there is a Solidity source code, a truffle project, or bytecode
that is involved, please provide that or links to it.
## Expected behavior
A clear and concise description of what you expected to happen.
## Screenshots
_This section is optional._
If applicable, add screenshots to help explain your problem.
## Environment
_This section sometimes is optional but helpful to us._
Please modify for your setup
- Mythril version: output from `myth --version` or `pip show mythril`
- Solidity compiler and version: `solc --version`
- Python version: `python -V`
- OS: [e.g. iOS]
- OS Version [e.g. 22]
## Additional Environment or Context
_This section is optional._
Add any other context about the problem here or special environment setup
Thanks for helping!

@ -1,28 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

@ -0,0 +1,24 @@
---
name: Feature Request
about: Tell us about a new feature that would make Mythril better
---
## Description
Replace this text with a short description of the feature.
## Background
Replace this text with any additional background for the
feature, for example: user scenarios, or the value of the feature.
## Tests
_This section is optional._
Replace this text with suggestions on how to test the feature,
if it is not obvious. This might require certain Solidity source,
bytecode, or a Truffle project. You can also provide
links to existing code.
Thanks for helping!

@ -1,8 +1,9 @@
import mythril.laser.ethereum.util as helper import mythril.laser.ethereum.util as helper
from mythril.ether.ethcontract import ETHContract from mythril.ether.ethcontract import ETHContract
from mythril.ether.util import * from mythril.ether.util import get_solc_json
from mythril.exceptions import NoContractFoundError from mythril.exceptions import NoContractFoundError
class SourceMapping: class SourceMapping:
def __init__(self, solidity_file_idx, offset, length, lineno): def __init__(self, solidity_file_idx, offset, length, lineno):
@ -53,11 +54,10 @@ class SolidityContract(ETHContract):
# If a contract name has been specified, find the bytecode of that specific contract # If a contract name has been specified, find the bytecode of that specific contract
if name: if name:
for key, contract in data['contracts'].items(): for key, contract in sorted(data['contracts'].items()):
filename, _name = key.split(":") filename, _name = key.split(":")
if filename == input_file and name == _name and len(contract['bin-runtime']): if filename == input_file and name == _name and len(contract['bin-runtime']):
name = name
code = contract['bin-runtime'] code = contract['bin-runtime']
creation_code = contract['bin'] creation_code = contract['bin']
srcmap = contract['srcmap-runtime'].split(";") srcmap = contract['srcmap-runtime'].split(";")
@ -67,11 +67,10 @@ class SolidityContract(ETHContract):
# If no contract name is specified, get the last bytecode entry for the input file # If no contract name is specified, get the last bytecode entry for the input file
else: else:
for key, contract in data['contracts'].items(): for key, contract in sorted(data['contracts'].items()):
filename, name = key.split(":") filename, name = key.split(":")
if filename == input_file and len(contract['bin-runtime']): if filename == input_file and len(contract['bin-runtime']):
name = name
code = contract['bin-runtime'] code = contract['bin-runtime']
creation_code = contract['bin'] creation_code = contract['bin']
srcmap = contract['srcmap-runtime'].split(";") srcmap = contract['srcmap-runtime'].split(";")
@ -93,8 +92,7 @@ class SolidityContract(ETHContract):
if len(mapping) > 2 and len(mapping[2]) > 0: if len(mapping) > 2 and len(mapping[2]) > 0:
idx = int(mapping[2]) idx = int(mapping[2])
lineno = self.solidity_files[idx].data.encode('utf-8')[0:offset].count('\n'.encode('utf-8')) + 1
lineno = self.solidity_files[idx].data[0:offset].count('\n') + 1
self.mappings.append(SourceMapping(idx, offset, length, lineno)) self.mappings.append(SourceMapping(idx, offset, length, lineno))
@ -111,7 +109,7 @@ class SolidityContract(ETHContract):
offset = self.mappings[index].offset offset = self.mappings[index].offset
length = self.mappings[index].length length = self.mappings[index].length
code = solidity_file.data[offset:offset + length] code = solidity_file.data.encode('utf-8')[offset:offset + length].decode('utf-8')
lineno = self.mappings[index].lineno lineno = self.mappings[index].lineno
return SourceCodeInfo(filename, lineno, code) return SourceCodeInfo(filename, lineno, code)

@ -92,12 +92,6 @@ def main():
print("Mythril version {}".format(VERSION)) print("Mythril version {}".format(VERSION))
sys.exit() sys.exit()
# -- args sanity checks --
# Detect unsupported combinations of command line args
if args.dynld and not args.address:
exit_with_error(args.outform, "Dynamic loader can be used in on-chain analysis mode only (-a).")
# Parse cmdline args # Parse cmdline args
if not (args.search or args.hash or args.disassemble or args.graph or args.fire_lasers if not (args.search or args.hash or args.disassemble or args.graph or args.fire_lasers
@ -116,15 +110,15 @@ def main():
print(Mythril.hash_for_function_signature(args.hash)) print(Mythril.hash_for_function_signature(args.hash))
sys.exit() sys.exit()
try: try:
# the mythril object should be our main interface # the mythril object should be our main interface
#infura = None, rpc = None, rpctls = None, ipc = None, # infura = None, rpc = None, rpctls = None, ipc = None,
#solc_args = None, dynld = None, max_recursion_depth = 12): # solc_args = None, dynld = None, max_recursion_depth = 12):
mythril = Mythril(solv=args.solv, dynld=args.dynld, mythril = Mythril(solv=args.solv, dynld=args.dynld,
solc_args=args.solc_args) solc_args=args.solc_args)
if args.dynld and not (args.ipc or args.rpc or args.i):
mythril.set_api_from_config_path()
if args.address and not args.leveldb: if args.address and not args.leveldb:
# Establish RPC/IPC connection if necessary # Establish RPC/IPC connection if necessary
@ -134,7 +128,7 @@ def main():
mythril.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls) mythril.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls)
elif args.ipc: elif args.ipc:
mythril.set_api_ipc() mythril.set_api_ipc()
else: elif not args.dynld:
mythril.set_api_rpc_localhost() mythril.set_api_rpc_localhost()
elif args.leveldb or args.search or args.contract_hash_to_address: elif args.leveldb or args.search or args.contract_hash_to_address:
# Open LevelDB if necessary # Open LevelDB if necessary
@ -170,9 +164,9 @@ def main():
address, _ = mythril.load_from_address(args.address) address, _ = mythril.load_from_address(args.address)
elif args.solidity_file: elif args.solidity_file:
# Compile Solidity source file(s) # Compile Solidity source file(s)
#if args.graph and len(args.solidity_file) > 1: if args.graph and len(args.solidity_file) > 1:
# exit_with_error(args.outform, exit_with_error(args.outform,
# "Cannot generate call graphs from multiple input files. Please do it one at a time.") "Cannot generate call graphs from multiple input files. Please do it one at a time.")
address, _ = mythril.load_from_solidity(args.solidity_file) # list of files address, _ = mythril.load_from_solidity(args.solidity_file) # list of files
else: else:
exit_with_error(args.outform, exit_with_error(args.outform,
@ -239,5 +233,6 @@ def main():
except CriticalError as ce: except CriticalError as ce:
exit_with_error(args.outform, str(ce)) exit_with_error(args.outform, str(ce))
if __name__ == "__main__": if __name__ == "__main__":
main() main()

@ -97,6 +97,7 @@ class Mythril(object):
raise CriticalError("Invalid JSON in signatures file " + self.sigs.signatures_file + "\n" + str(jde)) raise CriticalError("Invalid JSON in signatures file " + self.sigs.signatures_file + "\n" + str(jde))
self.solc_binary = self._init_solc_binary(solv) self.solc_binary = self._init_solc_binary(solv)
self.config_path = os.path.join(self.mythril_dir, 'config.ini')
self.leveldb_dir = self._init_config() self.leveldb_dir = self._init_config()
self.eth = None # ethereum API client self.eth = None # ethereum API client
@ -118,40 +119,66 @@ class Mythril(object):
return mythril_dir return mythril_dir
def _init_config(self): def _init_config(self):
"""
If no config file exists, create it and add default options.
Default LevelDB path is specified based on OS
dynamic loading is set to infura by default in the file
Returns: leveldb directory
"""
# If no config file exists, create it. Default LevelDB path is specified based on OS
config_path = os.path.join(self.mythril_dir, 'config.ini')
system = platform.system().lower() system = platform.system().lower()
fallback_dir = os.path.expanduser('~') leveldb_fallback_dir = os.path.expanduser('~')
if system.startswith("darwin"): if system.startswith("darwin"):
fallback_dir = os.path.join(fallback_dir, "Library", "Ethereum") leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "Library", "Ethereum")
elif system.startswith("windows"): elif system.startswith("windows"):
fallback_dir = os.path.join(fallback_dir, "AppData", "Roaming", "Ethereum") leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "AppData", "Roaming", "Ethereum")
else: else:
fallback_dir = os.path.join(fallback_dir, ".ethereum") leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, ".ethereum")
fallback_dir = os.path.join(fallback_dir, "geth", "chaindata") leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "geth", "chaindata")
if not os.path.exists(config_path): if not os.path.exists(self.config_path):
logging.info("No config file found. Creating default: " + config_path) logging.info("No config file found. Creating default: " + self.config_path)
open(self.config_path, 'a').close()
config = ConfigParser(allow_no_value=True)
config.optionxform = str
config.add_section('defaults')
config.set('defaults', "#Default chaindata locations:")
config.set('defaults', "#– Mac: ~/Library/Ethereum/geth/chaindata")
config.set('defaults', "#– Linux: ~/.ethereum/geth/chaindata")
config.set('defaults', "#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata")
config.set('defaults', 'leveldb_dir', fallback_dir)
with codecs.open(config_path, 'w', 'utf-8') as fp:
config.write(fp)
config = ConfigParser(allow_no_value=True) config = ConfigParser(allow_no_value=True)
config.optionxform = str config.optionxform = str
config.read(config_path, 'utf-8') config.read(self.config_path, 'utf-8')
leveldb_dir = config.get('defaults', 'leveldb_dir', fallback=fallback_dir) if 'defaults' not in config.sections():
self._add_default_options(config)
if not config.has_option('defaults', 'leveldb_dir'):
self._add_leveldb_option(config, leveldb_fallback_dir)
if not config.has_option('defaults', 'dynamic_loading'):
self._add_dynamic_loading_option(config)
with codecs.open(self.config_path, 'w', 'utf-8') as fp:
config.write(fp)
leveldb_dir = config.get('defaults', 'leveldb_dir', fallback=leveldb_fallback_dir)
return os.path.expanduser(leveldb_dir) return os.path.expanduser(leveldb_dir)
@staticmethod
def _add_default_options(config):
config.add_section('defaults')
@staticmethod
def _add_leveldb_option(config, leveldb_fallback_dir):
config.set('defaults', "#Default chaindata locations:")
config.set('defaults', "#– Mac: ~/Library/Ethereum/geth/chaindata")
config.set('defaults', "#– Linux: ~/.ethereum/geth/chaindata")
config.set('defaults', "#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata")
config.set('defaults', 'leveldb_dir', leveldb_fallback_dir)
@staticmethod
def _add_dynamic_loading_option(config):
config.set('defaults', '#– To connect to Infura use dynamic_loading: infura')
config.set('defaults', '#– To connect to Ipc use dynamic_loading: ipc')
config.set('defaults', '#– To connect to Rpc use '
'dynamic_loading: HOST:PORT / ganache / infura-[network_name]')
config.set('defaults', '#– To connect to local host use dynamic_loading: localhost')
config.set('defaults', 'dynamic_loading', 'infura')
def analyze_truffle_project(self, *args, **kwargs): def analyze_truffle_project(self, *args, **kwargs):
return analyze_truffle_project(*args, **kwargs) # just passthru for now return analyze_truffle_project(*args, **kwargs) # just passthru for now
@ -226,6 +253,23 @@ class Mythril(object):
self.eth = EthJsonRpc('localhost', 8545) self.eth = EthJsonRpc('localhost', 8545)
logging.info("Using default RPC settings: http://localhost:8545") logging.info("Using default RPC settings: http://localhost:8545")
def set_api_from_config_path(self):
config = ConfigParser(allow_no_value=False)
config.optionxform = str
config.read(self.config_path, 'utf-8')
if config.has_option('defaults', 'dynamic_loading'):
dynamic_loading = config.get('defaults', 'dynamic_loading')
else:
dynamic_loading = 'infura'
if dynamic_loading == 'ipc':
self.set_api_ipc()
elif dynamic_loading == 'infura':
self.set_api_rpc_infura()
elif dynamic_loading == 'localhost':
self.set_api_rpc_localhost()
else:
self.set_api_rpc(dynamic_loading)
def search_db(self, search): def search_db(self, search):
def search_callback(code_hash, code, addresses, balances): def search_callback(code_hash, code, addresses, balances):
@ -288,6 +332,9 @@ class Mythril(object):
# import signatures from solidity source # import signatures from solidity source
with open(file, encoding="utf-8") as f: with open(file, encoding="utf-8") as f:
self.sigs.import_from_solidity_source(f.read()) self.sigs.import_from_solidity_source(f.read())
# Save updated function signatures
self.sigs.write() # dump signatures to disk (previously opened file or default location)
if contract_name is not None: if contract_name is not None:
contract = SolidityContract(file, contract_name, solc_args=self.solc_args) contract = SolidityContract(file, contract_name, solc_args=self.solc_args)
self.contracts.append(contract) self.contracts.append(contract)
@ -297,6 +344,7 @@ class Mythril(object):
self.contracts.append(contract) self.contracts.append(contract)
contracts.append(contract) contracts.append(contract)
except FileNotFoundError: except FileNotFoundError:
raise CriticalError("Input file not found: " + file) raise CriticalError("Input file not found: " + file)
except CompilerError as e: except CompilerError as e:
@ -304,8 +352,6 @@ class Mythril(object):
except NoContractFoundError: except NoContractFoundError:
logging.info("The file " + file + " does not contain a compilable contract.") logging.info("The file " + file + " does not contain a compilable contract.")
# Save updated function signatures
self.sigs.write() # dump signatures to disk (previously opened file or default location)
return address, contracts return address, contracts
@ -327,10 +373,7 @@ class Mythril(object):
modules=None, modules=None,
verbose_report=False, max_depth=None, execution_timeout=None, ): verbose_report=False, max_depth=None, execution_timeout=None, ):
all_issues = [] all_issues = []
if self.dynld and self.eth is None:
self.set_api_rpc_infura()
for contract in (contracts or self.contracts): for contract in (contracts or self.contracts):
sym = SymExecWrapper(contract, address, strategy, sym = SymExecWrapper(contract, address, strategy,
dynloader=DynLoader(self.eth) if self.dynld else None, dynloader=DynLoader(self.eth) if self.dynld else None,

@ -154,7 +154,6 @@ class SignatureDb(object):
""" """
if not sighash.startswith("0x"): if not sighash.startswith("0x"):
sighash = "0x%s" % sighash # normalize sighash format sighash = "0x%s" % sighash # normalize sighash format
if self.enable_online_lookup and not self.signatures.get(sighash) and sighash not in self.online_lookup_miss and time.time() > self.online_directory_unavailable_until: if self.enable_online_lookup and not self.signatures.get(sighash) and sighash not in self.online_lookup_miss and time.time() > self.online_directory_unavailable_until:
# online lookup enabled, and signature not in cache, sighash was not a miss earlier, and online directory not down # online lookup enabled, and signature not in cache, sighash was not a miss earlier, and online directory not down
logging.debug("Signatures: performing online lookup for sighash %r" % sighash) logging.debug("Signatures: performing online lookup for sighash %r" % sighash)
@ -169,8 +168,11 @@ class SignatureDb(object):
except FourByteDirectoryOnlineLookupError as fbdole: except FourByteDirectoryOnlineLookupError as fbdole:
self.online_directory_unavailable_until = time.time() + 2 * 60 # wait at least 2 mins to try again self.online_directory_unavailable_until = time.time() + 2 * 60 # wait at least 2 mins to try again
logging.warning("online function signature lookup not available. will not try to lookup hash for the next 2 minutes. exception: %r" % fbdole) logging.warning("online function signature lookup not available. will not try to lookup hash for the next 2 minutes. exception: %r" % fbdole)
if type(self.signatures[sighash]) != list:
return [self.signatures[sighash]]
return self.signatures[sighash] # raise keyerror return self.signatures[sighash] # raise keyerror
def __getitem__(self, item): def __getitem__(self, item):
""" """
Provide dict interface Signatures()[sighash] Provide dict interface Signatures()[sighash]

@ -1,4 +1,5 @@
import os import os
from pathlib import PurePath
import re import re
import sys import sys
import json import json
@ -31,6 +32,7 @@ def analyze_truffle_project(args):
try: try:
name = contractdata['contractName'] name = contractdata['contractName']
bytecode = contractdata['deployedBytecode'] bytecode = contractdata['deployedBytecode']
filename = PurePath(contractdata['sourcePath']).name
except: except:
print("Unable to parse contract data. Please use Truffle 4 to compile your project.") print("Unable to parse contract data. Please use Truffle 4 to compile your project.")
sys.exit() sys.exit()
@ -74,7 +76,7 @@ def analyze_truffle_project(args):
if len(mapping) > 2 and len(mapping[2]) > 0: if len(mapping) > 2 and len(mapping[2]) > 0:
idx = int(mapping[2]) idx = int(mapping[2])
lineno = source[0:offset].count('\n') + 1 lineno = source.encode('utf-8')[0:offset].count('\n'.encode('utf-8')) + 1
mappings.append(SourceMapping(idx, offset, length, lineno)) mappings.append(SourceMapping(idx, offset, length, lineno))
@ -88,7 +90,7 @@ def analyze_truffle_project(args):
length = mappings[index].length length = mappings[index].length
issue.filename = filename issue.filename = filename
issue.code = source[offset:offset + length] issue.code = source.encode('utf-8')[offset:offset + length].decode('utf-8')
issue.lineno = mappings[index].lineno issue.lineno = mappings[index].lineno
except IndexError: except IndexError:
logging.debug("No code mapping at index %d", index) logging.debug("No code mapping at index %d", index)

Loading…
Cancel
Save