def testReplace(self): old_env = env.Env(b"") new_env = old_env.clean_copy() state = State(new_env) state.storage_written[old_env.caller] = old_env.value state.replace(functools.partial(env.replace, old_env, state.env)) self.assertIs(state.storage_written[new_env.caller], new_env.value)
def testReplace2(self): a = env.Env(b"") sa = a.value + a.caller + a.origin b = a.clean_copy() sb = b.value + b.caller + b.origin self.assertIsNot(sa, sb) self.assertIs(env.replace(a, b, sa), sb)
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 testSameHashIfDifferentOrder(self): a = State() b = State() self.assertEqual(hash(a), hash(b)) e = env.Env(b"") a.solver.add(e.value == 1) a.solver.add(e.block_timestamp == 2) # Same thing, different order b.solver.add(e.block_timestamp == 2) b.solver.add(e.value == 1) self.assertEqual(hash(a), hash(b))
def test_env_replace_merge_with_recursive_hash(self): old_env = env.Env(b"") new_env = old_env.clean_copy() old_state = State(old_env) old_state.solver.add(Sha3(Sha3(old_env.caller)) == Sha3(old_env.value)) self.assertTrue(old_state.solver.satisfiable()) self.assertFalse( old_state.solver.satisfiable( extra_constraints=[old_env.value == 5])) new_state = old_state.copy() new_state.replace(functools.partial(env.replace, old_env, new_env)) new_state.replace(new_state.solver.regenerate_hash_symbols()) self.assertTrue(new_state.solver.satisfiable()) self.assertFalse( new_state.solver.satisfiable( extra_constraints=[new_env.value == 5])) self.assertTrue( new_state.solver.satisfiable( extra_constraints=[old_env.value == 5])) new_state.solver.add(old_env.value == new_env.value) self.assertTrue(new_state.solver.satisfiable()) self.assertFalse( new_state.solver.satisfiable( extra_constraints=[new_env.value == 5])) self.assertFalse( new_state.solver.satisfiable( extra_constraints=[old_env.value == 5])) old_state.solver = old_state.solver.combine([new_state.solver]) self.assertTrue(new_state.solver.satisfiable()) self.assertEqual(len(old_state.solver.constraints), 3) self.assertEqual(len(old_state.solver.hashes), len(new_state.solver.hashes) * 2)
def testHashWorks(self): state = State(env.Env(b"")) state.pc = 5 state.memory.write(0, 1, claripy.BVV(42, 8)) state.memory.write(10, 1, claripy.BVV(43, 8)) state.memory.write(20, 1, claripy.BVV(44, 8)) state_copy = state.copy() self.assertEqual(hash(state), hash(state_copy)) state.pc = 6 self.assertNotEqual(hash(state), hash(state_copy)) state_copy.pc = 6 self.assertEqual(hash(state), hash(state_copy)) state.memory.write(10, 1, claripy.BVV(45, 8)) self.assertNotEqual(hash(state), hash(state_copy)) state_copy.memory.write(10, 1, claripy.BVV(45, 8)) self.assertEqual(hash(state), hash(state_copy)) state.stack_push(state.env.calldata.read(0, 1)) self.assertNotEqual(hash(state), hash(state_copy)) state_copy.stack_push(state_copy.env.calldata.read(0, 1)) self.assertEqual(hash(state), hash(state_copy))
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.")
def testReplace(self): a = env.Env(b"") calldata_a = a.calldata.read(0, 32) b = a.clean_copy() self.assertIs(env.replace(a, b, a.value), b.value) self.assertIs(env.replace(a, b, calldata_a), b.calldata.read(0, 32))