def __getitem__(self, item: Union[int, slice, BitVec]) -> Any: """ :param item: :return: """ if isinstance(item, int) or isinstance(item, Expression): return self._load(item) if isinstance(item, slice): start = 0 if item.start is None else item.start step = 1 if item.step is None else item.step stop = self.size if item.stop is None else item.stop try: current_index = ( start if isinstance(start, Expression) else symbol_factory.BitVecVal(start, 256) ) parts = [] while simplify(current_index != stop): element = self._load(current_index) if not isinstance(element, Expression): element = symbol_factory.BitVecVal(element, 8) parts.append(element) current_index = simplify(current_index + step) except Z3Exception: raise IndexError("Invalid Calldata Slice") return parts raise ValueError
def _load(self, item: Union[int, BitVec]) -> Any: """ :param item: :return: """ item = symbol_factory.BitVecVal(item, 256) if isinstance(item, int) else item return simplify( If( item < self._size, simplify(self._calldata[cast(BitVec, item)]), symbol_factory.BitVecVal(0, 8), ) )
def __setitem__( self, key: Union[int, BitVec, slice], value: Union[BitVec, int, List[Union[int, BitVec]]], ): """ :param key: :param value: """ if isinstance(key, slice): start, step, stop = key.start, key.step, key.stop if start is None: start = 0 if stop is None: raise IndexError("Invalid Memory Slice") if step is None: step = 1 else: assert False, "Currently mentioning step size is not supported" assert type(value) == list bvstart, bvstop, bvstep = ( convert_bv(start), convert_bv(stop), convert_bv(step), ) symbolic_len = False itr = symbol_factory.BitVecVal(0, 256) if (bvstop - bvstart).symbolic: symbolic_len = True while simplify(bvstart + bvstep * itr != bvstop) and ( not symbolic_len or itr <= APPROX_ITR ): self[bvstart + itr * bvstep] = cast(List[Union[int, BitVec]], value)[ itr.value ] itr += 1 else: bv_key = simplify(convert_bv(key)) if bv_key >= len(self): return if isinstance(value, int): assert 0 <= value <= 0xFF if isinstance(value, BitVec): assert value.size() == 8 self._memory[bv_key] = cast(Union[int, BitVec], value)
def get_word_at(self, index: int) -> Union[int, BitVec]: """Access a word from a specified memory index. :param index: integer representing the index to access :return: 32 byte word at the specified index """ try: return symbol_factory.BitVecVal( util.concrete_int_from_bytes( bytes([ util.get_concrete_int(b) for b in self[index:index + 32] ]), 0, ), 256, ) except TypeError: result = simplify( Concat([ b if isinstance(b, BitVec) else symbol_factory.BitVecVal( b, 8) for b in cast(List[Union[int, BitVec]], self[index:index + 32]) ])) assert result.size() == 256 return result
def extract_edges(statespace): """ :param statespace: :return: """ edges = [] for edge in statespace.edges: if edge.condition is None: label = "" else: try: label = str(simplify(edge.condition)).replace("\n", "") except Z3Exception: label = str(edge.condition).replace("\n", "") label = re.sub( r"([^_])([\d]{2}\d+)", lambda m: m.group(1) + hex(int(m.group(2))), label ) edges.append( { "from": str(edge.as_dict["from"]), "to": str(edge.as_dict["to"]), "arrows": "to", "label": label, "smooth": {"type": "cubicBezier"}, } ) return edges
def __getitem__(self, item: BitVec) -> BitVec: storage, is_keccak_storage = self._get_corresponding_storage(item) if is_keccak_storage: item = self._sanitize(cast(BitVecFunc, item).input_) value = storage[item] if (value.value == 0 and self.address and item.symbolic is False and self.address.value != 0 and (self.dynld and self.dynld.storage_loading)): try: storage[item] = symbol_factory.BitVecVal( int( self.dynld.read_storage( contract_address=hex(self.address.value), index=int(item.value), ), 16, ), 256, ) self.printable_storage[item] = storage[item] return storage[item] except ValueError: pass return simplify(storage[item])
def __getitem__(self, item: BitVec) -> BitVec: storage, is_keccak_storage = self._get_corresponding_storage(item) if is_keccak_storage: sanitized_item = self._sanitize(cast(BitVecFunc, item).input_) else: sanitized_item = item if ( self.address and self.address.value != 0 and item.symbolic is False and int(item.value) not in self.storage_keys_loaded and (self.dynld and self.dynld.storage_loading) ): try: storage[sanitized_item] = symbol_factory.BitVecVal( int( self.dynld.read_storage( contract_address="0x{:040X}".format(self.address.value), index=int(item.value), ), 16, ), 256, ) self.storage_keys_loaded.add(int(item.value)) self.printable_storage[item] = storage[sanitized_item] except ValueError as e: log.debug("Couldn't read storage at %s: %s", item, e) return simplify(storage[sanitized_item])
def get_word_at(self, offset: int) -> Expression: """Gets word at offset. :param offset: :return: """ parts = self[offset : offset + 32] return simplify(Concat(parts))
def _load(self, item: Union[int, BitVec]) -> BitVec: """ :param item: :return: """ item = symbol_factory.BitVecVal(item, 256) if isinstance(item, int) else item return simplify(self._calldata[item])
def get_map_index(key: BitVec) -> BitVec: if ( not isinstance(key, BitVecFunc) or key.func_name != "keccak256" or key.input_ is None ): return None index = Extract(255, 0, key.input_) return simplify(index)
def append(self, constraint: Union[bool, Bool]) -> None: """ :param constraint: The constraint to be appended """ constraint = (simplify(constraint) if isinstance(constraint, Bool) else Bool(constraint)) super(Constraints, self).append(constraint) self._is_possible = None
def get_callee_address( global_state: GlobalState, dynamic_loader: DynLoader, symbolic_to_address: Expression, ): """Gets the address of the callee. :param global_state: state to look in :param dynamic_loader: dynamic loader to use :param symbolic_to_address: The (symbolic) callee address :return: Address of the callee """ environment = global_state.environment try: callee_address = hex(util.get_concrete_int(symbolic_to_address)) except TypeError: log.debug("Symbolic call encountered") match = re.search(r"storage_(\d+)", str(simplify(symbolic_to_address))) log.debug("CALL to: " + str(simplify(symbolic_to_address))) if match is None or dynamic_loader is None: raise ValueError() index = int(match.group(1)) log.debug("Dynamic contract address at storage index {}".format(index)) # attempt to read the contract address from instance storage try: callee_address = dynamic_loader.read_storage( environment.active_account.address, index ) # TODO: verify whether this happens or not except: log.debug("Error accessing contract storage.") raise ValueError # testrpc simply returns the address, geth response is more elaborate. if not re.match(r"^0x[0-9a-f]{40}$", callee_address): callee_address = "0x" + callee_address[26:] return callee_address
def concrete_int_to_bytes(val): """ :param val: :return: """ # logging.debug("concrete_int_to_bytes " + str(val)) if type(val) == int: return val.to_bytes(32, byteorder="big") return simplify(val).value.to_bytes(32, byteorder="big")
def get_variable(i): """ :param i: :return: """ try: return Variable(util.get_concrete_int(i), VarType.CONCRETE) except TypeError: return Variable(simplify(i), VarType.SYMBOLIC)
def test_concrete_shr(val1, val2, expected): state = get_state() state.mstate.stack = [BVV(int(val1, 16), 256), BVV(int(val2, 16), 256)] expected = BVV(int(expected, 16), 256) instruction = Instruction("shr", dynamic_loader=None) # Act new_state = instruction.evaluate(state)[0] # Assert assert simplify(new_state.mstate.stack[-1]) == expected
def test_memory_write(): # Arrange mem = Memory() mem.extend(200 + 32) a = symbol_factory.BitVecSym("a", 256) b = symbol_factory.BitVecSym("b", 8) # Act mem[11] = 10 mem[12] = b mem.write_word_at(200, 0x12345) mem.write_word_at(100, a) # Assert assert mem[0] == 0 assert mem[11] == 10 assert mem[200 + 31] == 0x45 assert mem.get_word_at(200) == 0x12345 assert simplify(a == mem.get_word_at(100)) assert simplify(b == mem[12])
def test_shr(inputs, output): # Arrange state = get_state() state.mstate.stack = inputs instruction = Instruction("shr", dynamic_loader=None) # Act new_state = instruction.evaluate(state)[0] # Assert assert simplify(new_state.mstate.stack[-1]) == output
def get_call_data( global_state: GlobalState, memory_start: Union[int, BitVec], memory_size: Union[int, BitVec], ): """Gets call_data from the global_state. :param global_state: state to look in :param memory_start: Start index :param memory_size: Size :return: Tuple containing: call_data array from memory or empty array if symbolic, type found """ state = global_state.mstate transaction_id = "{}_internalcall".format(global_state.current_transaction.id) memory_start = cast( BitVec, ( symbol_factory.BitVecVal(memory_start, 256) if isinstance(memory_start, int) else memory_start ), ) memory_size = cast( BitVec, ( symbol_factory.BitVecVal(memory_size, 256) if isinstance(memory_size, int) else memory_size ), ) uses_entire_calldata = simplify( memory_size == global_state.environment.calldata.calldatasize ) if is_true(uses_entire_calldata): return global_state.environment.calldata try: calldata_from_mem = state.memory[ util.get_concrete_int(memory_start) : util.get_concrete_int( memory_start + memory_size ) ] return ConcreteCalldata(transaction_id, calldata_from_mem) except TypeError: log.debug( "Unsupported symbolic memory offset %s size %s", memory_start, memory_size ) return SymbolicCalldata(transaction_id)
def __getitem__( self, item: Union[BitVec, slice] ) -> Union[BitVec, int, List[Union[int, BitVec]]]: """ :param item: :return: """ if isinstance(item, slice): start, step, stop = item.start, item.step, item.stop if start is None: start = 0 if stop is None: # 2**256 is just a bit too big raise IndexError("Invalid Memory Slice") if step is None: step = 1 bvstart, bvstop, bvstep = ( convert_bv(start), convert_bv(stop), convert_bv(step), ) ret_lis = [] symbolic_len = False itr = symbol_factory.BitVecVal(0, 256) if (bvstop - bvstart).symbolic: symbolic_len = True while simplify(bvstart + bvstep * itr != bvstop) and ( not symbolic_len or itr <= APPROX_ITR ): ret_lis.append(self[bvstart + bvstep * itr]) itr += 1 return ret_lis item = simplify(convert_bv(item)) return self._memory.get(item, 0)
def _load(self, item: Union[int, BitVec], clean=False) -> Any: expr_item = (symbol_factory.BitVecVal(item, 256) if isinstance( item, int) else item) # type: BitVec symbolic_base_value = If( expr_item >= self._size, symbol_factory.BitVecVal(0, 8), BitVec( symbol_factory.BitVecSym( "{}_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 == expr_item, r_value, return_value) if not clean: self._reads.append((expr_item, symbolic_base_value)) return simplify(return_value)
def pop_bitvec(state: "MachineState") -> BitVec: """ :param state: :return: """ # pop one element from stack, converting boolean expressions and # concrete Python variables to BitVecVal item = state.stack.pop() if isinstance(item, Bool): return If( cast(Bool, item), symbol_factory.BitVecVal(1, 256), symbol_factory.BitVecVal(0, 256), ) elif isinstance(item, int): return symbol_factory.BitVecVal(item, 256) else: item = cast(BitVec, item) return simplify(item)
def _sha3_preprocess(self, state: GlobalState): """ Helper function that scans the memory for words that are concrete values, checks if their value exists in the cache, and in that case marks that value to be forwarded (later as an annotation) after the SHA3 operation gets executed. This is a hack for forwarding annotations of concrete values since once the Laser EVM stores a (concrete) value in memory, only the bytes get stored (not as BitVec instances), and all the annotations get lost. """ word_len = 32 lo = state.mstate.stack[-1].value hi = lo + state.mstate.stack[-2].value if hi - lo < word_len: return for i in range(lo, hi, word_len): data_list = [ b if isinstance(b, BitVec) else symbol_factory.BitVecVal(b, 8) for b in state.mstate.memory[i:i + word_len] ] data = simplify(Concat(data_list)) if data.symbolic is False and data.value in self.concrete_memory_cache: self.sha3_should_forward = True self.concrete_memory_cache.remove(data.value)
def get_serializable_statespace(statespace): """ :param statespace: :return: """ nodes = [] edges = [] color_map = {} i = 0 for k in statespace.accounts: color_map[statespace.accounts[k].contract_name] = colors[i] i += 1 for node_key in statespace.nodes: node = statespace.nodes[node_key] code = node.get_cfg_dict()["code"] code = re.sub("([0-9a-f]{8})[0-9a-f]+", lambda m: m.group(1) + "(...)", code) if NodeFlags.FUNC_ENTRY in node.flags: code = re.sub("JUMPDEST", node.function_name, code) code_split = code.split("\\n") truncated_code = ( code if (len(code_split) < 7) else "\\n".join(code_split[:6]) + "\\n(click to expand +)" ) color = color_map[node.get_cfg_dict()["contract_name"]] def get_state_accounts(node_state): """ :param node_state: :return: """ state_accounts = [] for key in node_state.accounts: account = node_state.accounts[key].as_dict account.pop("code", None) account["balance"] = str(account["balance"]) storage = {} for storage_key in account["storage"].keys(): storage[str(storage_key)] = str(account["storage"][storage_key]) state_accounts.append({"address": key, "storage": storage}) return state_accounts states = [ {"machine": x.mstate.as_dict, "accounts": get_state_accounts(x)} for x in node.states ] for state in states: state["machine"]["stack"] = [str(s) for s in state["machine"]["stack"]] state["machine"]["memory"] = [ str(m) for m in state["machine"]["memory"][: len(state["machine"]["memory"])] ] truncated_code = truncated_code.replace("\\n", "\n") code = code.replace("\\n", "\n") s_node = { "id": str(node_key), "func": str(node.function_name), "label": truncated_code, "code": code, "truncated": truncated_code, "states": states, "color": color, "instructions": code.split("\n"), } nodes.append(s_node) for edge in statespace.edges: if edge.condition is None: label = "" else: try: label = str(simplify(edge.condition)).replace("\n", "") except Z3Exception: label = str(edge.condition).replace("\n", "") label = re.sub( "([^_])([\d]{2}\d+)", lambda m: m.group(1) + hex(int(m.group(2))), label ) code = re.sub("([0-9a-f]{8})[0-9a-f]+", lambda m: m.group(1) + "(...)", code) s_edge = { "from": str(edge.as_dict["from"]), "to": str(edge.as_dict["to"]), "arrows": "to", "label": label, "smooth": {"type": "cubicBezier"}, } edges.append(s_edge) return {"edges": edges, "nodes": nodes}