def runTest(self): contract = ETHContract(self.code) xrefs = contract.get_xrefs() self.assertEqual(xrefs[0], "c3b2ae46792547a96b9f84405e36d0e07edcd05c", 'Error getting xrefs from contract')
def runTest(self): contract = ETHContract(self.code) disassembly = contract.get_easm() self.assertTrue( "PUSH1 0x60" in disassembly, 'Error obtaining EASM code through ETHContract.get_easm()')
def runTest(self): contract = ETHContract(self.code) instruction_list = contract.get_easm() self.assertTrue( "PUSH1 0x60" in instruction_list, 'Error obtaining EASM code through ETHContract.get_easm()')
def runTest(self): contract = ETHContract(self.code) self.assertTrue( contract.matches_expression("code#PUSH1# or code#PUSH1#"), 'Unexpected result in expression matching') self.assertFalse(contract.matches_expression("func#abcdef#"), 'Unexpected result in expression matching')
def runTest(self): contract = ETHContract(self.code) disassembly = contract.get_disassembly() self.assertEqual( len(disassembly), 71, 'Error disassembling code using ETHContract.get_disassembly()')
def runTest(self): contract = ETHContract(self.code, self.creation_code) disassembly = contract.get_disassembly() self.assertEqual( len(disassembly.instruction_list), 53, 'Error disassembling code using ETHContract.get_instruction_list()' )
def get_all_contracts(self): ''' get all contracts ''' if not self.all_contracts: self.all_contracts = [] self.active_contracts = [] self.instance_lists = [] state = self._get_head_state() accounts = state.get_all_accounts() for a in accounts: if a.code is not None: code = _encode_hex(a.code) md5 = hashlib.md5() md5.update(code.encode('UTF-8')) contract_hash = md5.digest() contract = ETHContract(code, name=contract_hash.hex()) self.all_contracts.append(contract) if a.balance != 0: md5 = InstanceList() md5.add(_encode_hex(a.address), a.balance) self.instance_lists.append(md5) self.active_contracts.append(contract) return self.all_contracts
def analyze(self, address): """ Analyse a contract using mythril :param address: Address of contract to analyse :return: Findings """ # The followin code is kinda straight from mythril, thanks ;) # Setup code = self.eth.eth_getCode(address) contract = ETHContract(code, name=address) sym = SymExecWrapper(contract, address, dynloader=DynLoader(self.eth)) # Starting analysis logging.debug( "Firing lasers on contract with address: {}".format(address)) issues = fire_lasers(sym) logging.debug("Found {} issues using mythril".format(len(issues))) # Build findings findings = [] for issue in issues: findings += [ Finding("Mythril analysis", issue.title, issue.description, issue.contract, issue.pc, issue.type) ] return findings
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: 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, enable_online_lookup=self.enable_online_lookup, )) return address, self.contracts[ -1] # return address and contract object
def symbolic_execute(code, address): contract = ETHContract(code) sym = SymExecWrapper(contract=contract, address=address, strategy="dfs") # populate graph object graph = MythrilSymExecGraph() for n in sym.nodes.values(): # g.add all nodes - just in case one is not connected which should not happen graph.add_node(n) for e in sym.edges: graph.add_edge(sym.nodes[e.node_from], sym.nodes[e.node_to], e) """ print(graph.iterate_graph()) print(sorted([x.uid for x in graph.nodes])) print(graph._first_added.uid) print(next(graph.get_basicblocks_by_address(0))) print(graph.get_basicblock_by_uid(0)) print(list(n.uid for n in graph.iterate_graph(graph.get_basicblock_by_uid(0)))) print(graph.find_all_paths(graph.get_basicblock_by_uid(0), graph.get_basicblock_by_uid(37))) print(graph.find_all_paths(graph.get_basicblock_by_uid(0),None)) print("streams") for stream in graph.get_streams(graph.get_basicblock_by_uid(0)): print([s.uid for s in stream]) print(list(s.get_current_instruction() for b,s in graph.get_block_and_state_by_instruction_name("PUSH",prn_cmp=lambda x,y:x.startswith(y)))) print(graph.get_stream_by_block(block=graph.get_block_by_uid(2))) """ # only work on the graph from now return graph
def load_from_bytecode(self, code): address = util.get_indexed_address(0) self.contracts.append( ETHContract(code, name="MAIN", enable_online_lookup=self.enable_online_lookup)) return address, self.contracts[ -1] # return address and contract object
def dynld(self, contract_address, dependency_address): logging.info("Dynld at contract " + contract_address + ": " + dependency_address) # Hack-ish m = re.match(r'(0x[0-9a-fA-F]{40})', dependency_address) if (m): dependency_address = m.group(1) else: m = re.search(r'storage_(\d+)', dependency_address) if (m): idx = int(m.group(1)) logging.info("Dynamic contract address at storage index " + str(idx)) # testrpc simply returns the address, geth response is more elaborate. dependency_address = self.eth.eth_getStorageAt( contract_address, position=idx, block='latest') if not re.match(r"0x[0-9a-f]{40}", dependency_address): dependency_address = "0x" + self.eth.eth_getStorageAt( contract_address, position=idx, block='latest')[26:] else: logging.info("Unable to resolve address.") return None logging.info("Dependency address: " + dependency_address) code = self.eth.eth_getCode(dependency_address) if (code == "0x"): return None else: contract = ETHContract(self.eth.eth_getCode(dependency_address), name=dependency_address, address=dependency_address) return contract.as_dict()
def get_contracts(self): ''' iterate through all contracts ''' for account in self._get_head_state().get_all_accounts(): if account.code is not None: code = _encode_hex(account.code) contract = ETHContract(code) yield contract, _encode_hex(account.address), account.balance
def initialize(self, rpchost, rpcport, sync_all): eth = EthJsonRpc(rpchost, rpcport) if self.last_block: blockNum = self.last_block print("Resuming synchronization from block " + str(blockNum)) else: blockNum = eth.eth_blockNumber() print("Starting synchronization from latest block: " + str(blockNum)) while (blockNum > 0): if not blockNum % 1000: print("Processing block " + str(blockNum) + ", " + str(len(self.contracts.keys())) + " individual contracts in database") block = eth.eth_getBlockByNumber(blockNum) for tx in block['transactions']: if not tx['to']: receipt = eth.eth_getTransactionReceipt(tx['hash']) contract_address = receipt['contractAddress'] contract_code = eth.eth_getCode(contract_address) contract_balance = eth.eth_getBalance(contract_address) if not contract_balance or sync_all: # skip contracts with zero balance (disable with --sync-all) continue code = ETHContract(contract_code, tx['input']) m = hashlib.md5() m.update(contract_code.encode('UTF-8')) contract_hash = m.digest() try: self.contracts[contract_hash] except KeyError: self.contracts[contract_hash] = code m = InstanceList() self.instance_lists[contract_hash] = m self.instance_lists[contract_hash].add( contract_address, contract_balance) transaction.commit() self.last_block = blockNum blockNum -= 1
def load_from_bytecode(self, code, bin_runtime=False): address = util.get_indexed_address(0) if bin_runtime: self.contracts.append( ETHContract( code=code, name="MAIN", enable_online_lookup=self.enable_online_lookup, )) else: self.contracts.append( ETHContract( creation_code=code, name="MAIN", enable_online_lookup=self.enable_online_lookup, )) return address, self.contracts[ -1] # return address and contract object
def get_contracts(self): """ iterate through all contracts """ for account in self.reader._get_head_state().get_all_accounts(): if account.code is not None: code = _encode_hex(account.code) contract = ETHContract(code, enable_online_lookup=False) yield contract, account.address, account.balance
def runTest(self): code = "0x60606040525b603c5b60006010603e565b9050593681016040523660008237602060003683856040603f5a0204f41560545760206000f35bfe5b50565b005b73c3b2ae46792547a96b9f84405e36d0e07edcd05c5b905600a165627a7a7230582062a884f947232ada573f95940cce9c8bfb7e4e14e21df5af4e884941afb55e590029" contract = ETHContract(code) sym = SymExecWrapper(contract, "0xd0a6E6C543bC68Db5db3A191B171A77407Ff7ccf") html = generate_graph(sym) self.assertTrue("0 PUSH1 0x60\\n2 PUSH1 0x40\\n4 MSTORE\\n5 JUMPDEST" in html)
def runTest(self): code = "0x60606040525b603c5b60006010603e565b9050593681016040523660008237602060003683856040603f5a0204f41560545760206000f35bfe5b50565b005b73c3b2ae46792547a96b9f84405e36d0e07edcd05c5b905600a165627a7a7230582062a884f947232ada573f95940cce9c8bfb7e4e14e21df5af4e884941afb55e590029" contract = ETHContract(code) statespace = StateSpace([contract]) html = generate_graph(statespace) self.assertTrue( "0 PUSH1 0x60\\n2 PUSH1 0x40\\n4 MSTORE\\n5 JUMPDEST" in html)
def get_contracts(self, search_all): ''' iterate through contracts with non-zero balance by default or all if search_all is set ''' for account in self._get_head_state().get_all_accounts(): if account.code is not None and (search_all or account.balance != 0): code = _encode_hex(account.code) md5 = hashlib.md5() md5.update(code.encode('UTF-8')) contract_hash = md5.digest() contract = ETHContract(code, name=contract_hash.hex()) yield contract, _encode_hex(account.address), account.balance
def get_contracts(self): ''' iterate through all contracts ''' indexer = AccountIndexer(self) for account in self.reader._get_head_state().get_all_accounts(): if account.code is not None: code = _encode_hex(account.code) contract = ETHContract(code) address = indexer.get_contract_by_hash(account.address) if address is None: address = account.address yield contract, _encode_hex(address), account.balance
def dynld(self, contract_address, dependency_address): logging.info("Dynld at contract " + contract_address + ": " + dependency_address) m = re.match(r'^(0x[0-9a-fA-F]{40})$', dependency_address) if (m): dependency_address = m.group(1) else: return None logging.info("Dependency address: " + dependency_address) code = self.eth.eth_getCode(dependency_address) if (code == "0x"): return None else: contract = ETHContract(self.eth.eth_getCode(dependency_address), name=dependency_address, address=dependency_address) return contract.as_dict()
def __call__(self, startblock): ''' Processesing method ''' logging.info("SYNC_BLOCKS %d to %d" % (startblock, startblock + BLOCKS_PER_THREAD)) contracts = {} for blockNum in range(startblock, startblock + BLOCKS_PER_THREAD): block = self.eth.eth_getBlockByNumber(blockNum) for tx in block['transactions']: if not tx['to']: receipt = self.eth.eth_getTransactionReceipt(tx['hash']) if receipt is not None: contract_address = receipt['contractAddress'] contract_code = self.eth.eth_getCode(contract_address) contract_balance = self.eth.eth_getBalance( contract_address) if not contract_balance: continue ethcontract = ETHContract(contract_code, tx['input']) m = hashlib.md5() m.update(contract_code.encode('UTF-8')) contract_hash = m.digest() contracts[contract_hash] = { 'ethcontract': ethcontract, 'address': contract_address, 'balance': contract_balance } blockNum -= 1 return contracts
def analyze_truffle_project(args): project_root = os.getcwd() build_dir = os.path.join(project_root, "build", "contracts") files = os.listdir(build_dir) for filename in files: if re.match(r'.*\.json$', filename) and filename != "Migrations.json": with open(os.path.join(build_dir, filename)) as cf: contractdata = json.load(cf) try: name = contractdata['contractName'] bytecode = contractdata['deployedBytecode'] except: print( "Unable to parse contract data. Please use Truffle 4 to compile your project." ) sys.exit() if (len(bytecode) < 4): continue ethcontract = ETHContract(bytecode, name=name) address = util.get_indexed_address(0) sym = SymExecWrapper(ethcontract, address, max_depth=10) issues = fire_lasers(sym) if not len(issues): if (args.outform == 'text' or args.outform == 'markdown'): print("# Analysis result for " + name + "\n\nNo issues found.") else: result = { 'contract': name, 'result': { 'success': True, 'error': None, 'issues': [] } } print(json.dumps(result)) else: report = Report() # augment with source code disassembly = ethcontract.disassembly source = contractdata['source'] deployedSourceMap = contractdata['deployedSourceMap'].split( ";") mappings = [] for item in deployedSourceMap: mapping = item.split(":") if len(mapping) > 0 and len(mapping[0]) > 0: offset = int(mapping[0]) if len(mapping) > 1 and len(mapping[1]) > 0: length = int(mapping[1]) if len(mapping) > 2 and len(mapping[2]) > 0: idx = int(mapping[2]) lineno = source[0:offset].count('\n') + 1 mappings.append(SourceMapping(idx, offset, length, lineno)) for issue in issues: index = helper.get_instruction_index( disassembly.instruction_list, issue.address) if index: try: offset = mappings[index].offset length = mappings[index].length issue.filename = filename issue.code = source[offset:offset + length] issue.lineno = mappings[index].lineno except IndexError: logging.debug("No code mapping at index %d", index) report.append_issue(issue) if (args.outform == 'json'): result = { 'contract': name, 'result': { 'success': True, 'error': None, 'issues': list(map(lambda x: x.as_dict(), issues)) } } print(json.dumps(result)) else: if (args.outform == 'text'): print("# Analysis result for " + name + ":\n\n" + report.as_text()) elif (args.outform == 'markdown'): print(report.as_markdown())
def analyze_truffle_project(args): project_root = os.getcwd() build_dir = os.path.join(project_root, "build", "contracts") files = os.listdir(build_dir) for filename in files: if re.match(r'.*\.json$', filename) and filename != "Migrations.json": with open(os.path.join(build_dir, filename)) as cf: contractdata = json.load(cf) try: name = contractdata['contractName'] bytecode = contractdata['deployedBytecode'] except: print( "Unable to parse contract data. Please use Truffle 4 to compile your project." ) sys.exit() if (len(bytecode) < 4): continue ethcontract = ETHContract(bytecode, name=name, address=util.get_indexed_address(0)) states = StateSpace([ethcontract], max_depth=10) issues = fire_lasers(states) if not len(issues): if (args.outform == 'text' or args.outform == 'markdown'): print("Analysis result for " + name + ": No issues found.") else: result = { 'contract': name, 'result': { 'success': True, 'error': None, 'issues': [] } } print(json.dumps(result)) else: report = Report() # augment with source code disassembly = ethcontract.get_disassembly() source = contractdata['source'] deployedSourceMap = contractdata['deployedSourceMap'].split( ";") mappings = [] i = 0 for item in deployedSourceMap: mapping = item.split(":") if len(mapping) > 0 and len(mapping[0]) > 0: offset = int(mapping[0]) if len(mapping) > 1 and len(mapping[1]) > 0: length = int(mapping[1]) mappings.append((int(offset), int(length))) for issue in issues: index = helper.get_instruction_index( disassembly.instruction_list, issue.pc) if index: issue.code_start = mappings[index][0] issue.code_length = mappings[index][1] issue.code = source[ mappings[index][0]:mappings[index][0] + mappings[index][1]] report.append_issue(issue) if (args.outform == 'json'): result = { 'contract': name, 'result': { 'success': True, 'error': None, 'issues': list(map(lambda x: x.as_dict(), issues)) } } print(json.dumps(result)) else: if (args.outform == 'text'): print("Analysis result for " + name + ":\n" + report.as_text()) elif (args.outform == 'markdown'): print("Analysis result for " + name + ":\n" + report.as_markdown())
def analyze_truffle_project(): project_root = os.getcwd() build_dir = os.path.join(project_root, "build", "contracts") files = os.listdir(build_dir) for filename in files: if re.match(r'.*\.json$', filename) and filename != "Migrations.json": with open(os.path.join(build_dir, filename)) as cf: contractdata = json.load(cf) try: name = contractdata['contractName'] bytecode = contractdata['deployedBytecode'] except: print("Unable to parse contract data. Please use Truffle 4 to compile your project.") sys.exit() if (len(bytecode) < 4): continue ethcontract= ETHContract(bytecode, name=name, address = util.get_indexed_address(0)) contracts = [ethcontract] states = StateSpace(contracts, max_depth = 10) report = fire_lasers(states) # augment with source code disassembly = ethcontract.get_disassembly() source = contractdata['source'] deployedSourceMap = contractdata['deployedSourceMap'].split(";") mappings = [] i = 0 while(i < len(deployedSourceMap)): m = re.search(r"^(\d+):*(\d+)", deployedSourceMap[i]) if (m): offset = m.group(1) length = m.group(2) else: m = re.search(r"^:(\d+)", deployedSourceMap[i]) if m: length = m.group(1) mappings.append((int(offset), int(length))) i += 1 for key, issue in report.issues.items(): index = helper.get_instruction_index(disassembly.instruction_list, issue.pc) if index: issue.code_start = mappings[index][0] issue.code_length = mappings[index][1] issue.code = source[mappings[index][0]: mappings[index][0] + mappings[index][1]] if len(report.issues): print("Analysis result for " + name + ":\n" + report.as_text()) else: print("Analysis result for " + name + ": No issues found.")
def initialize(self, eth, sync_all): if self.last_block: blockNum = self.last_block print("Resuming synchronization from block " + str(blockNum)) else: blockNum = eth.eth_blockNumber() print("Starting synchronization from latest block: " + str(blockNum)) ''' On INFURA, the latest block is not immediately available. Here is a workaround to allow for database sync over INFURA. Note however that this is extremely slow, contracts should always be loaded from a local node. ''' block = eth.eth_getBlockByNumber(blockNum) if not block: blockNum -= 2 while (blockNum > 0): if not blockNum % 1000: print("Processing block " + str(blockNum) + ", " + str(len(self.contracts.keys())) + " unique contracts in database") block = eth.eth_getBlockByNumber(blockNum) for tx in block['transactions']: if not tx['to']: receipt = eth.eth_getTransactionReceipt(tx['hash']) if receipt is not None: contract_address = receipt['contractAddress'] contract_code = eth.eth_getCode(contract_address) contract_balance = eth.eth_getBalance(contract_address) if not contract_balance and not sync_all: # skip contracts with zero balance (disable with --sync-all) continue code = ETHContract(contract_code, tx['input']) m = hashlib.md5() m.update(contract_code.encode('UTF-8')) contract_hash = m.digest() try: self.contracts[contract_hash] except KeyError: self.contracts[contract_hash] = code m = InstanceList() self.instance_lists[contract_hash] = m self.instance_lists[contract_hash].add( contract_address, contract_balance) transaction.commit() self.last_block = blockNum blockNum -= 1 # If we've finished initializing the database, start over from the end of the chain if we want to initialize again self.last_block = 0 transaction.commit()
def analyze_truffle_project(sigs, args): project_root = os.getcwd() build_dir = os.path.join(project_root, "build", "contracts") files = os.listdir(build_dir) for filename in files: if re.match(r".*\.json$", filename) and filename != "Migrations.json": with open(os.path.join(build_dir, filename)) as cf: contractdata = json.load(cf) try: name = contractdata["contractName"] bytecode = contractdata["deployedBytecode"] filename = PurePath(contractdata["sourcePath"]).name except KeyError: print( "Unable to parse contract data. Please use Truffle 4 to compile your project." ) sys.exit() if len(bytecode) < 4: continue get_sigs_from_truffle(sigs, contractdata) ethcontract = ETHContract(bytecode, name=name) address = util.get_indexed_address(0) sym = SymExecWrapper( ethcontract, address, args.strategy, max_depth=args.max_depth, create_timeout=args.create_timeout, execution_timeout=args.execution_timeout, max_transaction_count=args.max_transaction_count, ) issues = fire_lasers(sym) if not len(issues): if args.outform == "text" or args.outform == "markdown": print("# Analysis result for " + name + "\n\nNo issues found.") else: result = { "contract": name, "result": { "success": True, "error": None, "issues": [] }, } print(json.dumps(result)) else: report = Report() # augment with source code deployed_disassembly = ethcontract.disassembly constructor_disassembly = ethcontract.creation_disassembly source = contractdata["source"] deployed_source_map = contractdata["deployedSourceMap"].split( ";") source_map = contractdata["sourceMap"].split(";") deployed_mappings = get_mappings(source, deployed_source_map) constructor_mappings = get_mappings(source, source_map) for issue in issues: if issue.function == "constructor": mappings = constructor_mappings disassembly = constructor_disassembly else: mappings = deployed_mappings disassembly = deployed_disassembly index = get_instruction_index(disassembly.instruction_list, issue.address) if index: try: offset = mappings[index].offset length = mappings[index].length issue.filename = filename 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) report.append_issue(issue) if args.outform == "json": result = { "contract": name, "result": { "success": True, "error": None, "issues": list(map(lambda x: x.as_dict, issues)), }, } print(json.dumps(result)) else: if args.outform == "text": print("# Analysis result for " + name + ":\n\n" + report.as_text()) elif args.outform == "markdown": print(report.as_markdown())
def main(): parser = argparse.ArgumentParser( description='Security analysis of Ethereum smart contracts') parser.add_argument("solidity_file", nargs='*') commands = parser.add_argument_group('commands') commands.add_argument('-g', '--graph', help='generate a control flow graph', metavar='OUTPUT_FILE') commands.add_argument( '-x', '--fire-lasers', action='store_true', help='detect vulnerabilities, use with -c, -a or solidity file(s)') commands.add_argument( '-t', '--truffle', action='store_true', help='analyze a truffle project (run from project dir)') commands.add_argument('-d', '--disassemble', action='store_true', help='print disassembly') commands.add_argument('-j', '--statespace-json', help='dumps the statespace json', metavar='OUTPUT_FILE') inputs = parser.add_argument_group('input arguments') inputs.add_argument('-c', '--code', help='hex-encoded bytecode string ("6060604052...")', metavar='BYTECODE') inputs.add_argument('-a', '--address', help='pull contract from the blockchain', metavar='CONTRACT_ADDRESS') inputs.add_argument('-l', '--dynld', action='store_true', help='auto-load dependencies from the blockchain') outputs = parser.add_argument_group('output formats') outputs.add_argument('-o', '--outform', choices=['text', 'markdown', 'json'], default='text', help='report output format', metavar='<text/json>') outputs.add_argument('--verbose-report', action='store_true', help='Include debugging information in report') database = parser.add_argument_group('local contracts database') database.add_argument('--init-db', action='store_true', help='initialize the contract database') database.add_argument('-s', '--search', help='search the contract database', metavar='EXPRESSION') utilities = parser.add_argument_group('utilities') utilities.add_argument('--hash', help='calculate function signature hash', metavar='SIGNATURE') utilities.add_argument( '--storage', help='read state variables from storage index, use with -a', metavar='INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]') utilities.add_argument( '--solv', help= 'specify solidity compiler version. If not present, will try to install it (Experimental)', metavar='SOLV') options = parser.add_argument_group('options') options.add_argument( '-m', '--modules', help='Comma-separated list of security analysis modules', metavar='MODULES') options.add_argument('--max-depth', type=int, default=12, help='Maximum recursion depth for symbolic execution') options.add_argument('--solc-args', help='Extra arguments for solc') options.add_argument('--phrack', action='store_true', help='Phrack-style call graph') options.add_argument('--enable-physics', action='store_true', help='enable graph physics simulation') options.add_argument('-v', type=int, help='log level (0-2)', metavar='LOG_LEVEL') options.add_argument('--leveldb', help='enable direct leveldb access operations', metavar='LEVELDB_PATH') rpc = parser.add_argument_group('RPC options') rpc.add_argument('-i', action='store_true', help='Preset: Infura Node service (Mainnet)') rpc.add_argument('--rpc', help='custom RPC settings', metavar='HOST:PORT / ganache / infura-[network_name]') rpc.add_argument('--rpctls', type=bool, default=False, help='RPC connection over TLS') rpc.add_argument('--ipc', action='store_true', help='Connect via local IPC') # Get config values args = parser.parse_args() try: mythril_dir = os.environ['MYTHRIL_DIR'] except KeyError: mythril_dir = os.path.join(os.path.expanduser('~'), ".mythril") # Detect unsupported combinations of command line args if args.dynld and not args.address: exitWithError( args.outform, "Dynamic loader can be used in on-chain analysis mode only (-a).") # Initialize data directory and signature database if not os.path.exists(mythril_dir): logging.info("Creating mythril data directory") os.mkdir(mythril_dir) # If no function signature file exists, create it. Function signatures from Solidity source code are added automatically. signatures_file = os.path.join(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 JSONDecodeError as e: exitWithError( args.outform, "Invalid JSON in signatures file " + signatures_file + "\n" + str(e)) # Parse cmdline args if not (args.search or args.init_db or args.hash or args.disassemble or args.graph or args.fire_lasers or args.storage or args.truffle or args.statespace_json): parser.print_help() sys.exit() if args.v: if 0 <= args.v < 3: logging.basicConfig( level=[logging.NOTSET, logging.INFO, logging.DEBUG][args.v]) else: exitWithError( args.outform, "Invalid -v value, you can find valid values in usage") if args.hash: print("0x" + utils.sha3(args.hash)[:4].hex()) sys.exit() if args.truffle: try: analyze_truffle_project(args) except FileNotFoundError: print( "Build directory not found. Make sure that you start the analysis from the project root, and that 'truffle compile' has executed successfully." ) sys.exit() # Figure out solc binary and version # Only proper versions are supported. No nightlies, commits etc (such as available in remix) if args.solv: version = args.solv # 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: exitWithError( args.outform, "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' # Open LevelDB if specified if args.leveldb: ethDB = EthLevelDB(args.leveldb) eth = ethDB # Establish RPC/IPC connection if necessary if (args.address or args.init_db) and not args.leveldb: if args.i: eth = EthJsonRpc('mainnet.infura.io', 443, True) logging.info("Using INFURA for RPC queries") elif args.rpc: if args.rpc == 'ganache': rpcconfig = ('localhost', 7545, False) else: m = re.match(r'infura-(.*)', args.rpc) if m and m.group(1) in [ 'mainnet', 'rinkeby', 'kovan', 'ropsten' ]: rpcconfig = (m.group(1) + '.infura.io', 443, True) else: try: host, port = args.rpc.split(":") rpcconfig = (host, int(port), args.rpctls) except ValueError: exitWithError( args.outform, "Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'" ) if (rpcconfig): eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2]) logging.info("Using RPC settings: %s" % str(rpcconfig)) else: exitWithError(args.outform, "Invalid RPC settings, check help for details.") elif args.ipc: try: eth = EthIpc() except Exception as e: exitWithError( args.outform, "IPC initialization failed. Please verify that your local Ethereum node is running, or use the -i flag to connect to INFURA. \n" + str(e)) else: # Default configuration if neither RPC or IPC are set eth = EthJsonRpc('localhost', 8545) logging.info("Using default RPC settings: http://localhost:8545") # Database search ops if args.search or args.init_db: contract_storage, _ = get_persistent_storage(mythril_dir) if args.search: try: if not args.leveldb: contract_storage.search(args.search, searchCallback) else: ethDB.search(args.search, searchCallback) except SyntaxError: exitWithError(args.outform, "Syntax error in search expression.") elif args.init_db: try: contract_storage.initialize(eth) except FileNotFoundError as e: exitWithError(args.outform, "Error syncing database over IPC: " + str(e)) except ConnectionError as e: exitWithError( args.outform, "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." ) sys.exit() # Load / compile input contracts contracts = [] address = None if args.code: address = util.get_indexed_address(0) contracts.append(ETHContract(args.code, name="MAIN")) # Get bytecode from a contract address elif args.address: address = args.address if not re.match(r'0x[a-fA-F0-9]{40}', args.address): exitWithError( args.outform, "Invalid contract address. Expected format is '0x...'.") try: code = eth.eth_getCode(args.address) except FileNotFoundError as e: exitWithError(args.outform, "IPC error: " + str(e)) except ConnectionError as e: exitWithError( args.outform, "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." ) except Exception as e: exitWithError(args.outform, "IPC / RPC error: " + str(e)) else: if code == "0x" or code == "0x0": exitWithError( args.outform, "Received an empty response from eth_getCode. Check the contract address and verify that you are on the correct chain." ) else: contracts.append(ETHContract(code, name=args.address)) # Compile Solidity source file(s) elif args.solidity_file: address = util.get_indexed_address(0) if args.graph and len(args.solidity_file) > 1: exitWithError( args.outform, "Cannot generate call graphs from multiple input files. Please do it one at a time." ) for file in args.solidity_file: if ":" in file: file, contract_name = file.split(":") else: contract_name = None file = os.path.expanduser(file) try: signatures.add_signatures_from_file(file, sigs) contract = SolidityContract(file, contract_name, solc_args=args.solc_args) logging.info("Analyzing contract %s:%s" % (file, contract.name)) except FileNotFoundError: exitWithError(args.outform, "Input file not found: " + file) except CompilerError as e: exitWithError(args.outform, e) except NoContractFoundError: logging.info("The file " + file + " does not contain a compilable contract.") else: contracts.append(contract) # Save updated function signatures with open(signatures_file, 'w') as f: json.dump(sigs, f) else: exitWithError( args.outform, "No input bytecode. Please provide EVM code via -c BYTECODE, -a ADDRESS, or -i SOLIDITY_FILES" ) # Commands if args.storage: if not args.address: exitWithError( args.outform, "To read storage, provide the address of a deployed contract with the -a option." ) else: (position, length, mappings) = (0, 1, []) try: params = args.storage.split(",") if params[0] == "mapping": if len(params) < 3: exitWithError(args.outform, "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: exitWithError(args.outform, "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: exitWithError( args.outform, "Invalid storage index. Please provide a numeric value.") try: if length == 1: print("{}: {}".format( position, eth.eth_getStorageAt(args.address, position))) else: if len(mappings) > 0: for i in range(0, len(mappings)): position = mappings[i] print("{}: {}".format( hex(position), eth.eth_getStorageAt(args.address, position))) else: for i in range(position, position + length): print("{}: {}".format( hex(i), eth.eth_getStorageAt(args.address, i))) except FileNotFoundError as e: exitWithError(args.outform, "IPC error: " + str(e)) except ConnectionError as e: exitWithError( args.outform, "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." ) elif args.disassemble: easm_text = contracts[0].get_easm() sys.stdout.write(easm_text) elif args.graph or args.fire_lasers: if not contracts: exitWithError(args.outform, "input files do not contain any valid contracts") if args.graph: if args.dynld: sym = SymExecWrapper(contracts[0], address, dynloader=DynLoader(eth), max_depth=args.max_depth) else: sym = SymExecWrapper(contracts[0], address, max_depth=args.max_depth) html = generate_graph(sym, physics=args.enable_physics, phrackify=args.phrack) try: with open(args.graph, "w") as f: f.write(html) except Exception as e: exitWithError(args.outform, "Error saving graph: " + str(e)) else: all_issues = [] for contract in contracts: if args.dynld: sym = SymExecWrapper(contract, address, dynloader=DynLoader(eth), max_depth=args.max_depth) else: sym = SymExecWrapper(contract, address, max_depth=args.max_depth) if args.modules: issues = fire_lasers(sym, args.modules.split(",")) else: issues = fire_lasers(sym) if type(contract) == SolidityContract: for issue in issues: issue.add_code_info(contract) all_issues += issues # Finally, output the results report = Report(args.verbose_report) for issue in all_issues: report.append_issue(issue) outputs = { 'json': report.as_json(), 'text': report.as_text() or "The analysis was completed successfully. No issues were detected.", 'markdown': report.as_markdown() or "The analysis was completed successfully. No issues were detected." } print(outputs[args.outform]) elif args.statespace_json: if not contracts: exitWithError(args.outform, "input files do not contain any valid contracts") if args.dynld: sym = SymExecWrapper(contracts[0], address, dynloader=DynLoader(eth), max_depth=args.max_depth) else: sym = SymExecWrapper(contracts[0], address, max_depth=args.max_depth) try: with open(args.statespace_json, "w") as f: json.dump(get_serializable_statespace(sym), f) except Exception as e: exitWithError(args.outform, "Error saving json: " + str(e)) else: parser.print_help()