示例#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
文件: integer.py 项目: xahiru/mythril
    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)
示例#3
0
    def _balance_change(value: BitVec, global_state: GlobalState) -> bool:
        if not value.symbolic:
            assert value.value is not None
            return value.value > 0

        else:
            constraints = copy(global_state.mstate.constraints)

            try:
                solver.get_model(constraints +
                                 [value > symbol_factory.BitVecVal(0, 256)])
                return True
            except UnsatError:
                return False
示例#4
0
def _is_precompile_call(global_state: GlobalState):
    to = global_state.mstate.stack[-2]  # type: BitVec
    constraints = copy(global_state.mstate.constraints)
    constraints += [
        Or(
            to < symbol_factory.BitVecVal(1, 256),
            to > symbol_factory.BitVecVal(16, 256),
        )
    ]

    try:
        solver.get_model(constraints)
        return False
    except UnsatError:
        return True
示例#5
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
示例#6
0
def execute(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 += "This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. "

                    debug = "The exception is triggered under the following conditions:\n\n"

                    debug += solver.pretty_print_model(model)

                    issues.append(
                        Issue(node.contract_name, node.function_name, address,
                              "Exception state", "Informational", description,
                              debug))

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

    return issues
示例#7
0
def execute(statespace):
    logging.debug("Executing module: ASSERT FAILS")
    issues = []

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

        for instruction in node.instruction_list:

            if (instruction['opcode'] == "INVALID"):
                try:
                    model = solver.get_model(node.constraints)
                    issue = Issue(node.module_name, node.function_name,
                                  instruction['address'], "Assert Fail",
                                  "Warning")
                    issue.description = "A possible assert failure exists in the function " + node.function_name
                    issues.append(issue)

                    for d in model.decls():
                        print("[ASSERT_FAIL] model: %s = 0x%x" %
                              (d.name(), model[d].as_long()))

                except UnsatError:
                    logging.debug(
                        "Couldn't find constraints to reach this invalid")

    return issues
示例#8
0
def execute(statespace):

    logging.debug("Executing module: INTEGER_UNDERFLOW")

    issues = []

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

        for instruction in node.instruction_list:

            if(instruction['opcode'] == "SUB"):

                stack = node.states[instruction['address']].stack

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

                constraints = copy.deepcopy(node.constraints)

                if type(op0) == int and type(op1) == int:
                    continue

                if (re.search(r'calldatasize_', str(op0))) \
                    or (re.search(r'256\*.*If\(1', str(op0), re.DOTALL) or re.search(r'256\*.*If\(1', str(op1), re.DOTALL)) \
                    or (re.search(r'32 \+.*calldata', str(op0), re.DOTALL) or re.search(r'32 \+.*calldata', str(op1), re.DOTALL)):

                    # Filter for patterns that contain possible (but apparently non-exploitable) Integer 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

                    # Both seem to be standard compiler outputs that exist in many contracts.

                    continue

                logging.debug("[INTEGER_UNDERFLOW] Checking SUB " + str(op0) + ", " + str(op1) + " at address " + str(instruction['address']))

                constraints.append(UGT(op1,op0))

                try:
                    
                    model = solver.get_model(constraints)

                    issue = Issue(node.module_name, node.function_name, instruction['address'], "Integer Underflow", "Warning")

                    issue.description = "A possible integer underflow exists in the function " + node.function_name + ".\n" \
                        "The SUB instruction at address " + str(instruction['address']) + " may result in a value < 0." 

                    issue.debug = "(" + str(op0) + ") - (" + str(op1) + ").]"

                    issues.append(issue)

                    for d in model.decls():
                        logging.debug("[INTEGER_UNDERFLOW] model: %s = 0x%x" % (d.name(), model[d].as_long()))

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

    return issues
示例#9
0
 def _try_constraints(constraints, new_constraints):
     """        Tries new constraints
     :return Model if satisfiable otherwise None
     """
     try:
         return solver.get_model(constraints + new_constraints)
     except UnsatError:
         return None
示例#10
0
def test_vmtest(
    test_name: str, pre_condition: dict, action: dict, post_condition: dict
) -> None:
    # Arrange

    accounts = {}
    for address, details in pre_condition.items():
        account = Account(address)
        account.code = Disassembly(details["code"][2:])
        account.balance = int(details["balance"], 16)
        account.nonce = int(details["nonce"], 16)

        accounts[address] = account

    laser_evm = LaserEVM(accounts)

    # Act
    laser_evm.time = datetime.now()

    # TODO: move this line below and check for VmExceptions when gas has been implemented
    if post_condition == {}:
        return

    execute_message_call(
        laser_evm,
        callee_address=action["address"],
        caller_address=action["caller"],
        origin_address=action["origin"],
        code=action["code"][2:],
        gas=action["gas"],
        data=binascii.a2b_hex(action["data"][2:]),
        gas_price=int(action["gasPrice"], 16),
        value=int(action["value"], 16),
    )

    # Assert

    assert len(laser_evm.open_states) == 1

    world_state = laser_evm.open_states[0]
    model = get_model(next(iter(laser_evm.nodes.values())).states[0].mstate.constraints)

    for address, details in post_condition.items():
        account = world_state[address]

        assert account.nonce == int(details["nonce"], 16)
        assert account.code.bytecode == details["code"][2:]

        for index, value in details["storage"].items():
            expected = int(value, 16)
            if type(account.storage[int(index, 16)]) != int:
                actual = model.eval(account.storage[int(index, 16)])
                actual = 1 if actual == True else 0 if actual == False else actual
            else:
                actual = account.storage[int(index, 16)]
            assert actual == expected
示例#11
0
def _check_integer_underflow(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 contain bening nteger 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)

                issue = Issue(node.contract_name, node.function_name, instruction['address'], "Integer Underflow",
                              "Warning")

                issue.description = "A possible integer underflow exists in the function " + node.function_name + ".\n" \
                                                                                                                  "The subtraction may result in a value < 0."

                issue.debug = solver.pretty_print_model(model)
                issues.append(issue)

            except UnsatError:
                logging.debug("[INTEGER_UNDERFLOW] no model found")
    return issues
def _can_change(constraints, variable):
    """ Checks if the variable can change given some constraints """
    _constraints = copy.deepcopy(constraints)
    try:
        model = solver.get_model(_constraints)
    except UnsatError:
        return False
    initial_value = int(str(model.eval(variable, model_completion=True)))
    return _try_constraints(constraints,
                            [variable != initial_value]) is not None
示例#13
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
def solve(call):
    try:
        model = solver.get_model(call.node.constraints)
        logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] MODEL: " + str(model))
        pretty_model = solver.pretty_print_model(model)

        logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] main model: \n%s" %
                      pretty_model)
        return True

    except UnsatError:
        logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] no model found")
        return False
示例#15
0
def solve(call):
    try:
        model = solver.get_model(call.node.constraints)
        logging.debug("[WEAK_RANDOM] MODEL: " + str(model))

        for d in model.decls():
            logging.debug("[WEAK_RANDOM] main model: %s = 0x%x" %
                          (d.name(), model[d].as_long()))
        return True

    except UnsatError:
        logging.debug("[WEAK_RANDOM] no model found")
        return False
示例#16
0
def _try_constraints(constraints, new_constraints):
    """
    Tries new constraints
    :return Model if satisfiable otherwise None
    """
    _constraints = copy.deepcopy(constraints)
    for constraint in new_constraints:
        _constraints.append(copy.deepcopy(constraint))
    try:
        model = solver.get_model(_constraints)
        return model
    except UnsatError:
        return None
示例#17
0
    def sstor_analysis(self):

        logging.info("Analyzing storage operations...")

        for index in self.sstors:
            for s in self.sstors[index]:

                # For now we simply 'taint' every storage location that is reachable without any constraint on msg.sender

                taint = True

                for constraint in s.node.constraints:
                    if ("caller" in str(constraint)):
                        taint = False
                        break

                if taint:
                    try:
                        solver.get_model(s.node.constraints)
                        s.tainted = True
                    except UnsatError:
                        s.tainted = False
示例#18
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
def solve(call):
    try:
        model = solver.get_model(call.node.constraints)
        logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] MODEL: " + str(model))

        for d in model.decls():
            logging.debug(
                "[DEPENDENCE_ON_PREDICTABLE_VARS] main model: %s = 0x%x" %
                (d.name(), model[d].as_long()))
        return True

    except UnsatError:
        logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] no model found")
        return False
示例#20
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 = "The function `" + node.function_name + "` executes the SUICIDE instruction. "

    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 = "SOLVER OUTPUT:\n" + solver.pretty_print_model(model)

        issue = Issue(node.contract_name, node.function_name,
                      instruction['address'], "Unchecked SUICIDE", "Warning",
                      description, debug)
        issues.append(issue)
    except UnsatError:
        logging.debug("[UNCHECKED_SUICIDE] no model found")

    return issues
示例#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 = []
    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
示例#22
0
    def wanna_execute(self, address: int,
                      annotation: DependencyAnnotation) -> bool:
        """Decide whether the basic block starting at 'address' should be executed.

        :param address
        :param storage_write_cache
        """

        storage_write_cache = annotation.get_storage_write_cache(
            self.iteration - 1)

        if address in self.calls_on_path:
            return True

        # Skip "pure" paths that don't have any dependencies.

        if address not in self.sloads_on_path:
            return False

        # Execute the path if there are state modifications along it that *could* be relevant

        if address in self.storage_accessed_global:
            for location in self.sstores_on_path:
                try:
                    solver.get_model((location == address, ))
                    return True

                except UnsatError:
                    continue

        dependencies = self.sloads_on_path[address]

        # Execute the path if there's any dependency on state modified in the previous transaction

        for location in storage_write_cache:
            for dependency in dependencies:

                # Is there a known read operation along this path that matches a write in the previous transaction?

                try:
                    solver.get_model((location == dependency, ))
                    return True

                except UnsatError:
                    continue

            # Has the *currently executed* path been influenced by a write operation in the previous transaction?

            for dependency in annotation.storage_loaded:
                try:
                    solver.get_model((location == dependency, ))
                    return True
                except UnsatError:
                    continue

        return False
示例#23
0
    def wanna_execute(self, address: int, storage_write_cache) -> bool:
        """Decide whether the basic block starting at 'address' should be executed.

        :param address
        :param storage_write_cache
        """

        if address in self.protected_addresses or address not in self.dependency_map:
            return True

        dependencies = self.dependency_map[address]

        # Return if *any* dependency is found

        for location in storage_write_cache:
            for dependency in dependencies:

                try:
                    solver.get_model([location == dependency])
                    return True
                except UnsatError:
                    continue

        return False
示例#24
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'] != "ADD":
        return []

    constraints = copy.deepcopy(node.constraints)

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

    # An integer overflow is possible if op0 + op1,
    constraints.append(UGT(op0 + op1, (2 ** 32) - 1))

    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(node.contract_name, node.function_name, instruction['address'], "Integer Overflow ",
                      "Warning")

        issue.description = "A possible integer overflow exists in the function {}.\n" \
                            "The addition may result in a value higher than the maximum representable integer.".format(node.function_name)
        issue.debug = solver.pretty_print_model(model)
        issues.append(issue)

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

    return issues
示例#25
0
def execute(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"):

                logging.debug("Opcode 0xfe detected.")

                try:
                    model = solver.get_model(node.constraints)
                    logging.debug("[EXCEPTIONS] MODEL: " + str(model))

                    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 += "This is acceptable in most situations. Note however that assert() should only be used to check invariants. Use require() for regular input checking. "
                    description += "The exception is triggered under the following conditions:\n\n"

                    for d in model.decls():

                        try:
                            condition = hex(model[d].as_long())
                        except:
                            condition = str(simplify(model[d]))

                        description += ("%s: %s\n" % (d.name(), condition))

                    description += "\n"

                    issues.append(
                        Issue(node.contract_name, node.function_name, address,
                              "Exception state", "Informational", description))

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

    return issues
def solve(call: Call) -> bool:
    """

    :param call:
    :return:
    """
    try:
        model = solver.get_model(call.state.mstate.constraints)
        log.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] MODEL: " + str(model))
        pretty_model = solver.pretty_print_model(model)

        log.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] main model: \n%s" % pretty_model)
        return True

    except UnsatError:
        log.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] no model found")
        return False
    def _can_change(self, constraints, variable):
        """Checks if the variable can change given some constraints.

        :param constraints:
        :param variable:
        :return:
        """
        _constraints = copy.deepcopy(constraints)
        try:
            model = solver.get_model(_constraints)
        except UnsatError:
            return False
        try:
            initial_value = int(str(model.eval(variable, model_completion=True)))
            return (
                self._try_constraints(constraints, [variable != initial_value])
                is not None
            )
        except AttributeError:
            return False
示例#28
0
def execute(statespace):

    logging.debug("Executing module: INTEGER")

    issues = []

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

        for state in node.states:

            instruction = state.get_current_instruction()

            if (instruction['opcode'] == "SUB"):

                stack = state.mstate.stack

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

                constraints = copy.deepcopy(node.constraints)

                if type(op0) == int and type(op1) == int:
                    continue

                if (re.search(r'calldatasize_', str(op0))) \
                    or (re.search(r'256\*.*If\(1', str(op0), re.DOTALL) or re.search(r'256\*.*If\(1', str(op1), re.DOTALL)) \
                    or (re.search(r'32 \+.*calldata', str(op0), re.DOTALL) or re.search(r'32 \+.*calldata', str(op1), re.DOTALL)):

                    # Filter for patterns that contain bening nteger 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

                    continue

                logging.debug("[INTEGER_UNDERFLOW] Checking SUB " + str(op0) +
                              ", " + str(op1) + " at address " +
                              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)

                        issue = Issue(node.contract_name, node.function_name,
                                      instruction['address'],
                                      "Integer Underflow", "Warning")

                        issue.description = "A possible integer underflow exists in the function " + node.function_name + ".\n" \
                            "The substraction may result in a value < 0."

                        issue.debug = solver.pretty_print_model(model)
                        issues.append(issue)

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

    return issues
示例#29
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=node.function_name,
                              address=instruction['address'],
                              swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
                              title="Integer Underflow",
                              _type="Warning")

                issue.description = "A possible integer underflow exists in the function `" + node.function_name + "`.\n" \
                                                                                                                   "The subtraction may result in a value < 0."

                issue.debug = solver.pretty_print_model(model)
                issues.append(issue)

            except UnsatError:
                logging.debug("[INTEGER_UNDERFLOW] no model found")
    return issues
示例#30
0
def _analyze_states(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):
                    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),
                    )
                    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"],
                        ))
                    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(constraint)
                state.annotate(OldBlockNumberUsedAnnotation())

            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):
                state.mstate.stack[-1].annotate(
                    PredictableValueAnnotation(
                        "block hash of a previous block"))
        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