class EthPluginsTests(unittest.TestCase): def setUp(self): self.mevm = ManticoreEVM() def tearDown(self): # shutil.rmtree(self.mevm.workspace) del self.mevm @unittest.skip("failing") def test_verbose_trace(self): source_code = """contract X {}""" self.mevm.register_plugin(VerboseTrace()) # owner address is hardcodded so the contract address is predictable owner = self.mevm.create_account( balance=1000, address=0xAFB6D63079413D167770DE9C3F50DB6477811BDB ) # Initialize contract so it's constructor function will be traced self.mevm.solidity_create_contract(source_code, owner=owner, gas=90000) files = set(os.listdir(self.mevm.workspace)) # self.assertEqual(len(files), 0) # just a sanity check? workspace # contains .state_id and other config files # Shall produce a verbose trace file with self.assertLogs("manticore.core.manticore", level="INFO") as cm: self.mevm.finalize() prefix = "\x1b[34mINFO:\x1b[0m:m.c.manticore" # self.assertEqual(f'{prefix}:Generated testcase No. 0 - RETURN', cm.output[0]) self.assertEqual(f"{prefix}:Results in {self.mevm.workspace}", cm.output[0]) # self.assertEqual(f'{prefix}:Total time: {self.mevm._last_run_stats["time_elapsed"]}', cm.output[2]) self.assertEqual(len(cm.output), 1) import re files = set((f for f in os.listdir(self.mevm.workspace) if re.match(r"[^.].*", f))) expected_files = { "global_X.runtime_visited", "global_X_runtime.bytecode", "test_00000000.verbose_trace", "global_X.sol", "global_X.runtime_asm", "global_X.init_asm", "global_X.init_visited", "test_00000000.constraints", "command.sh", "global_X_init.bytecode", "test_00000000.tx", "test_00000000.pkl", "manticore.yml", "global.summary", "test_00000000.summary", "test_00000000.tx.json", "test_00000000.logs", "test_00000000.trace", } self.assertEqual(files, expected_files) result_vt_path = os.path.join(self.mevm.workspace, "test_00000000.verbose_trace") expected_vt_path = os.path.join(THIS_DIR, "data/verbose_trace_plugin_out") with open(result_vt_path) as res_fp, open(expected_vt_path) as exp_fp: res = res_fp.readlines() exp = exp_fp.readlines() self.assertEqual(len(res), len(exp)) self.assertEqual(len(res), 204) # Till line 184 the outputs shall be the same # Next there is a CODESIZE instruction that concretizes to different values each run # and as a result, the values in memory might differ. # # For some reason even setting `(set-option :random-seed 1)` in z3 doesn't help for i in range(184): self.assertEqual(res[i], exp[i], f"Difference on line {i}") till = 130 # number of chars that doesn't differ for i in range(184, 188): self.assertEqual(res[i][:till], exp[i][:till], f"Difference on line {i}") for i in range(188, 195): self.assertEqual(res[i], exp[i], f"Difference on line {i}") for i in range(195, 200): self.assertEqual(res[i][:till], exp[i][:till], f"Difference on line {i}") for i in range(200, len(res)): self.assertEqual(res[i], exp[i], f"Difference on line {i}")
# Bytecode only based analisys # No solidity, no compiler, no metadata m = ManticoreEVM() init_bytecode = unhexlify( b"608060405234801561001057600080fd5b506101cc806100206000396000f30060806040527f41000000000000000000000000000000000000000000000000000000000000006000366000818110151561003557fe5b905001357f010000000000000000000000000000000000000000000000000000000000000090047f0100000000000000000000000000000000000000000000000000000000000000027effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161415610135577fcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab6040518080602001828103825260088152602001807f476f7420616e204100000000000000000000000000000000000000000000000081525060200191505060405180910390a161019e565b7fcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab6040518080602001828103825260128152602001807f476f7420736f6d657468696e6720656c7365000000000000000000000000000081525060200191505060405180910390a15b0000a165627a7a72305820fd5ec850d8409e19cfe593b9ee3276cc3ac12b0e3406d965317dc9c1aeb7f2670029" ) user_account = m.create_account(balance=1000) print("[+] Creating a user account", user_account) print("[+] Init bytecode:", hexlify(init_bytecode)) print("[+] EVM init assembler:") for instr in evm.EVMAsm.disassemble_all(init_bytecode[:-44]): print(hex(instr.pc), instr) contract_account = m.create_contract(owner=user_account, init=init_bytecode) print("[+] Creating a contract account", contract_account) print("[+] Now the symbolic values") symbolic_data = m.make_symbolic_buffer(320) symbolic_value = m.make_symbolic_value() m.transaction(caller=user_account, address=contract_account, data=symbolic_data, value=symbolic_value) #Let seth know we are not sending more transactions m.finalize() print("[+] Look for results in %s" % m.workspace)
class EthBenchmark(unittest.TestCase): """ https://consensys.net/diligence/evm-analyzer-benchmark-suite/ """ def setUp(self): self.mevm = ManticoreEVM() self.mevm.verbosity(0) self.worksp = self.mevm.workspace def tearDown(self): self.mevm.finalize() shutil.rmtree(self.worksp) def _test(self, name, should_find, use_ctor_sym_arg=False): """ Tests DetectInvalid over the consensys benchmark suite """ mevm = self.mevm mevm.register_detector(DetectInvalid()) mevm.register_detector(DetectIntegerOverflow()) mevm.register_detector(DetectReentrancyAdvanced()) filename = os.path.join(THIS_DIR, 'contracts', 'consensys_benchmark', f'{name}.sol') if use_ctor_sym_arg: ctor_arg = (mevm.make_symbolic_value(), ) else: ctor_arg = () mevm.multi_tx_analysis(filename, contract_name='Benchmark', args=ctor_arg) expected_findings = set(((c, d) for b, c, d in should_find)) actual_findings = set(((c, d) for a, b, c, d in mevm.global_findings)) self.assertEqual(expected_findings, actual_findings) def test_assert_minimal(self): self._test('assert_minimal', {(95, 'INVALID instruction', False)}) def test_assert_constructor(self): self._test('assert_constructor', {(23, 'INVALID instruction', True)}) def test_assert_multitx_1(self): self._test('assert_multitx_1', set(), True) def test_assert_multitx_2(self): self._test('assert_multitx_2', {(150, 'INVALID instruction', False)}, True) def test_integer_overflow_minimal(self): self._test( 'integer_overflow_minimal', {(163, 'Unsigned integer overflow at SUB instruction', False)}) def test_integer_overflow_add(self): self._test( 'integer_overflow_add', {(163, 'Unsigned integer overflow at ADD instruction', False)}) def test_integer_overflow_mul(self): self._test( 'integer_overflow_mul', {(163, 'Unsigned integer overflow at MUL instruction', False)}) def test_integer_overflow_path_1(self): self._test('integer_overflow_path_1', set()) def test_integer_overflow_benign_1(self): self._test('integer_overflow_benign_1', set()) def test_integer_overflow_benign_2(self): self._test('integer_overflow_benign_2', set()) def test_integer_overflow_multitx_onefunc_feasible(self): self._test( 'integer_overflow_multitx_onefunc_feasible', {(185, 'Unsigned integer overflow at SUB instruction', False)}) def test_integer_overflow_multitx_onefunc_infeasible(self): self._test('integer_overflow_multitx_onefunc_infeasible', set()) def test_integer_overflow_multitx_multifunc_feasible(self): self._test( 'integer_overflow_multitx_multifunc_feasible', {(205, 'Unsigned integer overflow at SUB instruction', False)}) def test_integer_overflow_storageinvariant(self): self._test('integer_overflow_storageinvariant', set()) def test_integer_overflow_mapping_sym_1(self): self._test( 'integer_overflow_mapping_sym_1', {(135, 'Unsigned integer overflow at SUB instruction', False)}) def test_integer_overflow_mapping_sym_2(self): self._test('integer_overflow_mapping_sym_2', set()) @unittest.skip("Unsupported") def test_attribute_store(self): self._test('attribute_store', set()) @unittest.skip("Unsupported") def test_integer_overflow_mapping_strkey(self): self._test('integer_overflow_mapping_strkey', set()) def test_integer_overflow_storagepacking(self): self._test('integer_overflow_storagepacking', set()) @unittest.skip("Unsupported") def test_integer_overflow_bytes_param(self): self._test('integer_overflow_bytes_param', set()) def test_integer_overflow_staticarray(self): self._test('integer_overflow_staticarray', set()) def test_integer_overflow_mapping_word(self): self._test('integer_overflow_mapping_word', set()) def test_integer_overflow_mapping_struct(self): name = inspect.currentframe().f_code.co_name[5:] self._test(name, set()) def test_integer_overflow_mapping_mapping(self): name = inspect.currentframe().f_code.co_name[5:] self._test(name, set()) def test_integer_overflow_mapping_staticarray(self): name = inspect.currentframe().f_code.co_name[5:] self._test(name, set()) def test_integer_overflow_dynarray(self): name = inspect.currentframe().f_code.co_name[5:] self._test(name, set()) def test_reentrancy_nostateeffect(self): name = inspect.currentframe().f_code.co_name[5:] self._test(name, set()) def test_reentrancy_dao_fixed(self): name = inspect.currentframe().f_code.co_name[5:] self._test(name, set()) def test_reentrancy_dao(self): name = inspect.currentframe().f_code.co_name[5:] self._test(name, {(247, 'Reentrancy multi-million ether bug', False)}) @unittest.skip('too slow') #FIXME #TODO def test_eth_tx_order_dependence_multitx_1(self): name = inspect.currentframe().f_code.co_name[5:] self._test(name, set())
from manticore.ethereum import ManticoreEVM m = ManticoreEVM() with open('example.sol') as f: source_code = f.read() user_account = m.create_account(balance=1000) contract_account = m.solidity_create_contract(source_code, owner=user_account) symbolic_var = m.make_symbolic_value() contract_account.f(symbolic_var) print("Results are in {}".format(m.workspace)) m.finalize() # stop the exploration
class EthPluginsTests(unittest.TestCase): def setUp(self): self.mevm = ManticoreEVM() def tearDown(self): shutil.rmtree(self.mevm.workspace) del self.mevm def test_verbose_trace(self): source_code = '''contract X {}''' self.mevm.register_plugin(VerboseTrace()) owner = self.mevm.create_account(balance=1000) # Initialize contract so it's constructor function will be traced self.mevm.solidity_create_contract(source_code, owner=owner, gas=90000) files = set(os.listdir(self.mevm.workspace)) self.assertEqual(len(files), 0) # just a sanity check? # Shall produce a verbose trace file with self.assertLogs('manticore.core.manticore', level='INFO') as cm: self.mevm.finalize() prefix = '\x1b[34mINFO:\x1b[0m:m.c.manticore' self.assertEqual(f'{prefix}:Generated testcase No. 0 - RETURN', cm.output[0]) self.assertEqual(f'{prefix}:Results in {self.mevm.workspace}', cm.output[1]) self.assertEqual( f'{prefix}:Total time: {self.mevm._last_run_stats["time_elapsed"]}', cm.output[2]) self.assertEqual(len(cm.output), 3) files = set(os.listdir(self.mevm.workspace)) expected_files = { 'global_X.runtime_visited', 'global_X_runtime.bytecode', 'test_00000000.verbose_trace', 'global_X.sol', 'global_X.runtime_asm', 'global_X.init_asm', 'global_X.init_visited', 'test_00000000.constraints', 'command.sh', 'global_X_init.bytecode', 'test_00000000.tx', 'test_00000000.pkl', 'manticore.yml', 'global.summary', 'test_00000000.summary', 'test_00000000.tx.json', 'test_00000000.logs', 'test_00000000.trace' } self.assertEqual(files, expected_files) result_vt_path = os.path.join(self.mevm.workspace, 'test_00000000.verbose_trace') expected_vt_path = os.path.join(THIS_DIR, 'data/verbose_trace_plugin_out') with open(result_vt_path) as res_fp, open(expected_vt_path) as exp_fp: res = res_fp.readlines() exp = exp_fp.readlines() self.assertEqual(len(res), len(exp)) self.assertEqual(len(res), 204) # Till line 184 the outputs shall be the same # Next there is a CODESIZE instruction that concretizes to different values each run # and as a result, the values in memory might differ. # # For some reason even setting `(set-option :random-seed 1)` in z3 doesn't help for i in range(184): self.assertEqual(res[i], exp[i], f'Difference on line {i}') till = 130 # number of chars that doesn't differ for i in range(184, 188): self.assertEqual(res[i][:till], exp[i][:till], f'Difference on line {i}') for i in range(188, 195): self.assertEqual(res[i], exp[i], f'Difference on line {i}') for i in range(195, 200): self.assertEqual(res[i][:till], exp[i][:till], f'Difference on line {i}') for i in range(200, len(res)): self.assertEqual(res[i], exp[i], f'Difference on line {i}')
from manticore.ethereum import ManticoreEVM # initiate the blockchain m = ManticoreEVM() source_code = ''' pragma solidity^0.4.20; contract Simple { function f(uint a) payable public { if (a == 65) { revert(); } } } ''' # Initiate the accounts user_account = m.create_account(balance=1000) contract_account = m.solidity_create_contract(source_code, owner=user_account, balance=0) # Call f(a), with a symbolic value contract_account.f(m.SValue, user_account) print "Results are in %s" % m.workspace m.finalize() # stop the exploration
class Analyzer: def __init__(self, args): self._args = args self._main_evm = None self._test_cases = [] self._all_mutants = [] self._main_contract_results = [] self._conc_testcases = [] def _find_test_cases(self): print('Finding test cases ...') self._main_evm = ManticoreEVM(workspace_url=self._args.workspace) if self._args.quick_mode: self._args.avoid_constant = True self._args.exclude_all = True self._args.only_alive_testcases = True consts_evm = config.get_group("evm") consts_evm.oog = "ignore" consts.skip_reverts = True if consts.skip_reverts: self._main_evm.register_plugin(SkipRevertBasicBlocks()) if consts.explore_balance: self._main_evm.register_plugin(KeepOnlyIfStorageChanges()) if self._args.limit_loops: self._main_evm.register_plugin(LoopDepthLimiter()) with self._main_evm.kill_timeout(): self._main_evm.multi_tx_analysis( self._args.argv[0], contract_name=self._args.contract, tx_limit=self._args.txlimit, tx_use_coverage=not self._args.txnocoverage, tx_send_ether=not self._args.txnoether, tx_account=self._args.txaccount, tx_preconstrain=self._args.txpreconstrain, compile_args=vars(self._args), ) self._test_cases = list(self._main_evm.all_states) def _concretizing_testcases(self): for test_case_number in range(len(self._test_cases)): test_case = self._test_cases[test_case_number] print(f'Concretizing testcase {test_case_number + 1}') conc_txs = [] blockchain = test_case.platform self._main_evm.fix_unsound_symbolication(test_case) human_transactions = list(blockchain.human_transactions) for sym_tx in human_transactions: try: conc_tx = sym_tx.concretize(test_case) except: break conc_txs.append(conc_tx) if conc_txs: self._conc_testcases.append(conc_txs) def _find_mutants(self): self._all_mutants = sorted(os.listdir(self._args.argv[1])) def _run_test_case_on_mutant(self, mutant_name, conc_txs): mutant_mevm = self._run_test_case_on_contract( os.path.join(self._args.argv[1], mutant_name), conc_txs) mutant_blockchain_state = BlockChainState.create_from_evm(mutant_mevm) # terminate mutant_mevm.kill() mutant_mevm.remove_all() return mutant_blockchain_state def _run_test_cases_on_main_contract(self): for test_case_number in range(len(self._test_cases)): print( f'Start running test case {test_case_number + 1} on main contract' ) conc_txs = self._conc_testcases[test_case_number] mevm = self._run_test_case_on_contract(self._args.argv[0], conc_txs) main_blockchain_state = BlockChainState.create_from_evm(mevm) self._main_contract_results.append(main_blockchain_state) mevm.kill() mevm.remove_all() def _run_testcases_on_single_mutant(self, mutant_name, result_dict): for test_case_number in range(len(self._test_cases)): print( f'Start running test case {test_case_number + 1} on {mutant_name}' ) conc_txs = self._conc_testcases[test_case_number] mutant_blockchain_state = self._run_test_case_on_mutant( mutant_name, conc_txs) main_blockchain_state = self._main_contract_results[ test_case_number] if mutant_blockchain_state != main_blockchain_state: print( f'Mutant {mutant_name} killed by testcase {test_case_number + 1}' ) result_dict[mutant_name] = test_case_number return result_dict[mutant_name] = None @staticmethod def get_mutants_in_batch(all_mutants): mutants_num = len(all_mutants) number_of_batch = min(int(mutants_num / 10) + 1, 10) mutants_batch = [[] for _ in range(number_of_batch)] for i in range(mutants_num): mutants_batch[i % number_of_batch].append(all_mutants[i]) return mutants_batch def _run_test_cases_on_mutants(self): print(f'Running {len(self._test_cases)} testcases on mutants ...') manager = multiprocessing.Manager() result_dict = manager.dict() mutant_batchs = self.get_mutants_in_batch(self._all_mutants) workers = [] for i in range(len(mutant_batchs)): p = multiprocessing.Process( target=self._run_test_cases_on_mutants_batch, args=(mutant_batchs[i], result_dict)) workers.append(p) p.start() for p in workers: p.join() self._results = result_dict def _run_test_cases_on_mutants_batch(self, mutant_list, result_dict): for mutant in mutant_list: try: self._run_testcases_on_single_mutant(mutant, result_dict) except Exception as e: print(e) print( 'exception on running test case on mutant. continuing ...') def run(self): try: self._find_mutants() with record_manticore_time(): self._find_test_cases() self._concretizing_testcases() with record_project_time(): self._run_test_cases_on_main_contract() self._run_test_cases_on_mutants() self._print_result() global manticore_run_time, project_run_time print('manticore run time: ' + str(manticore_run_time)) print('project run time: ' + str(project_run_time)) if not self._args.no_testcases: self._main_evm.finalize( only_alive_states=self._args.only_alive_testcases) else: self._main_evm.kill() for plugin in list(self._main_evm.plugins): self._main_evm.unregister_plugin(plugin) finally: clean_dir() def _print_result(self): print('Write result in result.txt') f = open(f'result.txt', 'w') f.write('Not killed mutants:\n\n') not_killed_mutants = [ mutant_name for mutant_name, test_case_number in self._results.items() if test_case_number is None ] for mutant_name in not_killed_mutants: f.write(f'{mutant_name}\n') f.write('\n') f.write('Selected test cases\n\n') f2 = open('test_cases.txt', 'w') f2.write('All test cases:\n\n') selected_test_case_number = set([ test_case_number for test_case_number in self._results.values() if test_case_number is not None ]) for test_case_number in range(len(self._test_cases)): test_case = self._main_contract_results[test_case_number] is_selected = test_case_number in selected_test_case_number f2.write( f'------------------------- Test case {test_case_number+1} -------------------------\n\n' ) if is_selected: f2.write(f'* SELECTED\n\n') f.write( f'------------------------- Test case {test_case_number+1} -------------------------\n\n' ) f.write(str(test_case.transaction_state)) f.write('\n\n') f2.write(str(test_case.transaction_state)) f2.write('\n\n') f.close() f2.close() print('') print(f'Number of mutants: {len(self._all_mutants)}') print( f'Number of killed mutants: {len(self._all_mutants) - len(not_killed_mutants)}' ) def _run_test_case_on_contract(self, contract_code, conc_txs): m2 = ManticoreEVM() owner_account = m2.create_account( balance=10**10, name="owner", address=self._main_evm.accounts.get('owner').address) attacker_account = m2.create_account( balance=10**10, name="attacker", address=self._main_evm.accounts.get('attacker').address) try: call_args = get_argument_from_create_transaction( self._main_evm, conc_txs[0]) create_value = m2.make_symbolic_value() m2.constrain(create_value == conc_txs[0].value) contract_account = solidity_create_contract_with_zero_price( m2, contract_code, owner=owner_account, args=call_args, balance=create_value, gas=0, ) except Exception as e: return m2 for conc_tx in conc_txs[1:]: try: m2.transaction( caller=conc_tx.caller, address=contract_account, value=conc_tx.value, data=conc_tx. data, # data has all needed metadata like function id ([:4]) and argument passed to function gas=0, price=0) except Exception as e: return m2 return m2