コード例 #1
0
    def _add_external_call(global_state: GlobalState) -> None:
        gas = global_state.mstate.stack[-1]
        to = global_state.mstate.stack[-2]
        try:
            constraints = copy(global_state.mstate.constraints)
            solver.get_model(constraints + [
                UGT(gas, symbol_factory.BitVecVal(2300, 256)),
                Or(
                    to > symbol_factory.BitVecVal(16, 256),
                    to == symbol_factory.BitVecVal(0, 256),
                ),
            ])

            # Check whether we can also set the callee address
            try:
                constraints += [
                    to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
                ]
                solver.get_model(constraints)

                global_state.annotate(
                    StateChangeCallsAnnotation(global_state, True))
            except UnsatError:
                global_state.annotate(
                    StateChangeCallsAnnotation(global_state, False))
        except UnsatError:
            pass
コード例 #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,
        )
コード例 #3
0
ファイル: delegatecall.py プロジェクト: mygirl8893/mythril
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
コード例 #4
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]
コード例 #5
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]
コード例 #6
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]
コード例 #7
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]
コード例 #8
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]