def sym_exec(self, main_address=None, creation_code=None, contract_name=None, code_extension=None): logging.debug("Starting LASER execution") self.time = datetime.now() if main_address: logging.info("Starting message call transaction to {}".format(main_address)) execute_message_call(self, main_address) elif creation_code: logging.info("Starting contract creation transaction") created_account = execute_contract_creation(self, creation_code, contract_name=contract_name, code_extension=code_extension) logging.info("Finished contract creation, found {} open states".format(len(self.open_states))) if len(self.open_states) == 0: print("No contract was created during the execution of contract creation " "Try to increase the resources for creation exection (max-depth or create_timeout)") printd("Finished outer creation") # Reset code coverage self.coverage = {} self.time = datetime.now() logging.info("Starting message call transaction") execute_message_call(self, created_account.address) #self.time = datetime.now() #execute_message_call(self, created_account.address) logging.info("Finished symbolic execution") logging.info("%d nodes, %d edges, %d total states", len(self.nodes), len(self.edges), self.total_states) for code, coverage in self.coverage.items(): 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 build_annotated_contracts(self): for contract in self.contracts: if contract.name not in self.annotation_map: continue annotations = self.annotation_map[contract.name] sol_file = get_containing_file(contract) file_code = sol_file.data contract_range = find_contract_idx_range(contract) contract_code = file_code[contract_range[0]:(contract_range[2] + 1)] contract_prefix = file_code[:contract_range[0]] contract_suffix = file_code[(contract_range[2] + 1):] contract_prefix_as_rew = expand_rew("", contract_code, (contract_prefix, 0)) rew_contract_code = contract_code rewritings = [] for annotation in annotations: rewritings.extend(annotation.rewrite_code(file_code, contract_code, contract_range)) rewriting_set = [] for rewriting in rewritings: if rewriting not in rewriting_set: rewriting_set.append(rewriting) rewritings = rewriting_set rewritings = sorted(rewritings, key=lambda rew:(rew.pos, rew.gname, get_sorting_priority(rew.text))) for rew_idx in range(len(rewritings)): rew = rewritings[rew_idx] rew_contract_code = apply_rewriting(rew_contract_code, rew) increase_rewritten_pos(rewritings, rewritings[rew_idx], get_newlinetype(file_code)) increase_rewritten_pos(rewritings, contract_prefix_as_rew, get_newlinetype(file_code)) rew_file_code = contract_prefix + rew_contract_code + contract_suffix printd("Rewritten code") printd(rew_file_code) write_code(sol_file.filename, rew_file_code) annotation_contract = SolidityContract(sol_file.filename, contract.name, solc_args=self.solc_args) annotation_contract.is_rew = True contract.is_rew = False augment_with_ast_info(annotation_contract) annotation_contract.rewritings = rewritings annotation_contract.origin_file_code = file_code annotation_contract.original_contract = contract self.annotated_contracts.append(annotation_contract) for annotation in self.annotation_map[contract.name]: annotation.set_annotation_contract(annotation_contract) write_code(sol_file.filename, file_code)
def pp_trace(self): printd() printd("Trace lvl: {}".format(self.lvl)) printd("Storage: {}".format({ k: str(v.slot).replace("\n", " ") for k, v in self.storage.items() })) printd("Tran_constraints: {}".format( list( map(lambda x: str(x.constraint).replace("\n", " "), self.tran_constraints)))) printd()
def get_traces(statespace, contract): states = [] for k in statespace.nodes: node = statespace.nodes[k] for state in node.states: state.contract = contract states.append(state) printd("Sequential Tracebuilding") results = [get_trace_for_state(state) for state in states] printd("Finished") return [trace for trace_type, trace in results if trace is not None and trace_type == "c"], \ [trace for trace_type, trace in results if trace is not None and trace_type == "t"]
def get_construction_traces(statespace): printd("get_constructor_traces") num_elimi_traces= 0 traces = [] for k in statespace.nodes: node = statespace.nodes[k] for state in node.states: instruction = state.get_current_instruction() if instruction['opcode'] in ["STOP", "RETURN"]: storage = state.environment.active_account.storage if storage and not is_storage_primitive(storage) and are_z3_satisfiable(state.mstate.constraints): traces.append(TransactionTrace(state)) else: num_elimi_traces += 1 printd("Construction traces: " + str(len(traces)) + " eliminated: " + str(num_elimi_traces)) return traces
def check_annotations(self): self.build_annotated_contracts() self.build_traces_and_violations() printd("Chain_building") # Individual violation processing, set states and filter those where it is not decided for chaining # Chaining and status update until all have fixed states or a certain limit is reached for contract in self.annotated_contracts: annotations = self.annotation_map[contract.name] chain_strat = BackwardChainStrategy(contract.const_traces, contract.trans_traces, annotations, self.config) printd("Start") if self.config.mythril_depth > 0 and self.config.chain_verification: chain_strat.check_violations() for annotation in annotations: status = annotation.status for violation in annotation.violations: if violation.status.value > status.value: status = violation.status annotation.status = status printd("Stop") printt("CHECK ANNOTATONS")
def execute_state(self, global_state): instructions = global_state.environment.code.instruction_list try: op_code = instructions[global_state.mstate.pc]['opcode'] except IndexError: self.open_states.append(global_state.world_state) return [], None self._execute_pre_hook(op_code, global_state) try: self._measure_coverage(global_state) if self.prepostprocessor: global_state = self.prepostprocessor.preprocess(global_state) op_code = global_state.get_current_instruction()['opcode'] new_global_states = Instruction(op_code, self.dynamic_loader).evaluate(global_state) if self.prepostprocessor: new_global_states = self.prepostprocessor.postprocess(global_state, new_global_states) except CreateNewContractSignal as c: laser_evm = LaserEVM(self.accounts, self.dynamic_loader, self.max_depth, self.execution_timeout, self.create_timeout, strategy=DepthFirstSearchStrategy, prepostprocessor=self.prepostprocessor) laser_evm.open_states = [self.world_state] printd("Start subconstruction") code_extension = SymbolicCodeExtension("calldata", "Subcontract", c.extension_byte_size, c.predefined_map) created_account = execute_contract_creation(laser_evm, c.bytecode, contract_name="Subcontract", code_extension=code_extension, callvalue=c.callvalue) if laser_evm.open_states: created_account = laser_evm.open_states[0].accounts[created_account.address] paused_state = c.paused_state paused_state.accounts[created_account.address] = created_account # Computing address from hex str returned to z3 BitVec representation address_as_int = 0 new_address = created_account.address.replace("0x", "") i = 0 while i < len(new_address): address_as_int *= 256 address_as_int += int(new_address[i:i + 2], 16) i += 2 paused_state.mstate.stack.append(BitVecVal(address_as_int, 256)) paused_state.mstate.pc += 1 # Because the post code of instruction wrapper was not executed after Signal new_global_states = [paused_state] if self.prepostprocessor: new_global_states = self.prepostprocessor.postprocess(global_state, new_global_states) printd("End subcontruction") except TransactionStartSignal as e: printd("Transaction start") # Setup new global state new_global_state = e.transaction.initial_global_state() new_global_state.transaction_stack = copy(global_state.transaction_stack) + [(e.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: printd("Transaction end") 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: e.global_state.world_state.node = global_state.node self.open_states.append(e.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]) # 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.world_state = copy(global_state.world_state) return_global_state.environment.active_account = \ global_state.accounts[return_global_state.environment.active_account.address] #if self.prepostprocessor: # global_state = self.prepostprocessor.preprocess(global_state) # op_code = global_state.get_current_instruction()['opcode'] # Execute the post instruction handler new_global_states = Instruction(op_code, self.dynamic_loader).evaluate(return_global_state, True) #if self.prepostprocessor: # new_global_states = self.prepostprocessor.postprocess(global_state, new_global_states) # 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 self._execute_post_hook(op_code, new_global_states) # Todo Maybe also filter here return new_global_states, op_code
def check_violations(self): traces = self.const_traces + self.trans_traces while self.annotations: annotation = self.annotations.popleft() violations = deque(annotation.violations) printd(annotation.annotation_str) try: # Checks one violation at a time. while violations: # True when a chain starting at a construction trace was found constructor_chain = None violation = violations.popleft() # skips already verified violations if violation.status == Status.VSINGLE: continue vts = deque([violation.trace]) try: # Goes only until a preconfigured maximal transaction chain for i in range(self.config.max_transaction_depth): new_vs = [] while vts: v = vts.popleft() # Violation is finished when a chain or single transaction with non state referencing # constraints was found if violation.status in [ Status.VSINGLE or Status.VCHAIN ]: raise ViolationFinishedException for t in traces: is_const = t in self.const_traces # Does not consider constructor traces, if such a trace was already found. if constructor_chain and is_const: continue # Only applies a trace to the current violation trace if it changes the # constraints if refines_constraints( t.storage, v.tran_constraints): vt_new = t.apply_trace(v) if not vt_new: # If resulting trace contains not satisfiable constraints, the trace is skipped continue if is_const: # If the constructor is reached the evm read the def value 0 from storage zeroize_storage_vars(vt_new) # Satisfiability check already done when combining traces, only repeat # here because zeroizing might renderhe constraints unsatisfiable if not are_z3_satisfiable([ constraint.constraint for constraint in vt_new.tran_constraints ]): continue # No storage references implies that violating trace can be applied to any # storage state of the contract and be satifiable if not contains_storage_reference( vt_new): if self.config.search_for_indipendent_chain and is_const: # If a independent chain should be searched after a construction chain # was found the former is cached constructor_chain = ( vt_new, Status.VCHAIN) else: # Found the violating chain violation.trace = vt_new violation.status = Status.VCHAIN raise ViolationFinishedException( ) else: # Add non construction chains to the worklist for the next depth if not is_const: new_vs.append(vt_new) if not new_vs: # if the space of trace chains was exhausted the algorithm returns with the found # construction trace or the statement that the annotations holds if constructor_chain: violation.trace = constructor_chain[0] violation.status = constructor_chain[1] else: violation.trace = None violation.status = Status.HOLDS raise ViolationFinishedException() else: vts = deque(new_vs) # When the maximal exploration depth was reached the found constructor chain or the statement # that the violation could not be confirmed is returned if vts: if constructor_chain: violation.trace = constructor_chain[0] violation.status = constructor_chain[1] else: violation.status = Status.VDEPTH violation.trace = vts[0] except ViolationFinishedException as v: pass except AnnotationFinishedException as a: pass status = Status.HSINGLE # Sets the anntotation confidence level according to the most sever found violation for violation in annotation.violations: if status.value < violation.status.value: status = violation.status annotation.status = status
def postprocess(self, global_state, new_global_states): returnable_new_states = [] if hasattr(global_state, 'ignore'): printd("carry") for new_state in new_global_states: new_state.ignore = global_state.ignore # Had to be added, because treatment of a single instruction does not carry over added attributes if hasattr(global_state, 'saved_state'): # Carry for new_state in new_global_states: new_state.saved_state = global_state.saved_state new_state.id = global_state.id for new_state in new_global_states: if hasattr(new_state, "duplicate"): del new_state.duplicate for state_idx in range(len(new_global_states)): new_state = new_global_states[state_idx] instr = global_state.environment.code.instruction_list[ global_state.mstate.pc] new_instr = self.get_context_instructions(new_state)[ new_state.mstate.pc] if self.is_this_or_previouse_ignore_type( new_state, IType.ENTRY) and not hasattr(new_state, 'ignore'): printd("Duplicate new") skip_state = new_state # Not using deepcopy here anymore, leeds to missing states in node in statespace new_global_states[state_idx] = None while self.is_this_or_previouse_ignore_type( skip_state, IType.ENTRY): printd( str( self.get_context_instructions(skip_state)[ skip_state.mstate.pc])) # Leave a new state to start the execution of the part to be ignored by the rest ign_state = deepcopy(skip_state) ign_state.ignore = "ignore" returnable_new_states.append(ign_state) printd("Leave ignore state to process " + str( self.get_context_instructions(ign_state) [instr_index( self.get_context_instructions(ign_state), instr)])) # set skip state to the next instruction that may again be a regular one ign_exit_istr = self.get_ignore_tuple( skip_state, IType.ENTRY)[IType.EXIT.value] istr_idx = instr_index( self.get_context_instructions(global_state), ign_exit_istr) skip_state.mstate.pc = istr_idx + 1 # After skip state finally reached an instruction that is not another entry it is marked as dublicate skip_state.duplicate = "duplicate" printd("Final skip state" + str( self.get_context_instructions(skip_state)[ skip_state.mstate.pc])) returnable_new_states.append(skip_state) # Is exit instruction if self.is_this_or_previouse_ignore_type( global_state, IType.EXIT) and not hasattr( global_state, "duplicate" ) and not self.is_this_or_previouse_ignore_type( global_state, IType.ENTRY ): # Changed here from "duplicate to fix false drop" printd("Drop at exit") return returnable_new_states # Todo Drop only the ignored once at exit? # if hasattr(new_state, 'saved_state'): # new_global_states[state_idx] = None # else: # raise RuntimeError("No saved global state at encounter of exit instruction. This should not happen") returnable_new_states.extend( list(filter(lambda state: state is not None, new_global_states))) return returnable_new_states
def preprocess(self, global_state): instructions = global_state.environment.code.instruction_list instr = instructions[global_state.mstate.pc] if hasattr(global_state, "duplicate"): printd("Pick up duplicate") message_type = "T" if isinstance(global_state.current_transaction, ContractCreationTransaction): message_type = "C" context_instructions = self.get_context_instructions(global_state) context_istr_idx = instr_index(context_instructions, instr) if context_istr_idx is None: printd(message_type + " " + str(instr) + " stack: " + str(global_state.mstate.stack).replace("\n", "") + " constr: " + str(global_state.mstate.constraints).replace("\n", "")) return global_state # In delgate functions, no beginning of rewriting instruction blocks exist instruction = context_instructions[context_istr_idx] printd(message_type + " " + str(instruction) + " stack: " + str(global_state.mstate.stack).replace("\n", "") + " constr: " + str(global_state.mstate.constraints).replace("\n", "")) if self.is_this_or_previouse_ignore_type(global_state, IType.ENTRY): if hasattr(global_state, 'saved_state'): # Skip printd("Skip") ign_exit_istr = self.get_ignore_tuple( global_state, IType.ENTRY)[IType.EXIT.value] istr_idx = instr_index(instructions, ign_exit_istr) global_state.mstate.pc = istr_idx + 1 else: # Save printd("Save") helper_state_ref = global_state global_state = deepcopy(helper_state_ref) global_state.saved_state = helper_state_ref global_state.id = self.state_ctr self.state_ctr += 1 if self.is_this_or_previouse_ignore_type(global_state, IType.VIOLATION): # violation printd("violation") # global_state.mstate.constraints[idx] = simplify(global_state.mstate.constraints[idx]) if True: # are_z3_satisfiable(global_state.mstate.constraints): violating_state = deepcopy(global_state) if hasattr(violating_state, 'saved_state'): del violating_state.saved_state # Todo Why do we delete this here? I think we need the mark to ignore it in graph building self.add_violation(violating_state) return global_state
def get_traces_and_build_violations(self, contract): dynloader = DynLoader(self.eth) if self.dynld else None # Building ignore lists for transactions and constructor executions create_ignore_list = [] trans_ignore_list = [] for annotation in self.annotation_map[contract.name]: create_ignore_list.extend(annotation.get_creation_ignore_list()) trans_ignore_list.extend(annotation.get_trans_ignore_list()) # Used to run annotations violation build in traces construction in parallel annotationsProcessor = None if any([len( annotation.viol_rew_instrs) > 0 for annotation in self.annotation_map[contract.name]]): annotationsProcessor = AnnotationProcessor(contract.creation_disassembly.instruction_list, contract.disassembly.instruction_list, create_ignore_list, trans_ignore_list, contract) printd("Constructor and Transaction") constr_calldata_len = get_minimal_constructor_param_encoding_len(abi_json_to_abi(contract.abi)) sym_code_extension = SymbolicCodeExtension("calldata", contract.name, constr_calldata_len) global keccak_map keccak_map = {} if self.max_depth: self.config.mythril_depth = self.max_depth printd("Sym Exe: " + str(contract.name)) start_meassure() sym_transactions = SymExecWrapper(contract, self.address, laser_strategy, dynloader, max_depth=self.config.mythril_depth, prepostprocessor=annotationsProcessor, code_extension=sym_code_extension) printt("Symbolic Execution") contract.states = [] contract.states = sym_transactions.laser.state_id_assigner.states contract.config = self.config end = time.time() # print(end - start) if annotationsProcessor: printd("Construction Violations: " + str(len(reduce(lambda x, y: x + y, annotationsProcessor.create_violations, [])))) printd("Transaction Violations: " + str(len(reduce(lambda x, y: x + y, annotationsProcessor.trans_violations, [])))) # Add found violations to the annotations they violated for ign_idx in range(len(trans_ignore_list)): annotation = trans_ignore_list[ign_idx][4] annotation.add_violations(annotationsProcessor.trans_violations[ign_idx], contract, length=0, vio_description="An assert with the annotations condition would fail here.") for ign_idx in range(len(create_ignore_list)): annotation = create_ignore_list[ign_idx][4] annotation.add_violations(annotationsProcessor.create_violations[ign_idx], contract, length=0, vio_description="An assert with the annotations condition would fail here.") for annotation in self.annotation_map[contract.name]: annotation.build_violations(sym_transactions) printt("BUILD VIOLATIONS") # Build traces from the regular annotation ignoring global states start_time = time.time() create_traces, trans_traces = get_traces(sym_transactions, contract) printt("BUILD TRACES") printd("--- %s seconds ---" % (time.time() - start_time)) return create_traces, trans_traces