Merge pull request #4 from ConsenSys/master

Update from main repo
pull/378/head
Bernhard Mueller 6 years ago committed by GitHub
commit e4a4addfaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      mythril/ether/soliditycontract.py
  2. 23
      mythril/interfaces/cli.py
  3. 89
      mythril/mythril.py
  4. 4
      mythril/support/signatures.py
  5. 6
      mythril/support/truffle.py

@ -3,6 +3,7 @@ from mythril.ether.ethcontract import ETHContract
from mythril.ether.util import *
from mythril.exceptions import NoContractFoundError
class SourceMapping:
def __init__(self, solidity_file_idx, offset, length, lineno):
@ -57,7 +58,6 @@ class SolidityContract(ETHContract):
filename, _name = key.split(":")
if filename == input_file and name == _name and len(contract['bin-runtime']):
name = name
code = contract['bin-runtime']
creation_code = contract['bin']
srcmap = contract['srcmap-runtime'].split(";")
@ -71,7 +71,6 @@ class SolidityContract(ETHContract):
filename, name = key.split(":")
if filename == input_file and len(contract['bin-runtime']):
name = name
code = contract['bin-runtime']
creation_code = contract['bin']
srcmap = contract['srcmap-runtime'].split(";")
@ -93,8 +92,7 @@ class SolidityContract(ETHContract):
if len(mapping) > 2 and len(mapping[2]) > 0:
idx = int(mapping[2])
lineno = self.solidity_files[idx].data[0:offset].count('\n') + 1
lineno = self.solidity_files[idx].data.encode('utf-8')[0:offset].count('\n'.encode('utf-8')) + 1
self.mappings.append(SourceMapping(idx, offset, length, lineno))
@ -111,7 +109,7 @@ class SolidityContract(ETHContract):
offset = self.mappings[index].offset
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
return SourceCodeInfo(filename, lineno, code)

@ -92,12 +92,6 @@ def main():
print("Mythril version {}".format(VERSION))
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
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))
sys.exit()
try:
# the mythril object should be our main interface
#infura = None, rpc = None, rpctls = None, ipc = None,
#solc_args = None, dynld = None, max_recursion_depth = 12):
# infura = None, rpc = None, rpctls = None, ipc = None,
# solc_args = None, dynld = None, max_recursion_depth = 12):
mythril = Mythril(solv=args.solv, dynld=args.dynld,
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:
# Establish RPC/IPC connection if necessary
@ -134,7 +128,7 @@ def main():
mythril.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls)
elif args.ipc:
mythril.set_api_ipc()
else:
elif not args.dynld:
mythril.set_api_rpc_localhost()
elif args.leveldb or args.search or args.contract_hash_to_address:
# Open LevelDB if necessary
@ -170,9 +164,9 @@ def main():
address, _ = mythril.load_from_address(args.address)
elif args.solidity_file:
# Compile Solidity source file(s)
#if args.graph and len(args.solidity_file) > 1:
# exit_with_error(args.outform,
# "Cannot generate call graphs from multiple input files. Please do it one at a time.")
if args.graph and len(args.solidity_file) > 1:
exit_with_error(args.outform,
"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
else:
exit_with_error(args.outform,
@ -239,5 +233,6 @@ def main():
except CriticalError as ce:
exit_with_error(args.outform, str(ce))
if __name__ == "__main__":
main()

@ -97,6 +97,7 @@ class Mythril(object):
raise CriticalError("Invalid JSON in signatures file " + self.sigs.signatures_file + "\n" + str(jde))
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.eth = None # ethereum API client
@ -118,39 +119,65 @@ class Mythril(object):
return mythril_dir
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()
fallback_dir = os.path.expanduser('~')
leveldb_fallback_dir = os.path.expanduser('~')
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"):
fallback_dir = os.path.join(fallback_dir, "AppData", "Roaming", "Ethereum")
leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "AppData", "Roaming", "Ethereum")
else:
fallback_dir = os.path.join(fallback_dir, ".ethereum")
fallback_dir = os.path.join(fallback_dir, "geth", "chaindata")
leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, ".ethereum")
leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "geth", "chaindata")
if not os.path.exists(config_path):
logging.info("No config file found. Creating default: " + config_path)
if not os.path.exists(self.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.read(self.config_path, 'utf-8')
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)
@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', fallback_dir)
with codecs.open(config_path, 'w', 'utf-8') as fp:
config.write(fp)
config.set('defaults', 'leveldb_dir', leveldb_fallback_dir)
config = ConfigParser(allow_no_value=True)
config.optionxform = str
config.read(config_path, 'utf-8')
leveldb_dir = config.get('defaults', 'leveldb_dir', fallback=fallback_dir)
return os.path.expanduser(leveldb_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):
return analyze_truffle_project(*args, **kwargs) # just passthru for now
@ -226,6 +253,23 @@ class Mythril(object):
self.eth = EthJsonRpc('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_callback(code_hash, code, addresses, balances):
@ -288,6 +332,9 @@ class Mythril(object):
# import signatures from solidity source
with open(file, encoding="utf-8") as f:
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:
contract = SolidityContract(file, contract_name, solc_args=self.solc_args)
self.contracts.append(contract)
@ -297,6 +344,7 @@ class Mythril(object):
self.contracts.append(contract)
contracts.append(contract)
except FileNotFoundError:
raise CriticalError("Input file not found: " + file)
except CompilerError as e:
@ -304,8 +352,6 @@ class Mythril(object):
except NoContractFoundError:
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
@ -327,10 +373,7 @@ class Mythril(object):
modules=None,
verbose_report=False, max_depth=None, execution_timeout=None, ):
all_issues = []
if self.dynld and self.eth is None:
self.set_api_rpc_infura()
for contract in (contracts or self.contracts):
sym = SymExecWrapper(contract, address, strategy,
dynloader=DynLoader(self.eth) if self.dynld else None,

@ -154,7 +154,6 @@ class SignatureDb(object):
"""
if not sighash.startswith("0x"):
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:
# 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)
@ -169,8 +168,11 @@ class SignatureDb(object):
except FourByteDirectoryOnlineLookupError as fbdole:
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)
if type(self.signatures[sighash]) != list:
return [self.signatures[sighash]]
return self.signatures[sighash] # raise keyerror
def __getitem__(self, item):
"""
Provide dict interface Signatures()[sighash]

@ -1,4 +1,5 @@
import os
from pathlib import PurePath
import re
import sys
import json
@ -31,6 +32,7 @@ def analyze_truffle_project(args):
try:
name = contractdata['contractName']
bytecode = contractdata['deployedBytecode']
filename = PurePath(contractdata['sourcePath']).name
except:
print("Unable to parse contract data. Please use Truffle 4 to compile your project.")
sys.exit()
@ -74,7 +76,7 @@ def analyze_truffle_project(args):
if len(mapping) > 2 and len(mapping[2]) > 0:
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))
@ -88,7 +90,7 @@ def analyze_truffle_project(args):
length = mappings[index].length
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
except IndexError:
logging.debug("No code mapping at index %d", index)

Loading…
Cancel
Save