Example #1
0
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())
Example #4
0
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
Example #5
0
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}')
Example #6
0
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
Example #7
0
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