Ejemplo n.º 1
0
    def test_sstore_symbolic(self):
        outcome1, outcome2 = self.outcomes([
            PUSH1,
            42,
            PUSH1,
            0,
            SSTORE,
            PUSH1,
            0,
            SLOAD,
            PUSH1,
            43,
            CALLVALUE,
            SSTORE,
            PUSH1,
            0,
            SLOAD,
        ])

        self.assertEqual(len(outcome1.storage_read), 0)
        self.assertEqual(len(outcome1.storage_written), 1)
        self.assertBEqual(outcome1.storage_written[utils.bvv(0)],
                          utils.bvv(43))

        self.assertEqual(len(outcome2.storage_read), 0)
        self.assertEqual(len(outcome2.storage_written), 2)
        self.assertBEqual(outcome2.storage_written[utils.bvv(0)],
                          utils.bvv(42))
        self.assertBEqual(outcome2.storage_written[outcome2.env.value],
                          utils.bvv(43))
Ejemplo n.º 2
0
    def test_write_write_and_selfdestruct(self):
        state = State(self.env)
        # Anybody can set owner
        state_write1 = state.copy()
        state_write1.storage_written = {utils.bvv(0): self.env.calldata.read(4, 32)}

        # Onlyowner: set a magic constant allowing the selfdestruct bug, at an
        # user-controlled storage key.
        state_write2 = state.copy()
        read_0 = claripy.BVS("storage[0]", 256)
        state_write2.storage_read = {utils.bvv(0): read_0}
        state_write2.storage_written = {
            self.env.calldata.read(36, 32): self.env.calldata.read(4, 32)
        }
        state_write2.solver.add(read_0 == self.env.caller)

        # Suicide, when owner and magic constant set
        state_selfdestruct = state.copy()
        read_0 = claripy.BVS("storage[0]", 256)
        read_40 = claripy.BVS("storage[4]", 256)
        state_selfdestruct.storage_read = {utils.bvv(0): read_0, utils.bvv(40): read_40}
        state_selfdestruct.solver.add(self.env.caller == read_0)
        state_selfdestruct.solver.add(read_40 == 1337)
        state_selfdestruct.selfdestruct_to = self.env.caller

        states = [state_write1, state_write2, state_selfdestruct]
        random.shuffle(states)

        storage = {0: 123456789, 40: 387642}
        for s in itertools.combinations(states, 2):
            self.assertFalse(self.check_states(s, mock_storage=storage))
        self.assertTrue(self.check_states(states, mock_storage=storage))
Ejemplo n.º 3
0
    def test_sha3_value2(self):
        """Same as above, but we need to pass the computed SHA3."""
        state = State(self.env)

        state_write = state.copy()
        state_write.storage_written = {
            utils.bvv(0): Sha3(self.env.calldata.read(4, 32))
        }

        state_selfdestruct = state.copy()
        state_selfdestruct.selfdestruct_to = self.env.calldata.read(36, 32)
        storage_input = claripy.BVS("storage[0]", 256)
        state_selfdestruct.storage_read = {utils.bvv(0): storage_input}
        state_selfdestruct.solver.add(
            storage_input == self.env.calldata.read(4, 32))
        state_selfdestruct.solver.add(storage_input != 0)

        storage = {0: 0}
        self.assertTrue(
            self.check_states([state_write, state_selfdestruct],
                              mock_storage=storage))
        self.assertFalse(
            self.check_states([state_selfdestruct], mock_storage=storage))
        self.assertFalse(self.check_states([state_write],
                                           mock_storage=storage))
Ejemplo n.º 4
0
    def _read_storage(self, state, key):
        # TODO: We do an approximation here: if it cannot be computed or it can
        # be multiple things, we assume the initial storage is 0...
        # Instead we could use a cascade of claripy.If(key, value, claripy.If(...
        # reflecting the actual storage (if there are not too many keys in storage.
        logger.debug("Reading storage %r" % key)
        try:
            keys = state.solver.eval(key, 2)
            if len(keys) > 1:
                logger.info("Multiple values possible for key %r", key)
                return utils.bvv(0)
            assert len(keys) == 1
            key = keys[0]
            assert isinstance(key, numbers.Number)
        except claripy.errors.UnsatError as e:
            # Should not be too bad, because for the same key we will reuse the
            # same cache.
            logger.debug("Encountered an exception when resolving key %r: %r",
                         key, e)
            return utils.bvv(0)

        if key in self.storage_cache:
            value = self.storage_cache[key]
        else:
            hex_addr = self.web3.toChecksumAddress(
                utils.number_to_address(utils.bvv_to_number(self.address)))
            value = self.web3.toInt(self.web3.eth.getStorageAt(hex_addr, key))
            self.storage_cache[key] = value

        return utils.bvv(value)
Ejemplo n.º 5
0
    def test_solver_one_var(self):
        s = get_solver()
        in1 = claripy.BVS("in1", 256)

        self.assertFalse(s.satisfiable(extra_constraints=[Sha3(in1) == 42]))
        self.assertFalse(s.satisfiable(extra_constraints=[Sha3(in1) == 0]))
        self.assertTrue(s.satisfiable(extra_constraints=[Sha3(in1) == Sha3(bvv(42))]))
        self.assertTrue(s.satisfiable(extra_constraints=[Sha3(in1) == Sha3(bvv(0))]))
        self.assertTrue(
            s.satisfiable(extra_constraints=[Sha3(in1 + 1) + 2 == Sha3(bvv(0)) + 2])
        )
Ejemplo n.º 6
0
 def get_delegatecall(self, to=None):
     if to is None:
         to = self.env.caller
     return [
         utils.bvv(0),
         utils.bvv(0),
         utils.bvv(0),
         utils.bvv(0),
         to,
         utils.bvv(0),
     ]
Ejemplo n.º 7
0
    def test_read_concrete(self):
        self.analyzer.storage_cache = FakeStorage({0: 0xBAD1DEA})

        self.state.storage_read[utils.bvv(0)] = claripy.BVS("storage[0]", 256)
        self.state.selfdestruct_to = self.state.storage_read[utils.bvv(0)]
        self.assertFalse(self.check_state(self.state))

        self.state.calls.append(
            self.get_call(
                Web3.toWei(1, "ether") *
                self.state.storage_read[utils.bvv(0)]))
        self.assertTrue(self.check_state(self.state))
Ejemplo n.º 8
0
 def get_call(self, value, to=None):
     if to is None:
         to = self.env.caller
     return [
         utils.bvv(0),
         utils.bvv(0),
         utils.bvv(0),
         utils.bvv(0),
         value,
         to,
         utils.bvv(0),
     ]
Ejemplo n.º 9
0
    def test_exhaustive_storage(self):
        self.analyzer.actual_storage = {1: 0xBAD1DEA}
        self.analyzer.actual_storage_exhaustive = True

        self.state.storage_read[utils.bvv(0)] = claripy.BVS("storage[0]", 256)
        self.state.selfdestruct_to = self.state.storage_read[utils.bvv(0)]

        # Same as above, but we suicide to 0 instead of caller.
        with patch.object(self.analyzer,
                          "_read_storage_key") as mock_read_storage_key:
            self.assertFalse(self.check_state(self.state))
            mock_read_storage_key.assert_not_called()
Ejemplo n.º 10
0
    def test_non_exhaustive_storage(self):
        self.analyzer.actual_storage = {1: 0xBAD1DEA}
        self.analyzer.actual_storage_exhaustive = False

        self.state.storage_read[utils.bvv(0)] = claripy.BVS("storage[0]", 256)
        self.state.selfdestruct_to = self.state.storage_read[utils.bvv(0)]

        # Suicide to storage[0] that contains our address (state.env.caller)
        with patch.object(self.analyzer,
                          "_read_storage_key") as mock_read_storage_key:
            mock_read_storage_key.return_value = utils.bvv_to_number(
                self.state.env.caller)
            self.assertTrue(self.check_state(self.state))
            mock_read_storage_key.assert_called_with(0)
Ejemplo n.º 11
0
    def test_non_exhaustive_storage2(self):
        """Same as the previous test, but we suicide to 0 so it doesn't work."""
        self.analyzer.actual_storage = {1: 0xBAD1DEA}
        self.analyzer.actual_storage_exhaustive = False

        self.state.storage_read[utils.bvv(0)] = claripy.BVS("storage[0]", 256)
        self.state.selfdestruct_to = self.state.storage_read[utils.bvv(0)]

        # Same as above, but we suicide to 0 instead of caller.
        with patch.object(self.analyzer,
                          "_read_storage_key") as mock_read_storage_key:
            mock_read_storage_key.return_value = 0
            self.assertFalse(self.check_state(self.state))
            mock_read_storage_key.assert_called_with(0)
Ejemplo n.º 12
0
    def runTest(self):
        logger.info("Compiling contract %s" % self.filename)
        p = subprocess.run(
            [
                "solc", "--optimize", "--combined-json=bin-runtime",
                self.filename
            ],
            capture_output=True,
            text=True,
        )
        self.assertEqual(p.returncode, 0,
                         "solc compilation failed:\n%s" % p.stderr)

        output = json.loads(p.stdout)

        assert "contracts" in output
        identifier, properties = list(output["contracts"].items())[0]
        bin_runtime = properties["bin-runtime"]

        logger.info("Runtime bytecode: %s", bin_runtime)
        bin_runtime = codecs.decode(bin_runtime, "hex")
        logger.info("Compiled. Symbolic execution.")

        e = env.Env(
            bin_runtime,
            address=utils.bvv(int(ADDRESS, 16)),
            caller=utils.DEFAULT_CALLER,
            origin=utils.DEFAULT_CALLER,
            balance=utils.bvv(BALANCE),
        )
        s = sm.SymbolicMachine(e)
        s.execute(timeout_sec=EXEC_TIMEOUT)

        self.assertTrue(s.outcomes)

        ra = recursive_analyzer.RecursiveAnalyzer(
            max_wei_to_send=MAX_TO_SEND,
            min_wei_to_receive=MIN_TO_RECEIVE,
            block="invalid",
        )

        # Never contact the blockchain, instead all the storage are 0
        ra.actual_storage = {}

        bug = ra.check_states(s.outcomes,
                              timeout=ANALYSIS_TIMEOUT,
                              max_depth=MAX_TRANSACTION_DEPTH)

        self.assertTrue(bug, self.filename)
Ejemplo n.º 13
0
 def test_fakestorage_raises(self):
     """If the code accesses a FakeStorage key that we didn't specify,
        it should crash (only meant for testing)."""
     self.analyzer.storage_cache = FakeStorage({42: 0xBAD1DEA})
     self.state.storage_read[utils.bvv(0)] = claripy.BVS("storage[0]", 256)
     with self.assertRaises(KeyError):
         self.check_state(self.state)
Ejemplo n.º 14
0
    def _read_storage(self, state, key):
        logger.debug("Reading storage %r" % key)

        if self.actual_storage is None:
            self._fill_actual_storage()

        # If our storage is not exhaustive, let's try to concretize the key and read the
        # corresponding storage directly.
        if not self.actual_storage_exhaustive:
            try:
                concrete_keys = state.solver.eval(key, 2)
            except claripy.errors.UnsatError as e:
                # We will lose accuracy, and assume that our actual_storage is exhaustive...
                logger.debug(
                    "Encountered an exception when resolving key %r: %r", key,
                    e)
            else:
                for concrete_key in concrete_keys:
                    if concrete_key not in self.actual_storage:
                        self.actual_storage[
                            concrete_key] = self._read_storage_key(
                                concrete_key)
                # Warning: Here we used to return the value if there was a single solution,
                # however sha3 solver may artificially pin a key temporarily and return a single
                # solution where there could be more. So we always use a claripy.If.

        symbolic_storage = utils.bvv(0)  # When uninitialized: 0
        for k, v in self.actual_storage.items():
            if v != 0:
                symbolic_storage = claripy.If(key == k, v, symbolic_storage)

        return symbolic_storage
Ejemplo n.º 15
0
    def test_sload_symbolic(self):
        outcome1, outcome2 = self.outcomes(
            [PUSH1, 42, CALLVALUE, SSTORE, PUSH1, 0, SLOAD])
        self.assertEqual(len(outcome1.storage_read), 0)
        self.assertEqual(len(outcome1.storage_written), 1)

        self.assertTrue(utils.bvv(0) in outcome2.storage_read)
        self.assertEqual(len(outcome2.storage_read), 1)
Ejemplo n.º 16
0
    def test_write_and_selfdestruct(self):
        state = State(self.env)

        state_write = state.copy()
        state_write.storage_written = {utils.bvv(0): self.env.calldata.read(4, 32)}

        state_selfdestruct = state.copy()
        state_selfdestruct.selfdestruct_to = self.env.calldata.read(4, 32)
        storage_0 = claripy.BVS("storage[0]", 256)
        state_selfdestruct.storage_read = {utils.bvv(0): storage_0}
        state_selfdestruct.solver.add(storage_0 == 0xDEADBEEF0101)

        storage = {0: 0xBAD1DEA}
        self.assertTrue(
            self.check_states([state_write, state_selfdestruct], mock_storage=storage)
        )
        self.assertFalse(self.check_states([state_selfdestruct], mock_storage=storage))
        self.assertFalse(self.check_states([state_write]))
Ejemplo n.º 17
0
    def test_solver_recursive(self):
        s = get_solver()
        in1 = claripy.BVS("in1", 256)
        in2 = claripy.BVS("in2", 256)

        self.assertFalse(
            s.satisfiable(extra_constraints=[Sha3(Sha3(in1)) == 0]))
        self.assertFalse(
            s.satisfiable(extra_constraints=[Sha3(Sha3(in1)) == Sha3(bvv(0))]))
        self.assertTrue(
            s.satisfiable(
                extra_constraints=[Sha3(Sha3(in1)) == Sha3(Sha3(bvv(0)))]))

        s.add(Sha3(in1) == Sha3(in2))
        self.assertTrue(s.satisfiable())

        self.assertTrue(
            s.satisfiable(
                extra_constraints=[Sha3(Sha3(in1) + 1) == Sha3(Sha3(in2) +
                                                               1)]))

        s.add(Sha3(Sha3(in1) + 1) == Sha3(Sha3(in2) + 1))
        self.assertTrue(s.satisfiable())

        self.assertFalse(
            s.satisfiable(
                extra_constraints=[Sha3(Sha3(in1) + 2) == Sha3(Sha3(in2) +
                                                               1)]))
        self.assertFalse(
            s.satisfiable(
                extra_constraints=[Sha3(Sha3(in1)) + 1 == Sha3(Sha3(in2) +
                                                               1)]))

        s.add(Sha3(Sha3(in1)) + 3 == Sha3(Sha3(in2)) + 1)
        self.assertFalse(s.satisfiable())

        s_copy = s.branch()
        self.assertFalse(s_copy.satisfiable())
Ejemplo n.º 18
0
    def test_sha3_value1(self):
        """Exercise comparison of two SHA3 (as values)."""
        state = State(self.env)

        state_write = state.copy()
        state_write.storage_written = {
            utils.bvv(0): Sha3(self.env.calldata.read(4, 32))
        }

        state_selfdestruct = state.copy()
        state_selfdestruct.selfdestruct_to = self.env.calldata.read(36, 32)
        storage_input = claripy.BVS("storage[0]", 256)
        state_selfdestruct.storage_read = {utils.bvv(0): storage_input}
        state_selfdestruct.solver.add(
            storage_input == Sha3(self.env.calldata.read(4, 32))
        )

        storage = {0: 0}
        self.assertTrue(
            self.check_states([state_write, state_selfdestruct], mock_storage=storage)
        )
        self.assertFalse(self.check_states([state_selfdestruct], mock_storage=storage))
        self.assertFalse(self.check_states([state_write], mock_storage=storage))
Ejemplo n.º 19
0
    def test_sstore(self):
        (outcome, ) = self.outcomes([
            PUSH1,
            42,
            PUSH1,
            0,
            SSTORE,
            PUSH1,
            0,
            SLOAD,
            PUSH1,
            43,
            PUSH1,
            0,
            SSTORE,
            PUSH1,
            0,
            SLOAD,
        ])

        self.assertEqual(len(outcome.storage_read), 0)
        self.assertEqual(len(outcome.storage_written), 1)
        self.assertBEqual(outcome.storage_written[utils.bvv(0)], utils.bvv(43))
Ejemplo n.º 20
0
    def test_solver_recursive_unbalanced(self):
        s = get_solver()
        in1 = claripy.BVS("in1", 256)
        in2 = claripy.BVS("in2", 256)

        self.assertFalse(
            s.satisfiable(extra_constraints=[Sha3(Sha3(in1)) == Sha3(bvv(0))])
        )
        self.assertTrue(s.satisfiable(extra_constraints=[Sha3(Sha3(in1)) == Sha3(in2)]))
        logging.debug("here")
        self.assertTrue(s.satisfiable(extra_constraints=[Sha3(in1) == Sha3(Sha3(in2))]))

        self.assertTrue(
            s.satisfiable(extra_constraints=[Sha3(Sha3(Sha3(in1))) == Sha3(in2)])
        )
        self.assertTrue(
            s.satisfiable(extra_constraints=[Sha3(in1) == Sha3(Sha3(Sha3(in2)))])
        )
Ejemplo n.º 21
0
    def _read_storage(self, state, key):
        logger.debug("Reading storage %r" % key)

        if self.actual_storage is None:
            self._fill_actual_storage()

        # If our storage is not exhaustive, let's try to concretize the key and read the
        # corresponding storage directly.
        if not self.actual_storage_exhaustive:
            try:
                concrete_keys = state.solver.eval(key, 2)
            except claripy.errors.UnsatError as e:
                # We will lose accuracy, and assume that our actual_storage is exhaustive...
                logger.debug(
                    "Encountered an exception when resolving key %r: %r", key,
                    e)
            else:
                for concrete_key in concrete_keys:
                    if concrete_key not in self.actual_storage:
                        self.actual_storage[
                            concrete_key] = self._read_storage_key(
                                concrete_key)
                if len(concrete_keys) == 1:
                    return self.actual_storage[concrete_keys[0]]
                else:
                    # We will lose accuracy, and assume that our actual_storage is exhaustive...
                    logger.debug(
                        "Non-exhaustive storage and multiple values possible for key %r",
                        key,
                    )

        symbolic_storage = utils.bvv(0)  # When uninitialized: 0
        for k, v in self.actual_storage.items():
            if v != 0:
                symbolic_storage = claripy.If(key == k, v, symbolic_storage)

        return symbolic_storage
Ejemplo n.º 22
0
 def test_sload(self):
     (outcome, ) = self.outcomes([PUSH1, 0, SLOAD, PUSH1, 0, SLOAD])
     self.assertTrue(utils.bvv(0) in outcome.storage_read)
     self.assertEqual(len(outcome.storage_read), 1)
Ejemplo n.º 23
0
 def test_delegatecall_to_other(self):
     self.state.calls.append(self.get_delegatecall(to=utils.bvv(0)))
     self.assertFalse(self.check_state(self.state))
Ejemplo n.º 24
0
    def check_state(self, state, path=None):
        """Check a reachable state for bugs"""
        logger.debug("Check state: %s", state)
        logger.debug("Constraints: %s", state.solver.constraints)

        solver = state.solver.branch()

        if path is None:
            path = [state]
            # Static read were we never wrote, but we know the key is not symbolic.
            # So we go and fetch it.
            for key, value in state.storage_read.items():
                constraint = state.storage_read[key] == self._read_storage(
                    state, key)
                solver.add(constraint)
                logger.debug("Add storage constraint: %s", constraint)

        for s in path:
            solver.add(list(s.env.extra_constraints()))
            solver.add([
                s.env.caller == utils.DEFAULT_CALLER,
                s.env.origin == utils.DEFAULT_CALLER,
            ])

        # Calls
        total_sent = sum(s.env.value for s in path)
        sent_constraints = [s.env.value < self.max_wei_to_send for s in path]

        total_received_by_me = utils.bvv(0)
        total_received = utils.bvv(0)

        for call in state.calls:
            # TODO: Improve delegatecall support! And make it clearer it's
            # delegatecall, not just based on the length.
            assert 6 <= len(call) <= 7
            value, to, gas = call[-3:]  # pylint: disable=unused-variable,invalid-name

            delegatecall = len(call) == 6

            if delegatecall:
                if solver.satisfiable(
                        extra_constraints=[to[159:0] == self.caller[159:0]]):
                    logger.info("Found delegatecall bug.")
                    solver.add(to[159:0] == self.caller[159:0])
                    return solver
            else:
                total_received_by_me += claripy.If(
                    to[159:0] == self.caller[159:0], value, utils.bvv(0))
                total_received += value
                solver.add(value <= total_sent + path[0].env.balance)

        final_balance = path[0].env.balance + total_sent - total_received

        # Suicide
        if state.selfdestruct_to is not None:
            constraints = [
                final_balance >= self.min_wei_to_receive,
                state.selfdestruct_to[159:0] == self.caller[159:0],
            ]
            logger.debug("Check for selfdestruct bug with constraints %s",
                         constraints)
            if solver.satisfiable(extra_constraints=constraints):
                logger.info("Found selfdestruct bug.")
                solver.add(constraints)
                return solver

        if total_received_by_me is utils.bvv(0):
            return

        logger.debug("Found calls back to caller: %s", total_received_by_me)

        solver.add(sent_constraints)
        solver.add([
            claripy.SGE(final_balance, 0),
            total_received_by_me > total_sent,  # I get more than what I sent?
            total_received_by_me > self.min_wei_to_receive,
        ])

        if solver.satisfiable():
            logger.info("Found call bug.")
            return solver
Ejemplo n.º 25
0
    def check_state(self, state, path=None):
        """Check a reachable state for bugs"""
        logger.debug("Check state: %s", state)
        logger.debug("Constraints: %s", state.solver.constraints)

        read_constraints = []
        extra_constraints = []  # From the environment (block number, whatever)

        if path is None:
            path = [state]
            # Static read were we never wrote, but we know the key is not symbolic.
            # So we go and fetch it.
            for key, value in state.storage_read.items():
                constraint = state.storage_read[key] == self._read_storage(
                    state, key)
                read_constraints.append(constraint)
                logger.debug("Add constraint: %s", constraint)

        for s in path:
            extra_constraints += s.env.extra_constraints()
            extra_constraints += [
                s.env.caller == utils.DEFAULT_CALLER,
                s.env.origin == utils.DEFAULT_CALLER,
            ]

        # Calls
        total_sent = sum(s.env.value for s in path)
        sent_constraints = [s.env.value < self.max_wei_to_send for s in path]
        total_received_by_me = utils.bvv(0)
        total_received_by_others = utils.bvv(0)

        for call in state.calls:
            value, to, gas = call[-3:]  # pylint: disable=unused-variable,invalid-name
            if state.solver.satisfiable(
                    extra_constraints=[to[159:0] == self.caller[159:0]]):
                state.solver.add(to[159:0] == self.caller[159:0])
                total_received_by_me += value
            else:
                total_received_by_others += value

        final_balance = (path[0].env.balance + total_sent -
                         total_received_by_me - total_received_by_others)

        # Suicide
        if state.selfdestruct_to is not None:
            constraints = (extra_constraints + read_constraints + [
                final_balance >= self.min_wei_to_receive,
                state.selfdestruct_to[159:0] == self.caller[159:0],
            ])
            logger.debug("Check for selfdestruct bug with constraints %s",
                         constraints)
            if state.solver.satisfiable(extra_constraints=constraints):
                logger.info("Found selfdestruct bug.")
                return True

        if total_received_by_me is utils.bvv(0):
            return False

        logger.debug("Found calls back to caller: %s", total_received_by_me)

        constraints = (
            sent_constraints + extra_constraints + read_constraints + [
                final_balance >= 0,
                total_received_by_me >
                total_sent,  # I get more than what I sent?
                total_received_by_me > self.min_wei_to_receive,
            ])

        logger.debug("Extra constraints: %r", constraints)

        if state.solver.satisfiable(extra_constraints=constraints):
            logger.info("Found call bug.")
            return True

        return False
Ejemplo n.º 26
0
 def test_send_back_nothing(self):
     self.state.calls.append(self.get_call(utils.bvv(0)))
     self.assertFalse(self.check_state(self.state))
Ejemplo n.º 27
0
    def test_send_after_write(self):
        state = State(self.env)

        # We send storage[0]
        state_send = state.copy()
        storage_0 = claripy.BVS("storage[0]", 256)
        state_send.storage_read = {utils.bvv(0): storage_0}
        state_send.calls.append(self.get_call(storage_0))

        # storage[0] is 0.5 ETH
        storage = {0: Web3.toWei(0.5, "ether")}
        self.assertTrue(self.check_states([state_send], mock_storage=storage))

        # storage[0] is 0 ETH
        storage = {0: 0}
        self.assertFalse(self.check_states([state_send], mock_storage=storage))

        # storage[0] is still 0 ETH initially, but we have an arbitrary write now
        state_write = state.copy()
        state_write.storage_written = {utils.bvv(0): self.env.calldata.read(4, 32)}
        state_write.solver.add(self.env.calldata.read(0, 4) == 0x1337)
        state_write.solver.add(self.env.calldata.read(4, 32) < Web3.toWei(1, "ether"))

        self.assertFalse(self.check_states([state_write], mock_storage=storage))
        self.assertTrue(
            self.check_states([state_send, state_write], mock_storage=storage)
        )
        self.assertTrue(
            self.check_states([state_write, state_send], mock_storage=storage)
        )

        # ...arbitrary write of 1 wei only, which is too little
        state_write_0 = state_write.copy()
        state_write_0.solver.add(self.env.calldata.read(4, 32) == 1)
        self.assertFalse(
            self.check_states([state_write_0, state_send], mock_storage=storage)
        )

        # ...arbitrary write only if the block timestamp is <10, which is impossible.
        state_write_ts = state_write.copy()
        state_write_ts.solver.add(self.env.block_timestamp < 10)
        self.assertFalse(
            self.check_states([state_write_ts, state_send], mock_storage=storage)
        )

        self.assertFalse(
            self.check_states(
                [state_write_0, state_send, state_write_ts], mock_storage=storage
            )
        )

        # now we put all these state_write* together, so there is a solution.
        self.assertTrue(
            self.check_states(
                [state_write_0, state_send, state_write, state_write_ts],
                mock_storage=storage,
            )
        )
        self.assertTrue(
            self.check_states(
                [state_write_0, state_write, state_write_ts, state_send],
                mock_storage=storage,
            )
        )
Ejemplo n.º 28
0
def main():
    args = parser.parse_args()

    if args.v.isnumeric():
        coloredlogs.install(level=int(args.v))
    elif hasattr(logging, args.v.upper()):
        coloredlogs.install(level=getattr(logging, args.v.upper()))
    else:
        err_exit("Logging should be DEBUG/INFO/WARNING/ERROR.")

    try:
        logging.debug("Node working. Block %i ", w3.eth.blockNumber)
    except web3.exceptions.CannotHandleRequest:
        err_exit(
            "Seems like Web3.py can't connect to your Ethereum node.\n"
            "If you don't have one and you want to use Infura, you can set "
            "WEB3_PROVIDER_URI as follows:\n"
            "$ export WEB3_PROVIDER_URI='https://mainnet.infura.io'")

    if args.contract_addr == "-":
        # Let's read the runtime bytecode from stdin
        code = sys.stdin.read().strip("\n")
        if not code.isalnum():
            err_exit(
                "Runtime bytecode read from stdin needs to be hexadecimal.")
        code = codecs.decode(code, "hex")
        # Dummy address, dummy balance
        args.contract_addr = "0xDEADBEEF00000000000000000000000000000000"
        if not args.force_balance:
            args.force_balance = Web3.toWei(1.337, "ether")
    else:
        code = w3.eth.getCode(args.contract_addr, block_identifier=args.block)

    balance = args.force_balance or w3.eth.getBalance(
        args.contract_addr, block_identifier=args.block)

    print("Analyzing contract at %s with balance %f ether." %
          (args.contract_addr, Web3.fromWei(balance, "ether")))

    if balance < args.min_to_receive:
        err_exit("Balance is smaller than --min-to-receive: "
                 "the analyzer will never find anything.")

    if args.summarize:
        logging.info(
            "Summarizer enabled, we won't constrain the caller/origin "
            "so more of the contract can get explored. "
            "It may be slower.")
        e = env.Env(
            code,
            address=utils.bvv(int(args.contract_addr, 16)),
            balance=utils.bvv(balance),
        )
    else:
        e = env.Env(
            code,
            address=utils.bvv(int(args.contract_addr, 16)),
            caller=utils.DEFAULT_CALLER,
            origin=utils.DEFAULT_CALLER,
            balance=utils.bvv(balance),
        )

    print("Starting symbolic execution step...")

    s = sm.SymbolicMachine(e, fuzz=not args.disable_fuzzing)
    s.execute(timeout_sec=args.exec_timeout)

    print("Symbolic execution finished with coverage %i%%." %
          int(s.get_coverage() * 100))
    print("Outcomes: %i interesting. %i total and %i unfinished paths." % (
        sum(int(o.is_interesting()) for o in s.outcomes),
        len(s.outcomes),
        len(s.partial_outcomes),
    ))

    if args.summarize:
        print()
        print("Methods from the summarizer:")
        summary.HumanSummarizer(s).print_methods()

    print()
    print("Starting analysis step...")

    ra = recursive_analyzer.RecursiveAnalyzer(
        max_wei_to_send=args.max_to_send,
        min_wei_to_receive=args.min_to_receive,
        block=args.block,
    )
    bug = ra.check_states(s.outcomes,
                          timeout=args.analysis_timeout,
                          max_depth=args.max_transaction_depth)

    if bug:
        solver = bug[2]
        if logging.getLogger().isEnabledFor(logging.DEBUG):
            print("Composite state:")
            print(bug[0].debug_string())
            print()
            print()
        print("Path:")
        for i, state in enumerate(bug[1]):
            print()
            print("Transaction %i, symbolic state:" % (i + 1))
            print(state.debug_string())
            print()
            print("Transaction %i, example solution:" % (i + 1))
            print(state.env.solution_string(solver))
            print()
        print()
        print("======> Bug found! Need %i transactions. <======" % len(bug[1]))
    else:
        print("Nothing to report.")
Ejemplo n.º 29
0
import random
import numbers
import codecs

import claripy

from pakala.env import Env
from pakala import sm
from pakala import utils

# pylint: disable=undefined-variable
# flake8: noqa

from eth.vm.opcode_values import *

BVV_0 = utils.bvv(0)
BVV_1 = utils.bvv(1)


class TestSymbolicMachine(unittest.TestCase):
    """Basic tests for the members of the symbolic machine."""
    def setUp(self):
        code = codecs.decode(
            "6003600302600f56601b60006000a15b6101a5600060"
            "00a160019003801515600f57600660006000a1",
            "hex",
        )
        env = Env(code)

        self.sm = sm.SymbolicMachine(env)
Ejemplo n.º 30
0
    def test_with_new_env(self):
        env = Env(b"")
        state = State(env)

        storage_0 = claripy.BVS("storage[0]", 256)
        storage_1 = claripy.BVS("storage[0]", 256)
        storage_2 = claripy.BVS("storage[0]", 256)
        state.storage_read[utils.bvv(0)] = storage_0
        state.storage_read[utils.bvv(1)] = storage_1
        state.storage_read[utils.bvv(2)] = storage_2
        state.storage_written[utils.bvv(0)] = utils.bvv(0)
        state.storage_written[utils.bvv(1)] = utils.bvv(0)
        state.storage_written[utils.bvv(2)] = utils.bvv(0)

        state.calls.append([
            utils.bvv(1),
            storage_0 + storage_1 + storage_2,
            utils.bvv(2),
            5 * (storage_0 + storage_1 + storage_2),
        ])
        state.solver.add(storage_0 == 42)
        state.solver.add(storage_1 == 0)
        state.solver.add(storage_2 == 0)

        self.assertEqual(state.solver.eval(state.calls[0][1], 2), (42, ))

        for i in range(3):
            new_state = with_new_env(state)

            self.assertIsNot(state.env.value, new_state.env.value)
            self.assertIsNot(state.storage_read[utils.bvv(0)],
                             new_state.storage_read[utils.bvv(0)])
            self.assertIsNot(state.storage_read[utils.bvv(1)],
                             new_state.storage_read[utils.bvv(1)])
            self.assertIsNot(state.storage_read[utils.bvv(2)],
                             new_state.storage_read[utils.bvv(2)])

            self.assertNotEqual(new_state.solver.eval(state.calls[0][1], 2),
                                (42, ))
            self.assertEqual(new_state.solver.eval(new_state.calls[0][1], 2),
                             (42, ))