def _end_message_call( self, return_global_state: GlobalState, global_state: GlobalState, revert_changes=False, return_data=None, ) -> List[GlobalState]: # 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 = 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] # 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 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_execute_node(mocker): record = TaintRecord() record.stack = [True, True, False, False] state_1 = GlobalState(None, None, None) state_1.mstate.stack = [1, 2, 3, 1] state_1.mstate.pc = 1 mocker.patch.object(state_1, 'get_current_instruction') state_1.get_current_instruction.return_value = {"opcode": "SWAP1"} state_2 = GlobalState(None, 1, None) state_2.mstate.stack = [1, 2, 4, 1] mocker.patch.object(state_2, 'get_current_instruction') state_2.get_current_instruction.return_value = {"opcode": "ADD"} node = Node("Test contract") node.states = [state_1, state_2] # Act records = TaintRunner.execute_node(node, record) # Assert assert len(records) == 2 assert records[0].stack == [True, True, False, False] assert records[1].stack == [True, True, False] assert state_2 in records[0].states assert state_1 in record.states
def execute_state(record: TaintRecord, state: GlobalState) -> TaintRecord: assert len(state.mstate.stack) == len(record.stack) """ Runs taint analysis on a state """ record.add_state(state) new_record = record.clone() # Apply Change op = state.get_current_instruction()["opcode"] if op in TaintRunner.stack_taint_table.keys(): mutator = TaintRunner.stack_taint_table[op] TaintRunner.mutate_stack(new_record, mutator) elif op.startswith("PUSH"): TaintRunner.mutate_push(op, new_record) elif op.startswith("DUP"): TaintRunner.mutate_dup(op, new_record) elif op.startswith("SWAP"): TaintRunner.mutate_swap(op, new_record) elif op is "MLOAD": TaintRunner.mutate_mload(new_record, state.mstate.stack[-1]) elif op.startswith("MSTORE"): TaintRunner.mutate_mstore(new_record, state.mstate.stack[-1]) elif op is "SLOAD": TaintRunner.mutate_sload(new_record, state.mstate.stack[-1]) elif op is "SSTORE": TaintRunner.mutate_sstore(new_record, state.mstate.stack[-1]) elif op.startswith("LOG"): TaintRunner.mutate_log(new_record, op) elif op in ("CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"): TaintRunner.mutate_call(new_record, op) else: logging.debug("Unknown operation encountered: {}".format(op)) return new_record
def sym_exec(self, main_address): logging.debug("Starting LASER execution") self.time = datetime.now() # Initialize the execution environment environment = Environment( self.accounts[main_address], BitVec("caller", 256), [], BitVec("gasprice", 256), BitVec("callvalue", 256), BitVec("origin", 256), calldata_type=CalldataType.SYMBOLIC, ) self.instructions_covered = [ False for _ in environment.code.instruction_list ] initial_node = Node(environment.active_account.contract_name) self.nodes[initial_node.uid] = initial_node global_state = GlobalState(self.accounts, environment, initial_node) global_state.environment.active_function_name = "fallback()" initial_node.states.append(global_state) # Empty the work_list before starting an execution self.work_list.append(global_state) self._sym_exec() logging.info("Execution complete") logging.info("Achieved {0:.3g}% coverage".format(self.coverage)) logging.info("%d nodes, %d edges, %d total states", len(self.nodes), len(self.edges), self.total_states)
def test_concrete_call(): # arrange address = "0x10" state = GlobalState(None, None, 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(1, VarType.CONCRETE) 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: 0x1"
def sym_exec(self, main_address): logging.debug("Starting LASER execution") # Initialize the execution environment environment = Environment( self.accounts[main_address], BitVec("caller", 256), [], BitVec("gasprice", 256), BitVec("callvalue", 256), BitVec("origin", 256), calldata_type=CalldataType.SYMBOLIC, ) # TODO: contact name fix initial_node = Node(environment.active_account.contract_name) self.nodes[initial_node.uid] = initial_node global_state = GlobalState(self.accounts, environment, initial_node) initial_node.states.append(global_state) # Empty the work_list before starting an execution self.work_list.append(global_state) self._sym_exec() logging.info("Execution complete") logging.info("%d nodes, %d edges, %d total states", len(self.nodes), len(self.edges), self.total_states)
def callcode_(self, global_state): instr = global_state.get_current_instruction() environment = global_state.environment try: callee_address, callee_account, call_data, value, call_data_type, gas, _, _ = get_call_parameters( global_state, self.dynamic_loader, True) except ValueError as e: logging.info( "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}" .format(e)) global_state.mstate.stack.append( BitVec("retval_" + str(instr['address']), 256)) return [global_state] global_state.call_stack.append(instr['address']) environment = deepcopy(environment) environment.callvalue = value environment.caller = environment.address environment.calldata = call_data new_global_state = GlobalState(global_state.accounts, environment, MachineState(gas)) new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.constraints = copy( global_state.mstate.constraints) return [new_global_state]
def run(self, open_world_states: list, evm): """ Runs this transaction on the evm starting from the open world states """ # Consume the open states open_states = open_world_states[:] del open_world_states[:] for open_world_state in open_states: # Initialize the execution environment environment = Environment( open_world_state[self.callee_address], self.caller, [], self.gas_price, self.call_value, self.origin, calldata_type=CalldataType.SYMBOLIC, ) new_node = Node(environment.active_account.contract_name) evm.instructions_covered = [False for _ in environment.code.instruction_list] evm.nodes[new_node.uid] = new_node if open_world_state.node: evm.edges.append(Edge(open_world_state.node.uid, new_node.uid, edge_type=JumpType.Transaction, condition=None)) global_state = GlobalState(open_world_state.accounts, environment, new_node) global_state.environment.active_function_name = 'fallback' new_node.states.append(global_state) evm.work_list.append(global_state) evm.exec() logging.info("Execution complete") logging.info("Achieved {0:.3g}% coverage".format(evm.coverage))
def get_constr_glbstate(contract, address): mstate = MachineState(gas=10000000) minimal_const_byte_len = get_minimal_constructor_param_encoding_len(abi_json_to_abi(contract.abi)) # better would be to append symbolic params to the bytecode such that the codecopy instruction that copies the # params into memory takes care of placing them onto the memory with the respective size. for i in range(int(minimal_const_byte_len / 32)): mstate.mem_extend(128 + 32 * i, 32) mstate.memory.insert(128 + 32 * i, BitVec('calldata_' + contract.name + '[' + str(i * 32)+ "]", 256)) # Todo Replace pure placement of enough symbolic 32 Byte-words with placement of symbolic variables that contain # the name of the solidity variables accounts = {address: Account(address, contract.disassembly, contract_name=contract.name)} environment = Environment( accounts[address], BitVec("caller", 256), [], BitVec("gasprice", 256), BitVec("callvalue", 256), BitVec("origin", 256), calldata_type=CalldataType.SYMBOLIC, ) # Todo find source for account info, maybe the std statespace? return GlobalState(accounts, environment, None, mstate)
def test_symbolic_call_calldata_to(mocker): # arrange address = "0x10" state = GlobalState(None, None, 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", 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 obtained from calldata. " "Be aware that the called contract gets unrestricted access to this contract's state." )
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 test_concrete_call_not_calldata(): # arrange state = GlobalState(None, None, None) state.mstate.memory = ["placeholder", "not_calldata"] meminstart = Variable(1, VarType.CONCRETE) # act issues = _concrete_call(None, state, None, meminstart) # assert assert issues == []
def test_execute(mocker): active_account = Account('0x00') environment = Environment(active_account, None, None, None, None, None) state_1 = GlobalState(None, environment, None, MachineState(gas=10000000)) state_1.mstate.stack = [1, 2] mocker.patch.object(state_1, 'get_current_instruction') state_1.get_current_instruction.return_value = {"opcode": "PUSH"} state_2 = GlobalState(None, environment, None, MachineState(gas=10000000)) state_2.mstate.stack = [1, 2, 3] mocker.patch.object(state_2, 'get_current_instruction') state_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=10000000)) 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 test_result_no_state(): # arrange taint_result = TaintResult() record = TaintRecord() state = GlobalState(2, None, None) state.mstate.stack = [1, 2, 3] # act taint_result.add_records([record]) tainted = taint_result.check(state, 2) # assert assert tainted is None assert record in taint_result.records
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 test_execute_state(mocker): record = TaintRecord() record.stack = [True, False, True] state = GlobalState(None, None, None) state.mstate.stack = [1, 2, 3] mocker.patch.object(state, 'get_current_instruction') state.get_current_instruction.return_value = {"opcode": "ADD"} # Act new_record = TaintRunner.execute_state(record, state) # Assert assert new_record.stack == [True, True] assert record.stack == [True, False, True]
def initial_global_state(self): # Initialize the execution environment environment = Environment(self.callee_account, self.caller, self.call_data, self.gas_price, self.call_value, self.origin, code=self.code or self.callee_account.code, calldata_type=self.call_data_type, can_write=self.can_write) global_state = GlobalState(self.world_state, environment, None) global_state.environment.active_function_name = 'fallback' return global_state
def initial_global_state(self): # Initialize the execution environment environment = Environment( self.callee_account, self.caller, self.call_data, self.gas_price, self.call_value, self.origin, self.code, calldata_type=self.call_data_type, ) if hasattr(self, "code_extension"): environment.code_extension = self.code_extension global_state = GlobalState(self.world_state, environment, None) global_state.environment.active_function_name = 'constructor' return global_state
def _new_node_state(self, state: GlobalState, edge_type=JumpType.UNCONDITIONAL, condition=None) -> None: new_node = Node(state.environment.active_account.contract_name) old_node = state.node state.node = new_node new_node.constraints = state.mstate.constraints self.nodes[new_node.uid] = new_node self.edges.append( Edge(old_node.uid, new_node.uid, edge_type=edge_type, condition=condition)) if edge_type == JumpType.RETURN: new_node.flags |= NodeFlags.CALL_RETURN elif edge_type == JumpType.CALL: try: if "retval" in str(state.mstate.stack[-1]): new_node.flags |= NodeFlags.CALL_RETURN else: new_node.flags |= NodeFlags.FUNC_ENTRY except StackUnderflowException: new_node.flags |= NodeFlags.FUNC_ENTRY address = state.environment.code.instruction_list[ state.mstate.pc]["address"] environment = state.environment disassembly = environment.code if address in disassembly.address_to_function_name: # Enter a new function environment.active_function_name = disassembly.address_to_function_name[ address] new_node.flags |= NodeFlags.FUNC_ENTRY logging.debug("- Entering function " + environment.active_account.contract_name + ":" + new_node.function_name) elif address == 0: environment.active_function_name = "fallback" new_node.function_name = environment.active_function_name
def initial_global_state(self): # Initialize the execution environment environment = Environment( self.callee_account, self.caller, self.call_data, self.gas_price, self.call_value, self.origin, self.code, calldata_type=self.call_data_type, ) global_state = GlobalState(self.world_state, environment, None) global_state.environment.active_function_name = "constructor" global_state.mstate.constraints.extend( global_state.environment.calldata.constraints) return global_state
def test_symbolic_call_storage_to(mocker): # arrange address = "0x10" active_account = Account(address) 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 call_(self, global_state): instr = global_state.get_current_instruction() environment = global_state.environment try: 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( "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}" .format(e)) # TODO: decide what to do in this case global_state.mstate.stack.append( BitVec("retval_" + str(instr['address']), 256)) return [global_state] if 0 < int(callee_address, 16) < 5: logging.info("Native contract called: " + callee_address) if call_data == [] and call_data_type == CalldataType.SYMBOLIC: logging.debug("CALL with symbolic data not supported") global_state.mstate.stack.append( BitVec("retval_" + str(instr['address']), 256)) return [global_state] data = natives.native_contracts(int(callee_address, 16), call_data) try: mem_out_start = helper.get_concrete_int(memory_out_offset) mem_out_sz = memory_out_size.as_long() except AttributeError: logging.debug( "CALL with symbolic start or offset not supported") global_state.mstate.stack.append( BitVec("retval_" + str(instr['address']), 256)) return [global_state] global_state.mstate.mem_extend(mem_out_start, mem_out_sz) try: for i in range(min(len(data), mem_out_sz) ): # If more data is used then it's chopped off global_state.mstate.memory[mem_out_start + i] = data[i] except: global_state.mstate.memory[mem_out_start] = BitVec(data, 256) # TODO: maybe use BitVec here constrained to 1 global_state.mstate.stack.append( BitVec("retval_" + str(instr['address']), 256)) return [global_state] global_state.call_stack.append(instr['address']) callee_environment = Environment( callee_account, BitVecVal(int(environment.active_account.address, 16), 256), call_data, environment.gasprice, value, environment.origin, calldata_type=call_data_type) new_global_state = GlobalState(global_state.accounts, callee_environment, MachineState(gas)) new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.constraints = copy( global_state.mstate.constraints) return [global_state]