From 6e0d775c09f32bb9e757b53b7f8d768f618f452a Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Mon, 1 Oct 2018 20:05:46 +0530 Subject: [PATCH 01/20] 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 3e6ca5e976de219fc444c7871c6f22da1c45b7af Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sat, 13 Oct 2018 22:53:36 +0200 Subject: [PATCH 02/20] 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 095e8d90b42dea21ad78a3170e135d446eaed35a Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Mon, 15 Oct 2018 21:59:59 +0530 Subject: [PATCH 03/20] 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 04/20] 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 05/20] 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 06/20] 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 a4eea864f8fef2484318343f64093ad10ca0b31f Mon Sep 17 00:00:00 2001 From: Yurii Rashkovskii Date: Tue, 16 Oct 2018 11:58:23 -0700 Subject: [PATCH 07/20] 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 08/20] 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 09/20] 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 10/20] 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 11/20] 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 12/20] 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 13/20] 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 14/20] 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 15/20] 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 16/20] 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 17/20] 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