Exemplo n.º 1
0
    def _analyze_state(self, state):
        log.info("Suicide module: Analyzing suicide instruction")
        instruction = state.get_current_instruction()
        if self._cache_address.get(instruction["address"], False):
            return []
        to = state.mstate.stack[-1]

        log.debug("[SUICIDE] SUICIDE in function " +
                  state.environment.active_function_name)

        description_head = "The contract can be killed by anyone."

        constraints = []

        for tx in state.world_state.transaction_sequence:
            if not isinstance(tx, ContractCreationTransaction):
                constraints.append(tx.caller == ATTACKER_ADDRESS)

        try:
            try:
                transaction_sequence = solver.get_transaction_sequence(
                    state,
                    state.mstate.constraints + constraints +
                    [to == ATTACKER_ADDRESS],
                )
                description_tail = (
                    "Anyone can kill this contract and withdraw its balance to an arbitrary "
                    "address.")
            except UnsatError:
                transaction_sequence = solver.get_transaction_sequence(
                    state, state.mstate.constraints + constraints)
                description_tail = "Arbitrary senders can kill this contract."

            self._cache_address[instruction["address"]] = True

            issue = Issue(
                contract=state.environment.active_account.contract_name,
                function_name=state.environment.active_function_name,
                address=instruction["address"],
                swc_id=UNPROTECTED_SELFDESTRUCT,
                bytecode=state.environment.code.bytecode,
                title="Unprotected Selfdestruct",
                severity="High",
                description_head=description_head,
                description_tail=description_tail,
                transaction_sequence=transaction_sequence,
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
            )
            return [issue]
        except UnsatError:
            log.info("[UNCHECKED_SUICIDE] no model found")

        return []
Exemplo n.º 2
0
    def get_issue(
        self, global_state: GlobalState, detector: DetectionModule
    ) -> Optional[PotentialIssue]:
        if not self.state_change_states:
            return None
        constraints = Constraints()
        gas = self.call_state.mstate.stack[-1]
        to = self.call_state.mstate.stack[-2]
        constraints += [
            UGT(gas, symbol_factory.BitVecVal(2300, 256)),
            Or(
                to > symbol_factory.BitVecVal(16, 256),
                to == symbol_factory.BitVecVal(0, 256),
            ),
        ]
        if self.user_defined_address:
            constraints += [to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF]

        try:
            solver.get_transaction_sequence(
                global_state, constraints + global_state.mstate.constraints
            )
        except UnsatError:
            return None

        severity = "Medium" if self.user_defined_address else "Low"
        address = global_state.get_current_instruction()["address"]
        logging.debug(
            "[EXTERNAL_CALLS] Detected state changes at addresses: {}".format(address)
        )
        description_head = (
            "The contract account state is changed after an external call. "
        )
        description_tail = (
            "Consider that the called contract could re-enter the function before this "
            "state change takes place. This can lead to business logic vulnerabilities."
        )

        return PotentialIssue(
            contract=global_state.environment.active_account.contract_name,
            function_name=global_state.environment.active_function_name,
            address=address,
            title="State change after external call",
            severity=severity,
            description_head=description_head,
            description_tail=description_tail,
            swc_id=REENTRANCY,
            bytecode=global_state.environment.code.bytecode,
            constraints=constraints,
            detector=detector,
        )
Exemplo n.º 3
0
    def _analyze_state(self, state):
        log.info("Suicide module: Analyzing suicide instruction")
        node = state.node
        instruction = state.get_current_instruction()
        if self._cache_address.get(instruction["address"], False):
            return []
        to = state.mstate.stack[-1]

        log.debug("[SUICIDE] SUICIDE in function " + node.function_name)

        description_head = "The contract can be killed by anyone."

        try:
            try:
                transaction_sequence = solver.get_transaction_sequence(
                    state,
                    node.constraints
                    + [to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF],
                )
                description_tail = (
                    "Anyone can kill this contract and withdraw its balance to an arbitrary "
                    "address."
                )
            except UnsatError:
                transaction_sequence = solver.get_transaction_sequence(
                    state, node.constraints
                )
                description_tail = "Arbitrary senders can kill this contract."

            debug = json.dumps(transaction_sequence, indent=4)
            self._cache_address[instruction["address"]] = True

            issue = Issue(
                contract=node.contract_name,
                function_name=node.function_name,
                address=instruction["address"],
                swc_id=UNPROTECTED_SELFDESTRUCT,
                bytecode=state.environment.code.bytecode,
                title="Unprotected Selfdestruct",
                severity="High",
                description_head=description_head,
                description_tail=description_tail,
                debug=debug,
                gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
            )
            return [issue]
        except UnsatError:
            log.info("[UNCHECKED_SUICIDE] no model found")

        return []
Exemplo n.º 4
0
def _analyze_state(state, node):
    issues = []
    instruction = state.get_current_instruction()

    if instruction["opcode"] != "SUICIDE":
        return []

    to = state.mstate.stack[-1]

    logging.debug("[UNCHECKED_SUICIDE] suicide in function " + node.function_name)

    description = "A reachable SUICIDE instruction was detected. "

    if "caller" in str(to):
        description += "The remaining Ether is sent to the caller's address.\n"
    elif "storage" in str(to):
        description += "The remaining Ether is sent to a stored address.\n"
    elif "calldata" in str(to):
        description += "The remaining Ether is sent to an address provided as a function argument.\n"
    elif type(to) == BitVecNumRef:
        description += "The remaining Ether is sent to: " + hex(to.as_long()) + "\n"
    else:
        description += "The remaining Ether is sent to: " + str(to) + "\n"

    not_creator_constraints = []
    if len(state.world_state.transaction_sequence) > 1:
        creator = state.world_state.transaction_sequence[0].caller
        for transaction in state.world_state.transaction_sequence[1:]:
            not_creator_constraints.append(
                Not(Extract(159, 0, transaction.caller) == Extract(159, 0, creator))
            )
            not_creator_constraints.append(
                Not(Extract(159, 0, transaction.caller) == 0)
            )

    try:
        model = solver.get_model(node.constraints + not_creator_constraints)

        debug = "Transaction Sequence: " + str(
            solver.get_transaction_sequence(
                state, node.constraints + not_creator_constraints
            )
        )

        issue = Issue(
            contract=node.contract_name,
            function_name=node.function_name,
            address=instruction["address"],
            swc_id=UNPROTECTED_SELFDESTRUCT,
            bytecode=state.environment.code.bytecode,
            title="Unchecked SUICIDE",
            _type="Warning",
            description=description,
            debug=debug,
        )
        issues.append(issue)
    except UnsatError:
        logging.debug("[UNCHECKED_SUICIDE] no model found")

    return issues
Exemplo n.º 5
0
def check_potential_issues(state: GlobalState) -> None:
    """
    Called at the end of a transaction, checks potential issues, and
    adds valid issues to the detector.

    :param state: The final global state of a transaction
    :return:
    """
    annotation = get_potential_issues_annotation(state)
    for potential_issue in annotation.potential_issues:
        try:
            transaction_sequence = get_transaction_sequence(
                state, state.mstate.constraints + potential_issue.constraints)
        except UnsatError:
            continue

        annotation.potential_issues.remove(potential_issue)
        potential_issue.detector.cache.add(potential_issue.address)
        potential_issue.detector.issues.append(
            Issue(
                contract=potential_issue.contract,
                function_name=potential_issue.function_name,
                address=potential_issue.address,
                title=potential_issue.title,
                bytecode=potential_issue.bytecode,
                swc_id=potential_issue.swc_id,
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
                severity=potential_issue.severity,
                description_head=potential_issue.description_head,
                description_tail=potential_issue.description_tail,
                transaction_sequence=transaction_sequence,
            ))
Exemplo n.º 6
0
    def _handle_transaction_end(self, state: GlobalState) -> None:

        state_annotation = _get_overflowunderflow_state_annotation(state)

        for annotation in state_annotation.overflowing_state_annotations:

            ostate = annotation.overflowing_state

            if ostate in self._ostates_unsatisfiable:
                continue

            if ostate not in self._ostates_satisfiable:
                try:
                    constraints = ostate.mstate.constraints + [
                        annotation.constraint
                    ]
                    solver.get_model(constraints)
                    self._ostates_satisfiable.add(ostate)
                except:
                    self._ostates_unsatisfiable.add(ostate)
                    continue

            log.debug(
                "Checking overflow in {} at transaction end address {}, ostate address {}"
                .format(
                    state.get_current_instruction()["opcode"],
                    state.get_current_instruction()["address"],
                    ostate.get_current_instruction()["address"],
                ))

            try:

                constraints = state.mstate.constraints + [
                    annotation.constraint
                ]
                transaction_sequence = solver.get_transaction_sequence(
                    state, constraints)
            except UnsatError:
                continue

            _type = "Underflow" if annotation.operator == "subtraction" else "Overflow"
            issue = Issue(
                contract=ostate.environment.active_account.contract_name,
                function_name=ostate.environment.active_function_name,
                address=ostate.get_current_instruction()["address"],
                swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
                bytecode=ostate.environment.code.bytecode,
                title=self._get_title(_type),
                severity="High",
                description_head=self._get_description_head(annotation, _type),
                description_tail=self._get_description_tail(annotation, _type),
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
                transaction_sequence=transaction_sequence,
            )

            address = _get_address_from_state(ostate)
            self.cache.add(address)
            self.issues.append(issue)
Exemplo n.º 7
0
    def _analyze_state(state) -> List[Issue]:
        instruction = state.get_current_instruction()
        address, value = state.mstate.stack[-1], state.mstate.stack[-2]

        target_slot = 0
        target_offset = 0

        # In the following array we'll describe all the conditions that need to hold for a take over of ownership
        vulnerable_conditions = [
            # Lets check that we're writing to address 0 (where the owner variable is located
            address == target_slot,
            # There is only a vulnerability if before the writing to the owner variable: owner != attacker
            Extract(
                20*8 + target_offset,
                0 + target_offset,
                state.environment.active_account.storage[symbol_factory.BitVecVal(0, 256)]
            ) != ACTORS.attacker,
            # There IS a vulnerability if the value being written to owner is the attacker address
            Extract(
                20*8 + target_offset,
                0 + target_offset,
                value,
            ) == ACTORS.attacker,
            # Lets only look for cases where the attacker makes themselves the owner by saying that the attacker
            # is the sender of this transaction
            state.environment.sender == ACTORS.attacker,
        ]

        try:
            # vulnerable_conditions describes when there is a vulnerability
            # lets check if the conditions are actually satisfiable by running the following command:
            # This will raise an UnsatError if the vulnerable_conditions are not satisfiable (i.e. not possible)
            transaction_sequence = solver.get_transaction_sequence(
                state,
                state.world_state.constraints
                + vulnerable_conditions,
            )
            # Note that get_transaction_sequence also gives us `transaction_sequence` which gives us a concrete
            # transaction trace that can be used to exploit/demonstrate the vulnerability.

            # Lets register an issue with Mythril so that the vulnerability is reported to the user!
            return [Issue(
                contract=state.environment.active_account.contract_name,
                function_name=state.environment.active_function_name,
                address=instruction["address"],
                swc_id='000',
                bytecode=state.environment.code.bytecode,
                title="Ownership Takeover",
                severity="High",
                description_head="An attacker can take over ownership of this contract.",
                description_tail="",
                transaction_sequence=transaction_sequence,
                gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
            )]
        except UnsatError:
            # Sadly (or happily), no vulnerabilities were found here.
            log.debug("Vulnerable conditions were not satisfiable")
            return list()
Exemplo n.º 8
0
    def _analyze_state(state: GlobalState):
        """
        :param state: the current state
        :return: returns the issues for that corresponding state
        """
        instruction = state.get_current_instruction()

        annotations = cast(
            List[MultipleSendsAnnotation],
            list(state.get_annotations(MultipleSendsAnnotation)),
        )
        if len(annotations) == 0:
            state.annotate(MultipleSendsAnnotation())
            annotations = cast(
                List[MultipleSendsAnnotation],
                list(state.get_annotations(MultipleSendsAnnotation)),
            )

        call_offsets = annotations[0].call_offsets

        if instruction["opcode"] in [
                "CALL", "DELEGATECALL", "STATICCALL", "CALLCODE"
        ]:
            call_offsets.append(state.get_current_instruction()["address"])

        else:  # RETURN or STOP

            for offset in call_offsets[1:]:
                try:
                    transaction_sequence = get_transaction_sequence(
                        state, state.mstate.constraints)
                except UnsatError:
                    continue
                description_tail = (
                    "This call is executed after a previous call in the same transaction. "
                    "Try to isolate each call, transfer or send into its own transaction."
                )

                issue = Issue(
                    contract=state.environment.active_account.contract_name,
                    function_name=state.environment.active_function_name,
                    address=offset,
                    swc_id=MULTIPLE_SENDS,
                    bytecode=state.environment.code.bytecode,
                    title="Multiple Calls in a Single Transaction",
                    severity="Low",
                    description_head=
                    "Multiple calls are executed in the same transaction.",
                    description_tail=description_tail,
                    gas_used=(state.mstate.min_gas_used,
                              state.mstate.max_gas_used),
                    transaction_sequence=transaction_sequence,
                )

                return [issue]

        return []
Exemplo n.º 9
0
    def _handle_transaction_end(self, state: GlobalState) -> None:
        for annotation in cast(
                List[OverUnderflowStateAnnotation],
                state.get_annotations(OverUnderflowStateAnnotation),
        ):

            ostate = annotation.overflowing_state
            address = _get_address_from_state(ostate)

            if annotation.operator == "subtraction" and self._underflow_cache.get(
                    address, False):
                continue

            if annotation.operator != "subtraction" and self._overflow_cache.get(
                    address, False):
                continue

            try:
                # This check can be disabled if the contraints are to difficult for z3 to solve
                # within any reasonable time.
                if DISABLE_EFFECT_CHECK:
                    constraints = ostate.mstate.constraints + [
                        annotation.constraint
                    ]
                else:
                    constraints = state.mstate.constraints + [
                        annotation.constraint
                    ]

                transaction_sequence = solver.get_transaction_sequence(
                    state, constraints)
            except UnsatError:
                continue

            _type = "Underflow" if annotation.operator == "subtraction" else "Overflow"
            issue = Issue(
                contract=ostate.environment.active_account.contract_name,
                function_name=ostate.environment.active_function_name,
                address=ostate.get_current_instruction()["address"],
                swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
                bytecode=ostate.environment.code.bytecode,
                title=self._get_title(_type),
                severity="High",
                description_head=self._get_description_head(annotation, _type),
                description_tail=self._get_description_tail(annotation, _type),
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
            )

            issue.debug = json.dumps(transaction_sequence, indent=4)

            if annotation.operator == "subtraction":
                self._underflow_cache[address] = True
            else:
                self._overflow_cache[address] = True
            self._issues.append(issue)
Exemplo n.º 10
0
def _analyze_state(state):
    instruction = state.get_current_instruction()
    node = state.node

    if instruction["opcode"] != "CALL":
        return []

    call_value = state.mstate.stack[-3]
    target = state.mstate.stack[-2]

    eth_sent_total = BitVecVal(0, 256)

    constraints = copy(node.constraints)

    for tx in state.world_state.transaction_sequence:
        if tx.caller == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF:

            # There's sometimes no overflow check on balances added.
            # But we don't care about attacks that require more 2^^256 ETH to be sent.

            constraints += [
                BVAddNoOverflow(eth_sent_total, tx.call_value, False)
            ]
            eth_sent_total = Sum(eth_sent_total, tx.call_value)
    constraints += [
        UGT(call_value, eth_sent_total), target == state.environment.sender
    ]

    try:

        transaction_sequence = solver.get_transaction_sequence(
            state, constraints)

        debug = str(transaction_sequence)

        issue = Issue(
            contract=node.contract_name,
            function_name=node.function_name,
            address=instruction["address"],
            swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
            title="Ether thief",
            _type="Warning",
            bytecode=state.environment.code.bytecode,
            description=
            "Arbitrary senders other than the contract creator can withdraw ETH from the contract"
            +
            " account without previously having sent an equivalent amount of ETH to it. This is likely to be"
            + " a vulnerability.",
            debug=debug,
            gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
        )
    except UnsatError:
        logging.debug("[ETHER_THIEF] no model found")
        return []

    return [issue]
Exemplo n.º 11
0
def _analyze_state(state):
    logging.info("Suicide module: Analyzing suicide instruction")
    node = state.node
    instruction = state.get_current_instruction()
    to = state.mstate.stack[-1]

    logging.debug("[SUICIDE] SUICIDE in function " + node.function_name)

    try:
        try:
            transaction_sequence = solver.get_transaction_sequence(
                state,
                node.constraints +
                [to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF],
            )
            description = "Anyone can kill this contract and withdraw its balance to their own account."
        except UnsatError:
            transaction_sequence = solver.get_transaction_sequence(
                state, node.constraints)
            description = (
                "The contract can be killed by anyone. Don't accidentally kill it."
            )

        debug = str(transaction_sequence)

        issue = Issue(
            contract=node.contract_name,
            function_name=node.function_name,
            address=instruction["address"],
            swc_id=UNPROTECTED_SELFDESTRUCT,
            bytecode=state.environment.code.bytecode,
            title="Unchecked SUICIDE",
            _type="Warning",
            description=description,
            debug=debug,
            gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
        )
        return [issue]
    except UnsatError:
        logging.info("[UNCHECKED_SUICIDE] no model found")

    return []
Exemplo n.º 12
0
def _analyze_state(state, node):
    issues = []
    instruction = state.get_current_instruction()

    if instruction["opcode"] != "CALL":
        return []

    call_value = state.mstate.stack[-3]
    target = state.mstate.stack[-2]

    not_creator_constraints, constrained = get_non_creator_constraints(state)
    if constrained:
        return []

    try:
        """
        FIXME: Instead of solving for call_value > 0, check whether call value can be greater than
        the total value of all transactions received by the caller
        """

        model = solver.get_model(node.constraints + not_creator_constraints +
                                 [call_value > 0])

        transaction_sequence = solver.get_transaction_sequence(
            state,
            node.constraints + not_creator_constraints + [call_value > 0])

        # For now we only report an issue if zero ETH has been sent to the contract account.

        for key, value in transaction_sequence.items():
            if int(value["call_value"], 16) > 0:
                return []

        debug = "Transaction Sequence: " + str(transaction_sequence)

        issue = Issue(
            contract=node.contract_name,
            function_name=node.function_name,
            address=instruction["address"],
            swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
            title="Ether thief",
            _type="Warning",
            bytecode=state.environment.code.bytecode,
            description=
            "Users other than the contract creator can withdraw ETH from the contract account"
            +
            " without previously having sent any ETH to it. This is likely to be vulnerability.",
            debug=debug,
        )
        issues.append(issue)
    except UnsatError:
        logging.debug("[ETHER_THIEF] no model found")

    return issues
Exemplo n.º 13
0
    def _analyze_state(state):
        """

        :param state:
        :return:
        """
        node = state.node
        instruction = state.get_current_instruction()

        if instruction["opcode"] == "ORIGIN":
            log.debug("ORIGIN in function " + node.function_name)
            title = "Use of tx.origin"
            description_head = "Use of tx.origin is deprecated."
            description_tail = (
                "The smart contract retrieves the transaction origin (tx.origin) using msg.origin. "
                "Use of msg.origin is deprecated and the instruction may be removed in the  future. "
                "Use msg.sender instead.\nSee also: "
                "https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin"
                .format(state.environment.active_function_name))
            swc_id = DEPRECATED_FUNCTIONS_USAGE

        elif instruction["opcode"] == "CALLCODE":
            log.debug("CALLCODE in function " +
                      state.environment.active_function_name)
            title = "Use of callcode"
            description_head = "Use of callcode is deprecated."
            description_tail = (
                "The callcode method executes code of another contract in the context of the caller account. "
                "Due to a bug in the implementation it does not persist sender and value over the call. It was "
                "therefore deprecated and may be removed in the future. Use the delegatecall method instead."
            )
            swc_id = DEPRECATED_FUNCTIONS_USAGE
        else:
            return
        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=instruction["address"],
            title=title,
            bytecode=state.environment.code.bytecode,
            swc_id=swc_id,
            severity="Medium",
            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]
Exemplo n.º 14
0
    def _handle_sstore(self, state):
        stack = state.mstate.stack
        value = stack[-2]

        if not isinstance(value, Expression):
            return
        for annotation in value.annotations:
            if not isinstance(annotation, OverUnderflowAnnotation):
                continue

            _type = "Underflow" if annotation.operator == "subtraction" else "Overflow"
            ostate = annotation.overflowing_state
            node = ostate.node

            issue = Issue(
                contract=node.contract_name,
                function_name=node.function_name,
                address=ostate.get_current_instruction()["address"],
                swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
                bytecode=ostate.environment.code.bytecode,
                title=self._get_title(_type),
                severity="High",
                description_head=self._get_description_head(annotation, _type),
                description_tail=self._get_description_tail(annotation, _type),
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
            )

            address = _get_address_from_state(ostate)

            if annotation.operator == "subtraction" and self._underflow_cache.get(
                    address, False):
                continue
            if annotation.operator != "subtraction" and self._overflow_cache.get(
                    address, False):
                continue

            try:

                transaction_sequence = solver.get_transaction_sequence(
                    state, node.constraints + [annotation.constraint])

                issue.debug = json.dumps(transaction_sequence, indent=4)

            except UnsatError:
                continue
            if annotation.operator == "subtraction":
                self._underflow_cache[address] = True
            else:
                self._overflow_cache[address] = True

            self._issues.append(issue)
Exemplo n.º 15
0
    def execute(self, statespace):

        logging.debug("Executing module: EXCEPTIONS")

        issues = []

        for k in statespace.nodes:
            node = statespace.nodes[k]

            for state in node.states:

                instruction = state.get_current_instruction()
                if instruction["opcode"] == "ASSERT_FAIL":
                    try:
                        model = solver.get_model(node.constraints)
                        address = state.get_current_instruction()["address"]

                        description = (
                            "A reachable exception (opcode 0xfe) has been detected. "
                            "This can be caused by type errors, division by zero, "
                            "out-of-bounds array access, or assert violations. "
                        )
                        description += (
                            "Note that explicit `assert()` should only be used to check invariants. "
                            "Use `require()` for regular input checking.")

                        debug = "Transaction Sequence: " + str(
                            solver.get_transaction_sequence(
                                state, node.constraints))

                        issues.append(
                            Issue(
                                contract=node.contract_name,
                                function_name=node.function_name,
                                address=address,
                                swc_id=ASSERT_VIOLATION,
                                title="Exception state",
                                _type="Informational",
                                description=description,
                                bytecode=state.environment.code.bytecode,
                                debug=debug,
                                gas_used=(
                                    state.mstate.min_gas_used,
                                    state.mstate.max_gas_used,
                                ),
                            ))

                    except UnsatError:
                        logging.debug("[EXCEPTIONS] no model found")

        return issues
Exemplo n.º 16
0
def _analyze_state(state, node):
    issues = []
    instruction = state.get_current_instruction()

    if instruction["opcode"] != "CALL":
        return []

    call_value = state.mstate.stack[-3]
    target = state.mstate.stack[-2]

    not_creator_constraints = []
    if len(state.world_state.transaction_sequence) > 1:
        creator = state.world_state.transaction_sequence[0].caller
        for transaction in state.world_state.transaction_sequence[1:]:
            not_creator_constraints.append(
                Not(
                    Extract(159, 0, transaction.caller) == Extract(
                        159, 0, creator)))
            not_creator_constraints.append(
                Not(Extract(159, 0, transaction.caller) == 0))

    try:
        model = solver.get_model(node.constraints + not_creator_constraints +
                                 [call_value > 0])

        debug = "Transaction Sequence: " + str(
            solver.get_transaction_sequence(
                state,
                node.constraints + not_creator_constraints + [call_value > 0]))

        issue = Issue(
            contract=node.contract_name,
            function_name=node.function_name,
            address=instruction["address"],
            swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
            title="Ether send",
            _type="Warning",
            bytecode=state.environment.code.bytecode,
            description=
            "It seems that an attacker is able to execute an call instruction,"
            " this can mean that the attacker is able to extract funds "
            "out of the contract.".format(target),
            debug=debug,
        )
        issues.append(issue)
    except UnsatError:
        logging.debug("[UNCHECKED_SUICIDE] no model found")

    return issues
Exemplo n.º 17
0
def _analyze_states(state: GlobalState) -> List[Issue]:
    """
    :param state: the current state
    :return: returns the issues for that corresponding state
    """
    issues = []
    op_code = state.get_current_instruction()["opcode"]
    annotations = cast(
        List[DelegateCallAnnotation],
        list(state.get_annotations(DelegateCallAnnotation)),
    )

    if len(annotations) == 0 and op_code in ("RETURN", "STOP"):
        return []

    if op_code == "DELEGATECALL":
        gas = state.mstate.stack[-1]
        to = state.mstate.stack[-2]

        constraints = [
            to == ATTACKER_ADDRESS,
            UGT(gas, symbol_factory.BitVecVal(2300, 256)),
        ]

        for tx in state.world_state.transaction_sequence:
            if not isinstance(tx, ContractCreationTransaction):
                constraints.append(tx.caller == ATTACKER_ADDRESS)

        state.annotate(DelegateCallAnnotation(state, constraints))

        return []
    else:
        for annotation in annotations:
            try:
                transaction_sequence = solver.get_transaction_sequence(
                    state,
                    state.mstate.constraints
                    + annotation.constraints
                    + [annotation.return_value == 1],
                )
                issues.append(
                    annotation.get_issue(
                        state, transaction_sequence=transaction_sequence
                    )
                )
            except UnsatError:
                continue

        return issues
Exemplo n.º 18
0
def _analyze_state(state) -> list:
    """

    :param state:
    :return:
    """
    log.info("Exceptions module: found ASSERT_FAIL instruction")
    node = state.node

    log.debug("ASSERT_FAIL in function " + node.function_name)

    try:
        address = state.get_current_instruction()["address"]

        description_tail = (
            "It is possible to trigger an exception (opcode 0xfe). "
            "Exceptions can be caused by type errors, division by zero, "
            "out-of-bounds array access, or assert violations. "
            "Note that explicit `assert()` should only be used to check invariants. "
            "Use `require()` for regular input checking.")

        transaction_sequence = solver.get_transaction_sequence(
            state, node.constraints)
        debug = json.dumps(transaction_sequence, indent=4)

        issue = Issue(
            contract=node.contract_name,
            function_name=node.function_name,
            address=address,
            swc_id=ASSERT_VIOLATION,
            title="Exception State",
            severity="Low",
            description_head="A reachable exception has been detected.",
            description_tail=description_tail,
            bytecode=state.environment.code.bytecode,
            debug=debug,
            gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
        )
        return [issue]

    except UnsatError:
        log.debug("no model found")

    return []
Exemplo n.º 19
0
    def _analyze_state(self, state):
        """

        :param state:
        :return:
        """
        instruction = state.get_current_instruction()

        if instruction["opcode"] != "CALL":
            return []

        address = instruction["address"]
        if self._cache_addresses.get(address, False):
            return []
        call_value = state.mstate.stack[-3]
        target = state.mstate.stack[-2]

        eth_sent_total = symbol_factory.BitVecVal(0, 256)

        constraints = copy(state.mstate.constraints)

        for tx in state.world_state.transaction_sequence:
            constraints += [
                BVAddNoOverflow(eth_sent_total, tx.call_value, False)
            ]
            eth_sent_total = Sum(eth_sent_total, tx.call_value)

            if not isinstance(tx, ContractCreationTransaction):
                constraints.append(tx.caller == ATTACKER_ADDRESS)

        constraints += [
            UGT(call_value, eth_sent_total), target == ATTACKER_ADDRESS
        ]

        try:

            transaction_sequence = solver.get_transaction_sequence(
                state, constraints)

            issue = Issue(
                contract=state.environment.active_account.contract_name,
                function_name=state.environment.active_function_name,
                address=instruction["address"],
                swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
                title="Unprotected Ether Withdrawal",
                severity="High",
                bytecode=state.environment.code.bytecode,
                description_head=
                "Anyone can withdraw ETH from the contract account.",
                description_tail=
                "Arbitrary senders other than the contract creator can withdraw ETH from the contract"
                +
                " account without previously having sent an equivalent amount of ETH to it. This is likely to be"
                + " a vulnerability.",
                transaction_sequence=transaction_sequence,
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
            )
        except UnsatError:
            log.debug("[ETHER_THIEF] no model found")
            return []

        # self._cache_addresses[address] = True

        return [issue]
Exemplo n.º 20
0
    def _analyze_state(self, state):
        """

        :param state:
        :return:
        """
        instruction = state.get_current_instruction()

        if instruction["opcode"] != "CALL":
            return []

        address = instruction["address"]
        if self._cache_addresses.get(address, False):
            return []
        value = state.mstate.stack[-3]
        target = state.mstate.stack[-2]

        eth_sent_by_attacker = symbol_factory.BitVecVal(0, 256)

        constraints = copy(state.mstate.constraints)

        for tx in state.world_state.transaction_sequence:
            """
            Constraint: The call value must be greater than the sum of Ether sent by the attacker over all
            transactions. This prevents false positives caused by legitimate refund functions.
            Also constrain the addition from overflowing (otherwise the solver produces solutions with 
            ridiculously high call values).
            """
            constraints += [
                BVAddNoOverflow(eth_sent_by_attacker, tx.call_value, False)
            ]
            eth_sent_by_attacker = Sum(
                eth_sent_by_attacker,
                tx.call_value * If(tx.caller == ATTACKER_ADDRESS, 1, 0),
            )
            """
            Constraint: All transactions must originate from regular users (not the creator/owner).
            This prevents false positives where the owner willingly transfers ownership to another address.
            """

            if not isinstance(tx, ContractCreationTransaction):
                constraints += [tx.caller != CREATOR_ADDRESS]
        """
        Require that the current transaction is sent by the attacker and
        that the Ether is sent to the attacker's address.
        """

        constraints += [
            UGT(value, eth_sent_by_attacker),
            target == ATTACKER_ADDRESS,
            state.current_transaction.caller == ATTACKER_ADDRESS,
        ]

        try:

            transaction_sequence = solver.get_transaction_sequence(
                state, constraints)

            issue = Issue(
                contract=state.environment.active_account.contract_name,
                function_name=state.environment.active_function_name,
                address=instruction["address"],
                swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
                title="Unprotected Ether Withdrawal",
                severity="High",
                bytecode=state.environment.code.bytecode,
                description_head=
                "Anyone can withdraw ETH from the contract account.",
                description_tail=
                "Arbitrary senders other than the contract creator can withdraw ETH from the contract"
                +
                " account without previously having sent an equivalent amount of ETH to it. This is likely to be"
                + " a vulnerability.",
                transaction_sequence=transaction_sequence,
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
            )
        except UnsatError:
            log.debug("[ETHER_THIEF] no model found")
            return []

        # self._cache_addresses[address] = True

        return [issue]
Exemplo n.º 21
0
    def _analyze_state(state, node):
        issues = []
        instruction = state.get_current_instruction()

        if instruction["opcode"] != "CALL":
            return []

        call_value = state.mstate.stack[-3]
        target = state.mstate.stack[-2]

        not_creator_constraints, constrained = get_non_creator_constraints(
            state)
        if constrained:
            return []

        eth_sent_total = BitVecVal(0, 256)

        for tx in state.world_state.transaction_sequence[1:]:
            eth_sent_total += tx.call_value

        try:

            model = solver.get_model(
                node.constraints + not_creator_constraints + [
                    UGT(call_value, eth_sent_total),
                    state.environment.sender == ARBITRARY_SENDER_ADDRESS,
                    target == state.environment.sender,
                ])

            transaction_sequence = solver.get_transaction_sequence(
                state,
                node.constraints + not_creator_constraints + [
                    call_value > eth_sent_total,
                    state.environment.sender == ARBITRARY_SENDER_ADDRESS,
                    target == state.environment.sender,
                ],
            )

            debug = "Transaction Sequence: " + str(transaction_sequence)

            issue = Issue(
                contract=node.contract_name,
                function_name=node.function_name,
                address=instruction["address"],
                swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
                title="Ether thief",
                _type="Warning",
                bytecode=state.environment.code.bytecode,
                description=
                "Arbitrary senders other than the contract creator can withdraw ETH from the contract"
                +
                " account without previously having sent an equivalent amount of ETH to it. This is likely to be"
                + " a vulnerability.",
                debug=debug,
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
            )
            issues.append(issue)
        except UnsatError:
            logging.debug("[ETHER_THIEF] no model found")

        return issues
    def _analyze_state(state: GlobalState) -> list:
        """

        :param state:
        :return:
        """

        issues = []

        if is_prehook():

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

            if opcode in final_ops:

                for annotation in state.annotations:

                    if isinstance(annotation, PredictablePathAnnotation):
                        if annotation.add_constraints:
                            constraints = (state.mstate.constraints +
                                           annotation.add_constraints)
                        else:
                            constraints = copy(state.mstate.constraints)
                        try:
                            transaction_sequence = solver.get_transaction_sequence(
                                state, constraints)
                        except UnsatError:
                            continue
                        description = (
                            "The " + annotation.operation +
                            " is used in to determine a control flow decision. "
                        )
                        description += (
                            "Note that the values of variables like coinbase, gaslimit, block number and timestamp "
                            "are predictable and can be manipulated by a malicious miner. Also keep in mind that attackers "
                            "know hashes of earlier blocks. Don't use any of those environment variables for random number "
                            "generation or to make critical control flow decisions."
                        )
                        """
                        Usually report low severity except in cases where the hash of a previous block is used to
                        determine control flow. 
                        """

                        severity = "Medium" if "hash" in annotation.operation else "Low"
                        """
                        Note: We report the location of the JUMPI that lead to this path. Usually this maps to an if or
                        require statement.
                        """

                        swc_id = (TIMESTAMP_DEPENDENCE if "timestamp"
                                  in annotation.operation else WEAK_RANDOMNESS)

                        issue = Issue(
                            contract=state.environment.active_account.
                            contract_name,
                            function_name=state.environment.
                            active_function_name,
                            address=annotation.location,
                            swc_id=swc_id,
                            bytecode=state.environment.code.bytecode,
                            title=
                            "Dependence on predictable environment variable",
                            severity=severity,
                            description_head=
                            "A control flow decision is made based on a predictable variable.",
                            description_tail=description,
                            gas_used=(
                                state.mstate.min_gas_used,
                                state.mstate.max_gas_used,
                            ),
                            transaction_sequence=transaction_sequence,
                        )

                        issues.append(issue)

            elif opcode == "JUMPI":

                # Look for predictable state variables in jump condition

                for annotation in state.mstate.stack[-2].annotations:

                    if isinstance(annotation, PredictableValueAnnotation):
                        state.annotate(
                            PredictablePathAnnotation(
                                annotation.operation,
                                state.get_current_instruction()["address"],
                                add_constraints=annotation.add_constraints,
                            ))
                        break

            elif opcode == "BLOCKHASH":

                param = state.mstate.stack[-1]

                try:
                    constraint = [
                        ULT(param, state.environment.block_number),
                        ULT(
                            state.environment.block_number,
                            symbol_factory.BitVecVal(2**255, 256),
                        ),
                    ]

                    # Why the second constraint? Because without it Z3 returns a solution where param overflows.

                    solver.get_model(state.mstate.constraints + constraint)
                    state.annotate(OldBlockNumberUsedAnnotation(constraint))

                except UnsatError:
                    pass

        else:
            # we're in post hook

            opcode = state.environment.code.instruction_list[state.mstate.pc -
                                                             1]["opcode"]

            if opcode == "BLOCKHASH":
                # if we're in the post hook of a BLOCKHASH op, check if an old block number was used to create it.

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

                if len(annotations):
                    # We can append any block constraint here
                    state.mstate.stack[-1].annotate(
                        PredictableValueAnnotation(
                            "block hash of a previous block",
                            add_constraints=annotations[0].block_constraints,
                        ))
            else:
                # Always create an annotation when COINBASE, GASLIMIT, TIMESTAMP or NUMBER is executed.

                state.mstate.stack[-1].annotate(
                    PredictableValueAnnotation(
                        "block.{} environment variable".format(
                            opcode.lower())))

        return issues
Exemplo n.º 23
0
    def _analyze_state(self, state):
        """

        :param state:
        :return:
        """
        instruction = state.get_current_instruction()
        node = state.node

        if instruction["opcode"] != "CALL":
            return []

        address = instruction["address"]
        if self._cache_addresses.get(address, False):
            return []
        call_value = state.mstate.stack[-3]
        target = state.mstate.stack[-2]

        eth_sent_total = symbol_factory.BitVecVal(0, 256)

        constraints = copy(node.constraints)

        for tx in state.world_state.transaction_sequence:
            if tx.caller == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF:
                # There's sometimes no overflow check on balances added.
                # But we don't care about attacks that require more 2^^256 ETH to be sent.

                constraints += [BVAddNoOverflow(eth_sent_total, tx.call_value, False)]
                eth_sent_total = Sum(eth_sent_total, tx.call_value)
        constraints += [
            UGT(call_value, eth_sent_total),
            target == state.environment.sender,
        ]

        try:

            transaction_sequence = solver.get_transaction_sequence(state, constraints)

            debug = json.dumps(transaction_sequence, indent=4)

            issue = Issue(
                contract=node.contract_name,
                function_name=node.function_name,
                address=instruction["address"],
                swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
                title="Unprotected Ether Withdrawal",
                severity="High",
                bytecode=state.environment.code.bytecode,
                description_head="Anyone can withdraw ETH from the contract account.",
                description_tail="Arbitrary senders other than the contract creator can withdraw ETH from the contract"
                + " account without previously having sent an equivalent amount of ETH to it. This is likely to be"
                + " a vulnerability.",
                debug=debug,
                gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
            )
        except UnsatError:
            log.debug("[ETHER_THIEF] no model found")
            return []

        self._cache_addresses[address] = True

        return [issue]
Exemplo n.º 24
0
def _analyze_state(state):

    node = state.node
    gas = state.mstate.stack[-1]
    to = state.mstate.stack[-2]

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

    try:
        constraints = node.constraints
        transaction_sequence = solver.get_transaction_sequence(
            state, constraints + [UGT(gas, 2300)])

        # Check whether we can also set the callee address

        try:
            constraints += [to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF]
            transaction_sequence = solver.get_transaction_sequence(
                state, constraints)

            debug = str(transaction_sequence)
            description = (
                "The contract executes a function call with high gas to a user-supplied address. "
                "Note that the callee can contain arbitrary code and may re-enter any function in this contract. "
                "Review the business logic carefully to prevent unanticipated effects on the contract state."
            )

            issue = Issue(
                contract=node.contract_name,
                function_name=node.function_name,
                address=address,
                swc_id=REENTRANCY,
                title="External call to user-supplied address",
                _type="Warning",
                bytecode=state.environment.code.bytecode,
                description=description,
                debug=debug,
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
            )

        except UnsatError:

            logging.debug(
                "[EXTERNAL_CALLS] Callee address cannot be modified. Reporting informational issue."
            )

            debug = str(transaction_sequence)
            description = (
                "The contract executes a function call to an external address. "
                "Verify that the code at this address is trusted and immutable."
            )

            issue = Issue(
                contract=node.contract_name,
                function_name=state.node.function_name,
                address=address,
                swc_id=REENTRANCY,
                title="External call",
                _type="Informational",
                bytecode=state.environment.code.bytecode,
                description=description,
                debug=debug,
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
            )

    except UnsatError:
        logging.debug("[EXTERNAL_CALLS] No model found.")
        return []

    return [issue]
Exemplo n.º 25
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 []
Exemplo n.º 26
0
def _check_integer_overflow(statespace, state, node):
    """
    Checks for integer overflow
    :param statespace: statespace that is being examined
    :param state: state from node to examine
    :param node: node to examine
    :return: found issue
    """
    issues = []

    # Check the instruction
    instruction = state.get_current_instruction()
    if instruction["opcode"] not in ("ADD", "MUL"):
        return issues

    # Formulate overflow constraints
    stack = state.mstate.stack
    op0, op1 = stack[-1], stack[-2]

    # An integer overflow is possible if op0 + op1 or op0 * op1 > MAX_UINT
    # Do a type check
    allowed_types = [int, BitVecRef, BitVecNumRef]
    if not (type(op0) in allowed_types and type(op1) in allowed_types):
        return issues

    # Change ints to BitVec
    if type(op0) is int:
        op0 = BitVecVal(op0, 256)
    if type(op1) is int:
        op1 = BitVecVal(op1, 256)

    # Formulate expression
    if instruction["opcode"] == "ADD":
        expr = op0 + op1
        # constraint = Not(BVAddNoOverflow(op0, op1, signed=False))
    else:
        expr = op1 * op0
        # constraint = Not(BVMulNoOverflow(op0, op1, signed=False))

    constraint = Or(And(ULT(expr, op0), op1 != 0), And(ULT(expr, op1), op0 != 0))
    # Check satisfiable
    model = _try_constraints(node.constraints, [constraint])

    if model is None:
        logging.debug("[INTEGER_OVERFLOW] no model found")
        return issues

    if not _verify_integer_overflow(
        statespace, node, expr, state, model, constraint, op0, op1
    ):
        return issues

    # Build issue
    issue = Issue(
        contract=node.contract_name,
        function_name=node.function_name,
        address=instruction["address"],
        swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
        bytecode=state.environment.code.bytecode,
        title="Integer Overflow",
        _type="Warning",
    )

    issue.description = "The arithmetic operation can result in integer overflow.\n"
    issue.debug = "Transaction Sequence: " + str(
        solver.get_transaction_sequence(state, node.constraints)
    )
    issues.append(issue)

    return issues
Exemplo n.º 27
0
def _check_integer_underflow(statespace, state, node):
    """
    Checks for integer underflow
    :param state: state from node to examine
    :param node: node to examine
    :return: found issue
    """
    issues = []
    instruction = state.get_current_instruction()
    if instruction["opcode"] == "SUB":

        stack = state.mstate.stack

        op0 = stack[-1]
        op1 = stack[-2]

        constraints = copy.deepcopy(node.constraints)

        # Filter for patterns that indicate benign underflows

        # Pattern 1: (96 + calldatasize_MAIN) - (96), where (96 + calldatasize_MAIN) would underflow if calldatasize is very large.
        # Pattern 2: (256*If(1 & storage_0 == 0, 1, 0)) - 1, this would underlow if storage_0 = 0
        if type(op0) == int and type(op1) == int:
            return []
        if re.search(r"calldatasize_", str(op0)):
            return []
        if re.search(r"256\*.*If\(1", str(op0), re.DOTALL) or re.search(
            r"256\*.*If\(1", str(op1), re.DOTALL
        ):
            return []
        if re.search(r"32 \+.*calldata", str(op0), re.DOTALL) or re.search(
            r"32 \+.*calldata", str(op1), re.DOTALL
        ):
            return []

        logging.debug(
            "[INTEGER_UNDERFLOW] Checking SUB {0}, {1} at address {2}".format(
                str(op0), str(op1), str(instruction["address"])
            )
        )
        allowed_types = [int, BitVecRef, BitVecNumRef]

        if type(op0) in allowed_types and type(op1) in allowed_types:
            constraints.append(UGT(op1, op0))

            try:

                model = solver.get_model(constraints)

                # If we get to this point then there has been an integer overflow
                # Find out if the overflowed value is actually used
                interesting_usages = _search_children(
                    statespace, node, (op0 - op1), index=node.states.index(state)
                )

                # Stop if it isn't
                if len(interesting_usages) == 0:
                    return issues

                issue = Issue(
                    contract=node.contract_name,
                    function_name=node.function_name,
                    address=instruction["address"],
                    swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
                    bytecode=state.environment.code.bytecode,
                    title="Integer Underflow",
                    _type="Warning",
                )

                issue.description = (
                    "The subtraction can result in an integer underflow.\n"
                )

                issue.debug = "Transaction Sequence: " + str(
                    solver.get_transaction_sequence(state, node.constraints)
                )
                issues.append(issue)

            except UnsatError:
                logging.debug("[INTEGER_UNDERFLOW] no model found")
    return issues
Exemplo n.º 28
0
    def _check_integer_overflow(self, statespace, state, node):
        """
        Checks for integer overflow
        :param statespace: statespace that is being examined
        :param state: state from node to examine
        :param node: node to examine
        :return: found issue
        """
        issues = []

        # Check the instruction
        instruction = state.get_current_instruction()
        if instruction["opcode"] not in ("ADD", "MUL"):
            return issues

        # Formulate overflow constraints
        stack = state.mstate.stack
        op0, op1 = stack[-1], stack[-2]

        # An integer overflow is possible if op0 + op1 or op0 * op1 > MAX_UINT
        # Do a type check
        allowed_types = [int, BitVecRef, BitVecNumRef]
        if not (type(op0) in allowed_types and type(op1) in allowed_types):
            return issues

        # Change ints to BitVec
        if type(op0) is int:
            op0 = BitVecVal(op0, 256)
        if type(op1) is int:
            op1 = BitVecVal(op1, 256)

        # Formulate expression
        # FIXME: handle exponentiation
        if instruction["opcode"] == "ADD":
            operator = "add"
            expr = op0 + op1
            constraint = Not(BVAddNoOverflow(op0, op1, signed=False))
        else:
            operator = "multiply"
            expr = op1 * op0
            constraint = Not(BVMulNoOverflow(op0, op1, signed=False))

        # Check satisfiable
        model = self._try_constraints(node.constraints, [constraint])

        if model is None:
            logging.debug("[INTEGER_OVERFLOW] no model found")
            return issues

        # Build issue
        issue = Issue(
            contract=node.contract_name,
            function_name=node.function_name,
            address=instruction["address"],
            swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
            bytecode=state.environment.code.bytecode,
            title="Integer Overflow",
            _type="Warning",
            gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
        )

        issue.description = "This binary {} operation can result in integer overflow.\n".format(
            operator)
        try:
            issue.debug = "Transaction Sequence: " + str(
                solver.get_transaction_sequence(state, node.constraints))
        except UnsatError:
            return issues

        issues.append(issue)

        return issues
Exemplo n.º 29
0
    def _analyze_state(state):
        """

        :param state:
        :return:
        """
        gas = state.mstate.stack[-1]
        to = state.mstate.stack[-2]

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

        try:
            constraints = copy(state.mstate.constraints)

            transaction_sequence = solver.get_transaction_sequence(
                state,
                constraints + [UGT(gas, symbol_factory.BitVecVal(2300, 256))])

            # Check whether we can also set the callee address

            try:
                constraints += [to == ATTACKER_ADDRESS]

                for tx in state.world_state.transaction_sequence:
                    if not isinstance(tx, ContractCreationTransaction):
                        constraints.append(tx.caller == ATTACKER_ADDRESS)

                transaction_sequence = solver.get_transaction_sequence(
                    state, constraints)

                description_head = "A call to a user-supplied address is executed."
                description_tail = (
                    "The callee address of an external message call can be set by "
                    "the caller. Note that the callee can contain arbitrary code and may re-enter any function "
                    "in this contract. Review the business logic carefully to prevent averse effects on the "
                    "contract state.")

                issue = Issue(
                    contract=state.environment.active_account.contract_name,
                    function_name=state.environment.active_function_name,
                    address=address,
                    swc_id=REENTRANCY,
                    title="External Call To User-Supplied Address",
                    bytecode=state.environment.code.bytecode,
                    severity="Medium",
                    description_head=description_head,
                    description_tail=description_tail,
                    transaction_sequence=transaction_sequence,
                    gas_used=(state.mstate.min_gas_used,
                              state.mstate.max_gas_used),
                )

            except UnsatError:
                if _is_precompile_call(state):
                    return []

                log.debug(
                    "[EXTERNAL_CALLS] Callee address cannot be modified. Reporting informational issue."
                )

                description_head = "The contract executes an external message call."
                description_tail = (
                    "An external function call to a fixed contract address is executed. Make sure "
                    "that the callee contract has been reviewed carefully.")

                issue = Issue(
                    contract=state.environment.active_account.contract_name,
                    function_name=state.environment.active_function_name,
                    address=address,
                    swc_id=REENTRANCY,
                    title="External Call To Fixed Address",
                    bytecode=state.environment.code.bytecode,
                    severity="Low",
                    description_head=description_head,
                    description_tail=description_tail,
                    transaction_sequence=transaction_sequence,
                    gas_used=(state.mstate.min_gas_used,
                              state.mstate.max_gas_used),
                )

        except UnsatError:
            log.debug("[EXTERNAL_CALLS] No model found.")
            return []

        return [issue]
Exemplo n.º 30
0
    def _analyze_state(state):
        """

        :param state:
        :return:
        """
        state = copy(state)
        instruction = state.get_current_instruction()

        value = state.mstate.stack[-3]
        target = state.mstate.stack[-2]

        constraints = copy(state.mstate.constraints)
        """
        Require that the current transaction is sent by the attacker and
        that the Ether sent to the attacker's address is greater than the
        amount of Ether the attacker sent.
        """
        for tx in state.world_state.transaction_sequence:
            """
            Constraint: All transactions must originate from regular users (not the creator/owner).
            This prevents false positives where the owner willingly transfers ownership to another address.
            """
            if not isinstance(tx, ContractCreationTransaction):
                constraints += [tx.caller != CREATOR_ADDRESS]

        attacker_address_bitvec = symbol_factory.BitVecVal(
            ATTACKER_ADDRESS, 256)

        constraints += [
            UGE(
                state.world_state.balances[
                    state.environment.active_account.address],
                value,
            )
        ]
        state.world_state.balances[attacker_address_bitvec] += value
        state.world_state.balances[
            state.environment.active_account.address] -= value

        constraints += [
            UGT(
                state.world_state.balances[attacker_address_bitvec],
                state.world_state.starting_balances[attacker_address_bitvec],
            ),
            target == ATTACKER_ADDRESS,
            state.current_transaction.caller == ATTACKER_ADDRESS,
        ]

        try:
            transaction_sequence = solver.get_transaction_sequence(
                state, constraints)

            issue = Issue(
                contract=state.environment.active_account.contract_name,
                function_name=state.environment.active_function_name,
                address=instruction["address"],
                swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
                title="Unprotected Ether Withdrawal",
                severity="High",
                bytecode=state.environment.code.bytecode,
                description_head=
                "Anyone can withdraw ETH from the contract account.",
                description_tail=
                "Arbitrary senders other than the contract creator can withdraw ETH from the contract"
                +
                " account without previously having sent an equivalent amount of ETH to it. This is likely to be"
                + " a vulnerability.",
                transaction_sequence=transaction_sequence,
                gas_used=(state.mstate.min_gas_used,
                          state.mstate.max_gas_used),
            )
        except UnsatError:
            log.debug("No model found")
            return []

        return [issue]