def test_delegate_call(sym_mock, concrete_mock, curr_instruction): # arrange # sym_mock = mocker.patch.object(delegatecall, "_symbolic_call") # concrete_mock = mocker.patch.object(delegatecall, "_concrete_call") sym_mock.return_value = [] concrete_mock.return_value = [] curr_instruction.return_value = {"address": "0x10"} active_account = Account("0x10") active_account.code = Disassembly("00") environment = Environment(active_account, None, None, None, None, None) state = GlobalState(None, environment, Node) state.mstate.memory = ["placeholder", "calldata_bling_0"] state.mstate.stack = [1, 2, 3] assert state.get_current_instruction() == {"address": "0x10"} node = Node("example") node.contract_name = "the contract name" node.function_name = "fallback" to = Variable("storage_1", VarType.SYMBOLIC) call = Call(node, state, None, "DELEGATECALL", to, None) statespace = MagicMock() statespace.calls = [call] # act execute(statespace) # assert assert concrete_mock.call_count == 1 assert sym_mock.call_count == 1
def test_concrete_call_symbolic_to(): # arrange address = "0x10" active_account = Account(address) active_account.code = Disassembly("00") environment = Environment(active_account, None, None, None, None, None) state = GlobalState(None, environment, None) state.mstate.memory = ["placeholder", "calldata_bling_0"] node = Node("example") node.contract_name = "the contract name" node.function_name = "the function name" to = Variable("calldata_3", VarType.SYMBOLIC) meminstart = Variable(1, VarType.CONCRETE) call = Call(node, state, None, None, to, None) # act issues = _concrete_call(call, state, address, meminstart) # assert issue = issues[0] assert issue.address == address assert issue.contract == node.contract_name assert issue.function == node.function_name assert issue.title == "Call data forwarded with delegatecall()" assert issue.type == "Informational" assert ( issue.description == "This contract forwards its call data via DELEGATECALL in its fallback function." " This means that any function in the called contract can be executed." " Note that the callee contract will have access to the storage of the " "calling contract.\n DELEGATECALL target: calldata_3" )
def create_account( self, balance=0, address=None, concrete_storage=False, dynamic_loader=None, creator=None, ) -> Account: """Create non-contract account. :param address: The account's address :param balance: Initial balance for the account :param concrete_storage: Interpret account storage as concrete :param dynamic_loader: used for dynamically loading storage from the block chain :return: The new account """ address = (symbol_factory.BitVecVal(address, 256) if address else self._generate_new_address(creator)) new_account = Account( address=address, balances=self.balances, dynamic_loader=dynamic_loader, concrete_storage=concrete_storage, ) if balance: new_account.add_balance(symbol_factory.BitVecVal(balance, 256)) self.put_account(new_account) return new_account
def _get_global_state(): active_account = Account("0x0", code=Disassembly("60606040")) passive_account = Account("0x325345346564645654645", code=Disassembly("6060604061626364")) environment = Environment(active_account, None, None, None, None, None) world_state = WorldState() world_state.put_account(active_account) world_state.put_account(passive_account) return GlobalState(world_state, environment, None, MachineState(gas_limit=8000000))
def get_callee_account( global_state: GlobalState, callee_address: Union[str, BitVec], dynamic_loader: DynLoader, ): """Gets the callees account from the global_state. :param global_state: state to look in :param callee_address: address of the callee :param dynamic_loader: dynamic loader to use :return: Account belonging to callee """ if isinstance(callee_address, BitVec): if callee_address.symbolic: return Account(callee_address, balances=global_state.world_state.balances) else: callee_address = hex(callee_address.value)[2:] try: return global_state.accounts[int(callee_address, 16)] except KeyError: # We have a valid call address, but contract is not in the modules list log.debug("Module with address %s not loaded.", callee_address) if dynamic_loader is None: raise ValueError("dynamic_loader is None") log.debug("Attempting to load dependency") try: code = dynamic_loader.dynld(callee_address) except ValueError as error: log.debug("Unable to execute dynamic loader because: %s", error) raise error if code is None: log.debug("No code returned, not a contract account?") raise ValueError("No code returned") log.debug("Dependency loaded: " + callee_address) callee_account = Account( symbol_factory.BitVecVal(int(callee_address, 16), 256), code, callee_address, dynamic_loader=dynamic_loader, balances=global_state.world_state.balances, ) global_state.accounts[int(callee_address, 16)] = callee_account return callee_account
def create_initialized_contract_account(self, contract_code, storage) -> None: """ Creates a new contract account, based on the contract code and storage provided The contract code only includes the runtime contract bytecode :param contract_code: Runtime bytecode for the contract :param storage: Initial storage for the contract :return: The new account """ # TODO: Add type hints new_account = Account(self._generate_new_address(), code=contract_code, balance=0) new_account.storage = storage self._put_account(new_account)
def test_laser_result(self): for input_file in TESTDATA_INPUTS_CONTRACTS.iterdir(): if input_file.name in ["weak_random.sol", "environments.sol"]: continue output_expected = TESTDATA_OUTPUTS_EXPECTED_LASER_RESULT / ( input_file.name + ".json" ) output_current = TESTDATA_OUTPUTS_CURRENT_LASER_RESULT / ( input_file.name + ".json" ) disassembly = SolidityContract(str(input_file)).disassembly account = Account("0x0000000000000000000000000000000000000000", disassembly) accounts = {account.address: account} laser = svm.LaserEVM(accounts, max_depth=22, max_transaction_count=1) laser.sym_exec(account.address) laser_info = _all_info(laser) output_current.write_text( json.dumps(laser_info, cls=LaserEncoder, indent=4) ) if not (output_expected.read_text() == output_expected.read_text()): self.found_changed_files(input_file, output_expected, output_current) self.assert_and_show_changed_files()
def put_account(self, account: Account) -> None: """ :param account: """ self._accounts[account.address.value] = account account._balances = self.balances
def get_state(): active_account = Account("0x0", code=Disassembly("60606040")) environment = Environment(active_account, None, None, None, None, None) state = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) state.transaction_stack.append( (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) ) return state
def test_intercontract_call(): # Arrange cfg.gbl_next_uid = 0 caller_code = Disassembly( "6080604052348015600f57600080fd5b5073deadbeefdeadbeefdeadbeefdeadbeefdeadbeef73ffffffffffffffffffffffffffffffffffffffff166389627e13336040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b15801560be57600080fd5b505af115801560d1573d6000803e3d6000fd5b505050506040513d602081101560e657600080fd5b8101908080519060200190929190505050500000a165627a7a72305820fdb1e90f0d9775c94820e516970e0d41380a94624fa963c556145e8fb645d4c90029" ) caller_address = "0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe" callee_code = Disassembly( "608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806389627e13146044575b600080fd5b348015604f57600080fd5b506082600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506084565b005b8073ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f1935050505015801560e0573d6000803e3d6000fd5b50505600a165627a7a72305820a6b1335d6f994632bc9a7092d0eaa425de3dea05e015af8a94ad70b3969e117a0029" ) callee_address = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" world_state = WorldState() caller_account = Account(caller_address, caller_code, contract_name="Caller") callee_account = Account(callee_address, callee_code, contract_name="Callee") world_state.put_account(callee_account) world_state.put_account(caller_account) laser = svm.LaserEVM() # Act laser.sym_exec(world_state=world_state, target_address=int(caller_address, 16)) # Assert # Initial node starts in contract caller assert len(laser.nodes.keys()) > 0 assert laser.nodes[0].contract_name == "Caller" # At one point we call into contract callee for node in laser.nodes.values(): if node.contract_name == "Callee": assert len(node.states[0].transaction_stack) > 1 return assert False
def test_symbolic_call_storage_to(mocker): # arrange address = "0x10" active_account = Account(address) active_account.code = Disassembly("00") environment = Environment(active_account, None, None, None, None, None) state = GlobalState(None, environment, None) state.mstate.memory = ["placeholder", "calldata_bling_0"] node = Node("example") node.contract_name = "the contract name" node.function_name = "the function name" to = Variable("storage_1", VarType.SYMBOLIC) call = Call(node, state, None, "Type: ", to, None) mocker.patch.object(SymExecWrapper, "__init__", lambda x, y: None) statespace = SymExecWrapper(1) mocker.patch.object(statespace, "find_storage_write") statespace.find_storage_write.return_value = "Function name" # act issues = _symbolic_call(call, state, address, statespace) # assert issue = issues[0] assert issue.address == address assert issue.contract == node.contract_name assert issue.function == node.function_name assert issue.title == "Type: to a user-supplied address" assert issue.type == "Informational" assert ( issue.description == "This contract delegates execution to a contract address in storage slot 1." " This storage slot can be written to by calling the function `Function name`. " "Be aware that the called contract gets unrestricted access to this contract's state." )
def __getitem__(self, item: BitVec) -> Account: """Gets an account from the worldstate using item as key. :param item: Address of the account to get :return: Account associated with the address """ try: return self._accounts[item.value] except KeyError: new_account = Account(address=item, code=None, balances=self.balances) self._accounts[item.value] = new_account return new_account
def test_codecopy_concrete(): # Arrange active_account = Account("0x0", code=Disassembly("60606040")) environment = Environment(active_account, None, None, None, None, None) og_state = GlobalState(None, environment, None, MachineState(gas=10000000)) og_state.mstate.stack = [2, 2, 2] instruction = Instruction("codecopy", dynamic_loader=None) # Act new_state = instruction.evaluate(og_state)[0] # Assert assert new_state.mstate.memory[2] == 96 assert new_state.mstate.memory[3] == 64
def runTest(): disassembly = SolidityContract("./tests/native_tests.sol").disassembly account = Account("0x0000000000000000000000000000000000000000", disassembly) accounts = {account.address: account} laser = svm.LaserEVM(accounts, max_depth=100, transaction_count=1) laser.sym_exec(account.address) laser_info = str(_all_info(laser)) print("\n") _test_natives(laser_info, SHA256_TEST, "SHA256") _test_natives(laser_info, RIPEMD160_TEST, "RIPEMD160") _test_natives(laser_info, ECRECOVER_TEST, "ECRECOVER") _test_natives(laser_info, IDENTITY_TEST, "IDENTITY")
def test_staticness_call_symbolic(f1): # Arrange state = get_global_state() state.environment.static = True state.mstate.stack = [] call_value = symbol_factory.BitVecSym("x", 256) code = Disassembly(code="616263") f1.return_value = ("0", Account(code=code, address="0x19"), 0, call_value, 0, 0, 0) instruction = Instruction("call", dynamic_loader=None) # Act and Assert with pytest.raises(TransactionStartSignal) as ts: instruction.evaluate(state) assert ts.value.transaction.static assert ts.value.global_state.mstate.constraints[-1] == (call_value == 0)
def test_staticness_call_concrete(f1, input, success): # Arrange state = get_global_state() state.environment.static = True state.mstate.stack = [] code = Disassembly(code="616263") f1.return_value = ("0", Account(code=code, address="0x19"), 0, input, 0, 0, 0) instruction = Instruction("call", dynamic_loader=None) # Act and Assert if success: with pytest.raises(TransactionStartSignal) as ts: instruction.evaluate(state) assert ts.value.transaction.static else: with pytest.raises(WriteProtection): instruction.evaluate(state)
def get_callee_account(global_state: GlobalState, callee_address: str, dynamic_loader: DynLoader): """Gets the callees account from the global_state. :param global_state: state to look in :param callee_address: address of the callee :param dynamic_loader: dynamic loader to use :return: Account belonging to callee """ environment = global_state.environment accounts = global_state.accounts try: return global_state.accounts[int(callee_address, 16)] except KeyError: # We have a valid call address, but contract is not in the modules list log.debug("Module with address " + callee_address + " not loaded.") if dynamic_loader is None: raise ValueError() log.debug("Attempting to load dependency") try: code = dynamic_loader.dynld(callee_address) except ValueError as error: log.debug("Unable to execute dynamic loader because: {}".format( str(error))) raise error if code is None: log.debug("No code returned, not a contract account?") raise ValueError() log.debug("Dependency loaded: " + callee_address) callee_account = Account( symbol_factory.BitVecVal(int(callee_address, 16), 256), code, callee_address, dynamic_loader=dynamic_loader, balances=global_state.world_state.balances, ) accounts[callee_address] = callee_account return callee_account
def test_codecopy_concrete(): # Arrange active_account = Account("0x0", code=Disassembly("60606040")) environment = Environment(active_account, None, None, None, None, None) og_state = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) og_state.transaction_stack.append( (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None)) og_state.mstate.stack = [2, 2, 2] instruction = Instruction("codecopy", dynamic_loader=None) # Act new_state = instruction.evaluate(og_state)[0] # Assert assert new_state.mstate.memory[2] == 96 assert new_state.mstate.memory[3] == 64
def test_execute(mocker): active_account = Account("0x00") environment = Environment(active_account, None, None, None, None, None) state_1 = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) state_1.mstate.stack = [1, 2] mocker.patch.object(state_1, "get_current_instruction") state_1.get_current_instruction.return_value = {"opcode": "PUSH"} state_2 = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) state_2.mstate.stack = [1, 2, 3] mocker.patch.object(state_2, "get_current_instruction") state_2.get_current_instruction.return_value = {"opcode": "ADD"} node_1 = Node("Test contract") node_1.states = [state_1, state_2] state_3 = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) state_3.mstate.stack = [1, 2] mocker.patch.object(state_3, "get_current_instruction") state_3.get_current_instruction.return_value = {"opcode": "ADD"} node_2 = Node("Test contract") node_2.states = [state_3] edge = Edge(node_1.uid, node_2.uid) statespace = LaserEVM(None) statespace.edges = [edge] statespace.nodes[node_1.uid] = node_1 statespace.nodes[node_2.uid] = node_2 # Act result = TaintRunner.execute(statespace, node_1, state_1, [True, True]) # Assert print(result) assert len(result.records) == 3 assert result.records[2].states == [] assert state_3 in result.records[1].states
def runTest(): """""" disassembly = SolidityContract( "./tests/native_tests.sol", solc_binary=MythrilDisassembler._init_solc_binary("0.5.3"), ).disassembly account = Account("0x0000000000000000000000000000000000000000", disassembly) world_state = WorldState() world_state.put_account(account) laser = svm.LaserEVM(max_depth=100, transaction_count=1) laser.sym_exec(world_state=world_state, target_address=account.address.value) laser_info = str(_all_info(laser)) _test_natives(laser_info, SHA256_TEST, "SHA256") _test_natives(laser_info, RIPEMD160_TEST, "RIPEMD160") _test_natives(laser_info, ECRECOVER_TEST, "ECRECOVER") _test_natives(laser_info, IDENTITY_TEST, "IDENTITY")
def test_execute_message_call(mocked_setup: MagicMock): # Arrange laser_evm = LaserEVM({}) world_state = WorldState() world_state.accounts["address"] = Account("address") laser_evm.open_states = [world_state] laser_evm.exec = MagicMock() mocked_setup.side_effect = _is_message_call # Act execute_message_call(laser_evm, "address") # Assert # laser_evm.exec.assert_called_once() assert laser_evm.exec.call_count == 1 # mocked_setup.assert_called_once() assert mocked_setup.call_count == 1 assert len(laser_evm.open_states) == 0
def test_execute_message_call(mocked_setup: MagicMock): # Arrange laser_evm = LaserEVM({}) world_state = WorldState() world_state.put_account(Account("0x0")) laser_evm.open_states = [world_state] laser_evm.exec = MagicMock() mocked_setup.side_effect = _is_message_call # Act execute_message_call(laser_evm, symbol_factory.BitVecVal(0, 256)) # Assert # laser_evm.exec.assert_called_once() assert laser_evm.exec.call_count == 1 # mocked_setup.assert_called_once() assert mocked_setup.call_count == 1 assert len(laser_evm.open_states) == 0
def create_account(self, balance=0, address=None, concrete_storage=False, dynamic_loader=None) -> Account: """ Create non-contract account :param address: The account's address :param balance: Initial balance for the account :param concrete_storage: Interpret account storage as concrete :param dynamic_loader: used for dynamically loading storage from the block chain :return: The new account """ address = address if address else self._generate_new_address() new_account = Account( address, balance=balance, dynamic_loader=dynamic_loader, concrete_storage=concrete_storage, ) self._put_account(new_account) return new_account
def __init__(self, contract, address, strategy, dynloader=None, max_depth=22, execution_timeout=None, create_timeout=None, transaction_count=2, priority=None): if strategy == "dfs": s_strategy = DepthFirstSearchStrategy elif strategy == "bfs": s_strategy = BreadthFirstSearchStrategy elif strategy == "naive-random": s_strategy = ReturnRandomNaivelyStrategy elif strategy == "weighted-random": s_strategy = ReturnWeightedRandomStrategy else: raise ValueError("Invalid strategy argument supplied") account = Account( address, contract.disassembly, dynamic_loader=dynloader, contract_name=contract.name, ) self.accounts = {address: account} self.laser = svm.LaserEVM( self.accounts, dynamic_loader=dynloader, max_depth=max_depth, execution_timeout=execution_timeout, strategy=s_strategy, create_timeout=create_timeout, transaction_count=transaction_count, ) self.laser.register_hooks(hook_type="pre", hook_dict=get_detection_module_hooks()) if isinstance(contract, SolidityContract): self.laser.sym_exec(creation_code=contract.creation_code, contract_name=contract.name, priority=priority) elif isinstance(contract, EVMContract) and contract.creation_code: self.laser.sym_exec(creation_code=contract.creation_code, contract_name=contract.name, priority=priority) else: self.laser.sym_exec(address, priority=priority) self.nodes = self.laser.nodes self.edges = self.laser.edges print('total states: ' + str(self.laser.total_states)) # Generate lists of interesting operations self.calls = [] self.sstors = {} for key in self.nodes: state_index = 0 for state in self.nodes[key].states: instruction = state.get_current_instruction() op = instruction["opcode"] if op in ("CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"): stack = state.mstate.stack if op in ("CALL", "CALLCODE"): gas, to, value, meminstart, meminsz, memoutstart, memoutsz = ( get_variable(stack[-1]), get_variable(stack[-2]), get_variable(stack[-3]), get_variable(stack[-4]), get_variable(stack[-5]), get_variable(stack[-6]), get_variable(stack[-7]), ) if to.type == VarType.CONCRETE and to.val < 5: # ignore prebuilts continue 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, )) else: gas, to, meminstart, meminsz, memoutstart, memoutsz = ( get_variable(stack[-1]), get_variable(stack[-2]), get_variable(stack[-3]), get_variable(stack[-4]), get_variable(stack[-5]), get_variable(stack[-6]), ) self.calls.append( Call(self.nodes[key], state, state_index, op, to, gas)) elif op == "SSTORE": stack = copy.deepcopy(state.mstate.stack) address = state.environment.active_account.address index, value = stack.pop(), stack.pop() try: self.sstors[address] except KeyError: self.sstors[address] = {} try: self.sstors[address][str(index)].append( SStore(self.nodes[key], state, state_index, value)) except KeyError: self.sstors[address][str(index)] = [ SStore(self.nodes[key], state, state_index, value) ] state_index += 1
def test_vmtest(test_name: str, pre_condition: dict, action: dict, post_condition: dict) -> None: # Arrange if test_name == "gasprice": return accounts = {} for address, details in pre_condition.items(): account = Account(address) account.code = Disassembly(details["code"][2:]) account.balance = int(details["balance"], 16) account.nonce = int(details["nonce"], 16) accounts[address] = account laser_evm = LaserEVM(accounts) # Act laser_evm.time = datetime.now() # TODO: move this line below and check for VmExceptions when gas has been implemented if post_condition == {}: return execute_message_call( laser_evm, callee_address=action["address"], caller_address=action["caller"], origin_address=binascii.a2b_hex(action["origin"][2:]), code=action["code"][2:], gas=action["gas"], data=binascii.a2b_hex(action["data"][2:]), gas_price=int(action["gasPrice"], 16), value=int(action["value"], 16), ) # Assert assert len(laser_evm.open_states) == 1 world_state = laser_evm.open_states[0] model = get_model( next(iter(laser_evm.nodes.values())).states[0].mstate.constraints) for address, details in post_condition.items(): account = world_state[address] assert account.nonce == int(details["nonce"], 16) assert account.code.bytecode == details["code"][2:] for index, value in details["storage"].items(): expected = int(value, 16) actual = account.storage[int(index, 16)] if isinstance(actual, ExprRef): actual = model.eval(actual) actual = ( 1 if actual == True else 0 if actual == False else actual ) # Comparisons should be done with == than 'is' here as actual can be a BoolRef else: if type(actual) == bytes: actual = int(binascii.b2a_hex(actual), 16) elif type(actual) == str: actual = int(actual, 16) assert actual == expected
def test_vmtest( test_name: str, environment: dict, pre_condition: dict, action: dict, gas_used: int, post_condition: dict, ) -> None: # Arrange if test_name == "gasprice": return accounts = {} for address, details in pre_condition.items(): account = Account(address) account.code = Disassembly(details["code"][2:]) account.balance = int(details["balance"], 16) account.nonce = int(details["nonce"], 16) accounts[address] = account laser_evm = LaserEVM(accounts) # Act laser_evm.time = datetime.now() final_states = execute_message_call( laser_evm, callee_address=action["address"], caller_address=action["caller"], origin_address=binascii.a2b_hex(action["origin"][2:]), code=action["code"][2:], gas_limit=int(action["gas"], 16), data=binascii.a2b_hex(action["data"][2:]), gas_price=int(action["gasPrice"], 16), value=int(action["value"], 16), track_gas=True, ) # Assert if gas_used is not None and gas_used < int(environment["currentGasLimit"], 16): # avoid gas usage larger than block gas limit # this currently exceeds our estimations gas_min_max = [(s.mstate.min_gas_used, s.mstate.max_gas_used) for s in final_states] gas_ranges = [g[0] <= gas_used for g in gas_min_max] assert all(map(lambda g: g[0] <= g[1], gas_min_max)) assert any(gas_ranges) if any( (v in test_name for v in ["error", "oog"])) and post_condition == {}: # no more work to do if error happens or out of gas assert len(laser_evm.open_states) == 0 else: assert len(laser_evm.open_states) == 1 world_state = laser_evm.open_states[0] model = get_model( next(iter(laser_evm.nodes.values())).states[0].mstate.constraints, enforce_execution_time=False, ) for address, details in post_condition.items(): account = world_state[address] assert account.nonce == int(details["nonce"], 16) assert account.code.bytecode == details["code"][2:] for index, value in details["storage"].items(): expected = int(value, 16) actual = account.storage[int(index, 16)] if isinstance(actual, Expression): actual = actual.value actual = 1 if actual is True else 0 if actual is False else actual else: if type(actual) == bytes: actual = int(binascii.b2a_hex(actual), 16) elif type(actual) == str: actual = int(actual, 16) assert actual == expected
def __init__( self, contract, address: Union[int, str, BitVec], strategy, dynloader=None, max_depth=22, execution_timeout=None, loop_bound=2, create_timeout=None, transaction_count=2, modules=(), compulsory_statespace=True, enable_iprof=False, disable_dependency_pruning=False, run_analysis_modules=True, ): """ :param contract: :param address: :param strategy: :param dynloader: :param max_depth: :param execution_timeout: :param create_timeout: :param transaction_count: :param modules: """ if isinstance(address, str): address = symbol_factory.BitVecVal(int(address, 16), 256) if isinstance(address, int): address = symbol_factory.BitVecVal(address, 256) if strategy == "dfs": s_strategy = DepthFirstSearchStrategy # type: Type[BasicSearchStrategy] elif strategy == "bfs": s_strategy = BreadthFirstSearchStrategy elif strategy == "naive-random": s_strategy = ReturnRandomNaivelyStrategy elif strategy == "weighted-random": s_strategy = ReturnWeightedRandomStrategy else: raise ValueError("Invalid strategy argument supplied") creator_account = Account(hex(CREATOR_ADDRESS), "", dynamic_loader=dynloader, contract_name=None) attacker_account = Account(hex(ATTACKER_ADDRESS), "", dynamic_loader=dynloader, contract_name=None) requires_statespace = (compulsory_statespace or len(get_detection_modules("post", modules)) > 0) if not contract.creation_code: self.accounts = {hex(ATTACKER_ADDRESS): attacker_account} else: self.accounts = { hex(CREATOR_ADDRESS): creator_account, hex(ATTACKER_ADDRESS): attacker_account, } self.laser = svm.LaserEVM( dynamic_loader=dynloader, max_depth=max_depth, execution_timeout=execution_timeout, strategy=s_strategy, create_timeout=create_timeout, transaction_count=transaction_count, requires_statespace=requires_statespace, enable_iprof=enable_iprof, ) if loop_bound is not None: self.laser.extend_strategy(BoundedLoopsStrategy, loop_bound) plugin_loader = LaserPluginLoader(self.laser) plugin_loader.load(PluginFactory.build_mutation_pruner_plugin()) plugin_loader.load(PluginFactory.build_instruction_coverage_plugin()) if not disable_dependency_pruning: plugin_loader.load(PluginFactory.build_dependency_pruner_plugin()) world_state = WorldState() for account in self.accounts.values(): world_state.put_account(account) if run_analysis_modules: self.laser.register_hooks( hook_type="pre", hook_dict=get_detection_module_hooks(modules, hook_type="pre"), ) self.laser.register_hooks( hook_type="post", hook_dict=get_detection_module_hooks(modules, hook_type="post"), ) if isinstance(contract, SolidityContract): self.laser.sym_exec( creation_code=contract.creation_code, contract_name=contract.name, world_state=world_state, ) elif isinstance(contract, EVMContract) and contract.creation_code: self.laser.sym_exec( creation_code=contract.creation_code, contract_name=contract.name, world_state=world_state, ) else: account = Account( address, contract.disassembly, dynamic_loader=dynloader, contract_name=contract.name, concrete_storage=True if (dynloader is not None and dynloader.storage_loading) else False, ) world_state.put_account(account) self.laser.sym_exec(world_state=world_state, target_address=address.value) if not requires_statespace: return self.nodes = self.laser.nodes self.edges = self.laser.edges # Parse calls to make them easily accessible self.calls = [] # type: List[Call] for key in self.nodes: state_index = 0 for state in self.nodes[key].states: instruction = state.get_current_instruction() op = instruction["opcode"] if op in ("CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"): stack = state.mstate.stack if op in ("CALL", "CALLCODE"): gas, to, value, meminstart, meminsz, memoutstart, memoutsz = ( get_variable(stack[-1]), get_variable(stack[-2]), get_variable(stack[-3]), get_variable(stack[-4]), get_variable(stack[-5]), get_variable(stack[-6]), get_variable(stack[-7]), ) if to.type == VarType.CONCRETE and to.val < 5: # ignore prebuilts continue 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, )) else: gas, to, meminstart, meminsz, memoutstart, memoutsz = ( get_variable(stack[-1]), get_variable(stack[-2]), get_variable(stack[-3]), get_variable(stack[-4]), get_variable(stack[-5]), get_variable(stack[-6]), ) self.calls.append( Call(self.nodes[key], state, state_index, op, to, gas)) state_index += 1
None, None, None) world_state = WorldState() world_state.put_account(active_account) state = GlobalState(world_state, environment, None, MachineState(gas_limit=8000000)) state.transaction_stack.append( (MessageCallTransaction(world_state=world_state, gas_limit=8000000), None)) return state @patch( "mythril.laser.ethereum.instructions.get_call_parameters", return_value=( "0", Account(code=Disassembly(code="0x00"), address="0x19"), 0, 0, 0, 0, 0, ), ) def test_staticcall(f1): # Arrange state = get_global_state() state.mstate.stack = [10, 10, 10, 10, 10, 10, 10, 10, 0] instruction = Instruction("staticcall", dynamic_loader=None) # Act and Assert with pytest.raises(TransactionStartSignal) as ts:
def test_vmtest( test_name: str, environment: dict, pre_condition: dict, action: dict, gas_used: int, post_condition: dict, ) -> None: # Arrange if test_name in ignored_test_names: return world_state = WorldState() for address, details in pre_condition.items(): account = Account(address, concrete_storage=True) account.code = Disassembly(details["code"][2:]) account.nonce = int(details["nonce"], 16) for key, value in details["storage"].items(): account.storage[int(key, 16)] = int(value, 16) world_state.put_account(account) account.set_balance(int(details["balance"], 16)) laser_evm = LaserEVM() laser_evm.open_states = [world_state] # Act laser_evm.time = datetime.now() final_states = execute_message_call( laser_evm, callee_address=symbol_factory.BitVecVal(int(action["address"], 16), 256), caller_address=symbol_factory.BitVecVal(int(action["caller"], 16), 256), origin_address=symbol_factory.BitVecVal(int(action["origin"], 16), 256), code=action["code"][2:], gas_limit=int(action["gas"], 16), data=binascii.a2b_hex(action["data"][2:]), gas_price=int(action["gasPrice"], 16), value=int(action["value"], 16), track_gas=True, ) # Assert if gas_used is not None and gas_used < int(environment["currentGasLimit"], 16): # avoid gas usage larger than block gas limit # this currently exceeds our estimations gas_min_max = [(s.mstate.min_gas_used, s.mstate.max_gas_used) for s in final_states] gas_ranges = [g[0] <= gas_used for g in gas_min_max] assert all(map(lambda g: g[0] <= g[1], gas_min_max)) assert any(gas_ranges) if post_condition == {}: # no more work to do if error happens or out of gas assert len(laser_evm.open_states) == 0 else: assert len(laser_evm.open_states) == 1 world_state = laser_evm.open_states[0] for address, details in post_condition.items(): account = world_state[symbol_factory.BitVecVal( int(address, 16), 256)] assert account.nonce == int(details["nonce"], 16) assert account.code.bytecode == details["code"][2:] for index, value in details["storage"].items(): expected = int(value, 16) actual = account.storage[int(index, 16)] if isinstance(actual, Expression): actual = actual.value actual = 1 if actual is True else 0 if actual is False else actual else: if type(actual) == bytes: actual = int(binascii.b2a_hex(actual), 16) elif type(actual) == str: actual = int(actual, 16) assert actual == expected
def __init__( self, contract, address: Union[int, str, BitVec], strategy: str, dynloader=None, max_depth: int = 22, execution_timeout: Optional[int] = None, loop_bound: int = 3, create_timeout: Optional[int] = None, transaction_count: int = 2, modules=(), compulsory_statespace: bool = True, iprof: Optional[InstructionProfiler] = None, disable_dependency_pruning: bool = False, run_analysis_modules: bool = True, enable_coverage_strategy: bool = False, custom_modules_directory: str = "", ): """ :param contract: Contract to symbolically execute :param address: Address of the contract to symbolically execute :param strategy: Execution strategy to use (bfs, dfs, etc) :param dynloader: Dynamic Loader :param max_depth: Max analysis depth :param execution_timeout: Timeout for the entire analysis :param create_timeout: Timeout for the creation transaction :param transaction_count: Number of transactions to symbolically execute :param modules: Analysis modules to run during analysis :param compulsory_statespace: Boolean indicating whether or not the statespace should be saved :param iprof: Instruction Profiler :param disable_dependency_pruning: Boolean indicating whether dependency pruning should be disabled :param run_analysis_modules: Boolean indicating whether analysis modules should be executed :param enable_coverage_strategy: Boolean indicating whether the coverage strategy should be enabled :param custom_modules_directory: The directory to read custom analysis modules from """ if isinstance(address, str): address = symbol_factory.BitVecVal(int(address, 16), 256) if isinstance(address, int): address = symbol_factory.BitVecVal(address, 256) if strategy == "dfs": s_strategy = DepthFirstSearchStrategy # type: Type[BasicSearchStrategy] elif strategy == "bfs": s_strategy = BreadthFirstSearchStrategy elif strategy == "naive-random": s_strategy = ReturnRandomNaivelyStrategy elif strategy == "weighted-random": s_strategy = ReturnWeightedRandomStrategy else: raise ValueError("Invalid strategy argument supplied") creator_account = Account( hex(CREATOR_ADDRESS), "", dynamic_loader=dynloader, contract_name=None ) attacker_account = Account( hex(ATTACKER_ADDRESS), "", dynamic_loader=dynloader, contract_name=None ) requires_statespace = ( compulsory_statespace or len(get_detection_modules("post", modules, custom_modules_directory)) > 0 ) if not contract.creation_code: self.accounts = {hex(ATTACKER_ADDRESS): attacker_account} else: self.accounts = { hex(CREATOR_ADDRESS): creator_account, hex(ATTACKER_ADDRESS): attacker_account, } instruction_laser_plugin = PluginFactory.build_instruction_coverage_plugin() self.laser = svm.LaserEVM( dynamic_loader=dynloader, max_depth=max_depth, execution_timeout=execution_timeout, strategy=s_strategy, create_timeout=create_timeout, transaction_count=transaction_count, requires_statespace=requires_statespace, iprof=iprof, enable_coverage_strategy=enable_coverage_strategy, instruction_laser_plugin=instruction_laser_plugin, ) if loop_bound is not None: self.laser.extend_strategy(BoundedLoopsStrategy, loop_bound) plugin_loader = LaserPluginLoader(self.laser) plugin_loader.load(PluginFactory.build_mutation_pruner_plugin()) plugin_loader.load(instruction_laser_plugin) if not disable_dependency_pruning: plugin_loader.load(PluginFactory.build_dependency_pruner_plugin()) world_state = WorldState() for account in self.accounts.values(): world_state.put_account(account) if run_analysis_modules: self.laser.register_hooks( hook_type="pre", hook_dict=get_detection_module_hooks( modules, hook_type="pre", custom_modules_directory=custom_modules_directory, ), ) self.laser.register_hooks( hook_type="post", hook_dict=get_detection_module_hooks( modules, hook_type="post", custom_modules_directory=custom_modules_directory, ), ) if isinstance(contract, SolidityContract): self.laser.sym_exec( creation_code=contract.creation_code, contract_name=contract.name, world_state=world_state, ) elif isinstance(contract, EVMContract) and contract.creation_code: self.laser.sym_exec( creation_code=contract.creation_code, contract_name=contract.name, world_state=world_state, ) else: account = Account( address, contract.disassembly, dynamic_loader=dynloader, contract_name=contract.name, concrete_storage=True if (dynloader is not None and dynloader.storage_loading) else False, ) world_state.put_account(account) self.laser.sym_exec(world_state=world_state, target_address=address.value) if not requires_statespace: return self.nodes = self.laser.nodes self.edges = self.laser.edges # Parse calls to make them easily accessible self.calls = [] # type: List[Call] for key in self.nodes: state_index = 0 for state in self.nodes[key].states: instruction = state.get_current_instruction() op = instruction["opcode"] if op in ("CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"): stack = state.mstate.stack if op in ("CALL", "CALLCODE"): gas, to, value, meminstart, meminsz, memoutstart, memoutsz = ( get_variable(stack[-1]), get_variable(stack[-2]), get_variable(stack[-3]), get_variable(stack[-4]), get_variable(stack[-5]), get_variable(stack[-6]), get_variable(stack[-7]), ) if ( to.type == VarType.CONCRETE and 0 < to.val <= PRECOMPILE_COUNT ): # ignore prebuilts continue 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 + meminstart.val ], ) ) else: self.calls.append( Call( self.nodes[key], state, state_index, op, to, gas, value, ) ) else: gas, to, meminstart, meminsz, memoutstart, memoutsz = ( get_variable(stack[-1]), get_variable(stack[-2]), get_variable(stack[-3]), get_variable(stack[-4]), get_variable(stack[-5]), get_variable(stack[-6]), ) self.calls.append( Call(self.nodes[key], state, state_index, op, to, gas) ) state_index += 1