def __getitem__(self, item): try: return self._storage[item] except KeyError: if self.address and int(self.address[2:], 16) != 0 and self.dynld: try: self._storage[item] = int(self.dynld.read_storage(contract_address=self.address, index=int(item)), 16) return self._storage[item] except ValueError: pass if self.concrete: return 0 self._storage[item] = BitVec("storage_" + str(item), 256) return self._storage[item]
def sha3_(self, global_state): state = global_state.mstate environment = global_state.environment op0, op1 = state.stack.pop(), state.stack.pop() try: index, length = util.get_concrete_int(op0), util.get_concrete_int( op1) # FIXME: broad exception catch except: # Can't access symbolic memory offsets state.stack.append(BitVec("KECCAC_mem_" + str(op0) + ")", 256)) return [global_state] try: data = b'' for i in range(index, index + length): data += util.get_concrete_int(state.memory[i]).to_bytes( 1, byteorder='big') i += 1 # FIXME: broad exception catch except: svar = str(state.memory[index]) svar = svar.replace(" ", "_") state.stack.append(BitVec("keccac_" + svar, 256)) return [global_state] keccac = utils.sha3(utils.bytearray_to_bytestr(data)) logging.debug("Computed SHA3 Hash: " + str(binascii.hexlify(keccac))) state.stack.append( BitVecVal(util.concrete_int_from_bytes(keccac, 0), 256)) return [global_state]
def extcodesize_(self, global_state): state = global_state.mstate addr = state.stack.pop() environment = global_state.environment try: addr = hex(helper.get_concrete_int(addr)) except AttributeError: logging.info("unsupported symbolic address for EXTCODESIZE") state.stack.append(BitVec("extcodesize_" + str(addr), 256)) return [global_state] try: code = self.dynamic_loader.dynld(environment.active_account.address, addr) except Exception as e: logging.info("error accessing contract storage due to: " + str(e)) state.stack.append(BitVec("extcodesize_" + str(addr), 256)) return [global_state] if code is None: state.stack.append(0) else: state.stack.append(len(code.bytecode) // 2) return [global_state]
def __init__( self, world_state, caller, identifier=None, callee_account=None, code=None, call_data=(), gas_price=None, call_value=None, origin=None, call_data_type=None, ): assert isinstance(world_state, WorldState) self.id = identifier or get_next_transaction_id() self.world_state = world_state # TODO: set correct balance for new account self.callee_account = callee_account if callee_account else world_state.create_account( 0, concrete_storage=True) self.caller = caller self.gas_price = BitVec("gasprice{}".format(identifier), 256) if gas_price is None else gas_price self.call_value = BitVec("callvalue{}".format(identifier), 256) if call_value is None else call_value self.origin = BitVec("origin{}".format(identifier), 256) if origin is None else origin self.call_data_type = BitVec( "call_data_type{}".format(identifier), 256) if call_data_type is None else call_data_type self.call_data = call_data self.origin = origin self.code = code self.return_data = None
def exp_(self, global_state): state = global_state.mstate # we only implement 2 ** x base, exponent = util.pop_bitvec(state), util.pop_bitvec(state) if (type(base) != BitVecNumRef) or (type(exponent) != BitVecNumRef): state.stack.append(BitVec("(" + str(simplify(base)) + ")^(" + str(simplify(exponent)) + ")", 256)) elif base.as_long() == 2: if exponent.as_long() == 0: state.stack.append(BitVecVal(1, 256)) else: state.stack.append(base << (exponent - 1)) else: state.stack.append(base) return [global_state]
def byte_(self, global_state): mstate = global_state.mstate op0, op1 = mstate.stack.pop(), mstate.stack.pop() try: index = util.get_concrete_int(op0) offset = (31 - index) * 8 result = Concat(BitVecVal(0, 248), Extract(offset + 7, offset, op1)) except AttributeError: logging.debug("BYTE: Unsupported symbolic byte offset") result = BitVec(str(simplify(op1)) + "_" + str(simplify(op0)), 256) mstate.stack.append(simplify(result)) return [global_state]
def set_function_id(self, fid: BitVecRef = None): if isinstance(fid, str) and len(fid) == 8: fid = BitVecVal(int(fid, 16), 32) elif isinstance(fid, int): fid = BitVecVal(fid, 32) elif isinstance(fid, BitVecRef) and fid.size() == 32: pass elif fid is None: fid = BitVec('function_id', 32) else: raise SettingError('illegal function id given') for i in range(4): fragment = Extract(i * 8 + 7, i * 8, fid) self.mstore8(3 - i, fragment)
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, mstate)
def constraints(self): constraints = [] if self.concrete: for calldata_byte in self.starting_calldata: if type(calldata_byte) == int: self._calldata.append(BitVecVal(calldata_byte, 8)) else: self._calldata.append(calldata_byte) constraints.append( self.calldatasize == len(self.starting_calldata)) else: x = BitVec("x", 256) constraints.append( ForAll(x, Implies(self[x] != 0, UGT(self.calldatasize, x)))) return constraints
def execute_message_call(laser_evm, callee_address: str, priority=None) -> None: """ Executes a message call transaction from all open states """ # TODO: Resolve circular import between .transaction and ..svm to import LaserEVM here # TODO: if the function of openstate.node.funcname is not in priority list, dont add it # TODO: This is for deleting repeated variables read # copy the open states from last iteration to this iteration # The working list is always empty when an iteration is done open_states = laser_evm.open_states[:] del laser_evm.open_states[:] for open_world_state in open_states: if open_world_state[callee_address].deleted: debug("Can not execute dead contract, skipping.") continue next_transaction_id = get_next_transaction_id() transaction = MessageCallTransaction( world_state=open_world_state, identifier=next_transaction_id, gas_price=BitVec("gas_price{}".format(next_transaction_id), 256), gas_limit=8000000, # block gas limit origin=BitVec("origin{}".format(next_transaction_id), 256), caller=BitVecVal(ATTACKER_ADDRESS, 256), callee_account=open_world_state[callee_address], call_data=SymbolicCalldata(next_transaction_id), call_data_type=CalldataType.SYMBOLIC, call_value=BitVec("call_value{}".format(next_transaction_id), 256), ) # the open states from last iterations are appended to work list here _setup_global_state_for_execution(laser_evm, transaction, open_world_state.node.function_name) laser_evm.exec(priority=None)
def execute_contract_creation(laser_evm, contract_initialization_code, contract_name=None): """ Executes a contract creation transaction from all open states""" open_states = laser_evm.open_states[:] del laser_evm.open_states[:] new_account = laser_evm.world_state.create_account(0, concrete_storage=True, dynamic_loader=None) if contract_name: new_account.contract_name = contract_name for open_world_state in open_states: transaction = ContractCreationTransaction( open_world_state, BitVec("caller", 256), new_account, Disassembly(contract_initialization_code), [], BitVec("gas_price", 256), BitVec("call_value", 256), BitVec("origin", 256), CalldataType.SYMBOLIC) _setup_global_state_for_execution(laser_evm, transaction) laser_evm.exec(True) return new_account
def visit_MLIL_ADDRESS_OF(self, expr): if expr.src.name: var_name = expr.src.name elif (expr.src.source_type == VariableSourceType.StackVariableSourceType): var_name = f'var_{abs(expr.src.storage):x}' else: var_name = expr.function.arch.get_reg_by_index(expr.src.storage) log_debug(f'var_name: {repr(var_name)}') return BitVec( f"&{var_name}", (expr.size * 8) if expr.size else expr.function.source_function.view.address_size * 8, )
def __init__(self, world_state, callee_account, caller, call_data=(), identifier=None, gas_price=None, call_value=None, origin=None, call_data_type=None, code=None ): assert isinstance(world_state, WorldState) self.id = identifier or get_next_transaction_id() self.world_state = world_state self.callee_account = callee_account self.caller = caller self.call_data = call_data self.gas_price = BitVec("gasprice{}".format(identifier), 256) if gas_price is None else gas_price self.call_value = BitVec("callvalue{}".format(identifier), 256) if call_value is None else call_value self.origin = BitVec("origin{}".format(identifier), 256) if origin is None else origin self.call_data_type = BitVec("call_data_type{}".format(identifier), 256) if call_data_type is None else call_data_type self.code = code self.return_data = None
def generate_input(self, length): """ Generate :length: bytes of symbolic data, add it to this, and return the bytes """ self._resolve_copies() length = self._concretize(length) new_bytes = [] for _ in range(length): val = BitVec('inp_{}'.format(len(self.data)), 8) self.add(val) new_bytes.append(val) self.grouped_inputs.append(new_bytes) return new_bytes
def _load(self, item: Union[int, ExprRef], clean=False) -> Any: x = BitVecVal(item, 256) if isinstance(item, int) else item symbolic_base_value = If( x > self._size, BitVecVal(0, 8), BitVec("{}_calldata_{}".format(self.tx_id, str(item)), 8), ) return_value = symbolic_base_value for r_index, r_value in self._reads: return_value = If(r_index == item, r_value, return_value) if not clean: self._reads.append((item, symbolic_base_value)) return simplify(return_value)
def zeroize_storage_vars(trace): """ Replaces all symbolic variables referencing storage in the constraints and transaction constraints list by the value 0. :param trace: to be substituted in. """ zeroize_constraints(trace.constraints) zeroize_constraints(trace.tran_constraints) for slot_idx, slot in trace.storage.items(): trace.storage[slot_idx].slot = substitute( slot.slot, [(BitVec(slot_name, 256), BitVecVal(0, 256)) for slot_name in slot.slot_names]) slot.tran_names = [ sym_name for sym_name in slot.tran_names if sym_name in slot.slot_names ] slot.slot_names = []
def flatten_quantifier(expr): boundvariables = [] quantifiers = [] es = flatten_and_tree(expr) assert (len(filter(is_quant, es)) <= 1) for idx, e in enumerate(es): negated = False if is_not(e): e = e.children()[0] negated = True if is_quant(e): while is_quant(e): variables = [] for i in range(e.num_vars()): # variables += [[expr.var_name(i), expr.var_sort(i)]] if str(e.var_sort(i)).startswith('BitVec'): var = BitVec(e.var_name(i), e.var_sort(i).size()) else: assert (str(e.var_sort(i)) == ('Bool')) var = Bool(e.var_name(i)) variables.append(var) # if var in boundvariables: # log('ERROR: Currently require all variable names to be unique.') # exit() if e.is_forall(): negated = not negated if negated: new_quant = 'a' else: new_quant = 'e' quantifiers += [[new_quant, variables]] boundvariables += variables e = e.body() es[idx] = e break # only supports the case that at most one of the conjuncts is a quantiifer return And(es), boundvariables, quantifiers
def __init__(self, address, code=None, contract_name="unknown", balance=None, concrete_storage=False, dynamic_loader=None): """ Constructor for account :param address: Address of the account :param code: The contract code of the account :param contract_name: The name associated with the account :param balance: The balance for the account :param concrete_storage: Interpret storage as concrete """ self.nonce = 0 self.code = code self.balance = balance if balance else BitVec("balance", 256) self.storage = Storage(concrete_storage, address=address, dynamic_loader=dynamic_loader) # Metadata self.address = address self.contract_name = contract_name
def extract_function_id(self, condition, block_state): if self.extracting_fid: m = get_model_and_time(condition) if m: fid = m[BitVec('function_id', 32)] # the case contract has only one function if fid is None: self.function_ids.add(BitVecVal(0, 32)) if 'callable' in block_state: self.callable_function_ids.add(BitVecVal(0, 32)) else: self.function_ids.add(fid) if 'callable' in block_state: self.callable_function_ids.add(fid) return True return False
def sload_(self, global_state): state = global_state.mstate index = state.stack.pop() logging.debug("Storage access at index " + str(index)) try: index = util.get_concrete_int(index) except AttributeError: index = str(index) try: data = global_state.environment.active_account.storage[index] except KeyError: data = BitVec("storage_" + str(index), 256) global_state.environment.active_account.storage[index] = data state.stack.append(data) return [global_state]
def get_storage_slot(index, storage): """ Returns the content of a specific storage slo :param index: :param storage: :return: """ try: index = get_concrete_int(index) except AttributeError: index = str(index) try: data = storage[index] except KeyError: data = BitVec("storage[" + str(index) + "]", 256) return data
def byte_(self, global_state): mstate = global_state.mstate op0, op1 = mstate.stack.pop(), mstate.stack.pop() if not isinstance(op1, ExprRef): op1 = BitVecVal(op1, 256) try: index = util.get_concrete_int(op0) offset = (31 - index) * 8 if offset >= 0: result = simplify(Concat(BitVecVal(0, 248), Extract(offset + 7, offset, op1))) else: result = 0 except AttributeError: logging.debug("BYTE: Unsupported symbolic byte offset") result = BitVec(str(simplify(op1)) + "[" + str(simplify(op0)) + "]", 256) mstate.stack.append(result) return [global_state]
def is_shifted_left(reg, n_instr): # FIX solve if register is load objective constraints = [] for s in range(n_instr): constraints.append( BitVec(reg + "_" + str(0), x86_model.BS) != BitVec("shl_src_" + str(s), x86_model.BS)) constraints.append( BitVec(reg + "_" + str(0), x86_model.BS) != BitVec("shl_dst_" + str(s), x86_model.BS)) constraints.append( BitVec(reg + "_" + str(0), x86_model.BS) != BitVec("rcl_src_" + str(s), x86_model.BS)) constraints.append( BitVec(reg + "_" + str(0), x86_model.BS) != BitVec("rcl_dst_" + str(s), x86_model.BS)) return constraints
def unmodified_during_execution(src, dest, n_instr): last_reg = BitVec(dest + "_" + str(n_instr), x86_model.BS) constraints = [BitVec(src + "_0", x86_model.BS) != last_reg] for s in range(n_instr): constraints.append( BitVec(dest + "_" + str(s + 1), x86_model.BS) != BitVec("load_" + str(s), x86_model.BS)) constraints.append( BitVec(dest + "_" + str(s), x86_model.BS) != BitVec("overwritten_" + str(s), x86_model.BS)) return constraints
def __init__(self, tx_id, starting_calldata=None): """ Constructor for Calldata :param tx_id: unique value representing the transaction the calldata is for :param starting_calldata: byte array representing the concrete calldata of a transaction """ self.tx_id = tx_id if starting_calldata: self._calldata = [] self.calldatasize = BitVecVal(len(starting_calldata), 256) self.concrete = True else: self._calldata = Array("{}_calldata".format(self.tx_id), BitVecSort(256), BitVecSort(8)) self.calldatasize = BitVec("{}_calldatasize".format(self.tx_id), 256) self.concrete = False self.starting_calldata = starting_calldata or []
def return_(self, global_state): # TODO: memory state = global_state.mstate offset, length = state.stack.pop(), state.stack.pop() try: _ = 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") #TODO: return 1 return_value = BitVec("retval_" + global_state.environment.active_function_name, 256) state.stack.append(return_value) if not global_state.call_stack: return [] global_state.mstate.pc = global_state.call_stack.pop() return [global_state]
def __init__(self, address, code=None, contract_name="unknown", balance=None): """ Constructor for account :param address: Address of the account :param code: The contract code of the account :param contract_name: The name associated with the account :param balance: The balance for the account """ self.nonce = 0 self.code = code self.balance = balance if balance else BitVec("balance", 256) self.storage = {} # Metadata self.address = address self.contract_name = contract_name
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] transaction = MessageCallTransaction( global_state.world_state, environment.active_account, environment.address, call_data, environment.gasprice, value, environment.origin, call_data_type, callee_account.code) raise TransactionStartSignal(transaction, self.op_code)
def get_ssa_variable(self, variable: SSAVariable): """ Look up SSA variable by executing the instruction in which it was defined :variable: SSAVariable to look up. """ definition_instruction = self.function.mlil.ssa_form.get_ssa_var_definition( variable ) result = None if definition_instruction and self.depth > 0: MLILInstructionExecutor(self.bv, definition_instruction).execute( self.next_state() ) result = self.variables[variable] else: name = repr(variable) size = variable.var.type.width * 8 result = BitVec(name, size) self.potential_inputs.append(result) self.variables[variable] = result return result
def __init__(self, tx_id, starting_calldata=None): """ Constructor for Calldata :param tx_id: unique value representing the transaction the calldata is for :param starting_calldata: byte array representing the concrete calldata of a transaction """ self.tx_id = tx_id if starting_calldata is not None: self._calldata = [] self.calldatasize = BitVecVal(len(starting_calldata), 256) self.concrete = True else: self._calldata = Array("{}_calldata".format(self.tx_id), BitVecSort(256), BitVecSort(8)) self.calldatasize = BitVec("{}_calldatasize".format(self.tx_id), 256) self.concrete = False if self.concrete: for calldata_byte in starting_calldata: if type(calldata_byte) == int: self._calldata.append(BitVecVal(calldata_byte, 8)) else: self._calldata.append(calldata_byte)