Пример #1
0
    def set_api_rpc(self, rpc=None, rpctls=False):
        """

        :param rpc:
        :param rpctls:
        """
        if rpc == "ganache":
            rpcconfig = ("localhost", 8545, False)
        else:
            m = re.match(r"infura-(.*)", rpc)
            if m and m.group(1) in ["mainnet", "rinkeby", "kovan", "ropsten"]:
                rpcconfig = (m.group(1) + ".infura.io", 443, True)
            else:
                try:
                    host, port = rpc.split(":")
                    rpcconfig = (host, int(port), rpctls)
                except ValueError:
                    raise CriticalError(
                        "Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'"
                    )

        if rpcconfig:
            self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]),
                                  rpcconfig[2])
            log.info("Using RPC settings: %s" % str(rpcconfig))
        else:
            raise CriticalError(
                "Invalid RPC settings, check help for details.")
Пример #2
0
    def _init_solc_binary(version):
        # Figure out solc binary and version
        # Only proper versions are supported. No nightlies, commits etc (such as available in remix)

        if not version:
            return os.environ.get("SOLC") or "solc"

        # tried converting input to semver, seemed not necessary so just slicing for now
        main_version = solc.main.get_solc_version_string()
        main_version_number = re.match(r"\d+.\d+.\d+", main_version)
        if main_version is None:
            raise CriticalError(
                "Could not extract solc version from string {}".format(
                    main_version))
        if version == main_version_number:
            logging.info("Given version matches installed version")
            solc_binary = os.environ.get("SOLC") or "solc"
        else:
            solc_binary = util.solc_exists(version)
            if solc_binary:
                logging.info("Given version is already installed")
            else:
                try:
                    solc.install_solc("v" + version)
                except SolcError:
                    raise CriticalError(
                        "There was an error when trying to install the specified solc version"
                    )

            logging.info("Setting the compiler to %s", solc_binary)

        return solc_binary
Пример #3
0
    def load_from_address(self, address: str) -> Tuple[str, EVMContract]:
        """
        Returns the contract given it's on chain address
        :param address: The on chain address of a contract
        :return: tuple(address, contract)
        """
        if not re.match(r"0x[a-fA-F0-9]{40}", address):
            raise CriticalError(
                "Invalid contract address. Expected format is '0x...'.")

        try:
            code = self.eth.eth_getCode(address)
        except FileNotFoundError as e:
            raise CriticalError("IPC error: " + str(e))
        except ConnectionError:
            raise CriticalError(
                "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly."
            )
        except Exception as e:
            raise CriticalError("IPC / RPC error: " + str(e))

        if code == "0x" or code == "0x0":
            raise CriticalError(
                "Received an empty response from eth_getCode. Check the contract address and verify that you are on the correct chain."
            )
        else:
            self.contracts.append(
                EVMContract(code,
                            name=address,
                            enable_online_lookup=self.enable_online_lookup))
        return address, self.contracts[
            -1]  # return address and contract object
Пример #4
0
    def load_from_solidity(self, solidity_files):
        """
        UPDATES self.sigs!
        :param solidity_files:
        :return:
        """
        address = util.get_indexed_address(0)
        contracts = []
        for file in solidity_files:
            if ":" in file:
                file, contract_name = file.split(":")
            else:
                contract_name = None

            file = os.path.expanduser(file)

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

        self._update_signatures(self.sigs)
        return address, contracts
Пример #5
0
 def init_db(self):
     contract_storage, _ = get_persistent_storage(self.mythril_dir)
     try:
         contract_storage.initialize(self.eth)
     except FileNotFoundError as e:
          raise CriticalError("Error syncing database over IPC: " + str(e))
     except ConnectionError as e:
         raise CriticalError("Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly.")
Пример #6
0
    def load_from_solidity(self, solidity_files):
        """
        UPDATES self.sigs!
        :param solidity_files:
        :return:
        """
        address = util.get_indexed_address(0)
        contracts = []
        for file in solidity_files:
            if ":" in file:
                file, contract_name = file.split(":")
            else:
                contract_name = None

            file = os.path.expanduser(file)

            try:
                # import signatures from solidity source
                self.sigs.import_from_solidity_source(
                    file,
                    solc_binary=self.solc_binary,
                    solc_args=self.solc_args)
                # Save updated function signatures
                self.sigs.write(
                )  # dump signatures to disk (previously opened file or default location)

                if contract_name is not None:
                    contract = SolidityContract(
                        input_file=file,
                        name=contract_name,
                        solc_args=self.solc_args,
                        solc_binary=self.solc_binary,
                    )
                    self.contracts.append(contract)
                    contracts.append(contract)
                else:
                    for contract in get_contracts_from_file(
                            input_file=file,
                            solc_args=self.solc_args,
                            solc_binary=self.solc_binary,
                    ):
                        self.contracts.append(contract)
                        contracts.append(contract)

            except FileNotFoundError:
                raise CriticalError("Input file not found: " + file)
            except CompilerError as e:
                raise CriticalError(e)
            except NoContractFoundError:
                logging.error("The file " + file +
                              " does not contain a compilable contract.")

        return address, contracts
Пример #7
0
    def _init_solc_binary(version: str) -> str:
        """
        Only proper versions are supported. No nightlies, commits etc (such as available in remix).
        :param version: Version of the solc binary required
        :return: The solc binary of the corresponding version
        """

        if not version:
            return os.environ.get("SOLC") or "solc"

        # tried converting input to semver, seemed not necessary so just slicing for now
        main_version = solc.main.get_solc_version_string()
        main_version_number = re.match(r"\d+.\d+.\d+", main_version)
        if main_version is None:
            raise CriticalError(
                "Could not extract solc version from string {}".format(
                    main_version))
        if version == main_version_number:
            log.info("Given version matches installed version")
            solc_binary = os.environ.get("SOLC") or "solc"
        else:
            solc_binary = util.solc_exists(version)
            if solc_binary and solc_binary != util.solc_exists(
                    "default_ubuntu_version"):
                log.info("Given version is already installed")
            else:
                if version.startswith("0.4"):
                    try:
                        solc.install_solc("v" + version)
                    except solc.exceptions.SolcError:
                        raise CriticalError(
                            "There was an error when trying to install the specified solc version"
                        )
                elif sys.version_info[1] >= 6:
                    # solcx supports python 3.6+
                    try:
                        solcx.install_solc("v" + version)
                    except solcx.exceptions.SolcError:
                        raise CriticalError(
                            "There was an error when trying to install the specified solc version"
                        )
                else:
                    raise CriticalError(
                        "Py-Solc doesn't support 0.5.*. You can switch to python 3.6 which uses solcx."
                    )

                solc_binary = util.solc_exists(version)
                if not solc_binary:
                    raise solc.exceptions.SolcError()

            log.info("Setting the compiler to %s", solc_binary)

        return solc_binary
Пример #8
0
    def get_state_variable_from_storage(self, address, params=[]):
        (position, length, mappings) = (0, 1, [])
        try:
            if params[0] == "mapping":
                if len(params) < 3:
                     raise CriticalError("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:
                     raise CriticalError("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:
             raise CriticalError("Invalid storage index. Please provide a numeric value.")

        outtxt = []

        try:
            if length == 1:
                outtxt.append("{}: {}".format(position, self.eth.eth_getStorageAt(address, position)))
            else:
                if len(mappings) > 0:
                    for i in range(0, len(mappings)):
                        position = mappings[i]
                        outtxt.append("{}: {}".format(hex(position), self.eth.eth_getStorageAt(address, position)))
                else:
                    for i in range(position, position + length):
                        outtxt.append("{}: {}".format(hex(i), self.eth.eth_getStorageAt(address, i)))
        except FileNotFoundError as e:
             raise CriticalError("IPC error: " + str(e))
        except ConnectionError as e:
            raise CriticalError("Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly.")
        return '\n'.join(outtxt)
Пример #9
0
    def _init_solc_binary(self, version):
        # Figure out solc binary and version
        # Only proper versions are supported. No nightlies, commits etc (such as available in remix)

        if version:
            # 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:
                        raise CriticalError(
                            "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'
        return solc_binary
Пример #10
0
 def set_api_ipc(self):
     try:
         self.eth = EthIpc()
     except Exception as e:
         raise CriticalError(
             "IPC initialization failed. Please verify that your local Ethereum node is running, or use the -i flag to connect to INFURA. \n"
             + str(e))
Пример #11
0
    def __init__(self, solv=None, solc_args=None, dynld=False):

        self.solv = solv
        self.solc_args = solc_args
        self.dynld = dynld

        self.mythril_dir = self._init_mythril_dir()

        self.sigs = signatures.SignatureDb()
        try:
            self.sigs.open(
            )  # tries mythril_dir/signatures.json by default (provide path= arg to make this configurable)
        except FileNotFoundError as fnfe:
            logging.info(
                "No signature database found. Creating database if sigs are loaded in: "
                + self.sigs.signatures_file + "\n" +
                "Consider replacing it with the pre-initialized database at https://raw.githubusercontent.com/ConsenSys/mythril/master/signatures.json"
            )
        except json.JSONDecodeError as jde:
            raise CriticalError("Invalid JSON in signatures file " +
                                self.sigs.signatures_file + "\n" + str(jde))

        self.solc_binary = self._init_solc_binary(solv)
        self.config_path = os.path.join(self.mythril_dir, 'config.ini')
        self.leveldb_dir = self._init_config()

        self.eth = None  # ethereum API client
        self.eth_db = None  # ethereum LevelDB client

        self.contracts = []  # loaded contracts
Пример #12
0
    def search_db(self, search):
        def search_callback(_, address, balance):
            print("Address: " + address + ", balance: " + str(balance))

        try:
            self.eth_db.search(search, search_callback)

        except SyntaxError:
            raise CriticalError("Syntax error in search expression.")
Пример #13
0
    def load_from_address(self, address):
        if not re.match(r'0x[a-fA-F0-9]{40}', address):
             raise CriticalError("Invalid contract address. Expected format is '0x...'.")

        try:
            code = self.eth.eth_getCode(address)
        except FileNotFoundError as e:
             raise CriticalError("IPC error: " + str(e))
        except ConnectionError as e:
            raise CriticalError("Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly.")
        except Exception as e:
             raise CriticalError("IPC / RPC error: " + str(e))
        else:
            if code == "0x" or code == "0x0":
                 raise CriticalError("Received an empty response from eth_getCode. Check the contract address and verify that you are on the correct chain.")
            else:
                self.contracts.append(ETHContract(code, name=address))
        return address, self.contracts[-1]  # return address and contract object
Пример #14
0
    def contract_hash_to_address(self, hash):
        """

        :param hash:
        """
        if not re.match(r"0x[a-fA-F0-9]{64}", hash):
            raise CriticalError(
                "Invalid address hash. Expected format is '0x...'.")

        print(self.eth_db.contract_hash_to_address(hash))
Пример #15
0
    def set_api_rpc(self, rpc=None, rpctls=False):
        if rpc == 'ganache':
            rpcconfig = ('localhost', 7545, False)
        else:
            m = re.match(r'infura-(.*)', rpc)
            if m and m.group(1) in ['mainnet', 'rinkeby', 'kovan', 'ropsten']:
                rpcconfig = (m.group(1) + '.infura.io', 443, True)
            else:
                try:
                    host, port = rpc.split(":")
                    rpcconfig = (host, int(port), rpctls)
                except ValueError:
                    raise CriticalError("Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'")

        if rpcconfig:
            self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2])
            logging.info("Using RPC settings: %s" % str(rpcconfig))
        else:
            raise CriticalError("Invalid RPC settings, check help for details.")
Пример #16
0
    def contract_hash_to_address(self, contract_hash):
        """
        Returns address of the corresponding hash by searching the leveldb
        :param contract_hash: Hash to be searched
        """
        if not re.match(r"0x[a-fA-F0-9]{64}", contract_hash):
            raise CriticalError(
                "Invalid address hash. Expected format is '0x...'.")

        print(self.leveldb.contract_hash_to_address(contract_hash))
Пример #17
0
    def load_from_solidity(self, solidity_files):
        """
        UPDATES self.sigs!
        :param solidity_files:
        :return:
        """
        address = util.get_indexed_address(0)
        contracts = []
        for file in solidity_files:
            if ":" in file:
                file, contract_name = file.split(":")
            else:
                contract_name = None

            file = os.path.expanduser(file)

            try:
                # import signatures from solidity source
                with open(file, encoding="utf-8") as f:
                    self.sigs.import_from_solidity_source(f.read())

                contract = SolidityContract(file,
                                            contract_name,
                                            solc_args=self.solc_args)
                logging.info("Analyzing contract %s:%s" %
                             (file, contract.name))
            except FileNotFoundError:
                raise CriticalError("Input file not found: " + file)
            except CompilerError as e:
                raise CriticalError(e)
            except NoContractFoundError:
                logging.info("The file " + file +
                             " does not contain a compilable contract.")
            else:
                self.contracts.append(contract)
                contracts.append(contract)

        # Save updated function signatures
        self.sigs.write(
        )  # dump signatures to disk (previously opened file or default location)

        return address, contracts
Пример #18
0
    def search_db(self, search):
        def search_callback(code_hash, code, addresses, balances):
            for i in range(0, len(addresses)):
                print("Address: " + addresses[i] + ", balance: " +
                      str(balances[i]))

        try:
            self.ethDb.search(search, search_callback)

        except SyntaxError:
            raise CriticalError("Syntax error in search expression.")
Пример #19
0
    def search_db(self, search):
        def search_callback(code_hash, code, addresses, balances):
            print("Matched contract with code hash " + code_hash)
            for i in range(0, len(addresses)):
                print("Address: " + addresses[i] + ", balance: " +
                      str(balances[i]))

        contract_storage, _ = get_persistent_storage(self.mythril_dir)
        try:
            if self.dbtype == "leveldb":
                contract_storage.search(search, search_callback)
            else:
                self.ethDB.search(search, search_callback)
        except SyntaxError:
            raise CriticalError("Syntax error in search expression.")
Пример #20
0
    def __init__(
        self,
        solv=None,
        solc_args=None,
        dynld=False,
        enable_online_lookup=False,
        onchain_storage_access=True,
    ):

        self.solv = solv
        self.solc_args = solc_args
        self.dynld = dynld
        self.onchain_storage_access = onchain_storage_access
        self.enable_online_lookup = enable_online_lookup

        self.mythril_dir = self._init_mythril_dir()

        self.sigs = {}
        try:
            # tries mythril_dir/signatures.json by default (provide path= arg to make this configurable)
            self.sigs = signatures.SignatureDb(
                enable_online_lookup=self.enable_online_lookup)
        except FileNotFoundError as e:
            logging.info(str(e))

            # Create empty db file if none exists

            f = open(os.path.join(self.mythril_dir, "signatures.json"), "w")
            f.write("{}")
            f.close()

            self.sigs = signatures.SignatureDb(
                enable_online_lookup=self.enable_online_lookup)

        except json.JSONDecodeError as e:
            raise CriticalError(str(e))

        self.solc_binary = self._init_solc_binary(solv)
        self.config_path = os.path.join(self.mythril_dir, "config.ini")
        self.leveldb_dir = self._init_config()

        self.eth = None  # ethereum API client
        self.eth_db = None  # ethereum LevelDB client

        self.contracts = []  # loaded contracts
Пример #21
0
    def _init_signatures(self):

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

        signatures_file = os.path.join(self.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 json.JSONDecodeError as e:
                raise CriticalError("Invalid JSON in signatures file " + signatures_file + "\n" + str(e))
        return signatures_file, sigs
Пример #22
0
    def search_db(self, search):
        """
        Searches the corresponding code
        :param search: The code part to be searched
        """
        def search_callback(_, address, balance):
            """

            :param _:
            :param address: The address of the contract with the code in search
            :param balance: The balance of the corresponding contract
            """
            print("Address: " + address + ", balance: " + str(balance))

        try:
            self.leveldb.search(search, search_callback)

        except SyntaxError:
            raise CriticalError("Syntax error in search expression.")
Пример #23
0
    def get_state_variable_from_storage(self,
                                        address: str,
                                        params: Optional[List[str]] = None
                                        ) -> str:
        """
        Get variables from the storage
        :param address: The contract address
        :param params: The list of parameters
                        param types: [position, length] or ["mapping", position, key1, key2, ...  ]
                        or [position, length, array]
        :return: The corresponding storage slot and its value
        """
        params = params or []
        (position, length, mappings) = (0, 1, [])
        try:
            if params[0] == "mapping":
                if len(params) < 3:
                    raise CriticalError("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:
                    raise CriticalError("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:
            raise CriticalError(
                "Invalid storage index. Please provide a numeric value.")

        outtxt = []

        try:
            if length == 1:
                outtxt.append("{}: {}".format(
                    position, self.eth.eth_getStorageAt(address, position)))
            else:
                if len(mappings) > 0:
                    for i in range(0, len(mappings)):
                        position = mappings[i]
                        outtxt.append("{}: {}".format(
                            hex(position),
                            self.eth.eth_getStorageAt(address, position),
                        ))
                else:
                    for i in range(position, position + length):
                        outtxt.append("{}: {}".format(
                            hex(i), self.eth.eth_getStorageAt(address, i)))
        except FileNotFoundError as e:
            raise CriticalError("IPC error: " + str(e))
        except ConnectionError:
            raise CriticalError(
                "Could not connect to RPC server. "
                "Make sure that your node is running and that RPC parameters are set correctly."
            )
        return "\n".join(outtxt)
Пример #24
0
    def load_from_solidity(
            self,
            solidity_files: List[str]) -> Tuple[str, List[SolidityContract]]:
        """

        :param solidity_files: List of solidity_files
        :return: tuple of address, contract class list
        """
        address = util.get_indexed_address(0)
        contracts = []
        for file in solidity_files:
            if ":" in file:
                file, contract_name = file.split(":")
            else:
                contract_name = None

            file = os.path.expanduser(file)

            try:
                # import signatures from solidity source
                self.sigs.import_solidity_file(
                    file,
                    solc_binary=self.solc_binary,
                    solc_settings_json=self.solc_settings_json,
                )
                if contract_name is not None:
                    contract = SolidityContract(
                        input_file=file,
                        name=contract_name,
                        solc_settings_json=self.solc_settings_json,
                        solc_binary=self.solc_binary,
                    )
                    self.contracts.append(contract)
                    contracts.append(contract)
                else:
                    for contract in get_contracts_from_file(
                            input_file=file,
                            solc_settings_json=self.solc_settings_json,
                            solc_binary=self.solc_binary,
                    ):
                        self.contracts.append(contract)
                        contracts.append(contract)

            except FileNotFoundError:
                raise CriticalError("Input file not found: " + file)
            except CompilerError as e:
                error_msg = str(e)
                # Check if error is related to solidity version mismatch
                if ("Error: Source file requires different compiler version"
                        in error_msg):
                    # Grab relevant line "pragma solidity <solv>...", excluding any comments
                    solv_pragma_line = error_msg.split("\n")[-3].split("//")[0]
                    # Grab solidity version from relevant line
                    solv_match = re.findall(r"[0-9]+\.[0-9]+\.[0-9]+",
                                            solv_pragma_line)
                    error_suggestion = ("<version_number>"
                                        if len(solv_match) != 1 else
                                        solv_match[0])
                    error_msg = (
                        error_msg +
                        '\nSolidityVersionMismatch: Try adding the option "--solv '
                        + error_suggestion + '"\n')

                raise CriticalError(error_msg)
            except NoContractFoundError:
                log.error("The file " + file +
                          " does not contain a compilable contract.")

        return address, contracts
Пример #25
0
    def contract_hash_to_address(self, hash):
        if not re.match(r'0x[a-fA-F0-9]{64}', hash):
            raise CriticalError(
                "Invalid address hash. Expected format is '0x...'.")

        print(self.ethDb.contract_hash_to_address(hash))