예제 #1
0
def before_first_request():
    print("creating/fixing coverage db")
    #conn = sqlite3.connect('coverage.db')
    c = conn.cursor()

    print("creating coverage table")
    try:
        c.execute('''CREATE TABLE mainnet
      (block integer primary key autoincrement,
       slotted bool default 0,
       filled bool default 0,
       mtime datetime )''')
        print(" done")
    except Exception as e:
        print(" oops: " + str(e))

    infura = EthJsonRpc("mainnet.infura.io", 443, True)
    currentblock = infura.eth_blockNumber()

    result = c.execute("select count(*) from mainnet")
    coveragecount = result.fetchone()[0]

    print("last known block is " + str(currentblock))
    print("last known coverage at " + str(coveragecount))

    for x in range(coveragecount, currentblock):
        c.execute('insert into "mainnet" default values')
    print("coverage updates complete")
    conn.commit()
예제 #2
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.")
예제 #3
0
def getwork():

    c = conn.cursor()

    # init the db
    infura = EthJsonRpc("mainnet.infura.io", 443, True)
    currentblock = infura.eth_blockNumber()

    result = c.execute("select count(*) from mainnet")
    coveragecount = result.fetchone()[0]

    print("last known block is " + str(currentblock))
    print("last known coverage at " + str(coveragecount))

    for x in range(coveragecount, currentblock):
        c.execute('insert into "mainnet" default values')
    print("coverage updates complete")

    # clean up slots
    c.execute(
        "update mainnet set slotted = false, mtime = 0 where slotted = true and filled = false and mtime < ? ",
        (round(time.time() - 60 * 60), ))  # wait one hour for slot submission

    # reserve the next slot
    result = c.execute("select max(block) from mainnet where slotted = false")
    newwork = result.fetchone()[0]
    print("new work is " + str(newwork))
    c.execute("update mainnet set slotted = true, mtime = ? where block = ?",
              (round(time.time()), newwork))
    conn.commit()

    work = {}
    work['type'] = 'karl'
    work['block'] = newwork
    return json.dumps(work)
예제 #4
0
파일: rpc_test.py 프로젝트: norhh/mythril
class RpcTest(TestCase):
    client = None

    def setUp(self):
        self.client = EthJsonRpc()

    def tearDown(self):
        self.client.close()

    def test_eth_coinbase(self):
        coinbase = self.client.eth_coinbase()
        self.assertTrue(coinbase.startswith("0x"),
                        "coinbase should be a hex string")
        self.assertEqual(len(coinbase), 42,
                         "coinbase is a string with length of 42")

    def test_eth_blockNumber(self):
        block_number = self.client.eth_blockNumber()
        self.assertGreater(
            block_number, 0,
            "we have made sure the blockNumber is > 0 for testing")

    def test_eth_getBalance(self):
        balance = self.client.eth_getBalance(
            address="0x0000000000000000000000000000000000000000")
        self.assertGreater(balance, 10000000,
                           "specified address should have a lot of balance")

    def test_eth_getStorageAt(self):
        storage = self.client.eth_getStorageAt(
            address="0x0000000000000000000000000000000000000000")
        self.assertEqual(
            storage,
            "0x0000000000000000000000000000000000000000000000000000000000000000",
        )

    def test_eth_getBlockByNumber(self):
        block = self.client.eth_getBlockByNumber(0)
        self.assertEqual(
            block["extraData"],
            "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
            "the data of the first block should be right",
        )

    def test_eth_getCode(self):
        # TODO: can't find a proper address for getting code
        code = self.client.eth_getCode(
            address="0x0000000000000000000000000000000000000001")
        self.assertEqual(code, "0x")

    def test_eth_getTransactionReceipt(self):
        transaction = self.client.eth_getTransactionReceipt(
            tx_hash=
            "0xe363505adc6b2996111f8bd774f8653a61d244cc6567b5372c2e781c6c63b737"
        )
        self.assertEqual(transaction["from"],
                         "0x22f2dcff5ad78c3eb6850b5cb951127b659522e6")
        self.assertEqual(transaction["to"],
                         "0x0000000000000000000000000000000000000000")
예제 #5
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
예제 #6
0
    def set_api_rpc(self, rpc=None, rpctls=False):
        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])
            logging.info("Using RPC settings: %s" % str(rpcconfig))
        else:
            raise CriticalError(
                "Invalid RPC settings, check help for details.")
예제 #7
0
 def set_api_rpc_infura(self):
     """Set the RPC mode to INFURA on mainnet."""
     self.eth = EthJsonRpc("mainnet.infura.io", 443, True)
     log.info("Using INFURA for RPC queries")
예제 #8
0
from flask import render_template, request, Flask, g, url_for

import time
import os
import json
import random
import sys
import traceback
import sqlite3

import models_elastic as nakatomidb

from datetime import datetime

from mythril.ethereum.interface.rpc.client import EthJsonRpc
infura = EthJsonRpc("mainnet.infura.io", 443, True)

app = Flask(__name__, static_url_path='/static')
app.config.from_object('config')
app.jinja_env.add_extension('jinja2.ext.do')

conn = sqlite3.connect('coverage.db', check_same_thread=False)
#c = conn.cursor()


@app.before_request
def before_request():
    g.preview_length = app.config['PREVIEW_LENGTH']


@app.before_first_request
예제 #9
0
파일: mythril.py 프로젝트: norhh/mythril
 def set_api_rpc_infura(self):
     self.eth = EthJsonRpc("mainnet.infura.io", 443, True)
     logging.info("Using INFURA for RPC queries")
예제 #10
0
파일: mythril.py 프로젝트: norhh/mythril
 def set_api_rpc_localhost(self):
     self.eth = EthJsonRpc("localhost", 8545)
     logging.info("Using default RPC settings: http://localhost:8545")
예제 #11
0
def exploits_from_mythril(
    rpcHTTP="http://localhost:8545",
    rpcWS=None,
    rpcIPC=None,
    timeout=300,
    contract="",
    account_pk="",
    strategy="bfs",
    modules=["ether_thief", "suicide"],
    transaction_count=3,
    execution_timeout=120,
    max_depth=32,
    loop_bound=3,
    disable_dependency_pruning=False,
    onchain_storage_access=True,
    enable_online_lookup=False,
):
    if re.match(r"^https", rpcHTTP):
        host, port = rpcHTTP[8:].split(":")
        rpc_tls = True
    else:
        host, port = rpcHTTP[7:].split(":")
        rpc_tls = False

    try:
        # Disassembler
        disassembler = MythrilDisassembler(
            eth=EthJsonRpc(host=host, port=port, tls=rpc_tls),
            solc_version=None,
            solc_args=None,
            enable_online_lookup=enable_online_lookup,
        )
        disassembler.load_from_address(contract)
        # Analyzer
        analyzer = MythrilAnalyzer(
            strategy=strategy,
            disassembler=disassembler,
            address=contract,
            execution_timeout=execution_timeout,
            max_depth=max_depth,
            loop_bound=loop_bound,
            disable_dependency_pruning=disable_dependency_pruning,
            onchain_storage_access=onchain_storage_access,
        )
        # Generate report
        report = analyzer.fire_lasers(
            modules=modules, transaction_count=transaction_count
        )
    except CriticalError as e:
        print(e)
        return []

    if rpcIPC is not None:
        print("Connecting to IPC: {rpc}.".format(rpc=rpcIPC))
        w3 = Web3(Web3.IPCProvider(rpcIPC, timeout=timeout))
    elif rpcWS is not None:
        print("Connecting to WebSocket: {rpc}.".format(rpc=rpcWS))
        w3 = Web3(Web3.WebsocketProvider(rpcWS, websocket_kwargs={"timeout": timeout}))
    else:
        print("Connecting to HTTP: {rpc}.".format(rpc=rpcHTTP))
        w3 = Web3(Web3.HTTPProvider(rpcHTTP, request_kwargs={"timeout": timeout}))

    exploits = []

    for ri in report.issues:
        txs = []
        issue = report.issues[ri]

        for si in issue.transaction_sequence["steps"]:
            txs.append(Tx(data=si["input"], value=si["value"], name=si["name"]))

        exploits.append(
            Exploit(
                txs=txs,
                w3=w3,
                contract=contract,
                account=private_key_to_account(account_pk),
                account_pk=account_pk,
                title=issue.title,
                description=issue.description,
                swc_id=issue.swc_id,
            )
        )

    return exploits
예제 #12
0
 def set_api_rpc_infura(self) -> None:
     """Set the RPC mode to INFURA on Mainnet."""
     log.info("Using INFURA Main Net for RPC queries")
     self.eth = EthJsonRpc("mainnet.infura.io", 443, True)
예제 #13
0
class Mythril(object):
    """
    Mythril main interface class.

    1. create mythril object
    2. set rpc or leveldb interface if needed
    3. load contracts (from solidity, bytecode, address)
    4. fire_lasers

    Example:
        mythril = Mythril()
        mythril.set_api_rpc_infura()

        # (optional) other API adapters
        mythril.set_api_rpc(args)
        mythril.set_api_rpc_localhost()
        mythril.set_api_leveldb(path)

        # (optional) other func
        mythril.analyze_truffle_project(args)
        mythril.search_db(args)

        # load contract
        mythril.load_from_bytecode(bytecode)
        mythril.load_from_address(address)
        mythril.load_from_solidity(solidity_file)

        # analyze
        print(mythril.fire_lasers(args).as_text())

        # (optional) graph
        for contract in mythril.contracts:
            print(mythril.graph_html(args))  # prints html or save it to file

        # (optional) other funcs
        mythril.dump_statespaces(args)
        mythril.disassemble(contract)
        mythril.get_state_variable_from_storage(args)

    """
    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

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

            # Initialize data directory and signature database

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

    def _init_config(self):
        """
        If no config file exists, create it and add default options.
        Default LevelDB path is specified based on OS
        dynamic loading is set to infura by default in the file
        Returns: leveldb directory
        """

        system = platform.system().lower()
        leveldb_fallback_dir = os.path.expanduser('~')
        if system.startswith("darwin"):
            leveldb_fallback_dir = os.path.join(leveldb_fallback_dir,
                                                "Library", "Ethereum")
        elif system.startswith("windows"):
            leveldb_fallback_dir = os.path.join(leveldb_fallback_dir,
                                                "AppData", "Roaming",
                                                "Ethereum")
        else:
            leveldb_fallback_dir = os.path.join(leveldb_fallback_dir,
                                                ".ethereum")
        leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "geth",
                                            "chaindata")

        if not os.path.exists(self.config_path):
            logging.info("No config file found. Creating default: " +
                         self.config_path)
            open(self.config_path, 'a').close()

        config = ConfigParser(allow_no_value=True)
        config.optionxform = str
        config.read(self.config_path, 'utf-8')
        if 'defaults' not in config.sections():
            self._add_default_options(config)

        if not config.has_option('defaults', 'leveldb_dir'):
            self._add_leveldb_option(config, leveldb_fallback_dir)

        if not config.has_option('defaults', 'dynamic_loading'):
            self._add_dynamic_loading_option(config)

        with codecs.open(self.config_path, 'w', 'utf-8') as fp:
            config.write(fp)

        leveldb_dir = config.get('defaults',
                                 'leveldb_dir',
                                 fallback=leveldb_fallback_dir)
        return os.path.expanduser(leveldb_dir)

    @staticmethod
    def _add_default_options(config):
        config.add_section('defaults')

    @staticmethod
    def _add_leveldb_option(config, leveldb_fallback_dir):
        config.set('defaults', "#Default chaindata locations:")
        config.set('defaults', "#– Mac: ~/Library/Ethereum/geth/chaindata")
        config.set('defaults', "#– Linux: ~/.ethereum/geth/chaindata")
        config.set(
            'defaults',
            "#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata"
        )
        config.set('defaults', 'leveldb_dir', leveldb_fallback_dir)

    @staticmethod
    def _add_dynamic_loading_option(config):
        config.set('defaults',
                   '#– To connect to Infura use dynamic_loading: infura')
        config.set(
            'defaults', '#– To connect to Rpc use '
            'dynamic_loading: HOST:PORT / ganache / infura-[network_name]')
        config.set(
            'defaults',
            '#– To connect to local host use dynamic_loading: localhost')
        config.set('defaults', 'dynamic_loading', 'infura')

    def analyze_truffle_project(self, *args, **kwargs):
        return analyze_truffle_project(
            self.sigs, *args,
            **kwargs)  # just passthru by passing signatures for now

    @staticmethod
    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 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

    def set_api_leveldb(self, leveldb):
        self.eth_db = EthLevelDB(leveldb)
        self.eth = self.eth_db
        return self.eth

    def set_api_rpc_infura(self):
        self.eth = EthJsonRpc('mainnet.infura.io', 443, True)
        logging.info("Using INFURA for RPC queries")

    def set_api_rpc(self, rpc=None, rpctls=False):
        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])
            logging.info("Using RPC settings: %s" % str(rpcconfig))
        else:
            raise CriticalError(
                "Invalid RPC settings, check help for details.")

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

    def set_api_from_config_path(self):
        config = ConfigParser(allow_no_value=False)
        config.optionxform = str
        config.read(self.config_path, 'utf-8')
        if config.has_option('defaults', 'dynamic_loading'):
            dynamic_loading = config.get('defaults', 'dynamic_loading')
        else:
            dynamic_loading = 'infura'
        if dynamic_loading == 'infura':
            self.set_api_rpc_infura()
        elif dynamic_loading == 'localhost':
            self.set_api_rpc_localhost()
        else:
            self.set_api_rpc(dynamic_loading)

    def search_db(self, search):
        def search_callback(contract, 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.")

    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.eth_db.contract_hash_to_address(hash))

    def load_from_bytecode(self, code):
        address = util.get_indexed_address(0)
        self.contracts.append(ETHContract(code, name="MAIN"))
        return address, self.contracts[
            -1]  # return address and contract object

    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

    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(file,
                                                contract_name,
                                                solc_args=self.solc_args)
                    self.contracts.append(contract)
                    contracts.append(contract)
                else:
                    for contract in get_contracts_from_file(
                            file, solc_args=self.solc_args):
                        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.info("The file " + file +
                             " does not contain a compilable contract.")

        return address, contracts

    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)

    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)

    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) if self.dynld else None,
                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

    def get_state_variable_from_storage(self, address, params=None):
        if params is None:
            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)

    @staticmethod
    def disassemble(contract):
        return contract.get_easm()

    @staticmethod
    def hash_for_function_signature(sig):
        return "0x%s" % utils.sha3(sig)[:4].hex()
예제 #14
0
 def set_api_rpc_localhost(self):
     """Set the RPC mode to a local instance."""
     self.eth = EthJsonRpc("localhost", 8545)
     log.info("Using default RPC settings: http://localhost:8545")
예제 #15
0
파일: rpc_test.py 프로젝트: norhh/mythril
 def setUp(self):
     self.client = EthJsonRpc()
예제 #16
0
class Mythril(object):
    """Mythril main interface class.

    1. create mythril object
    2. set rpc or leveldb interface if needed
    3. load contracts (from solidity, bytecode, address)
    4. fire_lasers

    .. code-block:: python

        mythril = Mythril()
        mythril.set_api_rpc_infura()

        # (optional) other API adapters
        mythril.set_api_rpc(args)
        mythril.set_api_rpc_localhost()
        mythril.set_api_leveldb(path)

        # (optional) other func
        mythril.analyze_truffle_project(args)
        mythril.search_db(args)

        # load contract
        mythril.load_from_bytecode(bytecode)
        mythril.load_from_address(address)
        mythril.load_from_solidity(solidity_file)

        # analyze
        print(mythril.fire_lasers(args).as_text())

        # (optional) graph
        for contract in mythril.contracts:
            # prints html or save it to file
            print(mythril.graph_html(args))

        # (optional) other funcs
        mythril.dump_statespaces(args)
        mythril.disassemble(contract)
        mythril.get_state_variable_from_storage(args)
    """
    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()

        # tries mythril_dir/signatures.db by default (provide path= arg to make this configurable)
        self.sigs = signatures.SignatureDB(
            enable_online_lookup=self.enable_online_lookup)

        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

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

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

        db_path = str(Path(mythril_dir) / "signatures.db")
        if not os.path.exists(db_path):
            # if the default mythril dir doesn't contain a signature DB
            # initialize it with the default one from the project root
            asset_dir = Path(__file__).parent / "support" / "assets"
            copyfile(str(asset_dir / "signatures.db"), db_path)

        return mythril_dir

    def _init_config(self):
        """If no config file exists, create it and add default options.

        Default LevelDB path is specified based on OS
        dynamic loading is set to infura by default in the file
        Returns: leveldb directory
        """

        system = platform.system().lower()
        leveldb_fallback_dir = os.path.expanduser("~")
        if system.startswith("darwin"):
            leveldb_fallback_dir = os.path.join(leveldb_fallback_dir,
                                                "Library", "Ethereum")
        elif system.startswith("windows"):
            leveldb_fallback_dir = os.path.join(leveldb_fallback_dir,
                                                "AppData", "Roaming",
                                                "Ethereum")
        else:
            leveldb_fallback_dir = os.path.join(leveldb_fallback_dir,
                                                ".ethereum")
        leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "geth",
                                            "chaindata")

        if not os.path.exists(self.config_path):
            log.info("No config file found. Creating default: " +
                     self.config_path)
            open(self.config_path, "a").close()

        config = ConfigParser(allow_no_value=True)
        config.optionxform = str
        config.read(self.config_path, "utf-8")
        if "defaults" not in config.sections():
            self._add_default_options(config)

        if not config.has_option("defaults", "leveldb_dir"):
            self._add_leveldb_option(config, leveldb_fallback_dir)

        if not config.has_option("defaults", "dynamic_loading"):
            self._add_dynamic_loading_option(config)

        with codecs.open(self.config_path, "w", "utf-8") as fp:
            config.write(fp)

        leveldb_dir = config.get("defaults",
                                 "leveldb_dir",
                                 fallback=leveldb_fallback_dir)
        return os.path.expanduser(leveldb_dir)

    @staticmethod
    def _add_default_options(config):
        config.add_section("defaults")

    @staticmethod
    def _add_leveldb_option(config, leveldb_fallback_dir):
        config.set("defaults", "#Default chaindata locations:")
        config.set("defaults", "#– Mac: ~/Library/Ethereum/geth/chaindata")
        config.set("defaults", "#– Linux: ~/.ethereum/geth/chaindata")
        config.set(
            "defaults",
            "#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata",
        )
        config.set("defaults", "leveldb_dir", leveldb_fallback_dir)

    @staticmethod
    def _add_dynamic_loading_option(config):
        config.set("defaults",
                   "#– To connect to Infura use dynamic_loading: infura")
        config.set(
            "defaults",
            "#– To connect to Rpc use "
            "dynamic_loading: HOST:PORT / ganache / infura-[network_name]",
        )
        config.set(
            "defaults",
            "#– To connect to local host use dynamic_loading: localhost")
        config.set("defaults", "dynamic_loading", "infura")

    def analyze_truffle_project(self, *args, **kwargs):
        """

        :param args:
        :param kwargs:
        :return:
        """
        return analyze_truffle_project(
            self.sigs, *args,
            **kwargs)  # just passthru by passing signatures for now

    @staticmethod
    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:
            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:
                log.info("Given version is already installed")
            else:
                try:
                    solc.install_solc("v" + version)
                    solc_binary = util.solc_exists(version)
                    if not solc_binary:
                        raise SolcError()
                except SolcError:
                    raise CriticalError(
                        "There was an error when trying to install the specified solc version"
                    )

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

        return solc_binary

    def set_api_leveldb(self, leveldb):
        """

        :param leveldb:
        :return:
        """
        self.eth_db = EthLevelDB(leveldb)
        self.eth = self.eth_db
        return self.eth

    def set_api_rpc_infura(self):
        """Set the RPC mode to INFURA on mainnet."""
        self.eth = EthJsonRpc("mainnet.infura.io", 443, True)
        log.info("Using INFURA for RPC queries")

    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.")

    def set_api_rpc_localhost(self):
        """Set the RPC mode to a local instance."""
        self.eth = EthJsonRpc("localhost", 8545)
        log.info("Using default RPC settings: http://localhost:8545")

    def set_api_from_config_path(self):
        """Set the RPC mode based on a given config file."""
        config = ConfigParser(allow_no_value=False)
        config.optionxform = str
        config.read(self.config_path, "utf-8")
        if config.has_option("defaults", "dynamic_loading"):
            dynamic_loading = config.get("defaults", "dynamic_loading")
        else:
            dynamic_loading = "infura"
        if dynamic_loading == "infura":
            self.set_api_rpc_infura()
        elif dynamic_loading == "localhost":
            self.set_api_rpc_localhost()
        else:
            self.set_api_rpc(dynamic_loading)

    def search_db(self, search):
        """

        :param search:
        """
        def search_callback(_, address, balance):
            """

            :param _:
            :param address:
            :param balance:
            """
            print("Address: " + address + ", balance: " + str(balance))

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

        except SyntaxError:
            raise CriticalError("Syntax error in search expression.")

    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))

    def load_from_bytecode(self, code, bin_runtime=False, address=None):
        """

        :param code:
        :param bin_runtime:
        :param address:
        :return:
        """
        if address is None:
            address = util.get_indexed_address(0)
        if bin_runtime:
            self.contracts.append(
                EVMContract(
                    code=code,
                    name="MAIN",
                    enable_online_lookup=self.enable_online_lookup,
                ))
        else:
            self.contracts.append(
                EVMContract(
                    creation_code=code,
                    name="MAIN",
                    enable_online_lookup=self.enable_online_lookup,
                ))
        return address, self.contracts[
            -1]  # return address and contract object

    def load_from_address(self, address):
        """

        :param address:
        :return:
        """
        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))
        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(
                    EVMContract(
                        code,
                        name=address,
                        enable_online_lookup=self.enable_online_lookup,
                    ))
        return address, self.contracts[
            -1]  # return address and contract object

    def load_from_solidity(self, solidity_files):
        """

        :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_solidity_file(file,
                                               solc_binary=self.solc_binary,
                                               solc_args=self.solc_args)
                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:
                log.error("The file " + file +
                          " does not contain a compilable contract.")

        return address, contracts

    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)

    def graph_html(
        self,
        strategy,
        contract,
        address,
        max_depth=None,
        enable_physics=False,
        phrackify=False,
        execution_timeout=None,
        create_timeout=None,
        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,
            enable_iprof=enable_iprof,
        )
        return generate_graph(sym, physics=enable_physics, phrackify=phrackify)

    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

    def get_state_variable_from_storage(self, address, params=None):
        """

        :param address:
        :param params:
        :return:
        """
        if params is None:
            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:
            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)

    @staticmethod
    def disassemble(contract):
        """

        :param contract:
        :return:
        """
        return contract.get_easm()

    @staticmethod
    def hash_for_function_signature(sig):
        """

        :param sig:
        :return:
        """
        return "0x%s" % utils.sha3(sig)[:4].hex()
    passive_account = Account("0x325345346564645654645",
                              code=Disassembly("6060604061626364"))
    environment = Environment(active_account, None, None, None, None, None)
    world_state = WorldState()
    world_state.put_account(active_account)
    world_state.put_account(passive_account)
    return GlobalState(world_state, environment, None,
                       MachineState(gas_limit=8000000))


@pytest.mark.parametrize(
    "addr, eth, code_len",
    [
        (
            "0xb09C477eCDAd49DD5Ac26c2C64914C3a6693843a",
            EthJsonRpc("rinkeby.infura.io", 443, True),
            1548,
        ),
        (
            "0x863DF6BFa4469f3ead0bE8f9F2AAE51c91A907b4",
            EthJsonRpc("mainnet.infura.io", 443, True),
            0,
        ),
        (
            "0x325345346564645654645",
            EthJsonRpc("mainnet.infura.io", 443, True),
            16,
        ),  # This contract tests Address Cache
    ],
)
def test_extraction(addr, eth, code_len):