From 86268ffce3664d3861c639f7da94744b576ae869 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sat, 29 Sep 2018 15:58:35 +0200 Subject: [PATCH 01/43] Adds invalid instruction Exception --- mythril/laser/ethereum/evm_exceptions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mythril/laser/ethereum/evm_exceptions.py b/mythril/laser/ethereum/evm_exceptions.py index 2f149aca..80223762 100644 --- a/mythril/laser/ethereum/evm_exceptions.py +++ b/mythril/laser/ethereum/evm_exceptions.py @@ -12,3 +12,7 @@ class StackOverflowException(VmException): class InvalidJumpDestination(VmException): pass + + +class InvalidInstruction(VmException): + pass From 88777350e1bc9e5bf8b7eb8d1592113a70edcb4c Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sat, 29 Sep 2018 15:59:50 +0200 Subject: [PATCH 02/43] Raise InvalidJumpDestination exception on invalid op, and optimize imports --- mythril/laser/ethereum/instructions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 3d5d2bf7..f4bf93a9 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -6,15 +6,15 @@ from ethereum import utils from z3 import Extract, UDiv, simplify, Concat, ULT, UGT, BitVecNumRef, Not, \ is_false, is_expr, ExprRef, URem, SRem, BitVec, Solver, is_true, BitVecVal, If, BoolRef, Or +import mythril.laser.ethereum.natives as natives import mythril.laser.ethereum.util as helper from mythril.laser.ethereum import util from mythril.laser.ethereum.call import get_call_parameters +from mythril.laser.ethereum.evm_exceptions import VmException, StackUnderflowException, InvalidJumpDestination +from mythril.laser.ethereum.keccak import KeccakFunctionManager from mythril.laser.ethereum.state import GlobalState, CalldataType -import mythril.laser.ethereum.natives as natives from mythril.laser.ethereum.transaction import MessageCallTransaction, TransactionStartSignal, \ ContractCreationTransaction -from mythril.laser.ethereum.evm_exceptions import VmException, StackUnderflowException, InvalidJumpDestination -from mythril.laser.ethereum.keccak import KeccakFunctionManager TT256 = 2 ** 256 TT256M1 = 2 ** 256 - 1 @@ -1008,7 +1008,7 @@ class Instruction: @StateTransition() def invalid_(self, global_state): - return [] + raise InvalidJumpDestination @StateTransition() def stop_(self, global_state): From 9bd6a08b47ea317fda5bf3c35536ad1e53a121dd Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sat, 29 Sep 2018 16:05:00 +0200 Subject: [PATCH 03/43] Add justification/documentation to vmExceptionHandling --- mythril/laser/ethereum/svm.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 9abf6ba0..1ea21b53 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -124,6 +124,9 @@ class LaserEVM: new_global_states = Instruction(op_code, self.dynamic_loader).evaluate(global_state) except VmException as e: + # In this case we don't put an unmodified world state in the open_states list + # Since in the case of an exceptional halt all changes should be discarded, and this world state would not + # provide us with a previously unseen world state logging.debug("Encountered a VmException, ending path: `{}`".format(str(e))) new_global_states = [] From dc0e281bd5092138ccdca7c1b3e22c62e2cd2ea4 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sat, 29 Sep 2018 16:22:53 +0200 Subject: [PATCH 04/43] Add assert_fail_ implementation --- mythril/laser/ethereum/instructions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index f4bf93a9..ea4b56c2 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -1004,7 +1004,8 @@ class Instruction: @StateTransition() def assert_fail_(self, global_state): - return [] + # 0xfe: designated invalid opcode + raise InvalidJumpDestination @StateTransition() def invalid_(self, global_state): From b5722f6948a27c641aad5faaa480e7c0e3484a88 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 1 Oct 2018 14:41:55 +0200 Subject: [PATCH 05/43] im --- mythril/laser/ethereum/svm.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 1ea21b53..a2cd294f 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -124,11 +124,31 @@ class LaserEVM: new_global_states = Instruction(op_code, self.dynamic_loader).evaluate(global_state) except VmException as e: - # In this case we don't put an unmodified world state in the open_states list - # Since in the case of an exceptional halt all changes should be discarded, and this world state would not - # provide us with a previously unseen world state - logging.debug("Encountered a VmException, ending path: `{}`".format(str(e))) new_global_states = [] + # transaction, return_global_state = global_state.transaction_stack.pop() + # + # if return_global_state is None: + # # In this case we don't put an unmodified world state in the open_states list Since in the case of an + # # exceptional halt all changes should be discarded, and this world state would not provide us with a + # # previously unseen world state + # logging.debug("Encountered a VmException, ending path: `{}`".format(str(e))) + # new_global_states = [] + # else: + # # First execute the post hook for the transaction ending instruction + # self._execute_post_hook(op_code, [global_state]) + # + # # Resume execution of the transaction initializing instruction + # op_code = return_global_state.environment.code.instruction_list[return_global_state.mstate.pc]['opcode'] + # + # # Set execution result in the return_state + # return_global_state.last_return_data = 0 + # + # # Execute the post instruction handler + # new_global_states = Instruction(op_code, self.dynamic_loader).evaluate(return_global_state, True) + # + # # In order to get a nice call graph we need to set the nodes here + # for state in new_global_states: + # state.node = global_state.node except TransactionStartSignal as e: # Setup new global state From 5a783b2184177b7bfb2f549233a3f4be0f1f8263 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 1 Oct 2018 14:41:55 +0200 Subject: [PATCH 06/43] VmException revert changes --- mythril/laser/ethereum/svm.py | 51 ++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 1ea21b53..14590e9a 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -124,11 +124,18 @@ class LaserEVM: new_global_states = Instruction(op_code, self.dynamic_loader).evaluate(global_state) except VmException as e: - # In this case we don't put an unmodified world state in the open_states list - # Since in the case of an exceptional halt all changes should be discarded, and this world state would not - # provide us with a previously unseen world state - logging.debug("Encountered a VmException, ending path: `{}`".format(str(e))) - new_global_states = [] + transaction, return_global_state = global_state.transaction_stack.pop() + + if return_global_state is None: + # In this case we don't put an unmodified world state in the open_states list Since in the case of an + # exceptional halt all changes should be discarded, and this world state would not provide us with a + # previously unseen world state + logging.debug("Encountered a VmException, ending path: `{}`".format(str(e))) + new_global_states = [] + else: + # First execute the post hook for the transaction ending instruction + self._execute_post_hook(op_code, [global_state]) + new_global_states = self._end_message_call(return_global_state, transaction, global_state, revert_changes=True) except TransactionStartSignal as e: # Setup new global state @@ -152,25 +159,31 @@ class LaserEVM: # First execute the post hook for the transaction ending instruction self._execute_post_hook(op_code, [e.global_state]) - # Resume execution of the transaction initializing instruction - op_code = return_global_state.environment.code.instruction_list[return_global_state.mstate.pc]['opcode'] + new_global_states = self._end_message_call(return_global_state, transaction, global_state, revert_changes=False) - # Set execution result in the return_state - return_global_state.last_return_data = transaction.return_data - return_global_state.world_state = copy(global_state.world_state) - return_global_state.environment.active_account = \ - global_state.accounts[return_global_state.environment.active_account.address] + self._execute_post_hook(op_code, new_global_states) - # Execute the post instruction handler - new_global_states = Instruction(op_code, self.dynamic_loader).evaluate(return_global_state, True) + return new_global_states, op_code - # In order to get a nice call graph we need to set the nodes here - for state in new_global_states: - state.node = global_state.node + def _end_message_call(self, return_global_state, transaction, global_state, revert_changes=False): + # Resume execution of the transaction initializing instruction + op_code = return_global_state.environment.code.instruction_list[return_global_state.mstate.pc]['opcode'] - self._execute_post_hook(op_code, new_global_states) + # Set execution result in the return_state + return_global_state.last_return_data = transaction.return_data + if not revert_changes: + return_global_state.world_state = copy(global_state.world_state) + return_global_state.environment.active_account = \ + global_state.accounts[return_global_state.environment.active_account.address] - return new_global_states, op_code + # Execute the post instruction handler + new_global_states = Instruction(op_code, self.dynamic_loader).evaluate(return_global_state, True) + + # In order to get a nice call graph we need to set the nodes here + for state in new_global_states: + state.node = global_state.node + + return new_global_states def _measure_coverage(self, global_state): code = global_state.environment.code.bytecode From 1ea3f00de2a835ec3ea99a5c9e5b185ec2a915aa Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 1 Oct 2018 14:56:14 +0200 Subject: [PATCH 07/43] Use correct exception --- mythril/laser/ethereum/instructions.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index ea4b56c2..a7c19959 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -10,7 +10,8 @@ import mythril.laser.ethereum.natives as natives import mythril.laser.ethereum.util as helper from mythril.laser.ethereum import util from mythril.laser.ethereum.call import get_call_parameters -from mythril.laser.ethereum.evm_exceptions import VmException, StackUnderflowException, InvalidJumpDestination +from mythril.laser.ethereum.evm_exceptions import VmException, StackUnderflowException, InvalidJumpDestination, \ + InvalidInstruction from mythril.laser.ethereum.keccak import KeccakFunctionManager from mythril.laser.ethereum.state import GlobalState, CalldataType from mythril.laser.ethereum.transaction import MessageCallTransaction, TransactionStartSignal, \ @@ -1005,11 +1006,11 @@ class Instruction: @StateTransition() def assert_fail_(self, global_state): # 0xfe: designated invalid opcode - raise InvalidJumpDestination + raise InvalidInstruction @StateTransition() def invalid_(self, global_state): - raise InvalidJumpDestination + raise InvalidInstruction @StateTransition() def stop_(self, global_state): From 87d485ac67d90dee05f5aba37ae7d658f05339b9 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 1 Oct 2018 15:15:52 +0200 Subject: [PATCH 08/43] Implement returndata --- mythril/laser/ethereum/svm.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 14590e9a..f68ba7b9 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -135,7 +135,8 @@ class LaserEVM: else: # First execute the post hook for the transaction ending instruction self._execute_post_hook(op_code, [global_state]) - new_global_states = self._end_message_call(return_global_state, transaction, global_state, revert_changes=True) + new_global_states = self._end_message_call(return_global_state, transaction, global_state, + revert_changes=True, return_data=0) except TransactionStartSignal as e: # Setup new global state @@ -159,18 +160,19 @@ class LaserEVM: # First execute the post hook for the transaction ending instruction self._execute_post_hook(op_code, [e.global_state]) - new_global_states = self._end_message_call(return_global_state, transaction, global_state, revert_changes=False) + new_global_states = self._end_message_call(return_global_state, transaction, global_state, + revert_changes=False, return_data=transaction.return_data) self._execute_post_hook(op_code, new_global_states) return new_global_states, op_code - def _end_message_call(self, return_global_state, transaction, global_state, revert_changes=False): + def _end_message_call(self, return_global_state, global_state, revert_changes=False, return_data=0): # Resume execution of the transaction initializing instruction op_code = return_global_state.environment.code.instruction_list[return_global_state.mstate.pc]['opcode'] # Set execution result in the return_state - return_global_state.last_return_data = transaction.return_data + return_global_state.last_return_data = return_data if not revert_changes: return_global_state.world_state = copy(global_state.world_state) return_global_state.environment.active_account = \ From 190cb523c9db582a655e6da2c34792d6ad734d0d Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 1 Oct 2018 15:31:18 +0200 Subject: [PATCH 09/43] Remove unused argument --- mythril/laser/ethereum/svm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index f68ba7b9..f4387e62 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -135,7 +135,7 @@ class LaserEVM: else: # First execute the post hook for the transaction ending instruction self._execute_post_hook(op_code, [global_state]) - new_global_states = self._end_message_call(return_global_state, transaction, global_state, + new_global_states = self._end_message_call(return_global_state, global_state, revert_changes=True, return_data=0) except TransactionStartSignal as e: @@ -160,7 +160,7 @@ class LaserEVM: # First execute the post hook for the transaction ending instruction self._execute_post_hook(op_code, [e.global_state]) - new_global_states = self._end_message_call(return_global_state, transaction, global_state, + new_global_states = self._end_message_call(return_global_state, global_state, revert_changes=False, return_data=transaction.return_data) self._execute_post_hook(op_code, new_global_states) From 6fab94528a21d56dd079b6de5f003d50aa12f6a6 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Thu, 4 Oct 2018 18:41:59 +0200 Subject: [PATCH 10/43] Make return data None on exception state --- mythril/laser/ethereum/svm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index f4387e62..cdf85484 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -136,7 +136,7 @@ class LaserEVM: # First execute the post hook for the transaction ending instruction self._execute_post_hook(op_code, [global_state]) new_global_states = self._end_message_call(return_global_state, global_state, - revert_changes=True, return_data=0) + revert_changes=True, return_data=None) except TransactionStartSignal as e: # Setup new global state @@ -167,7 +167,7 @@ class LaserEVM: return new_global_states, op_code - def _end_message_call(self, return_global_state, global_state, revert_changes=False, return_data=0): + def _end_message_call(self, return_global_state, global_state, revert_changes=False, return_data=None): # Resume execution of the transaction initializing instruction op_code = return_global_state.environment.code.instruction_list[return_global_state.mstate.pc]['opcode'] From d7df0c132cebb6f69f78c8c26c9ad5866f525e8f Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Sun, 7 Oct 2018 13:48:38 +0530 Subject: [PATCH 11/43] 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 2c72a5b0e4f6955543cb0f19301b8531e82559a8 Mon Sep 17 00:00:00 2001 From: tintinweb Date: Sun, 7 Oct 2018 13:46:48 +0200 Subject: [PATCH 12/43] fix signature db throwing FileNotFoundError when myhtril is used as a library but init mythril dirs was never called (common case for libs) --- mythril/support/signatures.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mythril/support/signatures.py b/mythril/support/signatures.py index 9b64a2c6..b93d0048 100644 --- a/mythril/support/signatures.py +++ b/mythril/support/signatures.py @@ -111,6 +111,8 @@ class SignatureDb(object): :return: self """ path = path or self.signatures_file + directory = os.path.split(path)[0] + if sync and os.path.exists(path): # reload and save if file exists with open(path, "r") as f: @@ -122,7 +124,10 @@ class SignatureDb(object): sigs.update(self.signatures) # reload file and merge cached sigs into what we load from file self.signatures = sigs - + + if not os.path.exists(directory): + os.makedirs(directory) # create folder structure if not existS + if not os.path.exists(path): # creates signatures.json file if it doesn't exist open(path, "w").close() From 2d5b1bbe4a68224b36fe895086aa9ac4e2f4ef29 Mon Sep 17 00:00:00 2001 From: tintinweb Date: Sun, 7 Oct 2018 21:03:58 +0200 Subject: [PATCH 13/43] fix FileNotFoundError if directory is empty (e.g. path is only a file in the pwd) --- mythril/support/signatures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/support/signatures.py b/mythril/support/signatures.py index b93d0048..7fb8f373 100644 --- a/mythril/support/signatures.py +++ b/mythril/support/signatures.py @@ -125,7 +125,7 @@ class SignatureDb(object): sigs.update(self.signatures) # reload file and merge cached sigs into what we load from file self.signatures = sigs - if not os.path.exists(directory): + if directory and not os.path.exists(directory): os.makedirs(directory) # create folder structure if not existS if not os.path.exists(path): # creates signatures.json file if it doesn't exist From 5e9f613911c2676d530fd36b8b5316a6a633da2b Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 8 Oct 2018 20:04:07 +0200 Subject: [PATCH 14/43] 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 15/43] 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 b51eee3605f2e51186120b0781e2b4d016a5ce3d Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Tue, 9 Oct 2018 10:49:11 +0200 Subject: [PATCH 16/43] First step to fix the solc_args option --- mythril/mythril.py | 2 +- mythril/support/signatures.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mythril/mythril.py b/mythril/mythril.py index 5d36a774..e20e1a12 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -317,7 +317,7 @@ class Mythril(object): try: # import signatures from solidity source - self.sigs.import_from_solidity_source(file) + self.sigs.import_from_solidity_source(file, solc_binary=self.solc_binary, solc_args=self.solc_args) # Save updated function signatures self.sigs.write() # dump signatures to disk (previously opened file or default location) diff --git a/mythril/support/signatures.py b/mythril/support/signatures.py index 7fb8f373..fd8d0c3a 100644 --- a/mythril/support/signatures.py +++ b/mythril/support/signatures.py @@ -177,13 +177,13 @@ class SignatureDb(object): """ return self.get(sighash=item) - def import_from_solidity_source(self, file_path): + def import_from_solidity_source(self, file_path, solc_binary="solc", solc_args=None): """ Import Function Signatures from solidity source files :param file_path: solidity source code file path :return: self """ - self.signatures.update(SignatureDb.get_sigs_from_file(file_path)) + self.signatures.update(SignatureDb.get_sigs_from_file(file_path, solc_binary, solc_args)) return self @staticmethod @@ -206,13 +206,15 @@ class SignatureDb(object): proxies=proxies)) @staticmethod - def get_sigs_from_file(file_name): + def get_sigs_from_file(file_name, solc_binary="solc", solc_args=None): """ :param file_name: accepts a filename :return: their signature mappings """ sigs = {} - cmd = ["solc", "--hashes", file_name] + cmd = [solc_binary, "--hashes", file_name] + if solc_args: + cmd.extend([i for i in solc_args.split(" ") if i != ""]) try: p = Popen(cmd, stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() From ba34b3629c89cb087a2ccafe88606bd97894b856 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 12:51:07 +0200 Subject: [PATCH 17/43] Fix mutable default arg definitions --- mythril/analysis/modules/external_calls.py | 5 +++-- mythril/analysis/modules/integer.py | 5 ++++- mythril/laser/ethereum/state.py | 4 +++- mythril/laser/ethereum/taint_analysis.py | 4 +++- mythril/mythril.py | 4 +++- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/mythril/analysis/modules/external_calls.py b/mythril/analysis/modules/external_calls.py index 9b7d4038..2f9350af 100644 --- a/mythril/analysis/modules/external_calls.py +++ b/mythril/analysis/modules/external_calls.py @@ -16,8 +16,9 @@ Check for call.value()() to external addresses MAX_SEARCH_DEPTH = 64 -def search_children(statespace, node, start_index=0, depth=0, results=[]): - +def search_children(statespace, node, start_index=0, depth=0, results=None): + if results is None: + results = [] logging.debug("SEARCHING NODE %d", node.uid) if depth < MAX_SEARCH_DEPTH: diff --git a/mythril/analysis/modules/integer.py b/mythril/analysis/modules/integer.py index d83fd7a3..48707c11 100644 --- a/mythril/analysis/modules/integer.py +++ b/mythril/analysis/modules/integer.py @@ -214,7 +214,7 @@ def _check_sstore(state, taint_result): return taint_result.check(state, -2) -def _search_children(statespace, node, expression, taint_result=None, constraint=[], index=0, depth=0, max_depth=64): +def _search_children(statespace, node, expression, taint_result=None, constraint=None, index=0, depth=0, max_depth=64): """ Checks the statespace for children states, with JUMPI or SSTORE instuctions, for dependency on expression @@ -227,6 +227,9 @@ def _search_children(statespace, node, expression, taint_result=None, constraint :param max_depth: Max depth to explore :return: List of states that match the opcodes and are dependent on expression """ + if constraint is None: + constraint = [] + logging.debug("SEARCHING NODE for usage of an overflowed variable %d", node.uid) if taint_result is None: diff --git a/mythril/laser/ethereum/state.py b/mythril/laser/ethereum/state.py index 03b271ac..b744fc7c 100644 --- a/mythril/laser/ethereum/state.py +++ b/mythril/laser/ethereum/state.py @@ -135,8 +135,10 @@ class MachineStack(list): """ STACK_LIMIT = 1024 - def __init__(self, default_list=[]): + def __init__(self, default_list=None): super(MachineStack, self).__init__(default_list) + if default_list is None: + default_list = [] def append(self, element): """ diff --git a/mythril/laser/ethereum/taint_analysis.py b/mythril/laser/ethereum/taint_analysis.py index 2fdb0c52..4f605008 100644 --- a/mythril/laser/ethereum/taint_analysis.py +++ b/mythril/laser/ethereum/taint_analysis.py @@ -82,7 +82,7 @@ class TaintRunner: """ @staticmethod - def execute(statespace, node, state, initial_stack=[]): + def execute(statespace, node, state, initial_stack=None): """ Runs taint analysis on the statespace :param statespace: symbolic statespace to run taint analysis on @@ -91,6 +91,8 @@ class TaintRunner: :param stack_indexes: stack indexes to introduce taint :return: TaintResult object containing analysis results """ + if initial_stack is None: + initial_stack = [] result = TaintResult() transaction_stack_length = len(node.states[0].transaction_stack) # Build initial current_node diff --git a/mythril/mythril.py b/mythril/mythril.py index 5d36a774..f7c4c8d7 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -382,7 +382,9 @@ class Mythril(object): return report - def get_state_variable_from_storage(self, address, params=[]): + def get_state_variable_from_storage(self, address, params=None): + if params is None: + params = [] (position, length, mappings) = (0, 1, []) try: if params[0] == "mapping": From f2677aa1eea897eee476b5b9f982db00f6212d6b Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 12:51:16 +0200 Subject: [PATCH 18/43] Replace dict creation with dict literal --- mythril/ether/asm.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/mythril/ether/asm.py b/mythril/ether/asm.py index 70fbb53a..5cc2b4b5 100644 --- a/mythril/ether/asm.py +++ b/mythril/ether/asm.py @@ -42,9 +42,7 @@ def easm_to_instruction_list(easm): # Invalid code line continue - instruction = {} - - instruction['opcode'] = m.group(1) + instruction = {'opcode': m.group(1)} if m.group(2): instruction['argument'] = m.group(2)[2:] @@ -101,9 +99,7 @@ def disassemble(bytecode): while addr < length: - instruction = {} - - instruction['address'] = addr + instruction = {'address': addr} try: if (sys.version_info > (3, 0)): From 462388cab253d5fccf760f7044348c4938903d29 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Tue, 9 Oct 2018 13:06:37 +0200 Subject: [PATCH 19/43] Set default list before super call --- mythril/laser/ethereum/state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/state.py b/mythril/laser/ethereum/state.py index b744fc7c..16c01c3a 100644 --- a/mythril/laser/ethereum/state.py +++ b/mythril/laser/ethereum/state.py @@ -136,9 +136,9 @@ class MachineStack(list): STACK_LIMIT = 1024 def __init__(self, default_list=None): - super(MachineStack, self).__init__(default_list) if default_list is None: default_list = [] + super(MachineStack, self).__init__(default_list) def append(self, element): """ From 8f16025ab02cdfe788db5c97fb49eb7a75d3bca5 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Tue, 9 Oct 2018 07:06:57 -0700 Subject: [PATCH 20/43] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index aec4b962..b82dfaa5 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,10 @@ Instructions for using the 'myth' tool are found on the [Wiki](https://github.co For support or general discussions please join the Mythril community on [Discord](https://discord.gg/E3YrVtG). +## Vulnerability Remediation + +Visit the [Smart Contract Vulnerability Classification Registry](https://smartcontractsecurity.github.io/SWC-registry/) to find detailed information and remediation guidance about the vulnerabilities reported. + ## Presentations, papers and articles - [Analyzing Ethereum Smart Contracts for Vulnerabilities](https://hackernoon.com/scanning-ethereum-smart-contracts-for-vulnerabilities-b5caefd995df) From 6fd8b76900bc2098e6007b126e5eb2995f43b6d1 Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Tue, 9 Oct 2018 11:10:14 +0200 Subject: [PATCH 21/43] Fix the second issue with solc_args --- mythril/ether/util.py | 11 +++++++++-- mythril/support/signatures.py | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/mythril/ether/util.py b/mythril/ether/util.py index ac09b4fb..0e4bae40 100644 --- a/mythril/ether/util.py +++ b/mythril/ether/util.py @@ -18,10 +18,17 @@ def safe_decode(hex_encoded_string): def get_solc_json(file, solc_binary="solc", solc_args=None): - cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime", '--allow-paths', "."] + cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime"] if solc_args: - cmd.extend(solc_args.split(" ")) + cmd.extend(solc_args.split()) + + if not "--allow-paths" in cmd: + cmd.extend(["--allow-paths", "."]) + else: + for i, arg in enumerate(cmd): + if arg == "--allow-paths": + cmd[i + 1] += ",." cmd.append(file) diff --git a/mythril/support/signatures.py b/mythril/support/signatures.py index fd8d0c3a..7c454089 100644 --- a/mythril/support/signatures.py +++ b/mythril/support/signatures.py @@ -183,7 +183,7 @@ class SignatureDb(object): :param file_path: solidity source code file path :return: self """ - self.signatures.update(SignatureDb.get_sigs_from_file(file_path, solc_binary, solc_args)) + self.signatures.update(SignatureDb.get_sigs_from_file(file_path, solc_binary=solc_binary, solc_args=solc_args)) return self @staticmethod @@ -214,7 +214,7 @@ class SignatureDb(object): sigs = {} cmd = [solc_binary, "--hashes", file_name] if solc_args: - cmd.extend([i for i in solc_args.split(" ") if i != ""]) + cmd.extend(solc_args.split()) try: p = Popen(cmd, stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() From 73acb9add20c0da83842e075b512a878e19b8a60 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Tue, 9 Oct 2018 10:46:41 -0700 Subject: [PATCH 22/43] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b82dfaa5..41566bac 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ For support or general discussions please join the Mythril community on [Discord ## Vulnerability Remediation -Visit the [Smart Contract Vulnerability Classification Registry](https://smartcontractsecurity.github.io/SWC-registry/) to find detailed information and remediation guidance about the vulnerabilities reported. +Visit the [Smart Contract Vulnerability Classification Registry](https://smartcontractsecurity.github.io/SWC-registry/) to find detailed information and remediation guidance for the vulnerabilities reported. ## Presentations, papers and articles From fc0ead33b5f9df484a2ece7d72d935f50a8b5cd9 Mon Sep 17 00:00:00 2001 From: JoranHonig Date: Wed, 10 Oct 2018 10:44:55 +0200 Subject: [PATCH 23/43] 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 24/43] 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 25/43] 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 26/43] 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 27/43] 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 28/43] 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 29/43] 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 30/43] 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 31/43] 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 32/43] 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 33/43] =?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 34/43] 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 35/43] 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 36/43] 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 37/43] 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 b50f72c1aa77e91f4cfcecbcf4dedd7095f1c71e Mon Sep 17 00:00:00 2001 From: p0n1 Date: Fri, 12 Oct 2018 10:01:33 +0800 Subject: [PATCH 38/43] 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 39/43] 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 40/43] 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 c2396a50faa3e9cb0b536756e8e1a31d88661c05 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sun, 14 Oct 2018 00:13:00 +0200 Subject: [PATCH 41/43] 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 42/43] 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 43/43] 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)