def before_first_request(): print("creating/fixing coverage db") #conn = sqlite3.connect('coverage.db') c = conn.cursor() print("creating coverage table") try: c.execute('''CREATE TABLE mainnet (block integer primary key autoincrement, slotted bool default 0, filled bool default 0, mtime datetime )''') print(" done") except Exception as e: print(" oops: " + str(e)) infura = EthJsonRpc("mainnet.infura.io", 443, True) currentblock = infura.eth_blockNumber() result = c.execute("select count(*) from mainnet") coveragecount = result.fetchone()[0] print("last known block is " + str(currentblock)) print("last known coverage at " + str(coveragecount)) for x in range(coveragecount, currentblock): c.execute('insert into "mainnet" default values') print("coverage updates complete") conn.commit()
def set_api_rpc(self, rpc=None, rpctls=False): """ :param rpc: :param rpctls: """ if rpc == "ganache": rpcconfig = ("localhost", 8545, False) else: m = re.match(r"infura-(.*)", rpc) if m and m.group(1) in ["mainnet", "rinkeby", "kovan", "ropsten"]: rpcconfig = (m.group(1) + ".infura.io", 443, True) else: try: host, port = rpc.split(":") rpcconfig = (host, int(port), rpctls) except ValueError: raise CriticalError( "Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'" ) if rpcconfig: self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2]) log.info("Using RPC settings: %s" % str(rpcconfig)) else: raise CriticalError( "Invalid RPC settings, check help for details.")
def getwork(): c = conn.cursor() # init the db infura = EthJsonRpc("mainnet.infura.io", 443, True) currentblock = infura.eth_blockNumber() result = c.execute("select count(*) from mainnet") coveragecount = result.fetchone()[0] print("last known block is " + str(currentblock)) print("last known coverage at " + str(coveragecount)) for x in range(coveragecount, currentblock): c.execute('insert into "mainnet" default values') print("coverage updates complete") # clean up slots c.execute( "update mainnet set slotted = false, mtime = 0 where slotted = true and filled = false and mtime < ? ", (round(time.time() - 60 * 60), )) # wait one hour for slot submission # reserve the next slot result = c.execute("select max(block) from mainnet where slotted = false") newwork = result.fetchone()[0] print("new work is " + str(newwork)) c.execute("update mainnet set slotted = true, mtime = ? where block = ?", (round(time.time()), newwork)) conn.commit() work = {} work['type'] = 'karl' work['block'] = newwork return json.dumps(work)
class RpcTest(TestCase): client = None def setUp(self): self.client = EthJsonRpc() def tearDown(self): self.client.close() def test_eth_coinbase(self): coinbase = self.client.eth_coinbase() self.assertTrue(coinbase.startswith("0x"), "coinbase should be a hex string") self.assertEqual(len(coinbase), 42, "coinbase is a string with length of 42") def test_eth_blockNumber(self): block_number = self.client.eth_blockNumber() self.assertGreater( block_number, 0, "we have made sure the blockNumber is > 0 for testing") def test_eth_getBalance(self): balance = self.client.eth_getBalance( address="0x0000000000000000000000000000000000000000") self.assertGreater(balance, 10000000, "specified address should have a lot of balance") def test_eth_getStorageAt(self): storage = self.client.eth_getStorageAt( address="0x0000000000000000000000000000000000000000") self.assertEqual( storage, "0x0000000000000000000000000000000000000000000000000000000000000000", ) def test_eth_getBlockByNumber(self): block = self.client.eth_getBlockByNumber(0) self.assertEqual( block["extraData"], "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", "the data of the first block should be right", ) def test_eth_getCode(self): # TODO: can't find a proper address for getting code code = self.client.eth_getCode( address="0x0000000000000000000000000000000000000001") self.assertEqual(code, "0x") def test_eth_getTransactionReceipt(self): transaction = self.client.eth_getTransactionReceipt( tx_hash= "0xe363505adc6b2996111f8bd774f8653a61d244cc6567b5372c2e781c6c63b737" ) self.assertEqual(transaction["from"], "0x22f2dcff5ad78c3eb6850b5cb951127b659522e6") self.assertEqual(transaction["to"], "0x0000000000000000000000000000000000000000")
def __init__(self, address: Text, rpc: Optional[Text] = None): assert address is not None, "No contract address provided" if rpc is None: eth_json_rpc = EthJsonRpc() else: match = re.match(r'(http(s)?:\/\/)?([a-zA-Z0-9\.\-]+)(:([0-9]+))?(\/.+)?', rpc) if match: host = match.group(3) port = match.group(5) if match.group(4) else None path = match.group(6) if match.group(6) else '' tls = bool(match.group(2)) log.debug('Parsed RPC provider params: host=%s, port=%s, tls=%r, path=%s', host, port, tls, path) eth_json_rpc = EthJsonRpc(host=host + path, port=port, tls=tls) else: raise ValidationError('Invalid JSON RPC URL provided: "%s"' % rpc) self._dyn_loader = DynLoader(eth_json_rpc) self._address = address
def set_api_rpc(self, rpc=None, rpctls=False): if rpc == 'ganache': rpcconfig = ('localhost', 8545, False) else: m = re.match(r'infura-(.*)', rpc) if m and m.group(1) in ['mainnet', 'rinkeby', 'kovan', 'ropsten']: rpcconfig = (m.group(1) + '.infura.io', 443, True) else: try: host, port = rpc.split(":") rpcconfig = (host, int(port), rpctls) except ValueError: raise CriticalError( "Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'" ) if rpcconfig: self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2]) logging.info("Using RPC settings: %s" % str(rpcconfig)) else: raise CriticalError( "Invalid RPC settings, check help for details.")
def set_api_rpc_infura(self): """Set the RPC mode to INFURA on mainnet.""" self.eth = EthJsonRpc("mainnet.infura.io", 443, True) log.info("Using INFURA for RPC queries")
from flask import render_template, request, Flask, g, url_for import time import os import json import random import sys import traceback import sqlite3 import models_elastic as nakatomidb from datetime import datetime from mythril.ethereum.interface.rpc.client import EthJsonRpc infura = EthJsonRpc("mainnet.infura.io", 443, True) app = Flask(__name__, static_url_path='/static') app.config.from_object('config') app.jinja_env.add_extension('jinja2.ext.do') conn = sqlite3.connect('coverage.db', check_same_thread=False) #c = conn.cursor() @app.before_request def before_request(): g.preview_length = app.config['PREVIEW_LENGTH'] @app.before_first_request
def set_api_rpc_infura(self): self.eth = EthJsonRpc("mainnet.infura.io", 443, True) logging.info("Using INFURA for RPC queries")
def set_api_rpc_localhost(self): self.eth = EthJsonRpc("localhost", 8545) logging.info("Using default RPC settings: http://localhost:8545")
def exploits_from_mythril( rpcHTTP="http://localhost:8545", rpcWS=None, rpcIPC=None, timeout=300, contract="", account_pk="", strategy="bfs", modules=["ether_thief", "suicide"], transaction_count=3, execution_timeout=120, max_depth=32, loop_bound=3, disable_dependency_pruning=False, onchain_storage_access=True, enable_online_lookup=False, ): if re.match(r"^https", rpcHTTP): host, port = rpcHTTP[8:].split(":") rpc_tls = True else: host, port = rpcHTTP[7:].split(":") rpc_tls = False try: # Disassembler disassembler = MythrilDisassembler( eth=EthJsonRpc(host=host, port=port, tls=rpc_tls), solc_version=None, solc_args=None, enable_online_lookup=enable_online_lookup, ) disassembler.load_from_address(contract) # Analyzer analyzer = MythrilAnalyzer( strategy=strategy, disassembler=disassembler, address=contract, execution_timeout=execution_timeout, max_depth=max_depth, loop_bound=loop_bound, disable_dependency_pruning=disable_dependency_pruning, onchain_storage_access=onchain_storage_access, ) # Generate report report = analyzer.fire_lasers( modules=modules, transaction_count=transaction_count ) except CriticalError as e: print(e) return [] if rpcIPC is not None: print("Connecting to IPC: {rpc}.".format(rpc=rpcIPC)) w3 = Web3(Web3.IPCProvider(rpcIPC, timeout=timeout)) elif rpcWS is not None: print("Connecting to WebSocket: {rpc}.".format(rpc=rpcWS)) w3 = Web3(Web3.WebsocketProvider(rpcWS, websocket_kwargs={"timeout": timeout})) else: print("Connecting to HTTP: {rpc}.".format(rpc=rpcHTTP)) w3 = Web3(Web3.HTTPProvider(rpcHTTP, request_kwargs={"timeout": timeout})) exploits = [] for ri in report.issues: txs = [] issue = report.issues[ri] for si in issue.transaction_sequence["steps"]: txs.append(Tx(data=si["input"], value=si["value"], name=si["name"])) exploits.append( Exploit( txs=txs, w3=w3, contract=contract, account=private_key_to_account(account_pk), account_pk=account_pk, title=issue.title, description=issue.description, swc_id=issue.swc_id, ) ) return exploits
def set_api_rpc_infura(self) -> None: """Set the RPC mode to INFURA on Mainnet.""" log.info("Using INFURA Main Net for RPC queries") self.eth = EthJsonRpc("mainnet.infura.io", 443, True)
class Mythril(object): """ Mythril main interface class. 1. create mythril object 2. set rpc or leveldb interface if needed 3. load contracts (from solidity, bytecode, address) 4. fire_lasers Example: mythril = Mythril() mythril.set_api_rpc_infura() # (optional) other API adapters mythril.set_api_rpc(args) mythril.set_api_rpc_localhost() mythril.set_api_leveldb(path) # (optional) other func mythril.analyze_truffle_project(args) mythril.search_db(args) # load contract mythril.load_from_bytecode(bytecode) mythril.load_from_address(address) mythril.load_from_solidity(solidity_file) # analyze print(mythril.fire_lasers(args).as_text()) # (optional) graph for contract in mythril.contracts: print(mythril.graph_html(args)) # prints html or save it to file # (optional) other funcs mythril.dump_statespaces(args) mythril.disassemble(contract) mythril.get_state_variable_from_storage(args) """ def __init__(self, solv=None, solc_args=None, dynld=False): self.solv = solv self.solc_args = solc_args self.dynld = dynld self.mythril_dir = self._init_mythril_dir() self.sigs = signatures.SignatureDb() try: self.sigs.open( ) # tries mythril_dir/signatures.json by default (provide path= arg to make this configurable) except FileNotFoundError as fnfe: logging.info( "No signature database found. Creating database if sigs are loaded in: " + self.sigs.signatures_file + "\n" + "Consider replacing it with the pre-initialized database at https://raw.githubusercontent.com/ConsenSys/mythril/master/signatures.json" ) except json.JSONDecodeError as jde: 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 self.eth_db = None # ethereum LevelDB client self.contracts = [] # loaded contracts @staticmethod def _init_mythril_dir(): try: mythril_dir = os.environ['MYTHRIL_DIR'] except KeyError: mythril_dir = os.path.join(os.path.expanduser('~'), ".mythril") # Initialize data directory and signature database if not os.path.exists(mythril_dir): logging.info("Creating mythril data directory") os.mkdir(mythril_dir) 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 """ system = platform.system().lower() leveldb_fallback_dir = os.path.expanduser('~') if system.startswith("darwin"): leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "Library", "Ethereum") elif system.startswith("windows"): leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "AppData", "Roaming", "Ethereum") else: 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(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', 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 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( self.sigs, *args, **kwargs) # just passthru by passing signatures for now @staticmethod def _init_solc_binary(version): # Figure out solc binary and version # Only proper versions are supported. No nightlies, commits etc (such as available in remix) if version: # tried converting input to semver, seemed not necessary so just slicing for now if version == str(solc.main.get_solc_version())[:6]: logging.info('Given version matches installed version') try: solc_binary = os.environ['SOLC'] except KeyError: solc_binary = 'solc' else: if util.solc_exists(version): logging.info('Given version is already installed') else: try: solc.install_solc('v' + version) except SolcError: raise CriticalError( "There was an error when trying to install the specified solc version" ) solc_binary = os.path.join(os.environ['HOME'], ".py-solc/solc-v" + version, "bin/solc") logging.info("Setting the compiler to " + str(solc_binary)) else: try: solc_binary = os.environ['SOLC'] except KeyError: solc_binary = 'solc' return solc_binary def set_api_leveldb(self, leveldb): self.eth_db = EthLevelDB(leveldb) self.eth = self.eth_db return self.eth def set_api_rpc_infura(self): self.eth = EthJsonRpc('mainnet.infura.io', 443, True) logging.info("Using INFURA for RPC queries") def set_api_rpc(self, rpc=None, rpctls=False): if rpc == 'ganache': rpcconfig = ('localhost', 8545, False) else: m = re.match(r'infura-(.*)', rpc) if m and m.group(1) in ['mainnet', 'rinkeby', 'kovan', 'ropsten']: rpcconfig = (m.group(1) + '.infura.io', 443, True) else: try: host, port = rpc.split(":") rpcconfig = (host, int(port), rpctls) except ValueError: raise CriticalError( "Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'" ) if rpcconfig: self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2]) logging.info("Using RPC settings: %s" % str(rpcconfig)) else: raise CriticalError( "Invalid RPC settings, check help for details.") def set_api_rpc_localhost(self): 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 == '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(contract, address, balance): print("Address: " + address + ", balance: " + str(balance)) try: self.eth_db.search(search, search_callback) except SyntaxError: raise CriticalError("Syntax error in search expression.") def contract_hash_to_address(self, hash): if not re.match(r'0x[a-fA-F0-9]{64}', hash): raise CriticalError( "Invalid address hash. Expected format is '0x...'.") print(self.eth_db.contract_hash_to_address(hash)) def load_from_bytecode(self, code): address = util.get_indexed_address(0) self.contracts.append(ETHContract(code, name="MAIN")) return address, self.contracts[ -1] # return address and contract object def load_from_address(self, address): if not re.match(r'0x[a-fA-F0-9]{40}', address): raise CriticalError( "Invalid contract address. Expected format is '0x...'.") try: code = self.eth.eth_getCode(address) except FileNotFoundError as e: raise CriticalError("IPC error: " + str(e)) except ConnectionError as e: raise CriticalError( "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." ) except Exception as e: raise CriticalError("IPC / RPC error: " + str(e)) else: if code == "0x" or code == "0x0": raise CriticalError( "Received an empty response from eth_getCode. Check the contract address and verify that you are on the correct chain." ) else: self.contracts.append(ETHContract(code, name=address)) return address, self.contracts[ -1] # return address and contract object def load_from_solidity(self, solidity_files): """ UPDATES self.sigs! :param solidity_files: :return: """ address = util.get_indexed_address(0) contracts = [] for file in solidity_files: if ":" in file: file, contract_name = file.split(":") else: contract_name = None file = os.path.expanduser(file) try: # import signatures from solidity source self.sigs.import_from_solidity_source( file, solc_binary=self.solc_binary, solc_args=self.solc_args) # 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) contracts.append(contract) else: for contract in get_contracts_from_file( file, solc_args=self.solc_args): self.contracts.append(contract) contracts.append(contract) except FileNotFoundError: raise CriticalError("Input file not found: " + file) except CompilerError as e: raise CriticalError(e) except NoContractFoundError: logging.info("The file " + file + " does not contain a compilable contract.") return address, contracts def dump_statespace(self, strategy, contract, address=None, max_depth=None, execution_timeout=None, create_timeout=None): sym = SymExecWrapper( contract, address, strategy, dynloader=DynLoader(self.eth) if self.dynld else None, max_depth=max_depth, execution_timeout=execution_timeout, create_timeout=create_timeout) return get_serializable_statespace(sym) def graph_html(self, strategy, contract, address, max_depth=None, enable_physics=False, phrackify=False, execution_timeout=None, create_timeout=None): sym = SymExecWrapper( contract, address, strategy, dynloader=DynLoader(self.eth) if self.dynld else None, max_depth=max_depth, execution_timeout=execution_timeout, create_timeout=create_timeout) return generate_graph(sym, physics=enable_physics, phrackify=phrackify) def fire_lasers(self, strategy, contracts=None, address=None, modules=None, verbose_report=False, max_depth=None, execution_timeout=None, create_timeout=None, max_transaction_count=None): all_issues = [] for contract in (contracts or self.contracts): sym = SymExecWrapper( contract, address, strategy, dynloader=DynLoader(self.eth) if self.dynld else None, max_depth=max_depth, execution_timeout=execution_timeout, create_timeout=create_timeout, max_transaction_count=max_transaction_count) issues = fire_lasers(sym, modules) if type(contract) == SolidityContract: for issue in issues: issue.add_code_info(contract) all_issues += issues # Finally, output the results report = Report(verbose_report) for issue in all_issues: report.append_issue(issue) return report def get_state_variable_from_storage(self, address, params=None): if params is None: params = [] (position, length, mappings) = (0, 1, []) try: if params[0] == "mapping": if len(params) < 3: raise CriticalError("Invalid number of parameters.") position = int(params[1]) position_formatted = utils.zpad( utils.int_to_big_endian(position), 32) for i in range(2, len(params)): key = bytes(params[i], 'utf8') key_formatted = utils.rzpad(key, 32) mappings.append( int.from_bytes(utils.sha3(key_formatted + position_formatted), byteorder='big')) length = len(mappings) if length == 1: position = mappings[0] else: if len(params) >= 4: raise CriticalError("Invalid number of parameters.") if len(params) >= 1: position = int(params[0]) if len(params) >= 2: length = int(params[1]) if len(params) == 3 and params[2] == "array": position_formatted = utils.zpad( utils.int_to_big_endian(position), 32) position = int.from_bytes(utils.sha3(position_formatted), byteorder='big') except ValueError: raise CriticalError( "Invalid storage index. Please provide a numeric value.") outtxt = [] try: if length == 1: outtxt.append("{}: {}".format( position, self.eth.eth_getStorageAt(address, position))) else: if len(mappings) > 0: for i in range(0, len(mappings)): position = mappings[i] outtxt.append("{}: {}".format( hex(position), self.eth.eth_getStorageAt(address, position))) else: for i in range(position, position + length): outtxt.append("{}: {}".format( hex(i), self.eth.eth_getStorageAt(address, i))) except FileNotFoundError as e: raise CriticalError("IPC error: " + str(e)) except ConnectionError as e: raise CriticalError( "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." ) return '\n'.join(outtxt) @staticmethod def disassemble(contract): return contract.get_easm() @staticmethod def hash_for_function_signature(sig): return "0x%s" % utils.sha3(sig)[:4].hex()
def set_api_rpc_localhost(self): """Set the RPC mode to a local instance.""" self.eth = EthJsonRpc("localhost", 8545) log.info("Using default RPC settings: http://localhost:8545")
def setUp(self): self.client = EthJsonRpc()
class Mythril(object): """Mythril main interface class. 1. create mythril object 2. set rpc or leveldb interface if needed 3. load contracts (from solidity, bytecode, address) 4. fire_lasers .. code-block:: python mythril = Mythril() mythril.set_api_rpc_infura() # (optional) other API adapters mythril.set_api_rpc(args) mythril.set_api_rpc_localhost() mythril.set_api_leveldb(path) # (optional) other func mythril.analyze_truffle_project(args) mythril.search_db(args) # load contract mythril.load_from_bytecode(bytecode) mythril.load_from_address(address) mythril.load_from_solidity(solidity_file) # analyze print(mythril.fire_lasers(args).as_text()) # (optional) graph for contract in mythril.contracts: # prints html or save it to file print(mythril.graph_html(args)) # (optional) other funcs mythril.dump_statespaces(args) mythril.disassemble(contract) mythril.get_state_variable_from_storage(args) """ def __init__( self, solv=None, solc_args=None, dynld=False, enable_online_lookup=False, onchain_storage_access=True, ): self.solv = solv self.solc_args = solc_args self.dynld = dynld self.onchain_storage_access = onchain_storage_access self.enable_online_lookup = enable_online_lookup self.mythril_dir = self._init_mythril_dir() # tries mythril_dir/signatures.db by default (provide path= arg to make this configurable) self.sigs = signatures.SignatureDB( enable_online_lookup=self.enable_online_lookup) 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 self.eth_db = None # ethereum LevelDB client self.contracts = [] # loaded contracts @staticmethod def _init_mythril_dir(): try: mythril_dir = os.environ["MYTHRIL_DIR"] except KeyError: mythril_dir = os.path.join(os.path.expanduser("~"), ".mythril") if not os.path.exists(mythril_dir): # Initialize data directory log.info("Creating mythril data directory") os.mkdir(mythril_dir) db_path = str(Path(mythril_dir) / "signatures.db") if not os.path.exists(db_path): # if the default mythril dir doesn't contain a signature DB # initialize it with the default one from the project root asset_dir = Path(__file__).parent / "support" / "assets" copyfile(str(asset_dir / "signatures.db"), db_path) 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 """ system = platform.system().lower() leveldb_fallback_dir = os.path.expanduser("~") if system.startswith("darwin"): leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "Library", "Ethereum") elif system.startswith("windows"): leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "AppData", "Roaming", "Ethereum") else: 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(self.config_path): log.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", 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 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): """ :param args: :param kwargs: :return: """ return analyze_truffle_project( self.sigs, *args, **kwargs) # just passthru by passing signatures for now @staticmethod def _init_solc_binary(version): """Figure out solc binary and version. Only proper versions are supported. No nightlies, commits etc (such as available in remix). """ if not version: return os.environ.get("SOLC") or "solc" # tried converting input to semver, seemed not necessary so just slicing for now main_version = solc.main.get_solc_version_string() main_version_number = re.match(r"\d+.\d+.\d+", main_version) if main_version is None: raise CriticalError( "Could not extract solc version from string {}".format( main_version)) if version == main_version_number: log.info("Given version matches installed version") solc_binary = os.environ.get("SOLC") or "solc" else: solc_binary = util.solc_exists(version) if solc_binary: log.info("Given version is already installed") else: try: solc.install_solc("v" + version) solc_binary = util.solc_exists(version) if not solc_binary: raise SolcError() except SolcError: raise CriticalError( "There was an error when trying to install the specified solc version" ) log.info("Setting the compiler to %s", solc_binary) return solc_binary def set_api_leveldb(self, leveldb): """ :param leveldb: :return: """ self.eth_db = EthLevelDB(leveldb) self.eth = self.eth_db return self.eth def set_api_rpc_infura(self): """Set the RPC mode to INFURA on mainnet.""" self.eth = EthJsonRpc("mainnet.infura.io", 443, True) log.info("Using INFURA for RPC queries") def set_api_rpc(self, rpc=None, rpctls=False): """ :param rpc: :param rpctls: """ if rpc == "ganache": rpcconfig = ("localhost", 8545, False) else: m = re.match(r"infura-(.*)", rpc) if m and m.group(1) in ["mainnet", "rinkeby", "kovan", "ropsten"]: rpcconfig = (m.group(1) + ".infura.io", 443, True) else: try: host, port = rpc.split(":") rpcconfig = (host, int(port), rpctls) except ValueError: raise CriticalError( "Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'" ) if rpcconfig: self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2]) log.info("Using RPC settings: %s" % str(rpcconfig)) else: raise CriticalError( "Invalid RPC settings, check help for details.") def set_api_rpc_localhost(self): """Set the RPC mode to a local instance.""" self.eth = EthJsonRpc("localhost", 8545) log.info("Using default RPC settings: http://localhost:8545") def set_api_from_config_path(self): """Set the RPC mode based on a given config file.""" 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 == "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): """ :param search: """ def search_callback(_, address, balance): """ :param _: :param address: :param balance: """ print("Address: " + address + ", balance: " + str(balance)) try: self.eth_db.search(search, search_callback) except SyntaxError: raise CriticalError("Syntax error in search expression.") def contract_hash_to_address(self, hash): """ :param hash: """ if not re.match(r"0x[a-fA-F0-9]{64}", hash): raise CriticalError( "Invalid address hash. Expected format is '0x...'.") print(self.eth_db.contract_hash_to_address(hash)) def load_from_bytecode(self, code, bin_runtime=False, address=None): """ :param code: :param bin_runtime: :param address: :return: """ if address is None: address = util.get_indexed_address(0) if bin_runtime: self.contracts.append( EVMContract( code=code, name="MAIN", enable_online_lookup=self.enable_online_lookup, )) else: self.contracts.append( EVMContract( creation_code=code, name="MAIN", enable_online_lookup=self.enable_online_lookup, )) return address, self.contracts[ -1] # return address and contract object def load_from_address(self, address): """ :param address: :return: """ if not re.match(r"0x[a-fA-F0-9]{40}", address): raise CriticalError( "Invalid contract address. Expected format is '0x...'.") try: code = self.eth.eth_getCode(address) except FileNotFoundError as e: raise CriticalError("IPC error: " + str(e)) except ConnectionError: raise CriticalError( "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." ) except Exception as e: raise CriticalError("IPC / RPC error: " + str(e)) else: if code == "0x" or code == "0x0": raise CriticalError( "Received an empty response from eth_getCode. Check the contract address and verify that you are on the correct chain." ) else: self.contracts.append( EVMContract( code, name=address, enable_online_lookup=self.enable_online_lookup, )) return address, self.contracts[ -1] # return address and contract object def load_from_solidity(self, solidity_files): """ :param solidity_files: :return: """ address = util.get_indexed_address(0) contracts = [] for file in solidity_files: if ":" in file: file, contract_name = file.split(":") else: contract_name = None file = os.path.expanduser(file) try: # import signatures from solidity source self.sigs.import_solidity_file(file, solc_binary=self.solc_binary, solc_args=self.solc_args) if contract_name is not None: contract = SolidityContract( input_file=file, name=contract_name, solc_args=self.solc_args, solc_binary=self.solc_binary, ) self.contracts.append(contract) contracts.append(contract) else: for contract in get_contracts_from_file( input_file=file, solc_args=self.solc_args, solc_binary=self.solc_binary, ): self.contracts.append(contract) contracts.append(contract) except FileNotFoundError: raise CriticalError("Input file not found: " + file) except CompilerError as e: raise CriticalError(e) except NoContractFoundError: log.error("The file " + file + " does not contain a compilable contract.") return address, contracts def dump_statespace( self, strategy, contract, address=None, max_depth=None, execution_timeout=None, create_timeout=None, enable_iprof=False, ): """ :param strategy: :param contract: :param address: :param max_depth: :param execution_timeout: :param create_timeout: :return: """ sym = SymExecWrapper( contract, address, strategy, dynloader=DynLoader( self.eth, storage_loading=self.onchain_storage_access, contract_loading=self.dynld, ), max_depth=max_depth, execution_timeout=execution_timeout, create_timeout=create_timeout, enable_iprof=enable_iprof, ) return get_serializable_statespace(sym) def graph_html( self, strategy, contract, address, max_depth=None, enable_physics=False, phrackify=False, execution_timeout=None, create_timeout=None, enable_iprof=False, ): """ :param strategy: :param contract: :param address: :param max_depth: :param enable_physics: :param phrackify: :param execution_timeout: :param create_timeout: :return: """ sym = SymExecWrapper( contract, address, strategy, dynloader=DynLoader( self.eth, storage_loading=self.onchain_storage_access, contract_loading=self.dynld, ), max_depth=max_depth, execution_timeout=execution_timeout, create_timeout=create_timeout, enable_iprof=enable_iprof, ) return generate_graph(sym, physics=enable_physics, phrackify=phrackify) def fire_lasers( self, strategy, contracts=None, address=None, modules=None, verbose_report=False, max_depth=None, execution_timeout=None, create_timeout=None, transaction_count=None, enable_iprof=False, ): """ :param strategy: :param contracts: :param address: :param modules: :param verbose_report: :param max_depth: :param execution_timeout: :param create_timeout: :param transaction_count: :return: """ all_issues = [] for contract in contracts or self.contracts: try: sym = SymExecWrapper( contract, address, strategy, dynloader=DynLoader( self.eth, storage_loading=self.onchain_storage_access, contract_loading=self.dynld, ), max_depth=max_depth, execution_timeout=execution_timeout, create_timeout=create_timeout, transaction_count=transaction_count, modules=modules, compulsory_statespace=False, enable_iprof=enable_iprof, ) issues = fire_lasers(sym, modules) except KeyboardInterrupt: log.critical("Keyboard Interrupt") issues = retrieve_callback_issues(modules) except Exception: log.critical( "Exception occurred, aborting analysis. Please report this issue to the Mythril GitHub page.\n" + traceback.format_exc()) issues = retrieve_callback_issues(modules) for issue in issues: issue.add_code_info(contract) all_issues += issues source_data = Source() source_data.get_source_from_contracts_list(self.contracts) # Finally, output the results report = Report(verbose_report, source_data) for issue in all_issues: report.append_issue(issue) return report def get_state_variable_from_storage(self, address, params=None): """ :param address: :param params: :return: """ if params is None: params = [] (position, length, mappings) = (0, 1, []) try: if params[0] == "mapping": if len(params) < 3: raise CriticalError("Invalid number of parameters.") position = int(params[1]) position_formatted = utils.zpad( utils.int_to_big_endian(position), 32) for i in range(2, len(params)): key = bytes(params[i], "utf8") key_formatted = utils.rzpad(key, 32) mappings.append( int.from_bytes( utils.sha3(key_formatted + position_formatted), byteorder="big", )) length = len(mappings) if length == 1: position = mappings[0] else: if len(params) >= 4: raise CriticalError("Invalid number of parameters.") if len(params) >= 1: position = int(params[0]) if len(params) >= 2: length = int(params[1]) if len(params) == 3 and params[2] == "array": position_formatted = utils.zpad( utils.int_to_big_endian(position), 32) position = int.from_bytes(utils.sha3(position_formatted), byteorder="big") except ValueError: raise CriticalError( "Invalid storage index. Please provide a numeric value.") outtxt = [] try: if length == 1: outtxt.append("{}: {}".format( position, self.eth.eth_getStorageAt(address, position))) else: if len(mappings) > 0: for i in range(0, len(mappings)): position = mappings[i] outtxt.append("{}: {}".format( hex(position), self.eth.eth_getStorageAt(address, position), )) else: for i in range(position, position + length): outtxt.append("{}: {}".format( hex(i), self.eth.eth_getStorageAt(address, i))) except FileNotFoundError as e: raise CriticalError("IPC error: " + str(e)) except ConnectionError: raise CriticalError( "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." ) return "\n".join(outtxt) @staticmethod def disassemble(contract): """ :param contract: :return: """ return contract.get_easm() @staticmethod def hash_for_function_signature(sig): """ :param sig: :return: """ return "0x%s" % utils.sha3(sig)[:4].hex()
passive_account = Account("0x325345346564645654645", code=Disassembly("6060604061626364")) environment = Environment(active_account, None, None, None, None, None) world_state = WorldState() world_state.put_account(active_account) world_state.put_account(passive_account) return GlobalState(world_state, environment, None, MachineState(gas_limit=8000000)) @pytest.mark.parametrize( "addr, eth, code_len", [ ( "0xb09C477eCDAd49DD5Ac26c2C64914C3a6693843a", EthJsonRpc("rinkeby.infura.io", 443, True), 1548, ), ( "0x863DF6BFa4469f3ead0bE8f9F2AAE51c91A907b4", EthJsonRpc("mainnet.infura.io", 443, True), 0, ), ( "0x325345346564645654645", EthJsonRpc("mainnet.infura.io", 443, True), 16, ), # This contract tests Address Cache ], ) def test_extraction(addr, eth, code_len):