Exemple #1
0
    def fire_lasers(self,
                    contracts=None,
                    address=None,
                    modules=None,
                    verbose_report=False,
                    max_depth=12):

        all_issues = []
        if self.dynld and self.eth is None:
            self.set_api_rpc_infura()
        for contract in (contracts or self.contracts):
            sym = SymExecWrapper(
                contract,
                address,
                dynloader=DynLoader(self.eth) if self.dynld else None,
                max_depth=max_depth)

            issues = fire_lasers(sym, modules)

            if type(contract) == SolidityContract:
                for issue in issues:
                    issue.add_code_info(contract)

            all_issues += issues

        # Finally, output the results
        report = Report(verbose_report)
        for issue in all_issues:
            report.append_issue(issue)

        return report
Exemple #2
0
    def slither_graph_html(self,
                           strategy,
                           contract,
                           address,
                           max_depth=None,
                           enable_physics=False,
                           phrackify=False,
                           execution_timeout=None,
                           create_timeout=None,
                           file=None):
        priority = self.parse_slither(contract=contract, file=file[0])

        sym = SymExecWrapper(contract,
                             address,
                             strategy,
                             dynloader=DynLoader(
                                 self.eth,
                                 storage_loading=self.onchain_storage_access,
                                 contract_loading=self.dynld,
                             ),
                             max_depth=max_depth,
                             execution_timeout=execution_timeout,
                             create_timeout=create_timeout,
                             priority=priority)
        return generate_graph(sym, physics=enable_physics, phrackify=phrackify)
Exemple #3
0
    def fire_lasers(
        self,
        modules: Optional[List[str]] = None,
        transaction_count: Optional[int] = None,
    ) -> Report:
        """
        :param modules: The analysis modules which should be executed
        :param transaction_count: The amount of transactions to be executed
        :return: The Report class which contains the all the issues/vulnerabilities
        """
        all_issues = []  # type: List[Issue]
        SolverStatistics().enabled = True
        exceptions = []
        for contract in self.contracts:
            StartTime()  # Reinitialize start time for new contracts
            try:
                sym = SymExecWrapper(
                    contract,
                    self.address,
                    self.strategy,
                    dynloader=DynLoader(
                        self.eth,
                        storage_loading=self.onchain_storage_access,
                        contract_loading=self.dynld,
                    ),
                    max_depth=self.max_depth,
                    execution_timeout=self.execution_timeout,
                    loop_bound=self.loop_bound,
                    create_timeout=self.create_timeout,
                    transaction_count=transaction_count,
                    modules=modules,
                    compulsory_statespace=False,
                    enable_iprof=self.enable_iprof,
                    disable_dependency_pruning=self.disable_dependency_pruning,
                )

                issues = fire_lasers(sym, modules)
            except KeyboardInterrupt:
                log.critical("Keyboard Interrupt")
                issues = retrieve_callback_issues(modules)
            except Exception:
                log.critical(
                    "Exception occurred, aborting analysis. Please report this issue to the Mythril GitHub page.\n"
                    + traceback.format_exc())
                issues = retrieve_callback_issues(modules)
                exceptions.append(traceback.format_exc())
            for issue in issues:
                issue.add_code_info(contract)

            all_issues += issues
            log.info("Solver statistics: \n{}".format(str(SolverStatistics())))

        source_data = Source()
        source_data.get_source_from_contracts_list(self.contracts)
        # Finally, output the results
        report = Report(contracts=self.contracts, exceptions=exceptions)
        for issue in all_issues:
            report.append_issue(issue)

        return report
Exemple #4
0
 def graph_html(
     self,
     strategy,
     contract,
     address,
     max_depth=None,
     enable_physics=False,
     phrackify=False,
     execution_timeout=None,
     create_timeout=None,
 ):
     sym = SymExecWrapper(
         contract,
         address,
         strategy,
         dynloader=DynLoader(
             self.eth,
             storage_loading=self.onchain_storage_access,
             contract_loading=self.dynld,
         ),
         max_depth=max_depth,
         execution_timeout=execution_timeout,
         create_timeout=create_timeout,
     )
     return generate_graph(sym, physics=enable_physics, phrackify=phrackify)
Exemple #5
0
    def dump_statespace(self, strategy, contract, address=None, max_depth=12):

        sym = SymExecWrapper(contract, address, strategy,
                             dynloader=DynLoader(self.eth) if self.dynld else None,
                             max_depth=max_depth)

        return get_serializable_statespace(sym)
Exemple #6
0
    def graph_html(
        self,
        contract: EVMContract = None,
        enable_physics: bool = False,
        phrackify: bool = False,
        transaction_count: Optional[int] = None,
    ) -> str:
        """

        :param contract: The Contract on which the analysis should be done
        :param enable_physics: If true then enables the graph physics simulation
        :param phrackify: If true generates Phrack-style call graph
        :param transaction_count: The amount of transactions to be executed
        :return: The generated graph in html format
        """
        sym = SymExecWrapper(
            contract or self.contracts[0],
            self.address,
            self.strategy,
            dynloader=DynLoader(
                self.eth,
                storage_loading=self.onchain_storage_access,
                contract_loading=self.dynld,
            ),
            max_depth=self.max_depth,
            execution_timeout=self.execution_timeout,
            transaction_count=transaction_count,
            create_timeout=self.create_timeout,
            enable_iprof=self.enable_iprof,
            disable_dependency_pruning=self.disable_dependency_pruning,
            run_analysis_modules=False,
        )
        return generate_graph(sym, physics=enable_physics, phrackify=phrackify)
Exemple #7
0
    def fire_lasers(
        self,
        strategy,
        contracts=None,
        address=None,
        modules=None,
        verbose_report=False,
        max_depth=None,
        execution_timeout=None,
    ):

        all_issues = []
        for contract in (contracts or self.contracts):
            sym = SymExecWrapper(
                contract,
                address,
                strategy,
                dynloader=DynLoader(self.eth) if self.dynld else None,
                max_depth=max_depth,
                execution_timeout=execution_timeout)

            issues = fire_lasers(sym, modules)

            if type(contract) == SolidityContract:
                for issue in issues:
                    issue.add_code_info(contract)

            all_issues += issues

        # Finally, output the results
        report = Report(verbose_report)
        for issue in all_issues:
            report.append_issue(issue)

        return report
Exemple #8
0
    def analyze(self, address):
        """
        Analyse a contract using mythril
        :param address: Address of contract to analyse
        :return: Findings
        """
        # The followin code is kinda straight from mythril, thanks ;)
        # Setup
        code = self.eth.eth_getCode(address)

        contract = ETHContract(code, name=address)
        sym = SymExecWrapper(contract, address, dynloader=DynLoader(self.eth))
        # Starting analysis
        logging.debug(
            "Firing lasers on contract with address: {}".format(address))
        issues = fire_lasers(sym)

        logging.debug("Found {} issues using mythril".format(len(issues)))

        # Build findings
        findings = []
        for issue in issues:
            findings += [
                Finding("Mythril analysis", issue.title, issue.description,
                        issue.contract, issue.pc, issue.type)
            ]

        return findings
Exemple #9
0
    def dump_statespace(self, contract: EVMContract = None) -> str:
        """
        Returns serializable statespace of the contract
        :param contract: The Contract on which the analysis should be done
        :return: The serialized state space
        """
        sym = SymExecWrapper(
            contract or self.contracts[0],
            self.address,
            self.strategy,
            dynloader=DynLoader(
                self.eth,
                storage_loading=self.onchain_storage_access,
                contract_loading=self.dynld,
            ),
            max_depth=self.max_depth,
            execution_timeout=self.execution_timeout,
            create_timeout=self.create_timeout,
            enable_iprof=self.enable_iprof,
            disable_dependency_pruning=self.disable_dependency_pruning,
            run_analysis_modules=False,
            enable_coverage_strategy=self.enable_coverage_strategy,
            custom_modules_directory=self.custom_modules_directory,
        )

        return get_serializable_statespace(sym)
Exemple #10
0
 def graph_html(self,
                strategy,
                contract,
                address,
                max_depth=None,
                enable_physics=False,
                phrackify=False,
                execution_timeout=None,
                create_timeout=None,
                transaction_count=2,
                enable_iprof=False):
     """
     :param strategy:
     :param contract:
     :param address:
     :param max_depth:
     :param enable_physics:
     :param phrackify:
     :param execution_timeout:
     :param create_timeout:
     :return:
     """
     sym = SymExecWrapper(contract,
                          address,
                          strategy,
                          dynloader=DynLoader(
                              self.eth,
                              storage_loading=self.onchain_storage_access,
                              contract_loading=self.dynld,
                          ),
                          max_depth=max_depth,
                          execution_timeout=execution_timeout,
                          create_timeout=create_timeout,
                          transaction_count=transaction_count)
     return generate_graph(sym, physics=enable_physics, phrackify=phrackify)
Exemple #11
0
    def dump_statespace(
        self,
        strategy,
        contract,
        address=None,
        max_depth=None,
        execution_timeout=None,
        create_timeout=None,
    ):

        sym = SymExecWrapper(
            contract,
            address,
            strategy,
            dynloader=DynLoader(
                self.eth,
                storage_loading=self.onchain_storage_access,
                contract_loading=self.dynld,
            ),
            max_depth=max_depth,
            execution_timeout=execution_timeout,
            create_timeout=create_timeout,
        )

        return get_serializable_statespace(sym)
Exemple #12
0
    def dump_statespaces(self, contracts=None, address=None, max_depth=12):
        statespaces = []

        for contract in (contracts or self.contracts):
            sym = SymExecWrapper(contract, address,
                                 dynloader=DynLoader(self.eth) if self.dynld else None,
                                 max_depth=max_depth)
            statespaces.append((contract, get_serializable_statespace(sym)))

        return statespaces
Exemple #13
0
 def graph_html(self,
                contract,
                address,
                max_depth=12,
                enable_physics=False,
                phrackify=False):
     sym = SymExecWrapper(
         contract,
         address,
         dynloader=DynLoader(self.eth) if self.dynld else None,
         max_depth=max_depth)
     return generate_graph(sym, physics=enable_physics, phrackify=phrackify)
def check_annotations(contracts, address, eth, dynld, max_depth=12):

    logging.debug("Executing annotations check")

    for contract in contracts:

        contr_to_const = deepcopy(contract)
        contr_to_const.disassembly = Disassembly(contr_to_const.creation_code)
        contr_to_const.code = contr_to_const.creation_code
        dynloader = DynLoader(eth) if dynld else None
        glbstate = get_constr_glbstate(contr_to_const, address)

        sym_constructor = SymExecWrapper(contr_to_const, address, dynloader,
                                         max_depth, glbstate)
        sym_contract = SymExecWrapper(contract,
                                      address,
                                      dynloader,
                                      max_depth=max_depth)

        constructor_trace = get_construction_traces(
            sym_constructor
        )  # Todo the traces here should not contain references to storages anymore
        for t in constructor_trace:
            t.pp_trace()

        traces = get_transaction_traces(sym_contract)
        print("Start")
        a = process_time()
        print("const: " + str(len(constructor_trace)) + " trans: " +
              str(len(traces)))
        trace_chains = []
        for trace in constructor_trace:
            comp_trace_lvls = trace.apply_up_to_trace_levels(traces, 1)
            if not trace_chains:
                trace_chains = comp_trace_lvls
            else:
                for index in range(len(trace_chains)):
                    trace_chains[index].extend(comp_trace_lvls[index])
            print()
        for lvl in trace_chains:
            print("Traces: " + str(len(lvl)))

        #for tt in trace_chains[-1]:
        #    tt.pp_trace()
        #    for trace_lvl in comp_trace_lvls:
        #        print(len(trace_lvl))
        #        print(sum(map(lambda t: len(t.tran_constraints), trace_lvl))/len(trace_lvl))
        #        for t in trace_lvl:
        #            print("constraints: " + str(len(t.tran_constraints)))
        #            t.pp_trace()
        print(process_time() - a)
Exemple #15
0
def get_callee_account(
    global_state: GlobalState,
    callee_address: Union[str, BitVec],
    dynamic_loader: DynLoader,
):
    """Gets the callees account from the global_state.

    :param global_state: state to look in
    :param callee_address: address of the callee
    :param dynamic_loader: dynamic loader to use
    :return: Account belonging to callee
    """
    if isinstance(callee_address, BitVec):
        if callee_address.symbolic:
            return Account(callee_address,
                           balances=global_state.world_state.balances)
        else:
            callee_address = hex(callee_address.value)[2:]

    try:
        return global_state.accounts[int(callee_address, 16)]
    except KeyError:
        # We have a valid call address, but contract is not in the modules list
        log.debug("Module with address %s not loaded.", callee_address)

    if dynamic_loader is None:
        raise ValueError("dynamic_loader is None")

    log.debug("Attempting to load dependency")

    try:
        code = dynamic_loader.dynld(callee_address)
    except ValueError as error:
        log.debug("Unable to execute dynamic loader because: %s", error)
        raise error
    if code is None:
        log.debug("No code returned, not a contract account?")
        raise ValueError("No code returned")
    log.debug("Dependency loaded: " + callee_address)

    callee_account = Account(
        symbol_factory.BitVecVal(int(callee_address, 16), 256),
        code,
        callee_address,
        dynamic_loader=dynamic_loader,
        balances=global_state.world_state.balances,
    )
    global_state.accounts[int(callee_address, 16)] = callee_account

    return callee_account
Exemple #16
0
 def graph_html(self,
                strategy,
                contract,
                address,
                max_depth=None,
                enable_physics=False,
                phrackify=False,
                execution_timeout=None,
                create_timeout=None):
     sym = SymExecWrapper(
         contract,
         address,
         strategy,
         dynloader=DynLoader(self.eth) if self.dynld else None,
         max_depth=max_depth,
         execution_timeout=execution_timeout,
         create_timeout=create_timeout)
     return generate_graph(sym, physics=enable_physics, phrackify=phrackify)
Exemple #17
0
    def dump_statespace(self,
                        strategy,
                        contract,
                        address=None,
                        max_depth=None,
                        execution_timeout=None,
                        create_timeout=None):

        sym = SymExecWrapper(
            contract,
            address,
            strategy,
            dynloader=DynLoader(self.eth) if self.dynld else None,
            max_depth=max_depth,
            execution_timeout=execution_timeout,
            create_timeout=create_timeout)

        return get_serializable_statespace(sym)
Exemple #18
0
    def __init__(self, address: Text, rpc: Optional[Text] = None):
        assert address is not None, "No contract address provided"

        if rpc is None:
            eth_json_rpc = EthJsonRpc()
        else:
            match = re.match(r'(http(s)?:\/\/)?([a-zA-Z0-9\.\-]+)(:([0-9]+))?(\/.+)?', rpc)
            if match:
                host = match.group(3)
                port = match.group(5) if match.group(4) else None
                path = match.group(6) if match.group(6) else ''
                tls = bool(match.group(2))
                log.debug('Parsed RPC provider params: host=%s, port=%s, tls=%r, path=%s', host, port, tls, path)
                eth_json_rpc = EthJsonRpc(host=host + path, port=port, tls=tls)
            else:
                raise ValidationError('Invalid JSON RPC URL provided: "%s"' % rpc)
        self._dyn_loader = DynLoader(eth_json_rpc)
        self._address = address
Exemple #19
0
def get_callee_account(global_state: GlobalState, callee_address: str,
                       dynamic_loader: DynLoader):
    """Gets the callees account from the global_state.

    :param global_state: state to look in
    :param callee_address: address of the callee
    :param dynamic_loader: dynamic loader to use
    :return: Account belonging to callee
    """
    environment = global_state.environment
    accounts = global_state.accounts

    try:
        return global_state.accounts[int(callee_address, 16)]
    except KeyError:
        # We have a valid call address, but contract is not in the modules list
        log.debug("Module with address " + callee_address + " not loaded.")

    if dynamic_loader is None:
        raise ValueError()

    log.debug("Attempting to load dependency")

    try:
        code = dynamic_loader.dynld(callee_address)
    except ValueError as error:
        log.debug("Unable to execute dynamic loader because: {}".format(
            str(error)))
        raise error
    if code is None:
        log.debug("No code returned, not a contract account?")
        raise ValueError()
    log.debug("Dependency loaded: " + callee_address)

    callee_account = Account(
        symbol_factory.BitVecVal(int(callee_address, 16), 256),
        code,
        callee_address,
        dynamic_loader=dynamic_loader,
        balances=global_state.world_state.balances,
    )
    accounts[callee_address] = callee_account

    return callee_account
Exemple #20
0
    def fire_lasers(
        self,
        strategy,
        contracts=None,
        address=None,
        modules=None,
        verbose_report=False,
        max_depth=None,
        execution_timeout=None,
        create_timeout=None,
        max_transaction_count=None,
    ):

        all_issues = []
        for contract in contracts or self.contracts:
            sym = SymExecWrapper(
                contract,
                address,
                strategy,
                dynloader=DynLoader(
                    self.eth,
                    storage_loading=self.onchain_storage_access,
                    contract_loading=self.dynld,
                ),
                max_depth=max_depth,
                execution_timeout=execution_timeout,
                create_timeout=create_timeout,
                max_transaction_count=max_transaction_count,
            )

            issues = fire_lasers(sym, modules)

            if type(contract) == SolidityContract:
                for issue in issues:
                    issue.add_code_info(contract)

            all_issues += issues

        # Finally, output the results
        report = Report(verbose_report)
        for issue in all_issues:
            report.append_issue(issue)

        return report
Exemple #21
0
def get_callee_address(
    global_state: GlobalState,
    dynamic_loader: DynLoader,
    symbolic_to_address: Expression,
):
    """Gets the address of the callee.

    :param global_state: state to look in
    :param dynamic_loader:  dynamic loader to use
    :param symbolic_to_address: The (symbolic) callee address
    :return: Address of the callee
    """
    environment = global_state.environment

    try:
        callee_address = hex(util.get_concrete_int(symbolic_to_address))
    except TypeError:
        log.debug("Symbolic call encountered")

        match = re.search(r"storage_(\d+)", str(simplify(symbolic_to_address)))
        log.debug("CALL to: " + str(simplify(symbolic_to_address)))

        if match is None or dynamic_loader is None:
            raise ValueError()

        index = int(match.group(1))
        log.debug("Dynamic contract address at storage index {}".format(index))

        # attempt to read the contract address from instance storage
        try:
            callee_address = dynamic_loader.read_storage(
                environment.active_account.address, index
            )
        # TODO: verify whether this happens or not
        except:
            log.debug("Error accessing contract storage.")
            raise ValueError

        # testrpc simply returns the address, geth response is more elaborate.
        if not re.match(r"^0x[0-9a-f]{40}$", callee_address):
            callee_address = "0x" + callee_address[26:]

    return callee_address
Exemple #22
0
 def _post_process_report(self, report: Report, target_address: Text,
                          dyn_loader: DynLoader) -> None:
     for result in [
             result for report_item in report.reports
             for result in report_item.results
     ]:
         for attr_name, attr_value in [
             (k, v) for k, v in result.attributes.items()
                 if k.startswith('_index')
         ]:
             attr_name_pretty = ' '.join(
                 map(lambda s: s.capitalize(),
                     attr_name.split('_')[2:]))
             result.add_attribute(f'{attr_name_pretty} Storage Index',
                                  attr_value)
             if dyn_loader:
                 result.add_attribute(
                     attr_name_pretty,
                     dyn_loader.read_storage(target_address, attr_value))
             result.remove_attribute(attr_name)
Exemple #23
0
 def accounts_exist_or_load(self, addr: str,
                            dynamic_loader: DynLoader) -> str:
     """
     returns account if it exists, else it loads from the dynamic loader
     :param addr: address
     :param dynamic_loader: Dynamic Loader
     :return: The code
     """
     addr_bitvec = symbol_factory.BitVecVal(int(addr, 16), 256)
     if addr_bitvec.value in self.accounts:
         code = self.accounts[addr_bitvec.value].code
     else:
         code = dynamic_loader.dynld(addr)
         self.create_account(balance=0,
                             address=addr_bitvec.value,
                             dynamic_loader=dynamic_loader)
     if code is None:
         code = ""
     else:
         code = code.bytecode
     return code
Exemple #24
0
    def dump_statespace(
        self,
        strategy,
        contract,
        address=None,
        max_depth=None,
        execution_timeout=None,
        create_timeout=None,
        enable_iprof=False,
    ):
        """

        :param strategy:
        :param contract:
        :param address:
        :param max_depth:
        :param execution_timeout:
        :param create_timeout:
        :return:
        """
        sym = SymExecWrapper(
            contract,
            address,
            strategy,
            dynloader=DynLoader(
                self.eth,
                storage_loading=self.onchain_storage_access,
                contract_loading=self.dynld,
            ),
            max_depth=max_depth,
            execution_timeout=execution_timeout,
            create_timeout=create_timeout,
            enable_iprof=enable_iprof,
        )

        return get_serializable_statespace(sym)
Exemple #25
0
    def fire_lasers(
        self,
        strategy,
        contracts=None,
        address=None,
        modules=None,
        verbose_report=False,
        max_depth=None,
        execution_timeout=None,
        create_timeout=None,
        transaction_count=None,
        enable_iprof=False,
    ):
        """

        :param strategy:
        :param contracts:
        :param address:
        :param modules:
        :param verbose_report:
        :param max_depth:
        :param execution_timeout:
        :param create_timeout:
        :param transaction_count:
        :return:
        """
        all_issues = []
        for contract in contracts or self.contracts:
            try:
                sym = SymExecWrapper(
                    contract,
                    address,
                    strategy,
                    dynloader=DynLoader(
                        self.eth,
                        storage_loading=self.onchain_storage_access,
                        contract_loading=self.dynld,
                    ),
                    max_depth=max_depth,
                    execution_timeout=execution_timeout,
                    create_timeout=create_timeout,
                    transaction_count=transaction_count,
                    modules=modules,
                    compulsory_statespace=False,
                    enable_iprof=enable_iprof,
                )
                issues = fire_lasers(sym, modules)
            except KeyboardInterrupt:
                log.critical("Keyboard Interrupt")
                issues = retrieve_callback_issues(modules)
            except Exception:
                log.critical(
                    "Exception occurred, aborting analysis. Please report this issue to the Mythril GitHub page.\n"
                    + traceback.format_exc())
                issues = retrieve_callback_issues(modules)

            for issue in issues:
                issue.add_code_info(contract)

            all_issues += issues

        source_data = Source()
        source_data.get_source_from_contracts_list(self.contracts)
        # Finally, output the results
        report = Report(verbose_report, source_data)
        for issue in all_issues:
            report.append_issue(issue)

        return report
Exemple #26
0
    def get_traces_and_build_violations(self, contract):
        dynloader = DynLoader(self.eth) if self.dynld else None

        # Building ignore lists for transactions and constructor executions
        create_ignore_list = []
        trans_ignore_list = []

        for annotation in self.annotation_map[contract.name]:
            create_ignore_list.extend(annotation.get_creation_ignore_list())
            trans_ignore_list.extend(annotation.get_trans_ignore_list())


        # Used to run annotations violation build in traces construction in parallel
        annotationsProcessor = None
        if any([len( annotation.viol_rew_instrs) > 0 for annotation in self.annotation_map[contract.name]]):
            annotationsProcessor = AnnotationProcessor(contract.creation_disassembly.instruction_list,
                                        contract.disassembly.instruction_list, create_ignore_list, trans_ignore_list,
                                        contract)

        printd("Constructor and Transaction")

        constr_calldata_len = get_minimal_constructor_param_encoding_len(abi_json_to_abi(contract.abi))
        sym_code_extension = SymbolicCodeExtension("calldata", contract.name, constr_calldata_len)

        global keccak_map
        keccak_map = {}
        if self.max_depth:
            self.config.mythril_depth = self.max_depth
        printd("Sym Exe: " + str(contract.name))


        start_meassure()
        sym_transactions = SymExecWrapper(contract, self.address, laser_strategy, dynloader, max_depth=self.config.mythril_depth,
                                          prepostprocessor=annotationsProcessor, code_extension=sym_code_extension)
        printt("Symbolic Execution")


        contract.states = []

        contract.states = sym_transactions.laser.state_id_assigner.states
        contract.config = self.config

        end = time.time()
        # print(end - start)
        if annotationsProcessor:
            printd("Construction Violations: " + str(len(reduce(lambda x, y: x + y, annotationsProcessor.create_violations, []))))
            printd("Transaction Violations: " + str(len(reduce(lambda x, y: x + y, annotationsProcessor.trans_violations, []))))

        # Add found violations to the annotations they violated
        for ign_idx in range(len(trans_ignore_list)):
            annotation = trans_ignore_list[ign_idx][4]
            annotation.add_violations(annotationsProcessor.trans_violations[ign_idx], contract, length=0,
                    vio_description="An assert with the annotations condition would fail here.")

        for ign_idx in range(len(create_ignore_list)):
            annotation = create_ignore_list[ign_idx][4]
            annotation.add_violations(annotationsProcessor.create_violations[ign_idx], contract, length=0,
                                      vio_description="An assert with the annotations condition would fail here.")

        for annotation in self.annotation_map[contract.name]:
            annotation.build_violations(sym_transactions)

        printt("BUILD VIOLATIONS")


        # Build traces from the regular annotation ignoring global states
        start_time = time.time()
        create_traces, trans_traces = get_traces(sym_transactions, contract)
        printt("BUILD TRACES")
        printd("--- %s seconds ---" % (time.time() - start_time))

        return create_traces, trans_traces
def test_extraction(addr, eth, code_len):
    global_state = _get_global_state()
    dynamic_loader = DynLoader(eth=eth)
    code = global_state.world_state.accounts_exist_or_load(
        addr, dynamic_loader)
    assert len(code) == code_len
Exemple #28
0
def main():
    parser = argparse.ArgumentParser(
        description='Security analysis of Ethereum smart contracts')
    parser.add_argument("solidity_file", nargs='*')

    commands = parser.add_argument_group('commands')
    commands.add_argument('-g',
                          '--graph',
                          help='generate a control flow graph',
                          metavar='OUTPUT_FILE')
    commands.add_argument(
        '-x',
        '--fire-lasers',
        action='store_true',
        help='detect vulnerabilities, use with -c, -a or solidity file(s)')
    commands.add_argument(
        '-t',
        '--truffle',
        action='store_true',
        help='analyze a truffle project (run from project dir)')
    commands.add_argument('-d',
                          '--disassemble',
                          action='store_true',
                          help='print disassembly')
    commands.add_argument('-j',
                          '--statespace-json',
                          help='dumps the statespace json',
                          metavar='OUTPUT_FILE')

    inputs = parser.add_argument_group('input arguments')
    inputs.add_argument('-c',
                        '--code',
                        help='hex-encoded bytecode string ("6060604052...")',
                        metavar='BYTECODE')
    inputs.add_argument('-a',
                        '--address',
                        help='pull contract from the blockchain',
                        metavar='CONTRACT_ADDRESS')
    inputs.add_argument('-l',
                        '--dynld',
                        action='store_true',
                        help='auto-load dependencies from the blockchain')

    outputs = parser.add_argument_group('output formats')
    outputs.add_argument('-o',
                         '--outform',
                         choices=['text', 'markdown', 'json'],
                         default='text',
                         help='report output format',
                         metavar='<text/json>')
    outputs.add_argument('--verbose-report',
                         action='store_true',
                         help='Include debugging information in report')

    database = parser.add_argument_group('local contracts database')
    database.add_argument('--init-db',
                          action='store_true',
                          help='initialize the contract database')
    database.add_argument('-s',
                          '--search',
                          help='search the contract database',
                          metavar='EXPRESSION')

    utilities = parser.add_argument_group('utilities')
    utilities.add_argument('--hash',
                           help='calculate function signature hash',
                           metavar='SIGNATURE')
    utilities.add_argument(
        '--storage',
        help='read state variables from storage index, use with -a',
        metavar='INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]')
    utilities.add_argument(
        '--solv',
        help=
        'specify solidity compiler version. If not present, will try to install it (Experimental)',
        metavar='SOLV')

    options = parser.add_argument_group('options')
    options.add_argument(
        '-m',
        '--modules',
        help='Comma-separated list of security analysis modules',
        metavar='MODULES')
    options.add_argument('--max-depth',
                         type=int,
                         default=12,
                         help='Maximum recursion depth for symbolic execution')
    options.add_argument('--solc-args', help='Extra arguments for solc')
    options.add_argument('--phrack',
                         action='store_true',
                         help='Phrack-style call graph')
    options.add_argument('--enable-physics',
                         action='store_true',
                         help='enable graph physics simulation')
    options.add_argument('-v',
                         type=int,
                         help='log level (0-2)',
                         metavar='LOG_LEVEL')
    options.add_argument('--leveldb',
                         help='enable direct leveldb access operations',
                         metavar='LEVELDB_PATH')

    rpc = parser.add_argument_group('RPC options')
    rpc.add_argument('-i',
                     action='store_true',
                     help='Preset: Infura Node service (Mainnet)')
    rpc.add_argument('--rpc',
                     help='custom RPC settings',
                     metavar='HOST:PORT / ganache / infura-[network_name]')
    rpc.add_argument('--rpctls',
                     type=bool,
                     default=False,
                     help='RPC connection over TLS')
    rpc.add_argument('--ipc',
                     action='store_true',
                     help='Connect via local IPC')

    # Get config values

    args = parser.parse_args()

    try:
        mythril_dir = os.environ['MYTHRIL_DIR']
    except KeyError:
        mythril_dir = os.path.join(os.path.expanduser('~'), ".mythril")

    # Detect unsupported combinations of command line args

    if args.dynld and not args.address:
        exitWithError(
            args.outform,
            "Dynamic loader can be used in on-chain analysis mode only (-a).")

    # Initialize data directory and signature database

    if not os.path.exists(mythril_dir):
        logging.info("Creating mythril data directory")
        os.mkdir(mythril_dir)

    # If no function signature file exists, create it. Function signatures from Solidity source code are added automatically.

    signatures_file = os.path.join(mythril_dir, 'signatures.json')

    sigs = {}
    if not os.path.exists(signatures_file):
        logging.info(
            "No signature database found. Creating empty database: " +
            signatures_file + "\n" +
            "Consider replacing it with the pre-initialized database at https://raw.githubusercontent.com/ConsenSys/mythril/master/signatures.json"
        )
        with open(signatures_file, 'a') as f:
            json.dump({}, f)

    with open(signatures_file) as f:
        try:
            sigs = json.load(f)
        except JSONDecodeError as e:
            exitWithError(
                args.outform, "Invalid JSON in signatures file " +
                signatures_file + "\n" + str(e))

    # Parse cmdline args

    if not (args.search or args.init_db or args.hash or args.disassemble
            or args.graph or args.fire_lasers or args.storage or args.truffle
            or args.statespace_json):
        parser.print_help()
        sys.exit()

    if args.v:
        if 0 <= args.v < 3:
            logging.basicConfig(
                level=[logging.NOTSET, logging.INFO, logging.DEBUG][args.v])
        else:
            exitWithError(
                args.outform,
                "Invalid -v value, you can find valid values in usage")

    if args.hash:
        print("0x" + utils.sha3(args.hash)[:4].hex())
        sys.exit()

    if args.truffle:
        try:
            analyze_truffle_project(args)
        except FileNotFoundError:
            print(
                "Build directory not found. Make sure that you start the analysis from the project root, and that 'truffle compile' has executed successfully."
            )
        sys.exit()

    # Figure out solc binary and version
    # Only proper versions are supported. No nightlies, commits etc (such as available in remix)

    if args.solv:
        version = args.solv
        # tried converting input to semver, seemed not necessary so just slicing for now
        if version == str(solc.main.get_solc_version())[:6]:
            logging.info('Given version matches installed version')
            try:
                solc_binary = os.environ['SOLC']
            except KeyError:
                solc_binary = 'solc'
        else:
            if util.solc_exists(version):
                logging.info('Given version is already installed')
            else:
                try:
                    solc.install_solc('v' + version)
                except SolcError:
                    exitWithError(
                        args.outform,
                        "There was an error when trying to install the specified solc version"
                    )

            solc_binary = os.path.join(os.environ['HOME'],
                                       ".py-solc/solc-v" + version, "bin/solc")
            logging.info("Setting the compiler to " + str(solc_binary))
    else:
        try:
            solc_binary = os.environ['SOLC']
        except KeyError:
            solc_binary = 'solc'

    # Open LevelDB if specified

    if args.leveldb:
        ethDB = EthLevelDB(args.leveldb)
        eth = ethDB

    # Establish RPC/IPC connection if necessary

    if (args.address or args.init_db) and not args.leveldb:

        if args.i:
            eth = EthJsonRpc('mainnet.infura.io', 443, True)
            logging.info("Using INFURA for RPC queries")
        elif args.rpc:

            if args.rpc == 'ganache':
                rpcconfig = ('localhost', 7545, False)

            else:

                m = re.match(r'infura-(.*)', args.rpc)

                if m and m.group(1) in [
                        'mainnet', 'rinkeby', 'kovan', 'ropsten'
                ]:
                    rpcconfig = (m.group(1) + '.infura.io', 443, True)

                else:
                    try:
                        host, port = args.rpc.split(":")
                        rpcconfig = (host, int(port), args.rpctls)

                    except ValueError:
                        exitWithError(
                            args.outform,
                            "Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'"
                        )

            if (rpcconfig):

                eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2])
                logging.info("Using RPC settings: %s" % str(rpcconfig))

            else:
                exitWithError(args.outform,
                              "Invalid RPC settings, check help for details.")

        elif args.ipc:
            try:
                eth = EthIpc()
            except Exception as e:
                exitWithError(
                    args.outform,
                    "IPC initialization failed. Please verify that your local Ethereum node is running, or use the -i flag to connect to INFURA. \n"
                    + str(e))

        else:  # Default configuration if neither RPC or IPC are set

            eth = EthJsonRpc('localhost', 8545)
            logging.info("Using default RPC settings: http://localhost:8545")

    # Database search ops

    if args.search or args.init_db:
        contract_storage, _ = get_persistent_storage(mythril_dir)
        if args.search:
            try:
                if not args.leveldb:
                    contract_storage.search(args.search, searchCallback)
                else:
                    ethDB.search(args.search, searchCallback)
            except SyntaxError:
                exitWithError(args.outform,
                              "Syntax error in search expression.")
        elif args.init_db:
            try:
                contract_storage.initialize(eth)
            except FileNotFoundError as e:
                exitWithError(args.outform,
                              "Error syncing database over IPC: " + str(e))
            except ConnectionError as e:
                exitWithError(
                    args.outform,
                    "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly."
                )

        sys.exit()

    # Load / compile input contracts

    contracts = []
    address = None

    if args.code:
        address = util.get_indexed_address(0)
        contracts.append(ETHContract(args.code, name="MAIN"))

    # Get bytecode from a contract address

    elif args.address:
        address = args.address
        if not re.match(r'0x[a-fA-F0-9]{40}', args.address):
            exitWithError(
                args.outform,
                "Invalid contract address. Expected format is '0x...'.")

        try:
            code = eth.eth_getCode(args.address)
        except FileNotFoundError as e:
            exitWithError(args.outform, "IPC error: " + str(e))
        except ConnectionError as e:
            exitWithError(
                args.outform,
                "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly."
            )
        except Exception as e:
            exitWithError(args.outform, "IPC / RPC error: " + str(e))
        else:
            if code == "0x" or code == "0x0":
                exitWithError(
                    args.outform,
                    "Received an empty response from eth_getCode. Check the contract address and verify that you are on the correct chain."
                )
            else:
                contracts.append(ETHContract(code, name=args.address))

    # Compile Solidity source file(s)

    elif args.solidity_file:
        address = util.get_indexed_address(0)
        if args.graph and len(args.solidity_file) > 1:
            exitWithError(
                args.outform,
                "Cannot generate call graphs from multiple input files. Please do it one at a time."
            )

        for file in args.solidity_file:
            if ":" in file:
                file, contract_name = file.split(":")
            else:
                contract_name = None

            file = os.path.expanduser(file)

            try:
                signatures.add_signatures_from_file(file, sigs)
                contract = SolidityContract(file,
                                            contract_name,
                                            solc_args=args.solc_args)
                logging.info("Analyzing contract %s:%s" %
                             (file, contract.name))
            except FileNotFoundError:
                exitWithError(args.outform, "Input file not found: " + file)
            except CompilerError as e:
                exitWithError(args.outform, e)
            except NoContractFoundError:
                logging.info("The file " + file +
                             " does not contain a compilable contract.")
            else:
                contracts.append(contract)

        # Save updated function signatures
        with open(signatures_file, 'w') as f:
            json.dump(sigs, f)

    else:
        exitWithError(
            args.outform,
            "No input bytecode. Please provide EVM code via -c BYTECODE, -a ADDRESS, or -i SOLIDITY_FILES"
        )

    # Commands

    if args.storage:
        if not args.address:
            exitWithError(
                args.outform,
                "To read storage, provide the address of a deployed contract with the -a option."
            )
        else:
            (position, length, mappings) = (0, 1, [])
            try:
                params = args.storage.split(",")
                if params[0] == "mapping":
                    if len(params) < 3:
                        exitWithError(args.outform,
                                      "Invalid number of parameters.")
                    position = int(params[1])
                    position_formatted = utils.zpad(
                        utils.int_to_big_endian(position), 32)
                    for i in range(2, len(params)):
                        key = bytes(params[i], 'utf8')
                        key_formatted = utils.rzpad(key, 32)
                        mappings.append(
                            int.from_bytes(utils.sha3(key_formatted +
                                                      position_formatted),
                                           byteorder='big'))

                    length = len(mappings)
                    if length == 1:
                        position = mappings[0]

                else:
                    if len(params) >= 4:
                        exitWithError(args.outform,
                                      "Invalid number of parameters.")

                    if len(params) >= 1:
                        position = int(params[0])
                    if len(params) >= 2:
                        length = int(params[1])
                    if len(params) == 3 and params[2] == "array":
                        position_formatted = utils.zpad(
                            utils.int_to_big_endian(position), 32)
                        position = int.from_bytes(
                            utils.sha3(position_formatted), byteorder='big')

            except ValueError:
                exitWithError(
                    args.outform,
                    "Invalid storage index. Please provide a numeric value.")

            try:
                if length == 1:
                    print("{}: {}".format(
                        position, eth.eth_getStorageAt(args.address,
                                                       position)))
                else:
                    if len(mappings) > 0:
                        for i in range(0, len(mappings)):
                            position = mappings[i]
                            print("{}: {}".format(
                                hex(position),
                                eth.eth_getStorageAt(args.address, position)))
                    else:
                        for i in range(position, position + length):
                            print("{}: {}".format(
                                hex(i), eth.eth_getStorageAt(args.address, i)))
            except FileNotFoundError as e:
                exitWithError(args.outform, "IPC error: " + str(e))
            except ConnectionError as e:
                exitWithError(
                    args.outform,
                    "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly."
                )

    elif args.disassemble:
        easm_text = contracts[0].get_easm()
        sys.stdout.write(easm_text)

    elif args.graph or args.fire_lasers:
        if not contracts:
            exitWithError(args.outform,
                          "input files do not contain any valid contracts")

        if args.graph:
            if args.dynld:
                sym = SymExecWrapper(contracts[0],
                                     address,
                                     dynloader=DynLoader(eth),
                                     max_depth=args.max_depth)
            else:
                sym = SymExecWrapper(contracts[0],
                                     address,
                                     max_depth=args.max_depth)

            html = generate_graph(sym,
                                  physics=args.enable_physics,
                                  phrackify=args.phrack)

            try:
                with open(args.graph, "w") as f:
                    f.write(html)
            except Exception as e:
                exitWithError(args.outform, "Error saving graph: " + str(e))

        else:
            all_issues = []
            for contract in contracts:
                if args.dynld:
                    sym = SymExecWrapper(contract,
                                         address,
                                         dynloader=DynLoader(eth),
                                         max_depth=args.max_depth)
                else:
                    sym = SymExecWrapper(contract,
                                         address,
                                         max_depth=args.max_depth)

                if args.modules:
                    issues = fire_lasers(sym, args.modules.split(","))
                else:
                    issues = fire_lasers(sym)

                if type(contract) == SolidityContract:
                    for issue in issues:
                        issue.add_code_info(contract)

                all_issues += issues

            # Finally, output the results
            report = Report(args.verbose_report)
            for issue in all_issues:
                report.append_issue(issue)

            outputs = {
                'json':
                report.as_json(),
                'text':
                report.as_text() or
                "The analysis was completed successfully. No issues were detected.",
                'markdown':
                report.as_markdown() or
                "The analysis was completed successfully. No issues were detected."
            }
            print(outputs[args.outform])

    elif args.statespace_json:
        if not contracts:
            exitWithError(args.outform,
                          "input files do not contain any valid contracts")

        if args.dynld:
            sym = SymExecWrapper(contracts[0],
                                 address,
                                 dynloader=DynLoader(eth),
                                 max_depth=args.max_depth)
        else:
            sym = SymExecWrapper(contracts[0],
                                 address,
                                 max_depth=args.max_depth)

        try:
            with open(args.statespace_json, "w") as f:
                json.dump(get_serializable_statespace(sym), f)
        except Exception as e:
            exitWithError(args.outform, "Error saving json: " + str(e))

    else:
        parser.print_help()