diff --git a/Dockerfile b/Dockerfile index 59c9123d..e204e2d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,15 +18,18 @@ RUN apt-get update \ git \ && ln -s /usr/bin/python3 /usr/local/bin/python -COPY . /opt/mythril +COPY ./requirements.txt /opt/mythril/requirements.txt RUN cd /opt/mythril \ - && pip3 install -r requirements.txt \ - && python setup.py install + && pip3 install -r requirements.txt RUN locale-gen en_US.UTF-8 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US.en ENV LC_ALL en_US.UTF-8 +COPY . /opt/mythril +RUN cd /opt/mythril \ + && python setup.py install + ENTRYPOINT ["/usr/local/bin/myth"] diff --git a/mythril/analysis/modules/dependence_on_predictable_vars.py b/mythril/analysis/modules/dependence_on_predictable_vars.py index 56cc3156..1e1085ab 100644 --- a/mythril/analysis/modules/dependence_on_predictable_vars.py +++ b/mythril/analysis/modules/dependence_on_predictable_vars.py @@ -120,8 +120,8 @@ def solve(call): model = solver.get_model(call.node.constraints) logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] MODEL: " + str(model)) - for d in model.decls(): - logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] main model: %s = 0x%x" % (d.name(), model[d].as_long())) + for decl in model.decls(): + logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] main model: %s = 0x%x" % (decl.name(), model[decl].as_long())) return True except UnsatError: diff --git a/mythril/analysis/modules/deprecated_ops.py b/mythril/analysis/modules/deprecated_ops.py index 84f39b6e..2b187e2d 100644 --- a/mythril/analysis/modules/deprecated_ops.py +++ b/mythril/analysis/modules/deprecated_ops.py @@ -24,7 +24,7 @@ def execute(statespace): instruction = state.get_current_instruction() if instruction['opcode'] == "ORIGIN": - description = "Function %s retrieves the transaction origin (tx.origin) using the ORIGIN opcode. " \ + description = "The function `{}` retrieves the transaction origin (tx.origin) using the ORIGIN opcode. " \ "Use msg.sender instead.\nSee also: " \ "https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin".format(node.function_name) diff --git a/mythril/analysis/modules/ether_send.py b/mythril/analysis/modules/ether_send.py index bfb0d057..00aac6a5 100644 --- a/mythril/analysis/modules/ether_send.py +++ b/mythril/analysis/modules/ether_send.py @@ -111,8 +111,8 @@ def execute(statespace): try: model = solver.get_model(node.constraints) - for d in model.decls(): - logging.debug("[ETHER_SEND] main model: %s = 0x%x" % (d.name(), model[d].as_long())) + for decl in model.decls(): + logging.debug("[ETHER_SEND] main model: %s = 0x%x" % (decl.name(), model[decl].as_long())) debug = "SOLVER OUTPUT:\n" + solver.pretty_print_model(model) diff --git a/mythril/analysis/modules/multiple_sends.py b/mythril/analysis/modules/multiple_sends.py index 0df17575..baaea896 100644 --- a/mythril/analysis/modules/multiple_sends.py +++ b/mythril/analysis/modules/multiple_sends.py @@ -25,8 +25,8 @@ def execute(statespace): swc_id=MULTIPLE_SENDS, title="Multiple Calls", _type="Informational") issue.description = \ - "Multiple sends exist in one transaction, try to isolate each external call into its own transaction." \ - " As external calls can fail accidentally or deliberately.\nConsecutive calls: \n" + "Multiple sends exist in one transaction. Try to isolate each external call into its own transaction," \ + " as external calls can fail accidentally or deliberately.\nConsecutive calls: \n" for finding in findings: issue.description += \ @@ -38,7 +38,7 @@ def execute(statespace): def _explore_nodes(call, statespace): children = _child_nodes(statespace, call.node) - sending_children = list(filter(lambda call: call.node in children, statespace.calls)) + sending_children = list(filter(lambda c: c.node in children, statespace.calls)) return sending_children diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index c9431257..1108f4f7 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -16,7 +16,6 @@ class SymExecWrapper: def __init__(self, contract, address, strategy, dynloader=None, max_depth=22, execution_timeout=None, create_timeout=None, max_transaction_count=3): - s_strategy = None if strategy == 'dfs': s_strategy = DepthFirstSearchStrategy elif strategy == 'bfs': diff --git a/mythril/analysis/templates/callgraph.html b/mythril/analysis/templates/callgraph.html index 5032b2c2..807ecfc6 100644 --- a/mythril/analysis/templates/callgraph.html +++ b/mythril/analysis/templates/callgraph.html @@ -1,7 +1,7 @@ - Laser - Call Graph + Call Graph diff --git a/mythril/analysis/traceexplore.py b/mythril/analysis/traceexplore.py index 62d9ea50..d70fdcbd 100644 --- a/mythril/analysis/traceexplore.py +++ b/mythril/analysis/traceexplore.py @@ -13,8 +13,8 @@ colors = [ {'border': '#4753bf', 'background': '#3b46a1', 'highlight': {'border': '#fff', 'background': '#424db3'}}, ] + def get_serializable_statespace(statespace): - nodes = [] edges = [] @@ -40,10 +40,10 @@ def get_serializable_statespace(statespace): color = color_map[node.get_cfg_dict()['contract_name']] - def get_state_accounts(state): + def get_state_accounts(node_state): state_accounts = [] - for key in state.accounts: - account = state.accounts[key].as_dict + for key in node_state.accounts: + account = node_state.accounts[key].as_dict account.pop('code', None) account['balance'] = str(account['balance']) diff --git a/mythril/ether/ethcontract.py b/mythril/ether/ethcontract.py index fbd87b69..801e063b 100644 --- a/mythril/ether/ethcontract.py +++ b/mythril/ether/ethcontract.py @@ -12,8 +12,8 @@ class ETHContract(persistent.Persistent): # Dynamic contract addresses of the format __[contract-name]_____________ are replaced with a generic address # Apply this for creation_code & code - creation_code = re.sub(r'(_+.*_+)', 'aa' * 20, creation_code) - code = re.sub(r'(_+.*_+)', 'aa' * 20, code) + creation_code = re.sub(r'(_{2}.{38})', 'aa' * 20, creation_code) + code = re.sub(r'(_{2}.{38})', 'aa' * 20, code) self.creation_code = creation_code self.name = name diff --git a/mythril/ether/evm.py b/mythril/ether/evm.py index 0bcc7206..f1cf2fa5 100644 --- a/mythril/ether/evm.py +++ b/mythril/ether/evm.py @@ -7,69 +7,52 @@ from io import StringIO import re -def trace(code, calldata = ""): - - log_handlers = ['eth.vm.op', 'eth.vm.op.stack', 'eth.vm.op.memory', 'eth.vm.op.storage'] - - output = StringIO() - stream_handler = StreamHandler(output) - - for handler in log_handlers: - log_vm_op = get_logger(handler) - log_vm_op.setLevel("TRACE") - log_vm_op.addHandler(stream_handler) - - addr = bytes.fromhex('0123456789ABCDEF0123456789ABCDEF01234567') - - state = State() - - ext = messages.VMExt(state, transactions.Transaction(0, 0, 21000, addr, 0, addr)) - - message = vm.Message(addr, addr, 0, 21000, calldata) - - res, gas, dat = vm.vm_execute(ext, message, util.safe_decode(code)) - - stream_handler.flush() - - ret = output.getvalue() - - lines = ret.split("\n") - - trace = [] - - for line in lines: - - m = re.search(r'pc=b\'(\d+)\'.*op=([A-Z0-9]+)', line) - - if m: - pc = m.group(1) - op = m.group(2) - - m = re.match(r'.*stack=(\[.*?\])', line) - - if m: - - stackitems = re.findall(r'b\'(\d+)\'', m.group(1)) - - stack = "[" - - if len(stackitems): - - for i in range(0, len(stackitems) - 1): - stack += hex(int(stackitems[i])) + ", " - - stack += hex(int(stackitems[-1])) - - stack += "]" - - else: - stack = "[]" - - if re.match(r'^PUSH.*', op): - val = re.search(r'pushvalue=(\d+)', line).group(1) - pushvalue = hex(int(val)) - trace.append({'pc': pc, 'op': op, 'stack': stack, 'pushvalue': pushvalue}) - else: - trace.append({'pc': pc, 'op': op, 'stack': stack}) - - return trace +def trace(code, calldata=""): + log_handlers = ['eth.vm.op', 'eth.vm.op.stack', 'eth.vm.op.memory', 'eth.vm.op.storage'] + output = StringIO() + stream_handler = StreamHandler(output) + + for handler in log_handlers: + log_vm_op = get_logger(handler) + log_vm_op.setLevel("TRACE") + log_vm_op.addHandler(stream_handler) + + addr = bytes.fromhex('0123456789ABCDEF0123456789ABCDEF01234567') + state = State() + + ext = messages.VMExt(state, transactions.Transaction(0, 0, 21000, addr, 0, addr)) + message = vm.Message(addr, addr, 0, 21000, calldata) + vm.vm_execute(ext, message, util.safe_decode(code)) + stream_handler.flush() + ret = output.getvalue() + lines = ret.split("\n") + + state_trace = [] + for line in lines: + m = re.search(r'pc=b\'(\d+)\'.*op=([A-Z0-9]+)', line) + if m: + pc = m.group(1) + op = m.group(2) + m = re.match(r'.*stack=(\[.*?\])', line) + + if m: + stackitems = re.findall(r'b\'(\d+)\'', m.group(1)) + stack = "[" + + if len(stackitems): + for i in range(0, len(stackitems) - 1): + stack += hex(int(stackitems[i])) + ", " + stack += hex(int(stackitems[-1])) + + stack += "]" + else: + stack = "[]" + + if re.match(r'^PUSH.*', op): + val = re.search(r'pushvalue=(\d+)', line).group(1) + pushvalue = hex(int(val)) + state_trace.append({'pc': pc, 'op': op, 'stack': stack, 'pushvalue': pushvalue}) + else: + state_trace.append({'pc': pc, 'op': op, 'stack': stack}) + + return state_trace diff --git a/mythril/ethereum/interface/leveldb/accountindexing.py b/mythril/ethereum/interface/leveldb/accountindexing.py index 7578afd1..9d94c31e 100644 --- a/mythril/ethereum/interface/leveldb/accountindexing.py +++ b/mythril/ethereum/interface/leveldb/accountindexing.py @@ -63,16 +63,15 @@ class AccountIndexer(object): def get_contract_by_hash(self, contract_hash): """ - get mapped address by its hash, if not found try indexing + get mapped contract_address by its hash, if not found try indexing """ - address = self.db.reader._get_address_by_hash(contract_hash) - if address is not None: - return address + contract_address = self.db.reader._get_address_by_hash(contract_hash) + if contract_address is not None: + return contract_address + else: raise AddressNotFoundError - return self.db.reader._get_address_by_hash(contract_hash) - def _process(self, startblock): """ Processesing method @@ -82,9 +81,9 @@ class AccountIndexer(object): addresses = [] for blockNum in range(startblock, startblock + BATCH_SIZE): - hash = self.db.reader._get_block_hash(blockNum) - if hash is not None: - receipts = self.db.reader._get_block_receipts(hash, blockNum) + block_hash = self.db.reader._get_block_hash(blockNum) + if block_hash is not None: + receipts = self.db.reader._get_block_receipts(block_hash, blockNum) for receipt in receipts: if receipt.contractAddress is not None and not all(b == 0 for b in receipt.contractAddress): diff --git a/mythril/ethereum/interface/leveldb/client.py b/mythril/ethereum/interface/leveldb/client.py index a1b4323b..bdc4b46b 100644 --- a/mythril/ethereum/interface/leveldb/client.py +++ b/mythril/ethereum/interface/leveldb/client.py @@ -79,52 +79,43 @@ class LevelDBReader(object): gets head block header """ if not self.head_block_header: - hash = self.db.get(head_header_key) - num = self._get_block_number(hash) - self.head_block_header = self._get_block_header(hash, num) + block_hash = self.db.get(head_header_key) + num = self._get_block_number(block_hash) + self.head_block_header = self._get_block_header(block_hash, num) # find header with valid state while not self.db.get(self.head_block_header.state_root) and self.head_block_header.prevhash is not None: - hash = self.head_block_header.prevhash - num = self._get_block_number(hash) - self.head_block_header = self._get_block_header(hash, num) + block_hash = self.head_block_header.prevhash + num = self._get_block_number(block_hash) + self.head_block_header = self._get_block_header(block_hash, num) return self.head_block_header - def _get_block_number(self, hash): - """ - gets block number by hash - """ - number_key = block_hash_prefix + hash + def _get_block_number(self, block_hash): + """Get block number by its hash""" + number_key = block_hash_prefix + block_hash return self.db.get(number_key) - def _get_block_header(self, hash, num): - """ - get block header by block header hash & number - """ - header_key = header_prefix + num + hash + def _get_block_header(self, block_hash, num): + """Get block header by block header hash & number""" + header_key = header_prefix + num + block_hash + block_header_data = self.db.get(header_key) header = rlp.decode(block_header_data, sedes=BlockHeader) return header - def _get_address_by_hash(self, hash): - """ - get mapped address by its hash - """ - address_key = address_prefix + hash + def _get_address_by_hash(self, block_hash): + """Get mapped address by its hash""" + address_key = address_prefix + block_hash return self.db.get(address_key) def _get_last_indexed_number(self): - """ - latest indexed block number - """ + """Get latest indexed block number""" return self.db.get(address_mapping_head_key) - def _get_block_receipts(self, hash, num): - """ - get block transaction receipts by block header hash & number - """ + def _get_block_receipts(self, block_hash, num): + """Get block transaction receipts by block header hash & number""" number = _format_block_number(num) - receipts_key = block_receipts_prefix + number + hash + receipts_key = block_receipts_prefix + number + block_hash receipts_data = self.db.get(receipts_key) receipts = rlp.decode(receipts_data, sedes=CountableList(ReceiptForStorage)) return receipts @@ -216,12 +207,10 @@ class EthLevelDB(object): if not cnt % 1000: logging.info("Searched %d contracts" % cnt) - def contract_hash_to_address(self, hash): - """ - tries to find corresponding account address - """ + def contract_hash_to_address(self, contract_hash): + """Tries to find corresponding account address""" - address_hash = binascii.a2b_hex(utils.remove_0x_head(hash)) + address_hash = binascii.a2b_hex(utils.remove_0x_head(contract_hash)) indexer = AccountIndexer(self) return _encode_hex(indexer.get_contract_by_hash(address_hash)) @@ -230,9 +219,9 @@ class EthLevelDB(object): """ gets block header by block number """ - hash = self.reader._get_block_hash(number) + block_hash = self.reader._get_block_hash(number) block_number = _format_block_number(number) - return self.reader._get_block_header(hash, block_number) + return self.reader._get_block_header(block_hash, block_number) def eth_getBlockByNumber(self, number): """ diff --git a/mythril/ethereum/interface/leveldb/state.py b/mythril/ethereum/interface/leveldb/state.py index e8f86331..83507b69 100644 --- a/mythril/ethereum/interface/leveldb/state.py +++ b/mythril/ethereum/interface/leveldb/state.py @@ -43,9 +43,9 @@ class Account(rlp.Serializable): ('code_hash', hash32) ] - def __init__(self, nonce, balance, storage, code_hash, db, address): + def __init__(self, nonce, balance, storage, code_hash, db, addr): self.db = db - self.address = address + self.address = addr super(Account, self).__init__(nonce, balance, storage, code_hash) self.storage_cache = {} self.storage_trie = SecureTrie(Trie(self.db)) @@ -73,12 +73,12 @@ class Account(rlp.Serializable): return self.storage_cache[key] @classmethod - def blank_account(cls, db, address, initial_nonce=0): + def blank_account(cls, db, addr, initial_nonce=0): """ creates a blank account """ db.put(BLANK_HASH, b'') - o = cls(initial_nonce, 0, trie.BLANK_ROOT, BLANK_HASH, db, address) + o = cls(initial_nonce, 0, trie.BLANK_ROOT, BLANK_HASH, db, addr) o.existent_at_start = False return o @@ -100,21 +100,21 @@ class State: self.journal = [] self.cache = {} - def get_and_cache_account(self, address): - """ - gets and caches an account for an addres, creates blank if not found - """ - if address in self.cache: - return self.cache[address] - rlpdata = self.secure_trie.get(address) - if rlpdata == trie.BLANK_NODE and len(address) == 32: # support for hashed addresses - rlpdata = self.trie.get(address) + def get_and_cache_account(self, addr): + """Gets and caches an account for an addres, creates blank if not found""" + + if addr in self.cache: + return self.cache[addr] + rlpdata = self.secure_trie.get(addr) + if rlpdata == trie.BLANK_NODE and len(addr) == 32: # support for hashed addresses + rlpdata = self.trie.get(addr) + if rlpdata != trie.BLANK_NODE: - o = rlp.decode(rlpdata, Account, db=self.db, address=address) + o = rlp.decode(rlpdata, Account, db=self.db, address=addr) else: o = Account.blank_account( - self.db, address, 0) - self.cache[address] = o + self.db, addr, 0) + self.cache[addr] = o o._mutable = True o._cached_rlp = None return o diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 1784add4..0562cb90 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -17,8 +17,8 @@ from mythril.mythril import Mythril from mythril.version import VERSION -def exit_with_error(format, message): - if format == 'text' or format == 'markdown': +def exit_with_error(format_, message): + if format_ == 'text' or format_ == 'markdown': print(message) else: result = {'success': False, 'error': str(message), 'issues': []} diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 583c6b2e..d599d2be 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -99,7 +99,7 @@ def get_callee_account(global_state, callee_address, dynamic_loader): try: code = dynamic_loader.dynld(environment.active_account.address, callee_address) - except Exception as e: + except Exception: logging.debug("Unable to execute dynamic loader.") raise ValueError() if code is None: diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index cea603b4..647b19c9 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -497,7 +497,6 @@ class Instruction: global keccak_function_manager state = global_state.mstate - environment = global_state.environment op0, op1 = state.stack.pop(), state.stack.pop() try: @@ -701,12 +700,9 @@ class Instruction: try: # Attempt to concretize value - _bytes = util.concrete_int_to_bytes(value) - - state.memory[mstart:mstart+len(_bytes)] = _bytes - - except (AttributeError, TypeError): + state.memory[mstart: mstart + len(_bytes)] = _bytes + except: try: state.memory[mstart] = value except TypeError: @@ -948,7 +944,7 @@ class Instruction: state = global_state.mstate dpth = int(self.op_code[3:]) state.stack.pop(), state.stack.pop() - [state.stack.pop() for x in range(dpth)] + [state.stack.pop() for _ in range(dpth)] # Not supported return [global_state] diff --git a/mythril/laser/ethereum/state.py b/mythril/laser/ethereum/state.py index 16c01c3a..fd35c86c 100644 --- a/mythril/laser/ethereum/state.py +++ b/mythril/laser/ethereum/state.py @@ -278,10 +278,10 @@ class GlobalState: def new_bitvec(self, name, size=256): transaction_id = self.current_transaction.id - node_id = self.node.uid return BitVec("{}_{}".format(transaction_id, name), size) + class WorldState: """ The WorldState class represents the world state as described in the yellow paper diff --git a/mythril/laser/ethereum/transaction/transaction_models.py b/mythril/laser/ethereum/transaction/transaction_models.py index 35826bcd..4060c4e1 100644 --- a/mythril/laser/ethereum/transaction/transaction_models.py +++ b/mythril/laser/ethereum/transaction/transaction_models.py @@ -129,7 +129,7 @@ class ContractCreationTransaction: def end(self, global_state, return_data=None, revert=False): - if not all([isinstance(element, int) for element in return_data]): + if not all([isinstance(element, int) for element in return_data]) or len(return_data) == 0: self.return_data = None raise TransactionEndSignal(global_state) @@ -137,6 +137,7 @@ class ContractCreationTransaction: global_state.environment.active_account.code = Disassembly(contract_code) self.return_data = global_state.environment.active_account.address + assert global_state.environment.active_account.code.instruction_list != [] raise TransactionEndSignal(global_state, revert=revert) diff --git a/mythril/mythril.py b/mythril/mythril.py index 123ab904..db80ef09 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -87,7 +87,7 @@ class Mythril(object): 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: + except FileNotFoundError: 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") @@ -261,8 +261,7 @@ class Mythril(object): def search_db(self, search): - def search_callback(contract, address, balance): - + def search_callback(_, address, balance): print("Address: " + address + ", balance: " + str(balance)) try: @@ -290,10 +289,10 @@ class Mythril(object): code = self.eth.eth_getCode(address) except FileNotFoundError as e: raise CriticalError("IPC error: " + str(e)) - except ConnectionError as 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)) + 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.") @@ -435,7 +434,7 @@ class Mythril(object): 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: + 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) diff --git a/static/Ownable.html b/static/Ownable.html index 964558e4..9dff1f1a 100644 --- a/static/Ownable.html +++ b/static/Ownable.html @@ -1,5 +1,7 @@ + - + + Call Graph