Ejemplo n.º 1
0
    def sstore_(self, global_state):
        global keccak_function_manager
        state = global_state.mstate
        index, value = state.stack.pop(), state.stack.pop()

        logging.debug("Write to storage[" + str(index) + "]")

        try:
            index = util.get_concrete_int(index)
            return self._sstore_helper(global_state, index, value)
        except AttributeError:
            is_keccak = keccak_function_manager.is_keccak(index)
            if not is_keccak:
                return self._sstore_helper(global_state, str(index), value)

            storage_keys = global_state.environment.active_account.storage.keys()
            keccak_keys = filter(keccak_function_manager.is_keccak, storage_keys)

            solver = Solver()
            solver.set(timeout=1000)

            results = []
            new = False

            for keccak_key in keccak_keys:
                key_argument = keccak_function_manager.get_argument(keccak_key)
                index_argument = keccak_function_manager.get_argument(index)

                if is_true(key_argument == index_argument):
                    return self._sstore_helper(copy(global_state), keccak_key, value, key_argument == index_argument)

                results += self._sstore_helper(copy(global_state), keccak_key, value, key_argument == index_argument)

                new = Or(new, key_argument != index_argument)

            if len(results) > 0:
                results += self._sstore_helper(copy(global_state), str(index), value, new)
                return results

            return self._sstore_helper(global_state, str(index), value)
Ejemplo n.º 2
0
def get_callee_address(global_state: GlobalState, dynamic_loader: DynLoader,
                       symbolic_to_address):
    """
    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:
        logging.debug("Symbolic call encountered")

        match = re.search(r'storage_(\d+)', str(simplify(symbolic_to_address)))
        logging.debug("CALL to: " + str(simplify(symbolic_to_address)))

        if match is None or dynamic_loader is None:
            raise ValueError()

        index = int(match.group(1))
        logging.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:
            logging.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
Ejemplo n.º 3
0
    def get_strategic_global_state(self) -> GlobalState:
        """
        :return:
        """

        while True:

            state = self.super_strategy.get_strategic_global_state()
            opcode = state.get_current_instruction()["opcode"]

            if opcode != "JUMPI":
                return state

            annotations = cast(
                List[JumpdestCountAnnotation],
                list(state.get_annotations(JumpdestCountAnnotation)),
            )

            if len(annotations) == 0:
                annotation = JumpdestCountAnnotation()
                state.annotate(annotation)
            else:
                annotation = annotations[0]

            try:
                target = util.get_concrete_int(state.mstate.stack[-1])
            except TypeError:
                return state

            try:
                annotation._jumpdest_count[target] += 1
            except KeyError:
                annotation._jumpdest_count[target] = 1

            if annotation._jumpdest_count[target] > self.jumpdest_limit:
                log.debug("JUMPDEST limit reached, skipping JUMPI")
                continue

            return state
Ejemplo n.º 4
0
    def sstore_(self, global_state):
        state = global_state.mstate
        index, value = state.stack.pop(), state.stack.pop()

        logging.debug("Write to storage[" + str(index) + "]")

        try:
            index = util.get_concrete_int(index)
        except AttributeError:
            index = str(index)

        try:
            global_state.environment.active_account = deepcopy(
                global_state.environment.active_account)
            global_state.accounts[
                global_state.environment.active_account.
                address] = global_state.environment.active_account

            global_state.environment.active_account.storage[index] = value
        except KeyError:
            logging.debug("Error writing to storage: Invalid index")
        return [global_state]
Ejemplo n.º 5
0
    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")

        return_value = BitVec(
            "retval_" + global_state.environment.active_function_name, 256)

        if not global_state.call_stack:
            return []

        new_global_state = deepcopy(global_state.call_stack.pop())
        new_global_state.node = global_state.node
        # TODO: copy memory

        return [new_global_state]
Ejemplo n.º 6
0
    def calldataload_(self, global_state):
        state = global_state.mstate
        environment = global_state.environment
        op0 = state.stack.pop()

        try:
            offset = util.get_concrete_int(simplify(op0))
            b = environment.calldata[offset]
        except AttributeError:
            logging.debug("CALLDATALOAD: Unsupported symbolic index")
            state.stack.append(BitVec(
                "calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256))
            return [global_state]
        except IndexError:
            logging.debug("Calldata not set, using symbolic variable instead")
            state.stack.append(BitVec(
                "calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256))
            return [global_state]

        if type(b) == int:
            val = b''

            try:
                for i in range(offset, offset + 32):
                    val += environment.calldata[i].to_bytes(1, byteorder='big')

                logging.debug("Final value: " + str(int.from_bytes(val, byteorder='big')))
                state.stack.append(BitVecVal(int.from_bytes(val, byteorder='big'), 256))
            # FIXME: broad exception catch
            except:
                state.stack.append(BitVec(
                    "calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256))
        else:
            # symbolic variable
            state.stack.append(BitVec(
                "calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256))

        return [global_state]
Ejemplo n.º 7
0
def get_callee_address(global_state, dynamic_loader, to):
    """
    Gets the address of the callee
    :param global_state: state to look in
    :param dynamic_loader:  dynamic loader to use
    :return: Address of the callee
    """
    environment = global_state.environment

    try:
        callee_address = hex(util.get_concrete_int(to))
    except AttributeError:
        logging.info("Symbolic call encountered")

        match = re.search(r'storage_(\d+)', str(simplify(to)))
        logging.debug("CALL to: " + str(simplify(to)))

        if match is None or dynamic_loader is None:
            raise ValueError()

        index = int(match.group(1))
        logging.info(
            "Dynamic contract address at storage index {}".format(index))

        # attempt to read the contract address from instance storage
        # TODO: we need to do this correctly using multi transactional analysis
        try:
            callee_address = dynamic_loader.read_storage(
                environment.active_account.address, index)
        except:
            logging.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
Ejemplo n.º 8
0
    def sload_(self, global_state):
        global keccak_function_manager
        
        state = global_state.mstate
        index = state.stack.pop()
        logging.debug("Storage access at index " + str(index))

        try:
            index = util.get_concrete_int(index)
            return self._sload_helper(global_state, index)

        except AttributeError:
            if not keccak_function_manager.is_keccak(index):
                return self._sload_helper(global_state, str(index))

            storage_keys = global_state.environment.active_account.storage.keys()
            keccak_keys = list(filter(keccak_function_manager.is_keccak, storage_keys))

            results = []
            constraints = []

            for keccak_key in keccak_keys:
                key_argument = keccak_function_manager.get_argument(keccak_key)
                index_argument = keccak_function_manager.get_argument(index)
                constraints.append((keccak_key, key_argument == index_argument))

            for (keccak_key, constraint) in constraints:
                if constraint in state.constraints:
                    results += self._sload_helper(global_state, keccak_key, [constraint])
            if len(results) > 0:
                return results

            for (keccak_key, constraint) in constraints:
                results += self._sload_helper(copy(global_state), keccak_key, [constraint])
            if len(results) > 0:
                return results
            
            return self._sload_helper(global_state, str(index))
Ejemplo n.º 9
0
    def __getitem__(self, item: Union[int, slice]) -> Any:
        if isinstance(item, slice):
            start, step, stop = item.start, item.step, item.stop
            try:
                if start is None:
                    start = 0
                if step is None:
                    step = 1
                if stop is None:
                    stop = self.calldatasize
                current_index = (start if isinstance(start, BitVecRef) else
                                 BitVecVal(start, 256))
                dataparts = []
                while simplify(current_index != stop):
                    dataparts.append(self[current_index])
                    current_index = simplify(current_index + step)
            except Z3Exception:
                raise IndexError("Invalid Calldata Slice")

            values, constraints = zip(*dataparts)
            result_constraints = []
            for c in constraints:
                result_constraints.extend(c)
            return simplify(Concat(values)), result_constraints

        if self.concrete:
            try:
                return self._calldata[get_concrete_int(item)], ()
            except IndexError:
                return BitVecVal(0, 8), ()
        else:
            constraints = [
                Implies(self._calldata[item] != 0, UGT(self.calldatasize,
                                                       item))
            ]

            return self._calldata[item], constraints
Ejemplo n.º 10
0
    def mstore_(self, global_state):
        state = global_state.mstate
        try:
            op0, value = state.stack.pop(), state.stack.pop()
        except IndexError:
            raise StackUnderflowException()

        try:
            mstart = util.get_concrete_int(op0)
        except AttributeError:
            logging.debug("MSTORE to symbolic index. Not supported")
            return [global_state]

        try:
            state.mem_extend(mstart, 32)
        except Exception:
            logging.debug("Error extending memory, mstart = " + str(mstart) +
                          ", size = 32")

        logging.debug("MSTORE to mem[" + str(mstart) + "]: " + str(value))

        try:
            # Attempt to concretize value
            _bytes = util.concrete_int_to_bytes(value)

            i = 0

            for b in _bytes:
                state.memory[mstart + i] = _bytes[i]
                i += 1
        except:
            try:
                state.memory[mstart] = value
            except:
                logging.debug("Invalid memory access")

        return [global_state]
Ejemplo n.º 11
0
    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]
Ejemplo n.º 12
0
def get_call_data(global_state, memory_start, memory_size, pad=True):
    """
    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
    try:
        # TODO: This only allows for either fully concrete or fully symbolic calldata.
        # Improve management of memory and callata to support a mix between both types.
        call_data = state.memory[util.get_concrete_int(memory_start):util.
                                 get_concrete_int(memory_start + memory_size)]
        if len(call_data) < 32 and pad:
            call_data += [0] * (32 - len(call_data))
        call_data_type = CalldataType.CONCRETE
        logging.debug("Calldata: " + str(call_data))
    except AttributeError:
        logging.info("Unsupported symbolic calldata offset")
        call_data_type = CalldataType.SYMBOLIC
        call_data = []

    return call_data, call_data_type
Ejemplo n.º 13
0
    def jump_(self, global_state):
        state = global_state.mstate
        disassembly = global_state.environment.code
        try:
            jump_addr = util.get_concrete_int(state.stack.pop())
        except AttributeError:
            raise InvalidJumpDestination("Invalid jump argument (symbolic address)")
        except IndexError:
            raise StackUnderflowException()

        index = util.get_instruction_index(disassembly.instruction_list, jump_addr)
        if index is None:
            raise InvalidJumpDestination("JUMP to invalid address")

        op_code = disassembly.instruction_list[index]['opcode']

        if op_code != "JUMPDEST":
            raise InvalidJumpDestination("Skipping JUMP to invalid destination (not JUMPDEST): " + str(jump_addr))

        new_state = copy(global_state)
        new_state.mstate.pc = index
        new_state.mstate.depth += 1

        return [new_state]
Ejemplo n.º 14
0
    def sstore_(self, global_state):
        state = global_state.mstate
        index, value = state.stack.pop(), state.stack.pop()

        logging.debug("Write to storage[" + str(index) + "]")

        try:
            index = util.get_concrete_int(index)
        except AttributeError:
            index = str(index)

        try:
            # Create a fresh copy of the account object before modifying storage

            for k in global_state.accounts:
                if global_state.accounts[k] == global_state.environment.active_account:
                    global_state.accounts[k] = deepcopy(global_state.accounts[k])
                    global_state.environment.active_account = global_state.accounts[k]
                    break

            global_state.environment.active_account.storage[index] = value
        except KeyError:
            logging.debug("Error writing to storage: Invalid index")
        return [global_state]
Ejemplo n.º 15
0
def get_variable(i):
    try:
        return Variable(util.get_concrete_int(i), VarType.CONCRETE)
    except AttributeError:
        return Variable(simplify(i), VarType.SYMBOLIC)
Ejemplo n.º 16
0
    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]
Ejemplo n.º 17
0
    def calldatacopy_(self, global_state):
        state = global_state.mstate
        environment = global_state.environment
        op0, op1, op2 = state.stack.pop(), state.stack.pop(), state.stack.pop()

        try:
            mstart = util.get_concrete_int(op0)
            # FIXME: broad exception catch
        except:
            logging.debug("Unsupported symbolic memory offset in CALLDATACOPY")
            return [global_state]

        try:
            dstart = util.get_concrete_int(op1)
            # FIXME: broad exception catch
        except:
            logging.debug(
                "Unsupported symbolic calldata offset in CALLDATACOPY")
            state.mem_extend(mstart, 1)
            state.memory[mstart] = BitVec(
                "calldata_" + str(environment.active_account.contract_name) +
                "_cpy", 256)
            return [global_state]

        try:
            size = util.get_concrete_int(op2)
            # FIXME: broad exception catch
        except:
            logging.debug("Unsupported symbolic size in CALLDATACOPY")
            state.mem_extend(mstart, 1)
            state.memory[mstart] = BitVec(
                "calldata_" + str(environment.active_account.contract_name) +
                "_" + str(dstart), 256)
            return [global_state]

        if size > 0:
            try:
                state.mem_extend(mstart, size)
            # FIXME: broad exception catch
            except:
                logging.debug("Memory allocation error: mstart = " +
                              str(mstart) + ", size = " + str(size))
                state.mem_extend(mstart, 1)
                state.memory[mstart] = BitVec(
                    "calldata_" +
                    str(environment.active_account.contract_name) + "_" +
                    str(dstart), 256)
                return [global_state]

            try:
                i_data = environment.calldata[dstart]

                for i in range(mstart, mstart + size):
                    state.memory[i] = environment.calldata[i_data]
                    i_data += 1
            except:
                logging.debug("Exception copying calldata to memory")

                state.memory[mstart] = BitVec(
                    "calldata_" +
                    str(environment.active_account.contract_name) + "_" +
                    str(dstart), 256)
        return [global_state]
Ejemplo n.º 18
0
    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]
        global_state.mstate.stack.append(
            BitVec("retval_" + str(instr['address']), 256))

        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")
                return [global_state]

            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")
                return [global_state]

            global_state.mstate.mem_extend(mem_out_start, mem_out_sz)
            call_address_int = int(callee_address, 16)
            try:
                data = natives.native_contracts(call_address_int, call_data)
            except natives.NativeContractException:
                contract_list = [
                    'ecerecover', 'sha256', 'ripemd160', 'identity'
                ]
                for i in range(mem_out_sz):
                    global_state.mstate.memory[mem_out_start + i] = BitVec(
                        contract_list[call_address_int - 1] + "(" +
                        str(call_data) + ")", 256)

                return [global_state]

            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]

            # TODO: maybe use BitVec here constrained to 1
            return [global_state]

        transaction = MessageCallTransaction(
            global_state.world_state, callee_account,
            BitVecVal(int(environment.active_account.address, 16),
                      256), call_data, environment.gasprice, value,
            environment.origin, call_data_type)
        raise TransactionStartSignal(transaction, self.op_code)
Ejemplo n.º 19
0
    def _analyze_state(self, state: GlobalState) -> List[Issue]:
        """
        :param state: the current state
        :return: returns the issues for that corresponding state
        """

        opcode = state.get_current_instruction()["opcode"]
        address = state.get_current_instruction()["address"]

        annotations = cast(List[VisitsAnnotation],
                           list(state.get_annotations(VisitsAnnotation)))

        if len(annotations) == 0:
            annotation = VisitsAnnotation()
            state.annotate(annotation)
        else:
            annotation = annotations[0]

        if opcode in ["JUMP", "JUMPI"]:

            if annotation.loop_start is not None:
                return []
            try:
                target = util.get_concrete_int(state.mstate.stack[-1])
            except TypeError:
                log.debug("Symbolic target encountered in dos module")
                return []
            if target in annotation.jump_targets:
                annotation.jump_targets[target] += 1
            else:
                annotation.jump_targets[target] = 1

            if annotation.jump_targets[target] > min(
                    2, analysis_args.loop_bound - 1):
                annotation.loop_start = address

        elif annotation.loop_start is not None:

            if opcode == "CALL":
                operation = "A message call"
            else:
                operation = "A storage modification"

            description_head = (
                "Potential denial-of-service if block gas limit is reached.")
            description_tail = "{} is executed in a loop. Be aware that the transaction may fail to execute if the loop is unbounded and the necessary gas exceeds the block gas limit.".format(
                operation)

            try:
                transaction_sequence = get_transaction_sequence(
                    state, state.mstate.constraints)
            except UnsatError:
                return []

            issue = Issue(
                contract=state.environment.active_account.contract_name,
                function_name=state.environment.active_function_name,
                address=annotation.loop_start,
                swc_id=DOS_WITH_BLOCK_GAS_LIMIT,
                bytecode=state.environment.code.bytecode,
                title=
                "Potential denial-of-service if block gas limit is reached",
                severity="Low",
                description_head=description_head,
                description_tail=description_tail,
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
                transaction_sequence=transaction_sequence,
            )

            return [issue]

        return []
Ejemplo n.º 20
0
    def _analyze_states(self, state: GlobalState) -> List[Issue]:
        """
        :param state: the current state
        :return: returns the issues for that corresponding state
        """

        opcode = state.get_current_instruction()["opcode"]
        address = state.get_current_instruction()["address"]

        if opcode == "JUMPI":

            target = util.get_concrete_int(state.mstate.stack[-1])

            transaction = state.current_transaction
            if state.current_transaction in self._jumpdest_count:

                try:
                    self._jumpdest_count[transaction][target] += 1
                    if self._jumpdest_count[transaction][target] == 3:

                        annotation = (
                            LoopAnnotation(address, target)
                            if target > address
                            else LoopAnnotation(target, address)
                        )

                        state.annotate(annotation)
                except KeyError:
                    self._jumpdest_count[transaction][target] = 0

            else:
                self._jumpdest_count[transaction] = {}
                self._jumpdest_count[transaction][target] = 0

        else:

            annotations = cast(
                List[LoopAnnotation], list(state.get_annotations(LoopAnnotation))
            )

            for annotation in annotations:

                if annotation.contains(address):

                    operation = (
                        "A storage modification"
                        if opcode == "SSTORE"
                        else "An external call"
                    )

                    description_head = (
                        "Potential denial-of-service if block gas limit is reached."
                    )
                    description_tail = "{} is executed in a loop.".format(operation)

                    issue = Issue(
                        contract=state.environment.active_account.contract_name,
                        function_name=state.environment.active_function_name,
                        address=annotation.loop_start,
                        swc_id=DOS_WITH_BLOCK_GAS_LIMIT,
                        bytecode=state.environment.code.bytecode,
                        title="Potential denial-of-service if block gas limit is reached",
                        severity="Low",
                        description_head=description_head,
                        description_tail=description_tail,
                        gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
                    )
                    return [issue]

        return []