From 6e0d775c09f32bb9e757b53b7f8d768f618f452a Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Mon, 1 Oct 2018 20:05:46 +0530 Subject: [PATCH 01/84] Remove all broad exception catches in laser --- mythril/laser/ethereum/call.py | 1 + mythril/laser/ethereum/instructions.py | 80 +++++++++++--------------- mythril/laser/ethereum/util.py | 12 +++- mythril/support/loader.py | 2 +- 4 files changed, 46 insertions(+), 49 deletions(-) diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 31e6990e..b2f6a9f7 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -63,6 +63,7 @@ def get_callee_address(global_state:GlobalState, dynamic_loader: DynLoader, symb # attempt to read the contract address from instance storage try: callee_address = dynamic_loader.read_storage(environment.active_account.address, index) + # TODO: verify whether this happens or not except: logging.debug("Error accessing contract storage.") raise ValueError diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 3d5d2bf7..27172f0a 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -264,18 +264,17 @@ class Instruction: try: s0 = util.get_concrete_int(s0) s1 = util.get_concrete_int(s1) + except ValueError: + return [] - if s0 <= 31: - testbit = s0 * 8 + 7 - if s1 & (1 << testbit): - state.stack.append(s1 | (TT256 - (1 << testbit))) - else: - state.stack.append(s1 & ((1 << testbit) - 1)) + if s0 <= 31: + testbit = s0 * 8 + 7 + if s1 & (1 << testbit): + state.stack.append(s1 | (TT256 - (1 << testbit))) else: - state.stack.append(s1) - # TODO: broad exception handler - except: - return [] + state.stack.append(s1 & ((1 << testbit) - 1)) + else: + state.stack.append(s1) return [global_state] @@ -367,16 +366,15 @@ class Instruction: return [global_state] if type(b) == int: - val = b'' try: - for i in range(offset, offset + 32): - val += environment.calldata[i].to_bytes(1, byteorder='big') + val = b''.join([calldata.to_bytes(1, byteorder='big') for calldata in + environment.calldata[offset:offset+32]]) logging.debug("Final value: " + str(int.from_bytes(val, byteorder='big'))) state.stack.append(BitVecVal(int.from_bytes(val, byteorder='big'), 256)) - # FIXME: broad exception catch - except: + + except (util.ConcreteIntException, AttributeError): state.stack.append(global_state.new_bitvec( "calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256)) else: @@ -404,16 +402,14 @@ class Instruction: try: mstart = util.get_concrete_int(op0) - # FIXME: broad exception catch - except: + except util.ConcreteIntException: logging.debug("Unsupported symbolic memory offset in CALLDATACOPY") return [global_state] dstart_sym = False try: dstart = util.get_concrete_int(op1) - # FIXME: broad exception catch - except: + except util.ConcreteIntException: logging.debug("Unsupported symbolic calldata offset in CALLDATACOPY") dstart = simplify(op1) dstart_sym = True @@ -421,8 +417,7 @@ class Instruction: size_sym = False try: size = util.get_concrete_int(op2) - # FIXME: broad exception catch - except: + except util.ConcreteIntException: logging.debug("Unsupported symbolic size in CALLDATACOPY") size = simplify(op2) size_sym = True @@ -437,8 +432,7 @@ class Instruction: if size > 0: try: state.mem_extend(mstart, size) - # FIXME: broad exception catch - except: + except TypeError: logging.debug("Memory allocation error: mstart = " + str(mstart) + ", size = " + str(size)) state.mem_extend(mstart, 1) state.memory[mstart] = global_state.new_bitvec( @@ -452,7 +446,7 @@ class Instruction: for i in range(mstart, mstart + size): state.memory[i] = environment.calldata[i_data] i_data += 1 - except: + except IndexError: logging.debug("Exception copying calldata to memory") state.memory[mstart] = global_state.new_bitvec( @@ -507,8 +501,7 @@ class Instruction: try: index, length = util.get_concrete_int(op0), util.get_concrete_int(op1) - # FIXME: broad exception catch - except: + except util.ConcreteIntException: # Can't access symbolic memory offsets if is_expr(op0): op0 = simplify(op0) @@ -520,7 +513,7 @@ class Instruction: data = b''.join([util.get_concrete_int(i).to_bytes(1, byteorder='big') for i in state.memory[index: index + length]]) - except AttributeError: + except util.ConcreteIntException: argument = str(state.memory[index]).replace(" ", "_") result = BitVec("KECCAC[{}]".format(argument), 256) @@ -552,7 +545,7 @@ class Instruction: try: concrete_size = helper.get_concrete_int(size) global_state.mstate.mem_extend(concrete_memory_offset, concrete_size) - except: + except (util.ConcreteIntException, TypeError): # except both attribute error and Exception global_state.mstate.mem_extend(concrete_memory_offset, 1) global_state.mstate.memory[concrete_memory_offset] = \ @@ -694,7 +687,7 @@ class Instruction: try: mstart = util.get_concrete_int(op0) - except AttributeError: + except util.ConcreteIntException: logging.debug("MSTORE to symbolic index. Not supported") return [global_state] @@ -707,17 +700,15 @@ class Instruction: try: # Attempt to concretize value + _bytes = util.concrete_int_to_bytes(value) - i = 0 + state.memory[mstart:mstart+len(_bytes)] = _bytes - for b in _bytes: - state.memory[mstart + i] = _bytes[i] - i += 1 - except: + except (AttributeError, TypeError): try: state.memory[mstart] = value - except: + except TypeError: logging.debug("Invalid memory access") return [global_state] @@ -729,7 +720,7 @@ class Instruction: try: offset = util.get_concrete_int(op0) - except AttributeError: + except util.ConcreteIntException: logging.debug("MSTORE to symbolic index. Not supported") return [global_state] @@ -750,7 +741,7 @@ class Instruction: index = util.get_concrete_int(index) return self._sload_helper(global_state, index) - except AttributeError: + except util.ConcreteIntException: if not keccak_function_manager.is_keccak(index): return self._sload_helper(global_state, str(index)) @@ -811,7 +802,7 @@ class Instruction: try: index = util.get_concrete_int(index) return self._sstore_helper(global_state, index, value) - except AttributeError: + except util.ConcreteIntException: is_keccak = keccak_function_manager.is_keccak(index) if not is_keccak: return self._sstore_helper(global_state, str(index), value) @@ -864,7 +855,7 @@ class Instruction: disassembly = global_state.environment.code try: jump_addr = util.get_concrete_int(state.stack.pop()) - except AttributeError: + except util.ConcreteIntException: raise InvalidJumpDestination("Invalid jump argument (symbolic address)") except IndexError: raise StackUnderflowException() @@ -894,8 +885,7 @@ class Instruction: try: jump_addr = util.get_concrete_int(op0) - # FIXME: to broad exception handler - except: + except util.ConcreteIntException: logging.debug("Skipping JUMPI to invalid destination.") global_state.mstate.pc += 1 return [global_state] @@ -975,7 +965,7 @@ class Instruction: return_data = [global_state.new_bitvec("return_data", 256)] try: return_data = state.memory[util.get_concrete_int(offset):util.get_concrete_int(offset + length)] - except AttributeError: + except util.ConcreteIntException: logging.debug("Return with symbolic length or offset. Not supported") global_state.current_transaction.end(global_state, return_data) @@ -1098,7 +1088,7 @@ class Instruction: try: memory_out_offset = util.get_concrete_int(memory_out_offset) if isinstance(memory_out_offset, ExprRef) else memory_out_offset memory_out_size = util.get_concrete_int(memory_out_size) if isinstance(memory_out_size, ExprRef) else memory_out_size - except AttributeError: + except util.ConcreteIntException: global_state.mstate.stack.append(global_state.new_bitvec("retval_" + str(instr['address']), 256)) return [global_state] @@ -1166,7 +1156,7 @@ class Instruction: try: memory_out_offset = util.get_concrete_int(memory_out_offset) if isinstance(memory_out_offset, ExprRef) else memory_out_offset memory_out_size = util.get_concrete_int(memory_out_size) if isinstance(memory_out_size, ExprRef) else memory_out_size - except AttributeError: + except util.ConcreteIntException: global_state.mstate.stack.append(global_state.new_bitvec("retval_" + str(instr['address']), 256)) return [global_state] @@ -1238,7 +1228,7 @@ class Instruction: ExprRef) else memory_out_offset memory_out_size = util.get_concrete_int(memory_out_size) if isinstance(memory_out_size, ExprRef) else memory_out_size - except AttributeError: + except util.ConcreteIntException: global_state.mstate.stack.append(global_state.new_bitvec("retval_" + str(instr['address']), 256)) return [global_state] diff --git a/mythril/laser/ethereum/util.py b/mythril/laser/ethereum/util.py index c6c8e5ce..7b9061ea 100644 --- a/mythril/laser/ethereum/util.py +++ b/mythril/laser/ethereum/util.py @@ -10,6 +10,10 @@ TT256M1 = 2 ** 256 - 1 TT255 = 2 ** 255 +class ConcreteIntException(AttributeError): + pass + + def sha3(seed): return _sha3.keccak_256(bytes(seed)).digest() @@ -80,10 +84,12 @@ def get_concrete_int(item): elif is_true(simplified): return 1 else: - raise ValueError("Symbolic boolref encountered") - - return simplify(item).as_long() + raise ConcreteIntException("Symbolic boolref encountered") + try: + return simplify(item).as_long() + except AttributeError: + raise ConcreteIntException("Got a symbolic BitVecRef") def concrete_int_from_bytes(_bytes, start_index): diff --git a/mythril/support/loader.py b/mythril/support/loader.py index 7c0855ec..16d2e683 100644 --- a/mythril/support/loader.py +++ b/mythril/support/loader.py @@ -47,7 +47,7 @@ class DynLoader: code = self.eth.eth_getCode(dependency_address) - if (code == "0x"): + if code == "0x": return None else: return Disassembly(code) From d7df0c132cebb6f69f78c8c26c9ad5866f525e8f Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Sun, 7 Oct 2018 13:48:38 +0530 Subject: [PATCH 02/84] check for new records to work on --- mythril/laser/ethereum/taint_analysis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/taint_analysis.py b/mythril/laser/ethereum/taint_analysis.py index 2fdb0c52..7fd9536d 100644 --- a/mythril/laser/ethereum/taint_analysis.py +++ b/mythril/laser/ethereum/taint_analysis.py @@ -107,7 +107,8 @@ class TaintRunner: records = TaintRunner.execute_node(node, record, index) result.add_records(records) - + if len(records) == 0: # continue if there is no record to work on + continue children = TaintRunner.children(node, statespace, environment, transaction_stack_length) for child in children: current_nodes.append((child, records[-1], 0)) From 5e9f613911c2676d530fd36b8b5316a6a633da2b Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 8 Oct 2018 20:04:07 +0200 Subject: [PATCH 03/84] Implement revert --- mythril/laser/ethereum/instructions.py | 9 ++++++++- mythril/laser/ethereum/svm.py | 5 +++-- .../ethereum/transaction/transaction_models.py | 14 +++++++++----- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index a7c19959..cb55447d 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -1001,7 +1001,14 @@ class Instruction: @StateTransition() def revert_(self, global_state): - return [] + state = global_state.mstate + offset, length = state.stack.pop(), state.stack.pop() + return_data = [global_state.new_bitvec("return_data", 256)] + try: + return_data = state.memory[util.get_concrete_int(offset):util.get_concrete_int(offset + length)] + except AttributeError: + logging.debug("Return with symbolic length or offset. Not supported") + global_state.current_transaction.end(global_state, return_data=return_data, revert=True) @StateTransition() def assert_fail_(self, global_state): diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index cdf85484..82a6551c 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -152,7 +152,7 @@ class LaserEVM: transaction, return_global_state = e.global_state.transaction_stack.pop() if return_global_state is None: - if not isinstance(transaction, ContractCreationTransaction) or transaction.return_data: + if (not isinstance(transaction, ContractCreationTransaction) or transaction.return_data) and not e.revert: e.global_state.world_state.node = global_state.node self.open_states.append(e.global_state.world_state) new_global_states = [] @@ -161,7 +161,8 @@ class LaserEVM: self._execute_post_hook(op_code, [e.global_state]) new_global_states = self._end_message_call(return_global_state, global_state, - revert_changes=False, return_data=transaction.return_data) + revert_changes=False or e.revert, + return_data=transaction.return_data) self._execute_post_hook(op_code, new_global_states) diff --git a/mythril/laser/ethereum/transaction/transaction_models.py b/mythril/laser/ethereum/transaction/transaction_models.py index fa60599d..35826bcd 100644 --- a/mythril/laser/ethereum/transaction/transaction_models.py +++ b/mythril/laser/ethereum/transaction/transaction_models.py @@ -12,10 +12,12 @@ def get_next_transaction_id(): _next_transaction_id += 1 return _next_transaction_id + class TransactionEndSignal(Exception): """ Exception raised when a transaction is finalized""" - def __init__(self, global_state): + def __init__(self, global_state, revert=False): self.global_state = global_state + self.revert = revert class TransactionStartSignal(Exception): @@ -70,9 +72,9 @@ class MessageCallTransaction: return global_state - def end(self, global_state, return_data=None): + def end(self, global_state, return_data=None, revert=False): self.return_data = return_data - raise TransactionEndSignal(global_state) + raise TransactionEndSignal(global_state, revert) class ContractCreationTransaction: @@ -125,7 +127,7 @@ class ContractCreationTransaction: return global_state - def end(self, global_state, return_data=None): + def end(self, global_state, return_data=None, revert=False): if not all([isinstance(element, int) for element in return_data]): self.return_data = None @@ -136,4 +138,6 @@ class ContractCreationTransaction: global_state.environment.active_account.code = Disassembly(contract_code) self.return_data = global_state.environment.active_account.address - raise TransactionEndSignal(global_state) + raise TransactionEndSignal(global_state, revert=revert) + + From 09e04a3360b77441db230068031a2be726d6dbaa Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 8 Oct 2018 20:54:45 +0200 Subject: [PATCH 04/84] Adds vmTests --- .../VMTests/vmTests/suicide.json | 51 +++++++++++++++++++ tests/laser/evm_testsuite/evm_test.py | 2 +- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/laser/evm_testsuite/VMTests/vmTests/suicide.json diff --git a/tests/laser/evm_testsuite/VMTests/vmTests/suicide.json b/tests/laser/evm_testsuite/VMTests/vmTests/suicide.json new file mode 100644 index 00000000..d815ceb8 --- /dev/null +++ b/tests/laser/evm_testsuite/VMTests/vmTests/suicide.json @@ -0,0 +1,51 @@ +{ + "suicide" : { + "_info" : { + "comment" : "", + "filledwith" : "testeth 1.5.0.dev2-52+commit.d419e0a2", + "lllcversion" : "Version: 0.4.26-develop.2018.9.19+commit.785cbf40.Linux.g++", + "source" : "src/VMTestsFiller/vmTests/suicideFiller.json", + "sourceHash" : "4622c577440f9db4b3954a1de60bf2fac55886dcb0ec4ecaf906c25bc77372e7" + }, + "callcreates" : [ + ], + "env" : { + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x0100", + "currentGasLimit" : "0x0f4240", + "currentNumber" : "0x00", + "currentTimestamp" : "0x01" + }, + "exec" : { + "address" : "0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "caller" : "0xcd1722f3947def4cf144679da39c4c32bdc35681", + "code" : "0x33ff", + "data" : "0x", + "gas" : "0x0186a0", + "gasPrice" : "0x5af3107a4000", + "origin" : "0xcd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "0x0de0b6b3a7640000" + }, + "gas" : "0x01869e", + "logs" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "out" : "0x", + "post" : { + "0xcd1722f3947def4cf144679da39c4c32bdc35681" : { + "balance" : "0x152d02c7e14af6800000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } + }, + "pre" : { + "0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "0x152d02c7e14af6800000", + "code" : "0x33ff", + "nonce" : "0x00", + "storage" : { + } + } + } + } +} \ No newline at end of file diff --git a/tests/laser/evm_testsuite/evm_test.py b/tests/laser/evm_testsuite/evm_test.py index 72bcacd5..82c2c973 100644 --- a/tests/laser/evm_testsuite/evm_test.py +++ b/tests/laser/evm_testsuite/evm_test.py @@ -12,7 +12,7 @@ import pytest evm_test_dir = Path(__file__).parent / 'VMTests' -test_types = ['vmArithmeticTest', 'vmBitwiseLogicOperation', 'vmPushDupSwapTest'] +test_types = ['vmArithmeticTest', 'vmBitwiseLogicOperation', 'vmPushDupSwapTest', 'vmTests'] def load_test_data(designations): From 217e9c01ea89e9fa140fdd81dba659fc3d1d8cb8 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 21:19:21 +0200 Subject: [PATCH 05/84] Remove unused variable --- mythril/laser/ethereum/call.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 31e6990e..4afdf49d 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -98,7 +98,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.info("Unable to execute dynamic loader.") raise ValueError() if code is None: From 6e86c183b2741cf3b481d37ae19881eb6aa2ef03 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 21:19:31 +0200 Subject: [PATCH 06/84] Reformat evm from tabs to spaces --- mythril/ether/evm.py | 81 ++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/mythril/ether/evm.py b/mythril/ether/evm.py index 449fcdcf..4cf03752 100644 --- a/mythril/ether/evm.py +++ b/mythril/ether/evm.py @@ -7,69 +7,68 @@ from io import StringIO import re -def trace(code, calldata = ""): +def trace(code, calldata=""): + log_handlers = ['eth.vm.op', 'eth.vm.op.stack', 'eth.vm.op.memory', 'eth.vm.op.storage'] - log_handlers = ['eth.vm.op', 'eth.vm.op.stack', 'eth.vm.op.memory', 'eth.vm.op.storage'] + output = StringIO() + stream_handler = StreamHandler(output) - 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) - 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') - addr = bytes.fromhex('0123456789ABCDEF0123456789ABCDEF01234567') + state = State() - state = State() + ext = messages.VMExt(state, transactions.Transaction(0, 0, 21000, addr, 0, addr)) - ext = messages.VMExt(state, transactions.Transaction(0, 0, 21000, addr, 0, addr)) + message = vm.Message(addr, addr, 0, 21000, calldata) - message = vm.Message(addr, addr, 0, 21000, calldata) + res, gas, dat = vm.vm_execute(ext, message, util.safe_decode(code)) - res, gas, dat = vm.vm_execute(ext, message, util.safe_decode(code)) + stream_handler.flush() - stream_handler.flush() + ret = output.getvalue() - ret = output.getvalue() + lines = ret.split("\n") - lines = ret.split("\n") + trace = [] - trace = [] + for line in lines: - for line in lines: + m = re.search(r'pc=b\'(\d+)\'.*op=([A-Z0-9]+)', line) - m = re.search(r'pc=b\'(\d+)\'.*op=([A-Z0-9]+)', line) + if m: + pc = m.group(1) + op = m.group(2) - if m: - pc = m.group(1) - op = m.group(2) + m = re.match(r'.*stack=(\[.*?\])', line) - m = re.match(r'.*stack=(\[.*?\])', line) - - if (m): + if (m): - stackitems = re.findall(r'b\'(\d+)\'', m.group(1)) + stackitems = re.findall(r'b\'(\d+)\'', m.group(1)) - stack = "["; + stack = "["; - if (len(stackitems)): + if (len(stackitems)): - for i in range(0, len(stackitems) - 1): - stack += hex(int(stackitems[i])) + ", " + for i in range(0, len(stackitems) - 1): + stack += hex(int(stackitems[i])) + ", " - stack += hex(int(stackitems[-1])) + stack += hex(int(stackitems[-1])) - stack += "]" + stack += "]" - else: - 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}) + 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 + return trace From 527dd0b1d8221245f74d5c826b69938efca531c9 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 21:20:00 +0200 Subject: [PATCH 07/84] Remove evm unused variables --- mythril/ether/evm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/ether/evm.py b/mythril/ether/evm.py index 4cf03752..4a099a1f 100644 --- a/mythril/ether/evm.py +++ b/mythril/ether/evm.py @@ -26,7 +26,7 @@ def trace(code, calldata=""): message = vm.Message(addr, addr, 0, 21000, calldata) - res, gas, dat = vm.vm_execute(ext, message, util.safe_decode(code)) + vm.vm_execute(ext, message, util.safe_decode(code)) stream_handler.flush() From 12f8460628f7e1c1ed05df0b21d858082737d0c0 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 21:21:02 +0200 Subject: [PATCH 08/84] Remove evm redundant parentheses --- mythril/ether/evm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mythril/ether/evm.py b/mythril/ether/evm.py index 4a099a1f..2cc40e97 100644 --- a/mythril/ether/evm.py +++ b/mythril/ether/evm.py @@ -52,7 +52,7 @@ def trace(code, calldata=""): stack = "["; - if (len(stackitems)): + if len(stackitems): for i in range(0, len(stackitems) - 1): stack += hex(int(stackitems[i])) + ", " @@ -64,7 +64,7 @@ def trace(code, calldata=""): else: stack = "[]" - if (re.match(r'^PUSH.*', op)): + 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}) From 355d5edd87a5f03cc282f63d15315d2ae26aa35d Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 21:22:14 +0200 Subject: [PATCH 09/84] Fix line spacing in evm --- mythril/ether/evm.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/mythril/ether/evm.py b/mythril/ether/evm.py index 2cc40e97..576223d5 100644 --- a/mythril/ether/evm.py +++ b/mythril/ether/evm.py @@ -9,7 +9,6 @@ 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) @@ -19,48 +18,33 @@ def trace(code, calldata=""): 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") 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): - + 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 = "[]" From 3d4484811a570bbdac0803de9c98b068c72c353f Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 21:26:03 +0200 Subject: [PATCH 10/84] Remove unused variables in instructions --- mythril/laser/ethereum/instructions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index a7c19959..ff253f3f 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -503,7 +503,6 @@ class Instruction: global keccak_function_manager state = global_state.mstate - environment = global_state.environment op0, op1 = state.stack.pop(), state.stack.pop() try: @@ -711,7 +710,6 @@ class Instruction: _bytes = util.concrete_int_to_bytes(value) i = 0 - for b in _bytes: state.memory[mstart + i] = _bytes[i] i += 1 @@ -956,7 +954,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] From 4dfff5fb2b917ba6a5d85061d2625340a9e980df Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 21:26:58 +0200 Subject: [PATCH 11/84] Refactor loop to use enumerate --- mythril/laser/ethereum/instructions.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index ff253f3f..34cd76c7 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -709,10 +709,8 @@ class Instruction: # Attempt to concretize value _bytes = util.concrete_int_to_bytes(value) - i = 0 - for b in _bytes: - state.memory[mstart + i] = _bytes[i] - i += 1 + for i, b in enumerate(_bytes): + state.memory[mstart + i] = b except: try: state.memory[mstart] = value From 9a90a92297540a2b7240c666fb84408ebf0821e5 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 21:28:46 +0200 Subject: [PATCH 12/84] Remove unused variables from tests --- tests/analysis/test_delegatecall.py | 2 +- tests/laser/state/mstack_test.py | 2 +- tests/laser/transaction/symbolic_test.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/analysis/test_delegatecall.py b/tests/analysis/test_delegatecall.py index 3fb02020..9e88c1fd 100644 --- a/tests/analysis/test_delegatecall.py +++ b/tests/analysis/test_delegatecall.py @@ -189,7 +189,7 @@ def test_delegate_call(sym_mock, concrete_mock, curr_instruction): statespace.calls = [call] # act - issues = execute(statespace) + execute(statespace) # assert assert concrete_mock.call_count == 1 diff --git a/tests/laser/state/mstack_test.py b/tests/laser/state/mstack_test.py index 7ccd51c1..6dba7031 100644 --- a/tests/laser/state/mstack_test.py +++ b/tests/laser/state/mstack_test.py @@ -45,7 +45,7 @@ class MachineStackTest(BaseTestCase): mstack = MachineStack([0, 1]) with pytest.raises(NotImplementedError): - mstack = mstack + [2] + mstack + [2] @staticmethod def test_mstack_no_support_iadd(): diff --git a/tests/laser/transaction/symbolic_test.py b/tests/laser/transaction/symbolic_test.py index 6c27b327..91ee00f5 100644 --- a/tests/laser/transaction/symbolic_test.py +++ b/tests/laser/transaction/symbolic_test.py @@ -49,7 +49,7 @@ def test_execute_contract_creation(mocked_setup: MagicMock): mocked_setup.side_effect = _is_contract_creation # Act - new_account = execute_contract_creation(laser_evm, "606000") + execute_contract_creation(laser_evm, "606000") # Assert # mocked_setup.assert_called() From a85918a6f700970642447303aec9a41d26a80ca2 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 21:35:11 +0200 Subject: [PATCH 13/84] Remove unused variables from mythril --- mythril/mythril.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mythril/mythril.py b/mythril/mythril.py index 080493fd..221d136d 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") @@ -259,8 +259,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: @@ -288,10 +287,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.") @@ -431,7 +430,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) From 1428343a3d91af7694d2ebe0e1cd84047a573b69 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 21:37:22 +0200 Subject: [PATCH 14/84] Remove even more unused variables --- mythril/analysis/symbolic.py | 1 - mythril/laser/ethereum/state.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index 09447a67..bc2373a7 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): - s_strategy = None if strategy == 'dfs': s_strategy = DepthFirstSearchStrategy elif strategy == 'bfs': 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 From 2547f43f8ab3bab211bf2515ae8be578f3a04539 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 21:39:11 +0200 Subject: [PATCH 15/84] Remove unreachable code --- mythril/leveldb/accountindexing.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mythril/leveldb/accountindexing.py b/mythril/leveldb/accountindexing.py index 26a4ab9a..5eca5547 100644 --- a/mythril/leveldb/accountindexing.py +++ b/mythril/leveldb/accountindexing.py @@ -71,8 +71,6 @@ class AccountIndexer(object): else: raise AddressNotFoundError - return self.db.reader._get_address_by_hash(contract_hash) - def _process(self, startblock): ''' Processesing method @@ -154,4 +152,4 @@ class AccountIndexer(object): self.db.writer._set_last_indexed_number(self.lastProcessedBlock) print("Finished indexing") - self.lastBlock = self.lastProcessedBlock \ No newline at end of file + self.lastBlock = self.lastProcessedBlock From 2e56cef99a8bcfb78b1a915b63be03266ff3c0ad Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 21:52:18 +0200 Subject: [PATCH 16/84] Fix outer scope variable shadowing --- .../modules/dependence_on_predictable_vars.py | 4 +-- mythril/analysis/modules/ether_send.py | 4 +-- mythril/analysis/modules/multiple_sends.py | 2 +- mythril/analysis/traceexplore.py | 10 +++---- mythril/ether/evm.py | 8 +++--- mythril/leveldb/accountindexing.py | 8 +++--- mythril/leveldb/state.py | 28 +++++++++---------- tests/taint_runner_test.py | 21 +++++++------- 8 files changed, 42 insertions(+), 43 deletions(-) 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/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..5b36f86d 100644 --- a/mythril/analysis/modules/multiple_sends.py +++ b/mythril/analysis/modules/multiple_sends.py @@ -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/traceexplore.py b/mythril/analysis/traceexplore.py index dc7af177..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']) @@ -81,7 +81,7 @@ def get_serializable_statespace(statespace): for edge in statespace.edges: - if (edge.condition is None): + if edge.condition is None: label = "" else: diff --git a/mythril/ether/evm.py b/mythril/ether/evm.py index 576223d5..545e30ff 100644 --- a/mythril/ether/evm.py +++ b/mythril/ether/evm.py @@ -27,7 +27,7 @@ def trace(code, calldata=""): ret = output.getvalue() lines = ret.split("\n") - trace = [] + state_trace = [] for line in lines: m = re.search(r'pc=b\'(\d+)\'.*op=([A-Z0-9]+)', line) if m: @@ -51,8 +51,8 @@ def trace(code, calldata=""): 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}) + state_trace.append({'pc': pc, 'op': op, 'stack': stack, 'pushvalue': pushvalue}) else: - trace.append({'pc': pc, 'op': op, 'stack': stack}) + state_trace.append({'pc': pc, 'op': op, 'stack': stack}) - return trace + return state_trace diff --git a/mythril/leveldb/accountindexing.py b/mythril/leveldb/accountindexing.py index 5eca5547..dacc2f0a 100644 --- a/mythril/leveldb/accountindexing.py +++ b/mythril/leveldb/accountindexing.py @@ -63,11 +63,11 @@ 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 diff --git a/mythril/leveldb/state.py b/mythril/leveldb/state.py index 96360300..c6a24119 100644 --- a/mythril/leveldb/state.py +++ b/mythril/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): + def get_and_cache_account(self, addr): ''' 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) + 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 @@ -125,4 +125,4 @@ class State(): ''' for address_hash, rlpdata in self.secure_trie.trie.iter_branch(): if rlpdata != trie.BLANK_NODE: - yield rlp.decode(rlpdata, Account, db=self.db, address=address_hash) \ No newline at end of file + yield rlp.decode(rlpdata, Account, db=self.db, address=address_hash) diff --git a/tests/taint_runner_test.py b/tests/taint_runner_test.py index 17ef00eb..9b533f3b 100644 --- a/tests/taint_runner_test.py +++ b/tests/taint_runner_test.py @@ -6,13 +6,14 @@ from mythril.laser.ethereum.cfg import Node, Edge from mythril.laser.ethereum.state import MachineState, Account, Environment, GlobalState from mythril.laser.ethereum.svm import LaserEVM -def test_execute_state(mocker): + +def test_execute_state(state_mocker): record = TaintRecord() record.stack = [True, False, True] state = GlobalState(None, None, None) state.mstate.stack = [1, 2, 3] - mocker.patch.object(state, 'get_current_instruction') + state_mocker.patch.object(state, 'get_current_instruction') state.get_current_instruction.return_value = {"opcode": "ADD"} # Act @@ -23,19 +24,19 @@ def test_execute_state(mocker): assert record.stack == [True, False, True] -def test_execute_node(mocker): +def test_execute_node(node_mocker): record = TaintRecord() record.stack = [True, True, False, False] state_1 = GlobalState(None, None, None) state_1.mstate.stack = [1, 2, 3, 1] state_1.mstate.pc = 1 - mocker.patch.object(state_1, 'get_current_instruction') + node_mocker.patch.object(state_1, 'get_current_instruction') state_1.get_current_instruction.return_value = {"opcode": "SWAP1"} state_2 = GlobalState(None, 1, None) state_2.mstate.stack = [1, 2, 4, 1] - mocker.patch.object(state_2, 'get_current_instruction') + node_mocker.patch.object(state_2, 'get_current_instruction') state_2.get_current_instruction.return_value = {"opcode": "ADD"} node = Node("Test contract") @@ -54,19 +55,17 @@ def test_execute_node(mocker): assert state_1 in record.states - - -def test_execute(mocker): +def test_execute(state_mocker): active_account = Account('0x00') environment = Environment(active_account, None, None, None, None, None) state_1 = GlobalState(None, environment, None, MachineState(gas=10000000)) state_1.mstate.stack = [1, 2] - mocker.patch.object(state_1, 'get_current_instruction') + state_mocker.patch.object(state_1, 'get_current_instruction') state_1.get_current_instruction.return_value = {"opcode": "PUSH"} state_2 = GlobalState(None, environment, None, MachineState(gas=10000000)) state_2.mstate.stack = [1, 2, 3] - mocker.patch.object(state_2, 'get_current_instruction') + state_mocker.patch.object(state_2, 'get_current_instruction') state_2.get_current_instruction.return_value = {"opcode": "ADD"} node_1 = Node("Test contract") @@ -74,7 +73,7 @@ def test_execute(mocker): state_3 = GlobalState(None, environment, None, MachineState(gas=10000000)) state_3.mstate.stack = [1, 2] - mocker.patch.object(state_3, 'get_current_instruction') + state_mocker.patch.object(state_3, 'get_current_instruction') state_3.get_current_instruction.return_value = {"opcode": "ADD"} node_2 = Node("Test contract") From 0142e6e41b2c9abe6b4ea2d1a6528c00669e129e Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 22:11:41 +0200 Subject: [PATCH 17/84] Fix builtin name shadowing --- mythril/interfaces/cli.py | 4 ++-- mythril/leveldb/accountindexing.py | 6 ++--- mythril/leveldb/client.py | 38 +++++++++++++++--------------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index be6cec59..e10b4e98 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(fmt, message): + if fmt == 'text' or fmt == 'markdown': print(message) else: result = {'success': False, 'error': str(message), 'issues': []} diff --git a/mythril/leveldb/accountindexing.py b/mythril/leveldb/accountindexing.py index dacc2f0a..f61ebe80 100644 --- a/mythril/leveldb/accountindexing.py +++ b/mythril/leveldb/accountindexing.py @@ -80,9 +80,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/leveldb/client.py b/mythril/leveldb/client.py index b192b004..7d0bb0df 100644 --- a/mythril/leveldb/client.py +++ b/mythril/leveldb/client.py @@ -79,38 +79,38 @@ 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): + def _get_block_number(self, block_hash): ''' - gets block number by hash + gets block number by its hash ''' - number_key = block_hash_prefix + hash + number_key = block_hash_prefix + block_hash return self.db.get(number_key) - def _get_block_header(self, hash, num): + def _get_block_header(self, block_hash, num): ''' get block header by block header hash & number ''' - header_key = header_prefix + num + hash + 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): + def _get_address_by_hash(self, block_hash): ''' get mapped address by its hash ''' - address_key = address_prefix + hash + address_key = address_prefix + block_hash return self.db.get(address_key) def _get_last_indexed_number(self): @@ -119,12 +119,12 @@ class LevelDBReader(object): ''' return self.db.get(address_mapping_head_key) - def _get_block_receipts(self, hash, num): + 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 +216,12 @@ class EthLevelDB(object): if not cnt % 1000: logging.info("Searched %d contracts" % cnt) - def contract_hash_to_address(self, hash): + 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 +230,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): ''' From 128a6f76e2bd51b1868dad7dd4ca283f03de14e0 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 22:26:48 +0200 Subject: [PATCH 18/84] Revert taint test refactoring to fix pytest fixtures --- tests/taint_runner_test.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/taint_runner_test.py b/tests/taint_runner_test.py index 9b533f3b..e8dd3078 100644 --- a/tests/taint_runner_test.py +++ b/tests/taint_runner_test.py @@ -7,13 +7,13 @@ from mythril.laser.ethereum.state import MachineState, Account, Environment, Glo from mythril.laser.ethereum.svm import LaserEVM -def test_execute_state(state_mocker): +def test_execute_state(mocker): record = TaintRecord() record.stack = [True, False, True] state = GlobalState(None, None, None) state.mstate.stack = [1, 2, 3] - state_mocker.patch.object(state, 'get_current_instruction') + mocker.patch.object(state, 'get_current_instruction') state.get_current_instruction.return_value = {"opcode": "ADD"} # Act @@ -24,19 +24,19 @@ def test_execute_state(state_mocker): assert record.stack == [True, False, True] -def test_execute_node(node_mocker): +def test_execute_node(mocker): record = TaintRecord() record.stack = [True, True, False, False] state_1 = GlobalState(None, None, None) state_1.mstate.stack = [1, 2, 3, 1] state_1.mstate.pc = 1 - node_mocker.patch.object(state_1, 'get_current_instruction') + mocker.patch.object(state_1, 'get_current_instruction') state_1.get_current_instruction.return_value = {"opcode": "SWAP1"} state_2 = GlobalState(None, 1, None) state_2.mstate.stack = [1, 2, 4, 1] - node_mocker.patch.object(state_2, 'get_current_instruction') + mocker.patch.object(state_2, 'get_current_instruction') state_2.get_current_instruction.return_value = {"opcode": "ADD"} node = Node("Test contract") @@ -55,17 +55,17 @@ def test_execute_node(node_mocker): assert state_1 in record.states -def test_execute(state_mocker): +def test_execute(mocker): active_account = Account('0x00') environment = Environment(active_account, None, None, None, None, None) state_1 = GlobalState(None, environment, None, MachineState(gas=10000000)) state_1.mstate.stack = [1, 2] - state_mocker.patch.object(state_1, 'get_current_instruction') + mocker.patch.object(state_1, 'get_current_instruction') state_1.get_current_instruction.return_value = {"opcode": "PUSH"} state_2 = GlobalState(None, environment, None, MachineState(gas=10000000)) state_2.mstate.stack = [1, 2, 3] - state_mocker.patch.object(state_2, 'get_current_instruction') + mocker.patch.object(state_2, 'get_current_instruction') state_2.get_current_instruction.return_value = {"opcode": "ADD"} node_1 = Node("Test contract") @@ -73,7 +73,7 @@ def test_execute(state_mocker): state_3 = GlobalState(None, environment, None, MachineState(gas=10000000)) state_3.mstate.stack = [1, 2] - state_mocker.patch.object(state_3, 'get_current_instruction') + mocker.patch.object(state_3, 'get_current_instruction') state_3.get_current_instruction.return_value = {"opcode": "ADD"} node_2 = Node("Test contract") From fc0ead33b5f9df484a2ece7d72d935f50a8b5cd9 Mon Sep 17 00:00:00 2001 From: JoranHonig Date: Wed, 10 Oct 2018 10:44:55 +0200 Subject: [PATCH 19/84] Add PyPI downloads badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41566bac..99a91d4f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![Master Build Status](https://img.shields.io/circleci/project/github/ConsenSys/mythril/master.svg) [![Waffle.io - Columns and their card count](https://badge.waffle.io/ConsenSys/mythril.svg?columns=all)](https://waffle.io/ConsenSys/mythril) [![Sonarcloud - Maintainability](https://sonarcloud.io/api/project_badges/measure?project=mythril&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=mythril) - +![mythril PyPI Downloads](https://pypistats.com/badge/mythril.png) mythril Mythril OSS is the classic security analysis tool for Ethereum smart contracts. It uses concolic analysis, taint analysis and control flow checking to detect a variety of security vulnerabilities. From 850032da415d43a1e769da40b13b99be23f89395 Mon Sep 17 00:00:00 2001 From: JoranHonig Date: Wed, 10 Oct 2018 10:48:46 +0200 Subject: [PATCH 20/84] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99a91d4f..3a57ede6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![Master Build Status](https://img.shields.io/circleci/project/github/ConsenSys/mythril/master.svg) [![Waffle.io - Columns and their card count](https://badge.waffle.io/ConsenSys/mythril.svg?columns=all)](https://waffle.io/ConsenSys/mythril) [![Sonarcloud - Maintainability](https://sonarcloud.io/api/project_badges/measure?project=mythril&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=mythril) -![mythril PyPI Downloads](https://pypistats.com/badge/mythril.png) +[![PyPI Statistics](https://pypistats.com/badge/mythril.svg)](https://pypistats.com/package/mythril) mythril Mythril OSS is the classic security analysis tool for Ethereum smart contracts. It uses concolic analysis, taint analysis and control flow checking to detect a variety of security vulnerabilities. From 06d0bca91083771855a9384f708910a0ad4568ad Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Wed, 10 Oct 2018 16:47:32 +0200 Subject: [PATCH 21/84] Add static methods where needed --- mythril/laser/ethereum/instructions.py | 10 ++++++---- mythril/mythril.py | 9 ++++++--- tests/native_test.py | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index a7c19959..57315bb3 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -779,7 +779,8 @@ class Instruction: return self._sload_helper(global_state, str(index)) - def _sload_helper(self, global_state, index, constraints=None): + @staticmethod + def _sload_helper(global_state, index, constraints=None): try: data = global_state.environment.active_account.storage[index] except KeyError: @@ -792,8 +793,8 @@ class Instruction: global_state.mstate.stack.append(data) return [global_state] - - def _get_constraints(self, keccak_keys, this_key, argument): + @staticmethod + def _get_constraints(keccak_keys, this_key, argument): global keccak_function_manager for keccak_key in keccak_keys: if keccak_key == this_key: @@ -843,7 +844,8 @@ class Instruction: return self._sstore_helper(global_state, str(index), value) - def _sstore_helper(self, global_state, index, value, constraint=None): + @staticmethod + def _sstore_helper(global_state, index, value, constraint=None): try: global_state.environment.active_account = deepcopy(global_state.environment.active_account) global_state.accounts[ diff --git a/mythril/mythril.py b/mythril/mythril.py index 080493fd..9cdeb0fd 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -103,7 +103,8 @@ class Mythril(object): self.contracts = [] # loaded contracts - def _init_mythril_dir(self): + @staticmethod + def _init_mythril_dir(): try: mythril_dir = os.environ['MYTHRIL_DIR'] except KeyError: @@ -179,7 +180,8 @@ class Mythril(object): def analyze_truffle_project(self, *args, **kwargs): return analyze_truffle_project(self.sigs, *args, **kwargs) # just passthru by passing signatures for now - def _init_solc_binary(self, version): + @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) @@ -435,7 +437,8 @@ class Mythril(object): 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 disassemble(self, contract): + @staticmethod + def disassemble(contract): return contract.get_easm() @staticmethod diff --git a/tests/native_test.py b/tests/native_test.py index a0942d42..eafd003b 100644 --- a/tests/native_test.py +++ b/tests/native_test.py @@ -98,9 +98,9 @@ def _test_natives(laser_info, test_list, test_name): assert(success == len(test_list)) - class NativeTests(BaseTestCase): - def runTest(self): + @staticmethod + def runTest(): disassembly = SolidityContract('./tests/native_tests.sol').disassembly account = Account("0x0000000000000000000000000000000000000000", disassembly) accounts = {account.address: account} From 0b422a5e42afda3e3787a914851e8767ba1216a1 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Wed, 10 Oct 2018 16:47:52 +0200 Subject: [PATCH 22/84] Remove redeclared vars without usage --- tests/native_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/native_test.py b/tests/native_test.py index eafd003b..bcb0eab9 100644 --- a/tests/native_test.py +++ b/tests/native_test.py @@ -6,13 +6,13 @@ from mythril.laser.ethereum import svm from tests import * -SHA256_TEST = [ (0,False) for i in range(6)] +SHA256_TEST = [(0, False) for _ in range(6)] -RIPEMD160_TEST = [ (0,False) for i in range(6)] +RIPEMD160_TEST = [(0, False) for _ in range(6)] -ECRECOVER_TEST = [ (0,False) for i in range(9)] +ECRECOVER_TEST = [(0, False) for _ in range(9)] -IDENTITY_TEST = [ (0, False) for i in range(4)] +IDENTITY_TEST = [(0, False) for _ in range(4)] SHA256_TEST[0] = (5555555555555555, True) #These are Random numbers to check whether the 'if condition' is entered or not(True means entered) SHA256_TEST[1] = (323232325445454546, True) From 0694867c2e8cbc1e7d5899581002422b5891d123 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Wed, 10 Oct 2018 16:48:50 +0200 Subject: [PATCH 23/84] Remove redundant parentheses --- mythril/analysis/symbolic.py | 4 ++-- mythril/analysis/traceexplore.py | 2 +- mythril/ether/asm.py | 4 ++-- mythril/ether/ethcontract.py | 4 ++-- mythril/ether/evm.py | 6 +++--- mythril/ether/util.py | 2 +- mythril/laser/ethereum/util.py | 4 ++-- mythril/leveldb/accountindexing.py | 4 ++-- mythril/leveldb/state.py | 4 ++-- mythril/support/loader.py | 4 ++-- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index 09447a67..d11d4189 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -67,7 +67,7 @@ class SymExecWrapper: # ignore prebuilts continue - if (meminstart.type == VarType.CONCRETE and meminsz.type == VarType.CONCRETE): + if meminstart.type == VarType.CONCRETE and meminsz.type == VarType.CONCRETE: self.calls.append(Call(self.nodes[key], state, state_index, op, to, gas, value, state.mstate.memory[meminstart.val:meminsz.val * 4])) else: self.calls.append(Call(self.nodes[key], state, state_index, op, to, gas, value)) @@ -105,7 +105,7 @@ class SymExecWrapper: taint = True for constraint in s.node.constraints: - if ("caller" in str(constraint)): + if "caller" in str(constraint): taint = False break diff --git a/mythril/analysis/traceexplore.py b/mythril/analysis/traceexplore.py index dc7af177..62d9ea50 100644 --- a/mythril/analysis/traceexplore.py +++ b/mythril/analysis/traceexplore.py @@ -81,7 +81,7 @@ def get_serializable_statespace(statespace): for edge in statespace.edges: - if (edge.condition is None): + if edge.condition is None: label = "" else: diff --git a/mythril/ether/asm.py b/mythril/ether/asm.py index 5cc2b4b5..5e2267ea 100644 --- a/mythril/ether/asm.py +++ b/mythril/ether/asm.py @@ -80,7 +80,7 @@ def find_opcode_sequence(pattern, instruction_list): matched = False break - if (matched): + if matched: match_indexes.append(i) return match_indexes @@ -102,7 +102,7 @@ def disassemble(bytecode): instruction = {'address': addr} try: - if (sys.version_info > (3, 0)): + if sys.version_info > (3, 0): opcode = opcodes[bytecode[addr]] else: opcode = opcodes[ord(bytecode[addr])] diff --git a/mythril/ether/ethcontract.py b/mythril/ether/ethcontract.py index b43b1919..0b7ec3a9 100644 --- a/mythril/ether/ethcontract.py +++ b/mythril/ether/ethcontract.py @@ -49,7 +49,7 @@ class ETHContract(persistent.Persistent): m = re.match(r'^code#([a-zA-Z0-9\s,\[\]]+)#', token) - if (m): + if m: if easm_code is None: easm_code = self.get_easm() @@ -59,7 +59,7 @@ class ETHContract(persistent.Persistent): m = re.match(r'^func#([a-zA-Z0-9\s_,(\\)\[\]]+)#$', token) - if (m): + if m: sign_hash = "0x" + utils.sha3(m.group(1))[:4].hex() diff --git a/mythril/ether/evm.py b/mythril/ether/evm.py index 449fcdcf..fcf2e0f1 100644 --- a/mythril/ether/evm.py +++ b/mythril/ether/evm.py @@ -47,13 +47,13 @@ def trace(code, calldata = ""): m = re.match(r'.*stack=(\[.*?\])', line) - if (m): + if m: stackitems = re.findall(r'b\'(\d+)\'', m.group(1)) stack = "["; - if (len(stackitems)): + if len(stackitems): for i in range(0, len(stackitems) - 1): stack += hex(int(stackitems[i])) + ", " @@ -65,7 +65,7 @@ def trace(code, calldata = ""): else: stack = "[]" - if (re.match(r'^PUSH.*', op)): + 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}) diff --git a/mythril/ether/util.py b/mythril/ether/util.py index 0e4bae40..6b351665 100644 --- a/mythril/ether/util.py +++ b/mythril/ether/util.py @@ -10,7 +10,7 @@ import json def safe_decode(hex_encoded_string): - if (hex_encoded_string.startswith("0x")): + if hex_encoded_string.startswith("0x"): return bytes.fromhex(hex_encoded_string[2:]) else: return bytes.fromhex(hex_encoded_string) diff --git a/mythril/laser/ethereum/util.py b/mythril/laser/ethereum/util.py index c6c8e5ce..8d680534 100644 --- a/mythril/laser/ethereum/util.py +++ b/mythril/laser/ethereum/util.py @@ -16,7 +16,7 @@ def sha3(seed): def safe_decode(hex_encoded_string): - if (hex_encoded_string.startswith("0x")): + if hex_encoded_string.startswith("0x"): return bytes.fromhex(hex_encoded_string[2:]) else: return bytes.fromhex(hex_encoded_string) @@ -99,7 +99,7 @@ def concrete_int_to_bytes(val): # logging.debug("concrete_int_to_bytes " + str(val)) - if (type(val) == int): + if type(val) == int: return val.to_bytes(32, byteorder='big') return (simplify(val).as_long()).to_bytes(32, byteorder='big') diff --git a/mythril/leveldb/accountindexing.py b/mythril/leveldb/accountindexing.py index 26a4ab9a..9732ca19 100644 --- a/mythril/leveldb/accountindexing.py +++ b/mythril/leveldb/accountindexing.py @@ -128,7 +128,7 @@ class AccountIndexer(object): count = 0 processed = 0 - while (blockNum <= self.lastBlock): + while blockNum <= self.lastBlock: # leveldb cannot be accessed on multiple processes (not even readonly) # multithread version performs significantly worse than serial try: @@ -154,4 +154,4 @@ class AccountIndexer(object): self.db.writer._set_last_indexed_number(self.lastProcessedBlock) print("Finished indexing") - self.lastBlock = self.lastProcessedBlock \ No newline at end of file + self.lastBlock = self.lastProcessedBlock diff --git a/mythril/leveldb/state.py b/mythril/leveldb/state.py index 96360300..135c783c 100644 --- a/mythril/leveldb/state.py +++ b/mythril/leveldb/state.py @@ -88,7 +88,7 @@ class Account(rlp.Serializable): ''' return self.nonce == 0 and self.balance == 0 and self.code_hash == BLANK_HASH -class State(): +class State: ''' adjusted state from ethereum.state ''' @@ -125,4 +125,4 @@ class State(): ''' for address_hash, rlpdata in self.secure_trie.trie.iter_branch(): if rlpdata != trie.BLANK_NODE: - yield rlp.decode(rlpdata, Account, db=self.db, address=address_hash) \ No newline at end of file + yield rlp.decode(rlpdata, Account, db=self.db, address=address_hash) diff --git a/mythril/support/loader.py b/mythril/support/loader.py index 7c0855ec..d219d17c 100644 --- a/mythril/support/loader.py +++ b/mythril/support/loader.py @@ -37,7 +37,7 @@ class DynLoader: m = re.match(r'^(0x[0-9a-fA-F]{40})$', dependency_address) - if (m): + if m: dependency_address = m.group(1) else: @@ -47,7 +47,7 @@ class DynLoader: code = self.eth.eth_getCode(dependency_address) - if (code == "0x"): + if code == "0x": return None else: return Disassembly(code) From cadf702acecfa5d1d9cdfebc2573366d5d44ee31 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Wed, 10 Oct 2018 16:52:56 +0200 Subject: [PATCH 24/84] Convert single-quoted to double-quoted docstrings --- mythril/analysis/symbolic.py | 4 +- mythril/leveldb/accountindexing.py | 20 +++--- mythril/leveldb/client.py | 104 ++++++++++++++--------------- mythril/leveldb/eth_db.py | 18 ++--- mythril/leveldb/state.py | 32 ++++----- mythril/rpc/base_client.py | 28 ++++---- mythril/rpc/client.py | 4 +- mythril/rpc/utils.py | 16 ++--- 8 files changed, 113 insertions(+), 113 deletions(-) diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index d11d4189..ac03303f 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -9,9 +9,9 @@ from mythril.laser.ethereum.strategy.basic import DepthFirstSearchStrategy, Brea class SymExecWrapper: - ''' + """ Wrapper class for the LASER Symbolic virtual machine. Symbolically executes the code and does a bit of pre-analysis for convenience. - ''' + """ def __init__(self, contract, address, strategy, dynloader=None, max_depth=22, execution_timeout=None, create_timeout=None): diff --git a/mythril/leveldb/accountindexing.py b/mythril/leveldb/accountindexing.py index 9732ca19..7578afd1 100644 --- a/mythril/leveldb/accountindexing.py +++ b/mythril/leveldb/accountindexing.py @@ -34,9 +34,9 @@ class CountableList(object): class ReceiptForStorage(rlp.Serializable): - ''' + """ Receipt format stored in levelDB - ''' + """ fields = [ ('state_root', binary), @@ -50,9 +50,9 @@ class ReceiptForStorage(rlp.Serializable): class AccountIndexer(object): - ''' + """ Updates address index - ''' + """ def __init__(self, ethDB): self.db = ethDB @@ -62,9 +62,9 @@ class AccountIndexer(object): self.updateIfNeeded() def get_contract_by_hash(self, contract_hash): - ''' + """ get mapped 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 @@ -74,9 +74,9 @@ class AccountIndexer(object): return self.db.reader._get_address_by_hash(contract_hash) def _process(self, startblock): - ''' + """ Processesing method - ''' + """ logging.debug("Processing blocks %d to %d" % (startblock, startblock + BATCH_SIZE)) addresses = [] @@ -96,9 +96,9 @@ class AccountIndexer(object): return addresses def updateIfNeeded(self): - ''' + """ update address index - ''' + """ headBlock = self.db.reader._get_head_block() if headBlock is not None: # avoid restarting search if head block is same & we already initialized diff --git a/mythril/leveldb/client.py b/mythril/leveldb/client.py index b192b004..81c3f9e4 100644 --- a/mythril/leveldb/client.py +++ b/mythril/leveldb/client.py @@ -26,23 +26,23 @@ address_mapping_head_key = b'accountMapping' # head (latest) number of indexed def _format_block_number(number): - ''' + """ formats block number to uint64 big endian - ''' + """ return utils.zpad(utils.int_to_big_endian(number), 8) def _encode_hex(v): - ''' + """ encodes hash as hex - ''' + """ return '0x' + utils.encode_hex(v) class LevelDBReader(object): - ''' + """ level db reading interface, can be used with snapshot - ''' + """ def __init__(self, db): self.db = db @@ -50,34 +50,34 @@ class LevelDBReader(object): self.head_state = None def _get_head_state(self): - ''' + """ gets head state - ''' + """ if not self.head_state: root = self._get_head_block().state_root self.head_state = State(self.db, root) return self.head_state def _get_account(self, address): - ''' + """ gets account by address - ''' + """ state = self._get_head_state() account_address = binascii.a2b_hex(utils.remove_0x_head(address)) return state.get_and_cache_account(account_address) def _get_block_hash(self, number): - ''' + """ gets block hash by block number - ''' + """ num = _format_block_number(number) hash_key = header_prefix + num + num_suffix return self.db.get(hash_key) def _get_head_block(self): - ''' + """ gets head block header - ''' + """ if not self.head_block_header: hash = self.db.get(head_header_key) num = self._get_block_number(hash) @@ -91,38 +91,38 @@ class LevelDBReader(object): return self.head_block_header def _get_block_number(self, hash): - ''' + """ gets block number by hash - ''' + """ number_key = block_hash_prefix + 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 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 return self.db.get(address_key) def _get_last_indexed_number(self): - ''' + """ 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 - ''' + """ number = _format_block_number(num) receipts_key = block_receipts_prefix + number + hash receipts_data = self.db.get(receipts_key) @@ -131,44 +131,44 @@ class LevelDBReader(object): class LevelDBWriter(object): - ''' + """ level db writing interface - ''' + """ def __init__(self, db): self.db = db self.wb = None def _set_last_indexed_number(self, number): - ''' + """ sets latest indexed block number - ''' + """ return self.db.put(address_mapping_head_key, _format_block_number(number)) def _start_writing(self): - ''' + """ start writing a batch - ''' + """ self.wb = self.db.write_batch() def _commit_batch(self): - ''' + """ commit batch - ''' + """ self.wb.write() def _store_account_address(self, address): - ''' + """ get block transaction receipts by block header hash & number - ''' + """ address_key = address_prefix + utils.sha3(address) self.wb.put(address_key, address) class EthLevelDB(object): - ''' + """ Go-Ethereum LevelDB client class - ''' + """ def __init__(self, path): self.path = path @@ -177,9 +177,9 @@ class EthLevelDB(object): self.writer = LevelDBWriter(self.db) 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) @@ -188,9 +188,9 @@ class EthLevelDB(object): yield contract, account.address, account.balance def search(self, expression, callback_func): - ''' + """ searches through all contract accounts - ''' + """ cnt = 0 indexer = AccountIndexer(self) @@ -217,9 +217,9 @@ class EthLevelDB(object): logging.info("Searched %d contracts" % cnt) def contract_hash_to_address(self, hash): - ''' + """ tries to find corresponding account address - ''' + """ address_hash = binascii.a2b_hex(utils.remove_0x_head(hash)) indexer = AccountIndexer(self) @@ -227,17 +227,17 @@ class EthLevelDB(object): return _encode_hex(indexer.get_contract_by_hash(address_hash)) def eth_getBlockHeaderByNumber(self, number): - ''' + """ gets block header by block number - ''' + """ hash = self.reader._get_block_hash(number) block_number = _format_block_number(number) return self.reader._get_block_header(hash, block_number) def eth_getBlockByNumber(self, number): - ''' + """ gets block body by block number - ''' + """ block_hash = self.reader._get_block_hash(number) block_number = _format_block_number(number) body_key = body_prefix + block_number + block_hash @@ -246,22 +246,22 @@ class EthLevelDB(object): return body def eth_getCode(self, address): - ''' + """ gets account code - ''' + """ account = self.reader._get_account(address) return _encode_hex(account.code) def eth_getBalance(self, address): - ''' + """ gets account balance - ''' + """ account = self.reader._get_account(address) return account.balance def eth_getStorageAt(self, address, position): - ''' + """ gets account storage data at position - ''' + """ account = self.reader._get_account(address) return _encode_hex(utils.zpad(utils.encode_int(account.get_storage_data(position)), 32)) diff --git a/mythril/leveldb/eth_db.py b/mythril/leveldb/eth_db.py index a46d9e93..ab9107fa 100644 --- a/mythril/leveldb/eth_db.py +++ b/mythril/leveldb/eth_db.py @@ -3,27 +3,27 @@ from ethereum.db import BaseDB class ETH_DB(BaseDB): - ''' + """ adopts pythereum BaseDB using plyvel - ''' + """ def __init__(self, path): self.db = plyvel.DB(path) def get(self, key): - ''' + """ gets value for key - ''' + """ return self.db.get(key) def put(self, key, value): - ''' + """ puts value for key - ''' + """ self.db.put(key, value) def write_batch(self): - ''' + """ start writing a batch - ''' - return self.db.write_batch() \ No newline at end of file + """ + return self.db.write_batch() diff --git a/mythril/leveldb/state.py b/mythril/leveldb/state.py index 135c783c..e8f86331 100644 --- a/mythril/leveldb/state.py +++ b/mythril/leveldb/state.py @@ -32,9 +32,9 @@ STATE_DEFAULTS = { class Account(rlp.Serializable): - ''' + """ adjusted account from ethereum.state - ''' + """ fields = [ ('nonce', big_endian_int), @@ -57,15 +57,15 @@ class Account(rlp.Serializable): @property def code(self): - ''' + """ code rlp data - ''' + """ return self.db.get(self.code_hash) def get_storage_data(self, key): - ''' + """ get storage data - ''' + """ if key not in self.storage_cache: v = self.storage_trie.get(utils.encode_int32(key)) self.storage_cache[key] = utils.big_endian_to_int( @@ -74,24 +74,24 @@ class Account(rlp.Serializable): @classmethod def blank_account(cls, db, address, 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.existent_at_start = False return o def is_blank(self): - ''' + """ checks if is a blank account - ''' + """ return self.nonce == 0 and self.balance == 0 and self.code_hash == BLANK_HASH class State: - ''' + """ adjusted state from ethereum.state - ''' + """ def __init__(self, db, root): self.db = db @@ -101,9 +101,9 @@ class State: 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) @@ -120,9 +120,9 @@ class State: return o def get_all_accounts(self): - ''' + """ iterates through trie to and yields non-blank leafs as accounts - ''' + """ for address_hash, rlpdata in self.secure_trie.trie.iter_branch(): if rlpdata != trie.BLANK_NODE: yield rlp.decode(rlpdata, Account, db=self.db, address=address_hash) diff --git a/mythril/rpc/base_client.py b/mythril/rpc/base_client.py index bc8b1994..9234ecf9 100644 --- a/mythril/rpc/base_client.py +++ b/mythril/rpc/base_client.py @@ -20,64 +20,64 @@ class BaseClient(object): pass def eth_coinbase(self): - ''' + """ https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_coinbase TESTED - ''' + """ return self._call('eth_coinbase') def eth_blockNumber(self): - ''' + """ https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_blocknumber TESTED - ''' + """ return hex_to_dec(self._call('eth_blockNumber')) def eth_getBalance(self, address=None, block=BLOCK_TAG_LATEST): - ''' + """ https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getbalance TESTED - ''' + """ address = address or self.eth_coinbase() block = validate_block(block) return hex_to_dec(self._call('eth_getBalance', [address, block])) def eth_getStorageAt(self, address=None, position=0, block=BLOCK_TAG_LATEST): - ''' + """ https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getstorageat TESTED - ''' + """ block = validate_block(block) return self._call('eth_getStorageAt', [address, hex(position), block]) def eth_getCode(self, address, default_block=BLOCK_TAG_LATEST): - ''' + """ https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getcode NEEDS TESTING - ''' + """ if isinstance(default_block, str): if default_block not in BLOCK_TAGS: raise ValueError return self._call('eth_getCode', [address, default_block]) def eth_getBlockByNumber(self, block=BLOCK_TAG_LATEST, tx_objects=True): - ''' + """ https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber TESTED - ''' + """ block = validate_block(block) return self._call('eth_getBlockByNumber', [block, tx_objects]) def eth_getTransactionReceipt(self, tx_hash): - ''' + """ https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt TESTED - ''' + """ return self._call('eth_getTransactionReceipt', [tx_hash]) diff --git a/mythril/rpc/client.py b/mythril/rpc/client.py index 6a7f0b96..1545092f 100644 --- a/mythril/rpc/client.py +++ b/mythril/rpc/client.py @@ -17,9 +17,9 @@ JSON_MEDIA_TYPE = 'application/json' This code is adapted from: https://github.com/ConsenSys/ethjsonrpc ''' class EthJsonRpc(BaseClient): - ''' + """ Ethereum JSON-RPC client class - ''' + """ def __init__(self, host='localhost', port=GETH_DEFAULT_RPC_PORT, tls=False): self.host = host diff --git a/mythril/rpc/utils.py b/mythril/rpc/utils.py index e87b7dc6..5f98fcea 100644 --- a/mythril/rpc/utils.py +++ b/mythril/rpc/utils.py @@ -2,17 +2,17 @@ from .constants import BLOCK_TAGS def hex_to_dec(x): - ''' + """ Convert hex to decimal - ''' + """ return int(x, 16) def clean_hex(d): - ''' + """ Convert decimal to hex and remove the "L" suffix that is appended to large numbers - ''' + """ return hex(d).rstrip('L') def validate_block(block): @@ -25,14 +25,14 @@ def validate_block(block): def wei_to_ether(wei): - ''' + """ Convert wei to ether - ''' + """ return 1.0 * wei / 10**18 def ether_to_wei(ether): - ''' + """ Convert ether to wei - ''' + """ return ether * 10**18 From 6e8527af80bbddd7e00ed99df78196fd49f2c959 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Wed, 10 Oct 2018 17:00:32 +0200 Subject: [PATCH 25/84] Remove trailing semicolon --- mythril/ether/evm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/ether/evm.py b/mythril/ether/evm.py index fcf2e0f1..0bcc7206 100644 --- a/mythril/ether/evm.py +++ b/mythril/ether/evm.py @@ -51,7 +51,7 @@ def trace(code, calldata = ""): stackitems = re.findall(r'b\'(\d+)\'', m.group(1)) - stack = "["; + stack = "[" if len(stackitems): From f6b2352a606a00bcb6e1d6449db0cfc83d7618a9 Mon Sep 17 00:00:00 2001 From: Nathan Date: Wed, 10 Oct 2018 12:31:08 -0400 Subject: [PATCH 26/84] Added coloredlogs --- mythril/interfaces/cli.py | 7 +++++-- requirements.txt | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index be6cec59..3e398704 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -5,7 +5,7 @@ http://www.github.com/ConsenSys/mythril """ -import logging +import logging, coloredlogs import json import sys import argparse @@ -103,7 +103,10 @@ def main(): if args.v: if 0 <= args.v < 3: - logging.basicConfig(level=[logging.NOTSET, logging.INFO, logging.DEBUG][args.v]) + coloredlogs.install( + fmt='%(name)s[%(process)d] %(levelname)s %(message)s', + level=[logging.NOTSET, logging.INFO, logging.DEBUG][args.v] + ) else: exit_with_error(args.outform, "Invalid -v value, you can find valid values in usage") diff --git a/requirements.txt b/requirements.txt index 53a4a086..30162f8d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +coloredlogs>=10.0 configparser>=3.5.0 coverage eth_abi>=1.0.0 From ca1ea9b40c3334e323ae6921e50598b3c63ab7f6 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Wed, 10 Oct 2018 11:12:33 -0700 Subject: [PATCH 27/84] Smaller Waffle Badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a57ede6..36734764 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Discord](https://img.shields.io/discord/481002907366588416.svg)](https://discord.gg/E3YrVtG) [![PyPI](https://badge.fury.io/py/mythril.svg)](https://pypi.python.org/pypi/mythril) ![Master Build Status](https://img.shields.io/circleci/project/github/ConsenSys/mythril/master.svg) -[![Waffle.io - Columns and their card count](https://badge.waffle.io/ConsenSys/mythril.svg?columns=all)](https://waffle.io/ConsenSys/mythril) +[![Waffle.io - Columns and their card count](https://badge.waffle.io/ConsenSys/mythril.svg?columns=In%20Progress) [![Sonarcloud - Maintainability](https://sonarcloud.io/api/project_badges/measure?project=mythril&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=mythril) [![PyPI Statistics](https://pypistats.com/badge/mythril.svg)](https://pypistats.com/package/mythril) mythril From 59f31f569e9a467c3d77ee47726853e06315a5bf Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Wed, 10 Oct 2018 11:13:59 -0700 Subject: [PATCH 28/84] Remve stray "[" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 36734764..74868731 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Discord](https://img.shields.io/discord/481002907366588416.svg)](https://discord.gg/E3YrVtG) [![PyPI](https://badge.fury.io/py/mythril.svg)](https://pypi.python.org/pypi/mythril) ![Master Build Status](https://img.shields.io/circleci/project/github/ConsenSys/mythril/master.svg) -[![Waffle.io - Columns and their card count](https://badge.waffle.io/ConsenSys/mythril.svg?columns=In%20Progress) +![Waffle.io - Columns and their card count](https://badge.waffle.io/ConsenSys/mythril.svg?columns=In%20Progress) [![Sonarcloud - Maintainability](https://sonarcloud.io/api/project_badges/measure?project=mythril&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=mythril) [![PyPI Statistics](https://pypistats.com/badge/mythril.svg)](https://pypistats.com/package/mythril) mythril From 0515a0b9dc7f757a3aa21d6c3f20d255325fdf82 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Wed, 10 Oct 2018 11:15:54 -0700 Subject: [PATCH 29/84] =?UTF-8?q?Re-add=20link=20#=E2=80=91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74868731..f6a1f38a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Discord](https://img.shields.io/discord/481002907366588416.svg)](https://discord.gg/E3YrVtG) [![PyPI](https://badge.fury.io/py/mythril.svg)](https://pypi.python.org/pypi/mythril) ![Master Build Status](https://img.shields.io/circleci/project/github/ConsenSys/mythril/master.svg) -![Waffle.io - Columns and their card count](https://badge.waffle.io/ConsenSys/mythril.svg?columns=In%20Progress) +[![Waffle.io - Columns and their card count](https://badge.waffle.io/ConsenSys/mythril.svg?columns=In%20Progress)](https://waffle.io/ConsenSys/mythril) [![Sonarcloud - Maintainability](https://sonarcloud.io/api/project_badges/measure?project=mythril&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=mythril) [![PyPI Statistics](https://pypistats.com/badge/mythril.svg)](https://pypistats.com/package/mythril) mythril From 818a340c3630fa6fbec7c8f09aadc23839235af7 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Wed, 10 Oct 2018 11:17:14 -0700 Subject: [PATCH 30/84] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f6a1f38a..a60f239f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Mythril OSS is the classic security analysis tool for Ethereum smart contracts. Whether you want to contribute, need support, or want to learn what we have cooking for the future, our [Discord server](https://discord.gg/E3YrVtG) will serve your needs! -Oh and by the way, we're now building a whole security tools ecosystem with [Mythril Platform](https://mythril.ai). You should definitely check that out as well. +Oh and by the way, we're building an easy-to-use SaaS solution and tools ecosystem for Ethereum developers called [Mythril Platform](https://mythril.ai). You should definitely check that out as well. ## Installation and setup @@ -25,7 +25,7 @@ Install from Pypi: ```bash $ pip3 install mythril -``` +```now See the [Wiki](https://github.com/ConsenSys/mythril/wiki/Installation-and-Setup) for more detailed instructions. From 4e403b018a7e43c4e72a84b80bac942f07ea0c9a Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Wed, 10 Oct 2018 11:29:36 -0700 Subject: [PATCH 31/84] Fix messed-up formatting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a60f239f..c5fbd20f 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Install from Pypi: ```bash $ pip3 install mythril -```now +``` See the [Wiki](https://github.com/ConsenSys/mythril/wiki/Installation-and-Setup) for more detailed instructions. From 39e58fcbdd996b1fdb4267853887260bc0b63c06 Mon Sep 17 00:00:00 2001 From: Nathan Date: Wed, 10 Oct 2018 19:42:29 -0400 Subject: [PATCH 32/84] added coloredlogs dependency to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index cfb6cb7b..2c92ce4a 100755 --- a/setup.py +++ b/setup.py @@ -82,6 +82,7 @@ setup( packages=find_packages(exclude=['contrib', 'docs', 'tests']), install_requires=[ + 'coloredlogs>=10.0', 'ethereum>=2.3.2', 'z3-solver>=4.5', 'requests', From b7d9c22767aee6e9cf4b4646eb6780348d9724ef Mon Sep 17 00:00:00 2001 From: p0n1 Date: Thu, 11 Oct 2018 16:40:21 +0800 Subject: [PATCH 33/84] ethcontract: replace placeholder for library in creation_code --- mythril/ether/ethcontract.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mythril/ether/ethcontract.py b/mythril/ether/ethcontract.py index b43b1919..cf7e5eed 100644 --- a/mythril/ether/ethcontract.py +++ b/mythril/ether/ethcontract.py @@ -7,7 +7,8 @@ import re class ETHContract(persistent.Persistent): def __init__(self, code, creation_code="", name="Unknown", enable_online_lookup=True): - + + creation_code = re.sub(r'(_+.*_+)', 'aa' * 20, creation_code) self.creation_code = creation_code self.name = name From b53ac587c57e07273e8078e934831fa2080b631f Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Thu, 11 Oct 2018 19:27:18 +0200 Subject: [PATCH 34/84] Rename fmt to format_type in cli interface --- mythril/interfaces/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index e10b4e98..9bab540a 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(fmt, message): - if fmt == 'text' or fmt == 'markdown': +def exit_with_error(format_type, message): + if format_type == 'text' or format_type == 'markdown': print(message) else: result = {'success': False, 'error': str(message), 'issues': []} From 8f8bf3e7c11bd9dd579778163a49004f3d6bc619 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Thu, 11 Oct 2018 21:27:43 +0200 Subject: [PATCH 35/84] Rename format var and refactor mstore memory loop --- mythril/interfaces/cli.py | 4 ++-- mythril/laser/ethereum/instructions.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 9bab540a..061044fd 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_type, message): - if format_type == 'text' or format_type == '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/instructions.py b/mythril/laser/ethereum/instructions.py index 34cd76c7..a1dbbe60 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -707,10 +707,8 @@ class Instruction: try: # Attempt to concretize value - _bytes = util.concrete_int_to_bytes(value) - - for i, b in enumerate(_bytes): - state.memory[mstart + i] = b + bytes_ = util.concrete_int_to_bytes(value) + state.memory[mstart: mstart + len(bytes_)] = bytes_ except: try: state.memory[mstart] = value From b50f72c1aa77e91f4cfcecbcf4dedd7095f1c71e Mon Sep 17 00:00:00 2001 From: p0n1 Date: Fri, 12 Oct 2018 10:01:33 +0800 Subject: [PATCH 36/84] ethcontract: improve comments for placeholder replacing --- mythril/ether/ethcontract.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mythril/ether/ethcontract.py b/mythril/ether/ethcontract.py index cf7e5eed..4e4149ee 100644 --- a/mythril/ether/ethcontract.py +++ b/mythril/ether/ethcontract.py @@ -8,15 +8,15 @@ class ETHContract(persistent.Persistent): def __init__(self, code, creation_code="", name="Unknown", enable_online_lookup=True): - creation_code = re.sub(r'(_+.*_+)', 'aa' * 20, creation_code) - self.creation_code = creation_code - self.name = name - # Workaround: We currently do not support compile-time linking. # 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) + self.creation_code = creation_code + self.name = name self.code = code self.disassembly = Disassembly(code, enable_online_lookup=enable_online_lookup) self.creation_disassembly = Disassembly(creation_code, enable_online_lookup=enable_online_lookup) From 4bb9e22cc5bd44e4552e3de4eba137453443c44b Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sat, 13 Oct 2018 21:31:11 +0200 Subject: [PATCH 37/84] Implement dynamic transaction execution --- mythril/laser/ethereum/svm.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index cdf85484..08f35ba4 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -60,7 +60,7 @@ class LaserEVM: def accounts(self): return self.world_state.accounts - def sym_exec(self, main_address=None, creation_code=None, contract_name=None): + def sym_exec(self, main_address=None, creation_code=None, contract_name=None, max_transactions=3): logging.debug("Starting LASER execution") self.time = datetime.now() @@ -77,12 +77,16 @@ class LaserEVM: # Reset code coverage self.coverage = {} - self.time = datetime.now() - logging.info("Starting message call transaction") - execute_message_call(self, created_account.address) + for i in range(max_transactions): + initial_coverage = self._get_covered_instructions() - self.time = datetime.now() - execute_message_call(self, created_account.address) + self.time = datetime.now() + logging.info("Starting message call transaction, iteration: {}".format(i)) + execute_message_call(self, created_account.address) + + end_coverage = self._get_covered_instructions() + if end_coverage == initial_coverage: + break logging.info("Finished symbolic execution") logging.info("%d nodes, %d edges, %d total states", len(self.nodes), len(self.edges), self.total_states) @@ -90,6 +94,13 @@ class LaserEVM: cov = reduce(lambda sum_, val: sum_ + 1 if val else sum_, coverage[1]) / float(coverage[0]) * 100 logging.info("Achieved {} coverage for code: {}".format(cov, code)) + def _get_covered_instructions(self) -> int: + """ Gets the total number of covered instructions for all accounts in the svm""" + total_covered_instructions = 0 + for _, cv in self.coverage.items(): + total_covered_instructions += reduce(lambda sum_, val: sum_ + 1 if val else sum_, cv[1]) + return total_covered_instructions + def exec(self, create=False): for global_state in self.strategy: if self.execution_timeout and not create: From dfae6b349e04fce301225b2386fdda172e704886 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sat, 13 Oct 2018 21:31:35 +0200 Subject: [PATCH 38/84] Change loglevel to debug for some statements --- mythril/laser/ethereum/call.py | 14 +++++++------- mythril/laser/ethereum/instructions.py | 2 +- mythril/laser/ethereum/svm.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 31e6990e..fd5e24bb 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -49,7 +49,7 @@ def get_callee_address(global_state:GlobalState, dynamic_loader: DynLoader, symb try: callee_address = hex(util.get_concrete_int(symbolic_to_address)) except AttributeError: - logging.info("Symbolic call encountered") + logging.debug("Symbolic call encountered") match = re.search(r'storage_(\d+)', str(simplify(symbolic_to_address))) logging.debug("CALL to: " + str(simplify(symbolic_to_address))) @@ -58,7 +58,7 @@ def get_callee_address(global_state:GlobalState, dynamic_loader: DynLoader, symb raise ValueError() index = int(match.group(1)) - logging.info("Dynamic contract address at storage index {}".format(index)) + logging.debug("Dynamic contract address at storage index {}".format(index)) # attempt to read the contract address from instance storage try: @@ -89,22 +89,22 @@ def get_callee_account(global_state, callee_address, dynamic_loader): return global_state.accounts[callee_address] except KeyError: # We have a valid call address, but contract is not in the modules list - logging.info("Module with address " + callee_address + " not loaded.") + logging.debug("Module with address " + callee_address + " not loaded.") if dynamic_loader is None: raise ValueError() - logging.info("Attempting to load dependency") + logging.debug("Attempting to load dependency") try: code = dynamic_loader.dynld(environment.active_account.address, callee_address) except Exception as e: - logging.info("Unable to execute dynamic loader.") + logging.debug("Unable to execute dynamic loader.") raise ValueError() if code is None: - logging.info("No code returned, not a contract account?") + logging.debug("No code returned, not a contract account?") raise ValueError() - logging.info("Dependency loaded: " + callee_address) + logging.debug("Dependency loaded: " + callee_address) callee_account = Account(callee_address, code, callee_address, dynamic_loader=dynamic_loader) accounts[callee_address] = callee_account diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 57315bb3..c2c01b4d 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -1028,7 +1028,7 @@ class Instruction: callee_address, callee_account, call_data, value, call_data_type, gas, memory_out_offset, memory_out_size = get_call_parameters( global_state, self.dynamic_loader, True) except ValueError as e: - logging.info( + logging.debug( "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format(e) ) # TODO: decide what to do in this case diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 08f35ba4..6c48dabe 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -263,7 +263,7 @@ class LaserEVM: environment.active_function_name = disassembly.addr_to_func[address] new_node.flags |= NodeFlags.FUNC_ENTRY - logging.info( + logging.debug( "- Entering function " + environment.active_account.contract_name + ":" + new_node.function_name) elif address == 0: environment.active_function_name = "fallback" From 3e6ca5e976de219fc444c7871c6f22da1c45b7af Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sat, 13 Oct 2018 22:53:36 +0200 Subject: [PATCH 39/84] Chnage regex to limit to correct pattern --- mythril/ether/ethcontract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From c2396a50faa3e9cb0b536756e8e1a31d88661c05 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sun, 14 Oct 2018 00:13:00 +0200 Subject: [PATCH 40/84] Move blockchain interfacing code to ethereum.interface --- mythril/{leveldb => ethereum}/__init__.py | 0 mythril/{rpc => ethereum/interface}/__init__.py | 0 mythril/ethereum/interface/leveldb/__init__.py | 0 .../{ => ethereum/interface}/leveldb/accountindexing.py | 0 mythril/{ => ethereum/interface}/leveldb/client.py | 8 ++++---- mythril/{ => ethereum/interface}/leveldb/eth_db.py | 0 mythril/{ => ethereum/interface}/leveldb/state.py | 0 mythril/ethereum/interface/rpc/__init__.py | 0 mythril/{ => ethereum/interface}/rpc/base_client.py | 0 mythril/{ => ethereum/interface}/rpc/client.py | 0 mythril/{ => ethereum/interface}/rpc/constants.py | 0 mythril/{ => ethereum/interface}/rpc/exceptions.py | 0 mythril/{ => ethereum/interface}/rpc/utils.py | 0 mythril/mythril.py | 6 +++--- tests/rpc_test.py | 2 +- 15 files changed, 8 insertions(+), 8 deletions(-) rename mythril/{leveldb => ethereum}/__init__.py (100%) rename mythril/{rpc => ethereum/interface}/__init__.py (100%) create mode 100644 mythril/ethereum/interface/leveldb/__init__.py rename mythril/{ => ethereum/interface}/leveldb/accountindexing.py (100%) rename mythril/{ => ethereum/interface}/leveldb/client.py (96%) rename mythril/{ => ethereum/interface}/leveldb/eth_db.py (100%) rename mythril/{ => ethereum/interface}/leveldb/state.py (100%) create mode 100644 mythril/ethereum/interface/rpc/__init__.py rename mythril/{ => ethereum/interface}/rpc/base_client.py (100%) rename mythril/{ => ethereum/interface}/rpc/client.py (100%) rename mythril/{ => ethereum/interface}/rpc/constants.py (100%) rename mythril/{ => ethereum/interface}/rpc/exceptions.py (100%) rename mythril/{ => ethereum/interface}/rpc/utils.py (100%) diff --git a/mythril/leveldb/__init__.py b/mythril/ethereum/__init__.py similarity index 100% rename from mythril/leveldb/__init__.py rename to mythril/ethereum/__init__.py diff --git a/mythril/rpc/__init__.py b/mythril/ethereum/interface/__init__.py similarity index 100% rename from mythril/rpc/__init__.py rename to mythril/ethereum/interface/__init__.py diff --git a/mythril/ethereum/interface/leveldb/__init__.py b/mythril/ethereum/interface/leveldb/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mythril/leveldb/accountindexing.py b/mythril/ethereum/interface/leveldb/accountindexing.py similarity index 100% rename from mythril/leveldb/accountindexing.py rename to mythril/ethereum/interface/leveldb/accountindexing.py diff --git a/mythril/leveldb/client.py b/mythril/ethereum/interface/leveldb/client.py similarity index 96% rename from mythril/leveldb/client.py rename to mythril/ethereum/interface/leveldb/client.py index 81c3f9e4..164185de 100644 --- a/mythril/leveldb/client.py +++ b/mythril/ethereum/interface/leveldb/client.py @@ -1,12 +1,12 @@ import binascii import rlp -from mythril.leveldb.accountindexing import CountableList -from mythril.leveldb.accountindexing import ReceiptForStorage, AccountIndexer +from mythril.ethereum.interface.leveldb.accountindexing import CountableList +from mythril.ethereum.interface.leveldb.accountindexing import ReceiptForStorage, AccountIndexer import logging from ethereum import utils from ethereum.block import BlockHeader, Block -from mythril.leveldb.state import State -from mythril.leveldb.eth_db import ETH_DB +from mythril.ethereum.interface.leveldb import State +from mythril.ethereum.interface.leveldb.eth_db import ETH_DB from mythril.ether.ethcontract import ETHContract from mythril.exceptions import AddressNotFoundError diff --git a/mythril/leveldb/eth_db.py b/mythril/ethereum/interface/leveldb/eth_db.py similarity index 100% rename from mythril/leveldb/eth_db.py rename to mythril/ethereum/interface/leveldb/eth_db.py diff --git a/mythril/leveldb/state.py b/mythril/ethereum/interface/leveldb/state.py similarity index 100% rename from mythril/leveldb/state.py rename to mythril/ethereum/interface/leveldb/state.py diff --git a/mythril/ethereum/interface/rpc/__init__.py b/mythril/ethereum/interface/rpc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mythril/rpc/base_client.py b/mythril/ethereum/interface/rpc/base_client.py similarity index 100% rename from mythril/rpc/base_client.py rename to mythril/ethereum/interface/rpc/base_client.py diff --git a/mythril/rpc/client.py b/mythril/ethereum/interface/rpc/client.py similarity index 100% rename from mythril/rpc/client.py rename to mythril/ethereum/interface/rpc/client.py diff --git a/mythril/rpc/constants.py b/mythril/ethereum/interface/rpc/constants.py similarity index 100% rename from mythril/rpc/constants.py rename to mythril/ethereum/interface/rpc/constants.py diff --git a/mythril/rpc/exceptions.py b/mythril/ethereum/interface/rpc/exceptions.py similarity index 100% rename from mythril/rpc/exceptions.py rename to mythril/ethereum/interface/rpc/exceptions.py diff --git a/mythril/rpc/utils.py b/mythril/ethereum/interface/rpc/utils.py similarity index 100% rename from mythril/rpc/utils.py rename to mythril/ethereum/interface/rpc/utils.py diff --git a/mythril/mythril.py b/mythril/mythril.py index 9cdeb0fd..027e1211 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -20,8 +20,8 @@ import platform from mythril.ether import util from mythril.ether.ethcontract import ETHContract from mythril.ether.soliditycontract import SolidityContract, get_contracts_from_file -from mythril.rpc.client import EthJsonRpc -from mythril.rpc.exceptions import ConnectionError +from mythril.ethereum.interface.rpc.client import EthJsonRpc +from mythril.ethereum.interface.rpc.exceptions import ConnectionError from mythril.support import signatures from mythril.support.truffle import analyze_truffle_project from mythril.support.loader import DynLoader @@ -31,7 +31,7 @@ from mythril.analysis.callgraph import generate_graph from mythril.analysis.traceexplore import get_serializable_statespace from mythril.analysis.security import fire_lasers from mythril.analysis.report import Report -from mythril.leveldb.client import EthLevelDB +from mythril.ethereum.interface.leveldb.client import EthLevelDB # logging.basicConfig(level=logging.DEBUG) diff --git a/tests/rpc_test.py b/tests/rpc_test.py index 89621b64..564c7da8 100644 --- a/tests/rpc_test.py +++ b/tests/rpc_test.py @@ -1,6 +1,6 @@ from unittest import TestCase -from mythril.rpc.client import EthJsonRpc +from mythril.ethereum.interface.rpc.client import EthJsonRpc class RpcTest(TestCase): client = None From 213250ddb332b5088e037158c965e9a4101a173e Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sun, 14 Oct 2018 00:17:19 +0200 Subject: [PATCH 41/84] Fix import --- mythril/ethereum/interface/leveldb/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/ethereum/interface/leveldb/client.py b/mythril/ethereum/interface/leveldb/client.py index 164185de..a1b4323b 100644 --- a/mythril/ethereum/interface/leveldb/client.py +++ b/mythril/ethereum/interface/leveldb/client.py @@ -5,7 +5,7 @@ from mythril.ethereum.interface.leveldb.accountindexing import ReceiptForStorage import logging from ethereum import utils from ethereum.block import BlockHeader, Block -from mythril.ethereum.interface.leveldb import State +from mythril.ethereum.interface.leveldb.state import State from mythril.ethereum.interface.leveldb.eth_db import ETH_DB from mythril.ether.ethcontract import ETHContract from mythril.exceptions import AddressNotFoundError From 9fb6a81df18723b29adf9d1ef28e210fc863fe40 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sun, 14 Oct 2018 19:50:05 +0200 Subject: [PATCH 42/84] Make name more verbose --- mythril/laser/ethereum/svm.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 82a6551c..7181be74 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -138,30 +138,30 @@ class LaserEVM: new_global_states = self._end_message_call(return_global_state, global_state, revert_changes=True, return_data=None) - except TransactionStartSignal as e: + except TransactionStartSignal as start_signal: # Setup new global state - new_global_state = e.transaction.initial_global_state() + new_global_state = start_signal.transaction.initial_global_state() - new_global_state.transaction_stack = copy(global_state.transaction_stack) + [(e.transaction, global_state)] + new_global_state.transaction_stack = copy(global_state.transaction_stack) + [(start_signal.transaction, global_state)] new_global_state.node = global_state.node new_global_state.mstate.constraints = global_state.mstate.constraints return [new_global_state], op_code - except TransactionEndSignal as e: - transaction, return_global_state = e.global_state.transaction_stack.pop() + except TransactionEndSignal as end_signal: + transaction, return_global_state = end_signal.global_state.transaction_stack.pop() if return_global_state is None: - if (not isinstance(transaction, ContractCreationTransaction) or transaction.return_data) and not e.revert: - e.global_state.world_state.node = global_state.node - self.open_states.append(e.global_state.world_state) + if (not isinstance(transaction, ContractCreationTransaction) or transaction.return_data) and not end_signal.revert: + end_signal.global_state.world_state.node = global_state.node + self.open_states.append(end_signal.global_state.world_state) new_global_states = [] else: # First execute the post hook for the transaction ending instruction - self._execute_post_hook(op_code, [e.global_state]) + self._execute_post_hook(op_code, [end_signal.global_state]) new_global_states = self._end_message_call(return_global_state, global_state, - revert_changes=False or e.revert, + revert_changes=False or end_signal.revert, return_data=transaction.return_data) self._execute_post_hook(op_code, new_global_states) From 499be11ea4e0a468f3daff9cc9d7ad484987848f Mon Sep 17 00:00:00 2001 From: Stamatis Katsaounis Date: Sun, 14 Oct 2018 23:56:50 +0300 Subject: [PATCH 43/84] Add max-transaction-count cli parameter. Signed-off-by: Stamatis Katsaounis --- mythril/analysis/symbolic.py | 5 +++-- mythril/interfaces/cli.py | 4 +++- mythril/laser/ethereum/svm.py | 7 ++++--- mythril/mythril.py | 6 ++++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index ac03303f..c9431257 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -14,7 +14,7 @@ class SymExecWrapper: """ def __init__(self, contract, address, strategy, dynloader=None, max_depth=22, - execution_timeout=None, create_timeout=None): + execution_timeout=None, create_timeout=None, max_transaction_count=3): s_strategy = None if strategy == 'dfs': @@ -30,7 +30,8 @@ class SymExecWrapper: self.laser = svm.LaserEVM(self.accounts, dynamic_loader=dynloader, max_depth=max_depth, execution_timeout=execution_timeout, strategy=s_strategy, - create_timeout=create_timeout) + create_timeout=create_timeout, + max_transaction_count=max_transaction_count) if isinstance(contract, SolidityContract): self.laser.sym_exec(creation_code=contract.creation_code, contract_name=contract.name) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 3e398704..1784add4 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -69,6 +69,7 @@ def main(): 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=22, help='Maximum recursion depth for symbolic execution') + options.add_argument('--max-transaction-count', type=int, default=3, help='Maximum number of transactions issued by laser') options.add_argument('--strategy', choices=['dfs', 'bfs'], default='dfs', help='Symbolic execution strategy') options.add_argument('--execution-timeout', type=int, default=600, help="The amount of seconds to spend on symbolic execution") options.add_argument('--create-timeout', type=int, default=10, help="The amount of seconds to spend on " @@ -218,7 +219,8 @@ def main(): modules=[m.strip() for m in args.modules.strip().split(",")] if args.modules else [], verbose_report=args.verbose_report, max_depth=args.max_depth, execution_timeout=args.execution_timeout, - create_timeout=args.create_timeout) + create_timeout=args.create_timeout, + max_transaction_count=args.max_transaction_count) outputs = { 'json': report.as_json(), 'text': report.as_text(), diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 3a173515..f1bd80b5 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -28,7 +28,7 @@ class LaserEVM: """ def __init__(self, accounts, dynamic_loader=None, max_depth=float('inf'), execution_timeout=60, create_timeout=10, - strategy=DepthFirstSearchStrategy): + strategy=DepthFirstSearchStrategy, max_transaction_count=3): world_state = WorldState() world_state.accounts = accounts # this sets the initial world state @@ -45,6 +45,7 @@ class LaserEVM: self.work_list = [] self.strategy = strategy(self.work_list, max_depth) self.max_depth = max_depth + self.max_transaction_count = max_transaction_count self.execution_timeout = execution_timeout self.create_timeout = create_timeout @@ -60,7 +61,7 @@ class LaserEVM: def accounts(self): return self.world_state.accounts - def sym_exec(self, main_address=None, creation_code=None, contract_name=None, max_transactions=3): + def sym_exec(self, main_address=None, creation_code=None, contract_name=None): logging.debug("Starting LASER execution") self.time = datetime.now() @@ -77,7 +78,7 @@ class LaserEVM: # Reset code coverage self.coverage = {} - for i in range(max_transactions): + for i in range(self.max_transaction_count): initial_coverage = self._get_covered_instructions() self.time = datetime.now() diff --git a/mythril/mythril.py b/mythril/mythril.py index 027e1211..123ab904 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -360,14 +360,16 @@ class Mythril(object): 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): + 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) + create_timeout=create_timeout, + max_transaction_count=max_transaction_count) issues = fire_lasers(sym, modules) From 095e8d90b42dea21ad78a3170e135d446eaed35a Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Mon, 15 Oct 2018 21:59:59 +0530 Subject: [PATCH 44/84] Convert util exception to TypeError --- mythril/laser/ethereum/instructions.py | 32 +++++++++++++------------- mythril/laser/ethereum/util.py | 15 ++++++------ 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 27172f0a..881a820a 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -374,7 +374,7 @@ class Instruction: logging.debug("Final value: " + str(int.from_bytes(val, byteorder='big'))) state.stack.append(BitVecVal(int.from_bytes(val, byteorder='big'), 256)) - except (util.ConcreteIntException, AttributeError): + except (TypeError, AttributeError): state.stack.append(global_state.new_bitvec( "calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256)) else: @@ -402,14 +402,14 @@ class Instruction: try: mstart = util.get_concrete_int(op0) - except util.ConcreteIntException: + except TypeError: logging.debug("Unsupported symbolic memory offset in CALLDATACOPY") return [global_state] dstart_sym = False try: dstart = util.get_concrete_int(op1) - except util.ConcreteIntException: + except TypeError: logging.debug("Unsupported symbolic calldata offset in CALLDATACOPY") dstart = simplify(op1) dstart_sym = True @@ -417,7 +417,7 @@ class Instruction: size_sym = False try: size = util.get_concrete_int(op2) - except util.ConcreteIntException: + except TypeError: logging.debug("Unsupported symbolic size in CALLDATACOPY") size = simplify(op2) size_sym = True @@ -501,7 +501,7 @@ class Instruction: try: index, length = util.get_concrete_int(op0), util.get_concrete_int(op1) - except util.ConcreteIntException: + except TypeError: # Can't access symbolic memory offsets if is_expr(op0): op0 = simplify(op0) @@ -545,7 +545,7 @@ class Instruction: try: concrete_size = helper.get_concrete_int(size) global_state.mstate.mem_extend(concrete_memory_offset, concrete_size) - except (util.ConcreteIntException, TypeError): + except TypeError: # except both attribute error and Exception global_state.mstate.mem_extend(concrete_memory_offset, 1) global_state.mstate.memory[concrete_memory_offset] = \ @@ -687,7 +687,7 @@ class Instruction: try: mstart = util.get_concrete_int(op0) - except util.ConcreteIntException: + except TypeError: logging.debug("MSTORE to symbolic index. Not supported") return [global_state] @@ -720,7 +720,7 @@ class Instruction: try: offset = util.get_concrete_int(op0) - except util.ConcreteIntException: + except TypeError: logging.debug("MSTORE to symbolic index. Not supported") return [global_state] @@ -741,7 +741,7 @@ class Instruction: index = util.get_concrete_int(index) return self._sload_helper(global_state, index) - except util.ConcreteIntException: + except TypeError: if not keccak_function_manager.is_keccak(index): return self._sload_helper(global_state, str(index)) @@ -802,7 +802,7 @@ class Instruction: try: index = util.get_concrete_int(index) return self._sstore_helper(global_state, index, value) - except util.ConcreteIntException: + except TypeError: is_keccak = keccak_function_manager.is_keccak(index) if not is_keccak: return self._sstore_helper(global_state, str(index), value) @@ -855,7 +855,7 @@ class Instruction: disassembly = global_state.environment.code try: jump_addr = util.get_concrete_int(state.stack.pop()) - except util.ConcreteIntException: + except TypeError: raise InvalidJumpDestination("Invalid jump argument (symbolic address)") except IndexError: raise StackUnderflowException() @@ -885,7 +885,7 @@ class Instruction: try: jump_addr = util.get_concrete_int(op0) - except util.ConcreteIntException: + except TypeError: logging.debug("Skipping JUMPI to invalid destination.") global_state.mstate.pc += 1 return [global_state] @@ -965,7 +965,7 @@ class Instruction: return_data = [global_state.new_bitvec("return_data", 256)] try: return_data = state.memory[util.get_concrete_int(offset):util.get_concrete_int(offset + length)] - except util.ConcreteIntException: + except TypeError: logging.debug("Return with symbolic length or offset. Not supported") global_state.current_transaction.end(global_state, return_data) @@ -1088,7 +1088,7 @@ class Instruction: try: memory_out_offset = util.get_concrete_int(memory_out_offset) if isinstance(memory_out_offset, ExprRef) else memory_out_offset memory_out_size = util.get_concrete_int(memory_out_size) if isinstance(memory_out_size, ExprRef) else memory_out_size - except util.ConcreteIntException: + except TypeError: global_state.mstate.stack.append(global_state.new_bitvec("retval_" + str(instr['address']), 256)) return [global_state] @@ -1156,7 +1156,7 @@ class Instruction: try: memory_out_offset = util.get_concrete_int(memory_out_offset) if isinstance(memory_out_offset, ExprRef) else memory_out_offset memory_out_size = util.get_concrete_int(memory_out_size) if isinstance(memory_out_size, ExprRef) else memory_out_size - except util.ConcreteIntException: + except TypeError: global_state.mstate.stack.append(global_state.new_bitvec("retval_" + str(instr['address']), 256)) return [global_state] @@ -1228,7 +1228,7 @@ class Instruction: ExprRef) else memory_out_offset memory_out_size = util.get_concrete_int(memory_out_size) if isinstance(memory_out_size, ExprRef) else memory_out_size - except util.ConcreteIntException: + except TypeError: global_state.mstate.stack.append(global_state.new_bitvec("retval_" + str(instr['address']), 256)) return [global_state] diff --git a/mythril/laser/ethereum/util.py b/mythril/laser/ethereum/util.py index 7b9061ea..7b4693f5 100644 --- a/mythril/laser/ethereum/util.py +++ b/mythril/laser/ethereum/util.py @@ -10,8 +10,6 @@ TT256M1 = 2 ** 256 - 1 TT255 = 2 ** 255 -class ConcreteIntException(AttributeError): - pass def sha3(seed): @@ -20,7 +18,7 @@ def sha3(seed): def safe_decode(hex_encoded_string): - if (hex_encoded_string.startswith("0x")): + if hex_encoded_string.startswith("0x"): return bytes.fromhex(hex_encoded_string[2:]) else: return bytes.fromhex(hex_encoded_string) @@ -84,12 +82,13 @@ def get_concrete_int(item): elif is_true(simplified): return 1 else: - raise ConcreteIntException("Symbolic boolref encountered") + raise TypeError("Symbolic boolref encountered") try: return simplify(item).as_long() except AttributeError: - raise ConcreteIntException("Got a symbolic BitVecRef") + raise TypeError("Got a symbolic BitVecRef") + def concrete_int_from_bytes(_bytes, start_index): @@ -105,11 +104,11 @@ def concrete_int_to_bytes(val): # logging.debug("concrete_int_to_bytes " + str(val)) - if (type(val) == int): + try: + return (simplify(val).as_long()).to_bytes(32, byteorder='big') + except Z3Exception: return val.to_bytes(32, byteorder='big') - return (simplify(val).as_long()).to_bytes(32, byteorder='big') - def bytearray_to_int(arr): o = 0 From 9ae224069841b2f3466ce942074d3284741ea5cb Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Mon, 15 Oct 2018 22:38:55 +0530 Subject: [PATCH 45/84] Change AttributeErrors to TypeErrors --- .../modules/transaction_order_dependence.py | 2 +- mythril/analysis/ops.py | 2 +- mythril/laser/ethereum/call.py | 5 ++--- mythril/laser/ethereum/instructions.py | 20 +++++++++---------- mythril/laser/ethereum/taint_analysis.py | 8 ++++---- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/mythril/analysis/modules/transaction_order_dependence.py b/mythril/analysis/modules/transaction_order_dependence.py index f5b45f5d..f6621293 100644 --- a/mythril/analysis/modules/transaction_order_dependence.py +++ b/mythril/analysis/modules/transaction_order_dependence.py @@ -112,7 +112,7 @@ def _get_influencing_sstores(statespace, interesting_storages): index, value = sstore_state.mstate.stack[-1], sstore_state.mstate.stack[-2] try: index = util.get_concrete_int(index) - except AttributeError: + except TypeError: index = str(index) if "storage_{}".format(index) not in interesting_storages: continue diff --git a/mythril/analysis/ops.py b/mythril/analysis/ops.py index 999bbb12..b2329294 100644 --- a/mythril/analysis/ops.py +++ b/mythril/analysis/ops.py @@ -21,7 +21,7 @@ class Variable: def get_variable(i): try: return Variable(util.get_concrete_int(i), VarType.CONCRETE) - except AttributeError: + except TypeError: return Variable(simplify(i), VarType.SYMBOLIC) diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 36062128..583c6b2e 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -48,7 +48,7 @@ def get_callee_address(global_state:GlobalState, dynamic_loader: DynLoader, symb try: callee_address = hex(util.get_concrete_int(symbolic_to_address)) - except AttributeError: + except TypeError: logging.debug("Symbolic call encountered") match = re.search(r'storage_(\d+)', str(simplify(symbolic_to_address))) @@ -113,7 +113,6 @@ def get_callee_account(global_state, callee_address, dynamic_loader): return callee_account - def get_call_data(global_state, memory_start, memory_size, pad=True): """ Gets call_data from the global_state @@ -131,7 +130,7 @@ def get_call_data(global_state, memory_start, memory_size, pad=True): call_data += [0] * (32 - len(call_data)) call_data_type = CalldataType.CONCRETE logging.debug("Calldata: " + str(call_data)) - except AttributeError: + except TypeError: logging.info("Unsupported symbolic calldata offset") call_data_type = CalldataType.SYMBOLIC call_data = [] diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index d8275423..cea603b4 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -175,7 +175,7 @@ class Instruction: result = simplify(Concat(BitVecVal(0, 248), Extract(offset + 7, offset, op1))) else: result = 0 - except AttributeError: + except TypeError: logging.debug("BYTE: Unsupported symbolic byte offset") result = global_state.new_bitvec(str(simplify(op1)) + "[" + str(simplify(op0)) + "]", 256) @@ -265,7 +265,7 @@ class Instruction: try: s0 = util.get_concrete_int(s0) s1 = util.get_concrete_int(s1) - except ValueError: + except TypeError: return [] if s0 <= 31: @@ -355,7 +355,7 @@ class Instruction: try: offset = util.get_concrete_int(simplify(op0)) b = environment.calldata[offset] - except AttributeError: + except TypeError: logging.debug("CALLDATALOAD: Unsupported symbolic index") state.stack.append(global_state.new_bitvec( "calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256)) @@ -514,7 +514,7 @@ class Instruction: data = b''.join([util.get_concrete_int(i).to_bytes(1, byteorder='big') for i in state.memory[index: index + length]]) - except util.ConcreteIntException: + except TypeError: argument = str(state.memory[index]).replace(" ", "_") result = BitVec("KECCAC[{}]".format(argument), 256) @@ -539,7 +539,7 @@ class Instruction: try: concrete_memory_offset = helper.get_concrete_int(memory_offset) - except AttributeError: + except TypeError: logging.debug("Unsupported symbolic memory offset in CODECOPY") return [global_state] @@ -555,7 +555,7 @@ class Instruction: try: concrete_code_offset = helper.get_concrete_int(code_offset) - except AttributeError: + except TypeError: logging.debug("Unsupported symbolic code offset in CODECOPY") global_state.mstate.mem_extend(concrete_memory_offset, concrete_size) for i in range(concrete_size): @@ -589,7 +589,7 @@ class Instruction: environment = global_state.environment try: addr = hex(helper.get_concrete_int(addr)) - except AttributeError: + except TypeError: logging.info("unsupported symbolic address for EXTCODESIZE") state.stack.append(global_state.new_bitvec("extcodesize_" + str(addr), 256)) return [global_state] @@ -663,7 +663,7 @@ class Instruction: try: offset = util.get_concrete_int(op0) - except AttributeError: + except TypeError: logging.debug("Can't MLOAD from symbolic index") data = global_state.new_bitvec("mem[" + str(simplify(op0)) + "]", 256) state.stack.append(data) @@ -998,7 +998,7 @@ class Instruction: return_data = [global_state.new_bitvec("return_data", 256)] try: return_data = state.memory[util.get_concrete_int(offset):util.get_concrete_int(offset + length)] - except AttributeError: + except TypeError: logging.debug("Return with symbolic length or offset. Not supported") global_state.current_transaction.end(global_state, return_data=return_data, revert=True) @@ -1042,7 +1042,7 @@ class Instruction: try: mem_out_start = helper.get_concrete_int(memory_out_offset) mem_out_sz = memory_out_size.as_long() - except AttributeError: + except TypeError: logging.debug("CALL with symbolic start or offset not supported") return [global_state] diff --git a/mythril/laser/ethereum/taint_analysis.py b/mythril/laser/ethereum/taint_analysis.py index 2144d864..061ab088 100644 --- a/mythril/laser/ethereum/taint_analysis.py +++ b/mythril/laser/ethereum/taint_analysis.py @@ -213,7 +213,7 @@ class TaintRunner: _ = record.stack.pop() try: index = helper.get_concrete_int(op0) - except AttributeError: + except TypeError: logging.debug("Can't MLOAD taint track symbolically") record.stack.append(False) return @@ -225,7 +225,7 @@ class TaintRunner: _, value_taint = record.stack.pop(), record.stack.pop() try: index = helper.get_concrete_int(op0) - except AttributeError: + except TypeError: logging.debug("Can't mstore taint track symbolically") return @@ -236,7 +236,7 @@ class TaintRunner: _ = record.stack.pop() try: index = helper.get_concrete_int(op0) - except AttributeError: + except TypeError: logging.debug("Can't MLOAD taint track symbolically") record.stack.append(False) return @@ -248,7 +248,7 @@ class TaintRunner: _, value_taint = record.stack.pop(), record.stack.pop() try: index = helper.get_concrete_int(op0) - except AttributeError: + except TypeError: logging.debug("Can't mstore taint track symbolically") return From 58395cb6c6c33d4e634ca8e95e23b23ecb83d349 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Mon, 15 Oct 2018 23:16:40 +0530 Subject: [PATCH 46/84] Pad 0s for the compressed hashes --- mythril/disassembler/disassembly.py | 8 +++++++- mythril/ether/asm.py | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/mythril/disassembler/disassembly.py b/mythril/disassembler/disassembly.py index 394f22b1..e31a6ba3 100644 --- a/mythril/disassembler/disassembly.py +++ b/mythril/disassembler/disassembly.py @@ -20,10 +20,16 @@ class Disassembly(object): # Parse jump table & resolve function names - jmptable_indices = asm.find_opcode_sequence(["PUSH4", "EQ"], self.instruction_list) + # Take from PUSH1 to PUSH4 because solc seems to remove excess 0s at the beginning for optimizing + jmptable_indices = asm.find_opcode_sequence([("PUSH1", "PUSH2", "PUSH3", "PUSH4"), ("EQ",)], + self.instruction_list) for i in jmptable_indices: func_hash = self.instruction_list[i]['argument'] + + # Append with missing 0s at the beginning + func_hash = "0x" + func_hash[2:].rjust(8, "0") + self.func_hashes.append(func_hash) try: # tries local cache, file and optional online lookup diff --git a/mythril/ether/asm.py b/mythril/ether/asm.py index 5e2267ea..985b2f07 100644 --- a/mythril/ether/asm.py +++ b/mythril/ether/asm.py @@ -70,13 +70,13 @@ def find_opcode_sequence(pattern, instruction_list): for i in range(0, len(instruction_list) - pattern_length + 1): - if instruction_list[i]['opcode'] == pattern[0]: + if instruction_list[i]['opcode'] in pattern[0]: matched = True for j in range(1, len(pattern)): - if not (instruction_list[i + j]['opcode'] == pattern[j]): + if not (instruction_list[i + j]['opcode'] in pattern[j]): matched = False break From 59020c6e4ae4a5ba449562108846600957ce55fc Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Mon, 15 Oct 2018 23:19:26 +0530 Subject: [PATCH 47/84] Comment correction --- mythril/disassembler/disassembly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/disassembler/disassembly.py b/mythril/disassembler/disassembly.py index e31a6ba3..93ec0c27 100644 --- a/mythril/disassembler/disassembly.py +++ b/mythril/disassembler/disassembly.py @@ -20,7 +20,7 @@ class Disassembly(object): # Parse jump table & resolve function names - # Take from PUSH1 to PUSH4 because solc seems to remove excess 0s at the beginning for optimizing + # Need to take from PUSH1 to PUSH4 because solc seems to remove excess 0s at the beginning for optimizing jmptable_indices = asm.find_opcode_sequence([("PUSH1", "PUSH2", "PUSH3", "PUSH4"), ("EQ",)], self.instruction_list) From 6bb6a3221678a603ed26a196cadfffea7cd99606 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Tue, 16 Oct 2018 01:30:59 +0530 Subject: [PATCH 48/84] Add the 2 simple probabilistic search strategies --- mythril/analysis/symbolic.py | 7 ++- mythril/interfaces/cli.py | 3 +- mythril/laser/ethereum/strategy/basic.py | 65 ++++++++++++++---------- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index 09447a67..922ce7e2 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -4,7 +4,8 @@ from mythril.ether.soliditycontract import SolidityContract import copy import logging from .ops import get_variable, SStore, Call, VarType -from mythril.laser.ethereum.strategy.basic import DepthFirstSearchStrategy, BreadthFirstSearchStrategy +from mythril.laser.ethereum.strategy.basic import DepthFirstSearchStrategy, BreadthFirstSearchStrategy, \ + ReturnRandomNaivelyStrategy, ReturnWeightedRandomStrategy class SymExecWrapper: @@ -21,6 +22,10 @@ class SymExecWrapper: s_strategy = DepthFirstSearchStrategy elif strategy == 'bfs': s_strategy = BreadthFirstSearchStrategy + elif strategy == 'naive-random': + s_strategy = ReturnRandomNaivelyStrategy + elif strategy == 'weighted-random': + s_strategy = ReturnWeightedRandomStrategy else: raise ValueError("Invalid strategy argument supplied") diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index be6cec59..6cbd2d8c 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -69,7 +69,8 @@ def main(): 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=22, help='Maximum recursion depth for symbolic execution') - options.add_argument('--strategy', choices=['dfs', 'bfs'], default='dfs', help='Symbolic execution strategy') + options.add_argument('--strategy', choices=['dfs', 'bfs', 'naive-random', 'weighted-random'], + default='dfs', help='Symbolic execution strategy') options.add_argument('--execution-timeout', type=int, default=600, help="The amount of seconds to spend on symbolic execution") options.add_argument('--create-timeout', type=int, default=10, help="The amount of seconds to spend on " "the initial contract creation") diff --git a/mythril/laser/ethereum/strategy/basic.py b/mythril/laser/ethereum/strategy/basic.py index 33dca443..21048a0a 100644 --- a/mythril/laser/ethereum/strategy/basic.py +++ b/mythril/laser/ethereum/strategy/basic.py @@ -1,13 +1,12 @@ """ This module implements basic symbolic execution search strategies """ +from abc import ABC, abstractmethod +from random import choices, randrange +class BasicStrategy(ABC): + __slots__ = 'work_list', 'max_depth', 'open_states' -class DepthFirstSearchStrategy: - """ - Implements a depth first search strategy - I.E. Follow one path to a leaf, and then continue to the next one - """ def __init__(self, work_list, max_depth): self.work_list = work_list self.max_depth = max_depth @@ -15,40 +14,52 @@ class DepthFirstSearchStrategy: def __iter__(self): return self + @abstractmethod + def get_strategic_global_state(self): + raise NotImplementedError("Must be implemented by a subclass") + def __next__(self): - """ Picks the next state to execute """ try: - # This strategies assumes that new states are appended at the end of the work_list - # By taking the last element we effectively pick the "newest" states, which amounts to dfs - global_state = self.work_list.pop() + global_state = self.get_strategic_global_state() if global_state.mstate.depth >= self.max_depth: return self.__next__() return global_state except IndexError: - raise StopIteration() + raise StopIteration + + +class DepthFirstSearchStrategy(BasicStrategy): + """ + Implements a depth first search strategy + I.E. Follow one path to a leaf, and then continue to the next one + """ + def get_strategic_global_state(self): + return self.work_list.pop() -class BreadthFirstSearchStrategy: + +class BreadthFirstSearchStrategy(BasicStrategy): """ Implements a breadth first search strategy I.E. Execute all states of a "level" before continuing """ - def __init__(self, work_list, max_depth): - self.work_list = work_list - self.max_depth = max_depth - def __iter__(self): - return self + def get_strategic_global_state(self): + return self.work_list.pop(0) - def __next__(self): - """ Picks the next state to execute """ - try: - # This strategies assumes that new states are appended at the end of the work_list - # By taking the first element we effectively pick the "oldest" states, which amounts to bfs - global_state = self.work_list.pop(0) - if global_state.mstate.depth >= self.max_depth: - return self.__next__() - return global_state - except IndexError: - raise StopIteration() + +class ReturnRandomNaivelyStrategy(BasicStrategy): + + def get_strategic_global_state(self): + if len(self.work_list) > 0: + return self.work_list.pop(randrange(len(self.work_list))) + else: + raise IndexError + + +class ReturnWeightedRandomStrategy(BasicStrategy): + + def get_strategic_global_state(self): + probability_distribution = [global_state.mstate.depth+1 for global_state in self.work_list] + return self.work_list.pop(choices(range(len(self.work_list)), probability_distribution)[0]) From d58aab7a555dd7f7dfc74f46b8fa246fb57843cc Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Tue, 16 Oct 2018 02:04:24 +0530 Subject: [PATCH 49/84] Add choices() for python3.5 --- mythril/laser/ethereum/strategy/basic.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/strategy/basic.py b/mythril/laser/ethereum/strategy/basic.py index 21048a0a..85bf1dc4 100644 --- a/mythril/laser/ethereum/strategy/basic.py +++ b/mythril/laser/ethereum/strategy/basic.py @@ -2,7 +2,22 @@ This module implements basic symbolic execution search strategies """ from abc import ABC, abstractmethod -from random import choices, randrange +from random import randrange + +try: + from random import choices +except ImportError: + + from itertools import accumulate + from random import random + from bisect import bisect + + def choices(population, weights=None): + if weights is None: + return [population[int(random() * len(population))]] + cum_weights = accumulate(weights) + return [population[bisect(cum_weights, random()*cum_weights[-1], 0, len(population)-1)]] + class BasicStrategy(ABC): __slots__ = 'work_list', 'max_depth', 'open_states' From a4eea864f8fef2484318343f64093ad10ca0b31f Mon Sep 17 00:00:00 2001 From: Yurii Rashkovskii Date: Tue, 16 Oct 2018 11:58:23 -0700 Subject: [PATCH 50/84] Problem: truffle project analysis ignores --solc-args This prevents passing custom `solc` arguments which is important in some cases (for example, `--allow-paths` is a very useful option) Solution: pass solc_args through --- mythril/support/truffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/support/truffle.py b/mythril/support/truffle.py index aced1086..6dd13b2e 100644 --- a/mythril/support/truffle.py +++ b/mythril/support/truffle.py @@ -40,7 +40,7 @@ def analyze_truffle_project(sigs, args): if len(bytecode) < 4: continue - sigs.import_from_solidity_source(contractdata['sourcePath']) + sigs.import_from_solidity_source(contractdata['sourcePath'], solc_args=args.solc_args) sigs.write() ethcontract = ETHContract(bytecode, name=name) From 0619cf2612ac9ae5048520ee0c078eef7e96808b Mon Sep 17 00:00:00 2001 From: Yurii Rashkovskii Date: Tue, 16 Oct 2018 12:01:30 -0700 Subject: [PATCH 51/84] Problem: docker build times Currenlty, every time a docker container is rebuilt for updated source code of the package, it'll start from scratch. This means it will not reuse layers with existing Python, solc, etc. installation. Solution: install Python first, copy source code after This allows to reuse the base layer. --- Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3e3cb592..59c9123d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,5 @@ FROM ubuntu:bionic -COPY . /opt/mythril - RUN apt-get update \ && apt-get install -y \ build-essential \ @@ -18,8 +16,11 @@ RUN apt-get update \ python3-dev \ pandoc \ git \ - && ln -s /usr/bin/python3 /usr/local/bin/python \ - && cd /opt/mythril \ + && ln -s /usr/bin/python3 /usr/local/bin/python + +COPY . /opt/mythril + +RUN cd /opt/mythril \ && pip3 install -r requirements.txt \ && python setup.py install From 4cf1f27f77e65468c0615eb0e4abacb4e098e00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20M=C3=B6nnig?= Date: Wed, 17 Oct 2018 12:39:04 +0200 Subject: [PATCH 52/84] Precopy requirements.txt --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 59c9123d..8594ecb0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,8 @@ 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 \ @@ -29,4 +30,6 @@ ENV LANG en_US.UTF-8 ENV LANGUAGE en_US.en ENV LC_ALL en_US.UTF-8 +COPY . /opt/mythril + ENTRYPOINT ["/usr/local/bin/myth"] From 9b027ed72bcaf7f5ed67c564c8360c7526c7a7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20M=C3=B6nnig?= Date: Wed, 17 Oct 2018 13:43:00 +0200 Subject: [PATCH 53/84] Add setup.py and Pipfile as precopy steps --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 8594ecb0..48cfe911 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,8 @@ RUN apt-get update \ git \ && ln -s /usr/bin/python3 /usr/local/bin/python - +COPY ./setup.py /opt/mythril/setup.py +COPY ./Pipfile /opt/mythril/Pipfile COPY ./requirements.txt /opt/mythril/requirements.txt RUN cd /opt/mythril \ From 96edc1a305fa8846b614743cf0ad4ed9592a4b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20M=C3=B6nnig?= Date: Wed, 17 Oct 2018 13:45:05 +0200 Subject: [PATCH 54/84] Remove Pipfile as precopy step --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 48cfe911..283c0c88 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,6 @@ RUN apt-get update \ && ln -s /usr/bin/python3 /usr/local/bin/python COPY ./setup.py /opt/mythril/setup.py -COPY ./Pipfile /opt/mythril/Pipfile COPY ./requirements.txt /opt/mythril/requirements.txt RUN cd /opt/mythril \ From 8555663adf426d03934646880485a01cd1c8b848 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Wed, 17 Oct 2018 17:20:57 +0530 Subject: [PATCH 55/84] Remove %s in formatting previously %s was used to display variable for string formatting which won't work. --- mythril/analysis/modules/deprecated_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From a939a3255a7a5aa211649b0fd2c1843b23d601b2 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Wed, 17 Oct 2018 17:32:10 +0530 Subject: [PATCH 56/84] Update origin.sol.o.json --- tests/testdata/outputs_expected/origin.sol.o.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata/outputs_expected/origin.sol.o.json b/tests/testdata/outputs_expected/origin.sol.o.json index 640bce9c..04d6ee81 100644 --- a/tests/testdata/outputs_expected/origin.sol.o.json +++ b/tests/testdata/outputs_expected/origin.sol.o.json @@ -1 +1 @@ -{"error": null, "issues": [{"address": 317, "contract": "Unknown", "debug": "", "description": "Function %s 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", "function": "transferOwnership(address)", "swc_id": "115", "title": "Use of tx.origin", "type": "Warning"}], "success": true} \ No newline at end of file +{"error": null, "issues": [{"address": 317, "contract": "Unknown", "debug": "", "description": "The function `transferOwnership(address)` 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", "function": "transferOwnership(address)", "swc_id": "115", "title": "Use of tx.origin", "type": "Warning"}], "success": true} From 5e48355d497e2c1ac8a28fbfed88d1b591216d86 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Wed, 17 Oct 2018 17:33:18 +0530 Subject: [PATCH 57/84] Remove the %s in markdown test --- tests/testdata/outputs_expected/origin.sol.o.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata/outputs_expected/origin.sol.o.markdown b/tests/testdata/outputs_expected/origin.sol.o.markdown index 772ef122..1e9d6d8b 100644 --- a/tests/testdata/outputs_expected/origin.sol.o.markdown +++ b/tests/testdata/outputs_expected/origin.sol.o.markdown @@ -9,5 +9,5 @@ ### Description -Function %s retrieves the transaction origin (tx.origin) using the ORIGIN opcode. Use msg.sender instead. +The function `transferOwnership(address)` retrieves the transaction origin (tx.origin) using the ORIGIN opcode. Use msg.sender instead. See also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin From c105b17211dd26964629552f10cd666b2f9697b6 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Wed, 17 Oct 2018 17:34:03 +0530 Subject: [PATCH 58/84] Remove %s in the text test --- tests/testdata/outputs_expected/origin.sol.o.text | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata/outputs_expected/origin.sol.o.text b/tests/testdata/outputs_expected/origin.sol.o.text index f159a8eb..b71422be 100644 --- a/tests/testdata/outputs_expected/origin.sol.o.text +++ b/tests/testdata/outputs_expected/origin.sol.o.text @@ -4,7 +4,7 @@ Type: Warning Contract: Unknown Function name: transferOwnership(address) PC address: 317 -Function %s retrieves the transaction origin (tx.origin) using the ORIGIN opcode. Use msg.sender instead. +The function `transferOwnership(address)` retrieves the transaction origin (tx.origin) using the ORIGIN opcode. Use msg.sender instead. See also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin -------------------- From 9e600168fb5537f97438c8b99c7d3e989491e226 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Wed, 17 Oct 2018 15:08:39 +0200 Subject: [PATCH 59/84] Also check for empty return data --- mythril/laser/ethereum/transaction/transaction_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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) From 39ace87e872c1f20a92555b0a1e58af05a3133b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20M=C3=B6nnig?= Date: Wed, 17 Oct 2018 18:14:39 +0200 Subject: [PATCH 60/84] Add and tags --- mythril/analysis/templates/callgraph.html | 2 +- static/Ownable.html | 4 +++- static/assertions.html | 5 +++-- static/mythril.html | 4 +++- tests/testdata/outputs_expected/calls.sol.o.graph.html | 2 ++ .../testdata/outputs_expected/environments.sol.o.graph.html | 2 ++ tests/testdata/outputs_expected/ether_send.sol.o.graph.html | 2 ++ tests/testdata/outputs_expected/exceptions.sol.o.graph.html | 2 ++ .../outputs_expected/kinds_of_calls.sol.o.graph.html | 2 ++ tests/testdata/outputs_expected/metacoin.sol.o.graph.html | 2 ++ .../outputs_expected/multi_contracts.sol.o.graph.html | 2 ++ tests/testdata/outputs_expected/nonascii.sol.o.graph.html | 2 ++ tests/testdata/outputs_expected/origin.sol.o.graph.html | 2 ++ tests/testdata/outputs_expected/overflow.sol.o.graph.html | 2 ++ tests/testdata/outputs_expected/returnvalue.sol.o.graph.html | 2 ++ tests/testdata/outputs_expected/suicide.sol.o.graph.html | 2 ++ tests/testdata/outputs_expected/underflow.sol.o.graph.html | 2 ++ 17 files changed, 36 insertions(+), 5 deletions(-) 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 @@ <!DOCTYPE html> <html> <head> - <title> Laser - Call Graph + Call Graph 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