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 _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: logging.info("Given version matches installed version") solc_binary = os.environ.get("SOLC") or "solc" else: solc_binary = util.solc_exists(version) if solc_binary: 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" ) logging.info("Setting the compiler to %s", solc_binary) return solc_binary
def load_from_address(self, address: str) -> Tuple[str, EVMContract]: """ Returns the contract given it's on chain address :param address: The on chain address of a contract :return: tuple(address, contract) """ 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)) 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): """ 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: signatures.add_signatures_from_file(file, self.sigs) contract = SolidityContract(file, contract_name, solc_args=self.solc_args) logging.info("Analyzing contract %s:%s" % (file, contract.name)) 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.") else: self.contracts.append(contract) contracts.append(contract) self._update_signatures(self.sigs) return address, contracts
def init_db(self): contract_storage, _ = get_persistent_storage(self.mythril_dir) try: contract_storage.initialize(self.eth) except FileNotFoundError as e: raise CriticalError("Error syncing database over IPC: " + 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.")
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( 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: logging.error("The file " + file + " does not contain a compilable contract.") return address, contracts
def _init_solc_binary(version: str) -> str: """ Only proper versions are supported. No nightlies, commits etc (such as available in remix). :param version: Version of the solc binary required :return: The solc binary of the corresponding version """ 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 and solc_binary != util.solc_exists( "default_ubuntu_version"): log.info("Given version is already installed") else: if version.startswith("0.4"): try: solc.install_solc("v" + version) except solc.exceptions.SolcError: raise CriticalError( "There was an error when trying to install the specified solc version" ) elif sys.version_info[1] >= 6: # solcx supports python 3.6+ try: solcx.install_solc("v" + version) except solcx.exceptions.SolcError: raise CriticalError( "There was an error when trying to install the specified solc version" ) else: raise CriticalError( "Py-Solc doesn't support 0.5.*. You can switch to python 3.6 which uses solcx." ) solc_binary = util.solc_exists(version) if not solc_binary: raise solc.exceptions.SolcError() log.info("Setting the compiler to %s", solc_binary) return solc_binary
def get_state_variable_from_storage(self, address, 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)
def _init_solc_binary(self, 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_ipc(self): try: self.eth = EthIpc() except Exception as e: raise CriticalError( "IPC initialization failed. Please verify that your local Ethereum node is running, or use the -i flag to connect to INFURA. \n" + str(e))
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
def search_db(self, search): def search_callback(_, 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 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 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 set_api_rpc(self, rpc=None, rpctls=False): if rpc == 'ganache': rpcconfig = ('localhost', 7545, 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 contract_hash_to_address(self, contract_hash): """ Returns address of the corresponding hash by searching the leveldb :param contract_hash: Hash to be searched """ if not re.match(r"0x[a-fA-F0-9]{64}", contract_hash): raise CriticalError( "Invalid address hash. Expected format is '0x...'.") print(self.leveldb.contract_hash_to_address(contract_hash))
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 with open(file, encoding="utf-8") as f: self.sigs.import_from_solidity_source(f.read()) contract = SolidityContract(file, contract_name, solc_args=self.solc_args) logging.info("Analyzing contract %s:%s" % (file, contract.name)) 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.") else: self.contracts.append(contract) contracts.append(contract) # Save updated function signatures self.sigs.write( ) # dump signatures to disk (previously opened file or default location) return address, contracts
def search_db(self, search): def search_callback(code_hash, code, addresses, balances): for i in range(0, len(addresses)): print("Address: " + addresses[i] + ", balance: " + str(balances[i])) try: self.ethDb.search(search, search_callback) except SyntaxError: raise CriticalError("Syntax error in search expression.")
def search_db(self, search): def search_callback(code_hash, code, addresses, balances): print("Matched contract with code hash " + code_hash) for i in range(0, len(addresses)): print("Address: " + addresses[i] + ", balance: " + str(balances[i])) contract_storage, _ = get_persistent_storage(self.mythril_dir) try: if self.dbtype == "leveldb": contract_storage.search(search, search_callback) else: self.ethDB.search(search, search_callback) except SyntaxError: raise CriticalError("Syntax error in search expression.")
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() self.sigs = {} try: # tries mythril_dir/signatures.json by default (provide path= arg to make this configurable) self.sigs = signatures.SignatureDb( enable_online_lookup=self.enable_online_lookup) except FileNotFoundError as e: logging.info(str(e)) # Create empty db file if none exists f = open(os.path.join(self.mythril_dir, "signatures.json"), "w") f.write("{}") f.close() self.sigs = signatures.SignatureDb( enable_online_lookup=self.enable_online_lookup) except json.JSONDecodeError as e: raise CriticalError(str(e)) 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
def _init_signatures(self): # If no function signature file exists, create it. Function signatures from Solidity source code are added automatically. signatures_file = os.path.join(self.mythril_dir, 'signatures.json') sigs = {} if not os.path.exists(signatures_file): logging.info("No signature database found. Creating empty database: " + signatures_file + "\n" + "Consider replacing it with the pre-initialized database at https://raw.githubusercontent.com/ConsenSys/mythril/master/signatures.json") with open(signatures_file, 'a') as f: json.dump({}, f) with open(signatures_file) as f: try: sigs = json.load(f) except json.JSONDecodeError as e: raise CriticalError("Invalid JSON in signatures file " + signatures_file + "\n" + str(e)) return signatures_file, sigs
def search_db(self, search): """ Searches the corresponding code :param search: The code part to be searched """ def search_callback(_, address, balance): """ :param _: :param address: The address of the contract with the code in search :param balance: The balance of the corresponding contract """ print("Address: " + address + ", balance: " + str(balance)) try: self.leveldb.search(search, search_callback) except SyntaxError: raise CriticalError("Syntax error in search expression.")
def get_state_variable_from_storage(self, address: str, params: Optional[List[str]] = None ) -> str: """ Get variables from the storage :param address: The contract address :param params: The list of parameters param types: [position, length] or ["mapping", position, key1, key2, ... ] or [position, length, array] :return: The corresponding storage slot and its value """ params = params or [] (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)
def load_from_solidity( self, solidity_files: List[str]) -> Tuple[str, List[SolidityContract]]: """ :param solidity_files: List of solidity_files :return: tuple of address, contract class list """ 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_settings_json=self.solc_settings_json, ) if contract_name is not None: contract = SolidityContract( input_file=file, name=contract_name, solc_settings_json=self.solc_settings_json, solc_binary=self.solc_binary, ) self.contracts.append(contract) contracts.append(contract) else: for contract in get_contracts_from_file( input_file=file, solc_settings_json=self.solc_settings_json, 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: error_msg = str(e) # Check if error is related to solidity version mismatch if ("Error: Source file requires different compiler version" in error_msg): # Grab relevant line "pragma solidity <solv>...", excluding any comments solv_pragma_line = error_msg.split("\n")[-3].split("//")[0] # Grab solidity version from relevant line solv_match = re.findall(r"[0-9]+\.[0-9]+\.[0-9]+", solv_pragma_line) error_suggestion = ("<version_number>" if len(solv_match) != 1 else solv_match[0]) error_msg = ( error_msg + '\nSolidityVersionMismatch: Try adding the option "--solv ' + error_suggestion + '"\n') raise CriticalError(error_msg) except NoContractFoundError: log.error("The file " + file + " does not contain a compilable contract.") return address, contracts
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.ethDb.contract_hash_to_address(hash))