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))
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))
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))
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)
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]) )
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), ]
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))
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), ]
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()
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)
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)
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)
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)
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
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)
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]))
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())
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))
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))
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)))]) )
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
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)
def test_delegatecall_to_other(self): self.state.calls.append(self.get_delegatecall(to=utils.bvv(0))) self.assertFalse(self.check_state(self.state))
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
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
def test_send_back_nothing(self): self.state.calls.append(self.get_call(utils.bvv(0))) self.assertFalse(self.check_state(self.state))
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, ) )
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.")
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)
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, ))