From dcc15e5a16b483bf05154c7d38ae1260fb64a745 Mon Sep 17 00:00:00 2001 From: ggrieco-tob Date: Fri, 3 Sep 2021 09:19:46 +0200 Subject: [PATCH 1/7] first step to remove manticore and avoid any logger issues --- etheno/__main__.py | 49 +--------- etheno/manticoreclient.py | 193 -------------------------------------- etheno/manticorelogger.py | 26 ----- etheno/manticoreutils.py | 148 ----------------------------- 4 files changed, 1 insertion(+), 415 deletions(-) delete mode 100644 etheno/manticoreclient.py delete mode 100644 etheno/manticorelogger.py delete mode 100644 etheno/manticoreutils.py diff --git a/etheno/__main__.py b/etheno/__main__.py index 747c822..ae403e9 100644 --- a/etheno/__main__.py +++ b/etheno/__main__.py @@ -19,14 +19,6 @@ from . import parity from . import truffle -try: - from .manticoreclient import ManticoreClient - from . import manticoreutils - MANTICORE_INSTALLED = True -except ModuleNotFoundError: - MANTICORE_INSTALLED = False - - def main(argv = None): parser = argparse.ArgumentParser(description='An Ethereum JSON RPC multiplexer and Manticore wrapper') parser.add_argument('--debug', action='store_true', default=False, help='Enable debugging from within the web server') @@ -36,9 +28,6 @@ def main(argv = None): parser.add_argument('-b', '--balance', type=float, default=100.0, help='Default balance (in Ether) to seed to each account (default=100.0)') parser.add_argument('-c', '--gas-price', type=int, default=None, help='Default gas price (default=20000000000)') parser.add_argument('-i', '--network-id', type=int, default=None, help='Specify a network ID (default is the network ID of the master client)') - parser.add_argument('-m', '--manticore', action='store_true', default=False, help='Run all transactions through manticore') - parser.add_argument('-r', '--manticore-script', type=argparse.FileType('rb'), default=None, help='Instead of running automated detectors and analyses, run this Manticore script') - parser.add_argument('--manticore-max-depth', type=int, default=None, help='Maximum state depth for Manticore to explore') parser.add_argument('-e', '--echidna', action='store_true', default=False, help='Fuzz the clients using transactions generated by Echidna') parser.add_argument('--fuzz-limit', type=int, default=None, help='The maximum number of transactions for Echidna to generate (default=unlimited)') parser.add_argument('--fuzz-contract', type=str, default=None, help='Path to a Solidity contract to have Echidna use for fuzzing (default is to use a builtin generic Echidna fuzzing contract)') @@ -259,27 +248,6 @@ def main(argv = None): for client in args.raw: ETHENO.add_client(RawTransactionClient(RpcProxyClient(client), accounts)) - manticore_client = None - if args.manticore: - if not MANTICORE_INSTALLED: - ETHENO.logger.error('Manticore is not installed! Running Etheno with Manticore requires Manticore version 0.2.2 or newer. Reinstall Etheno with Manticore support by running `pip3 install --user \'etheno[manticore]\'`, or install Manticore separately with `pip3 install --user \'manticore\'`') - sys.exit(1) - new_enough = manticoreutils.manticore_is_new_enough() - if new_enough is None: - ETHENO.logger.warning(f"Unknown Manticore version {manticoreutils.manticore_version()}; it may not be new enough to have Etheno support!") - elif not new_enough: - ETHENO.logger.error(f"The version of Manticore installed is {manticoreutils.manticore_version()}, but the minimum required version with Etheno support is 0.2.2. We will try to proceed, but things might not work correctly! Please upgrade Manticore.") - manticore_client = ManticoreClient() - ETHENO.add_client(manticore_client) - if args.manticore_max_depth is not None: - manticore_client.manticore.register_detector(manticoreutils.StopAtDepth(args.manticore_max_depth)) - if manticoreutils.manticore_is_new_enough(0, 2, 4): - # the verbosity static method was deprecated - from manticore.utils.log import set_verbosity - set_verbosity(getattr(logger, args.log_level)) - else: - manticore_client.manticore.verbosity(getattr(logger, args.log_level)) - if args.truffle: truffle_controller = truffle.Truffle(truffle_cmd=args.truffle_cmd, parent_logger=ETHENO.logger) @@ -296,22 +264,7 @@ def truffle_thread(): for plugin in ETHENO.plugins: plugin.finalize() - if manticore_client is not None: - if args.manticore_script is not None: - f = args.manticore_script - code = compile(f.read(), f.name, 'exec') - exec(code, { - 'manticore': manticore_client.manticore, - 'manticoreutils': manticoreutils, - 'logger': logger.EthenoLogger(os.path.basename(args.manticore_script.name), parent=manticore_client.logger) - }) - else: - manticoreutils.register_all_detectors(manticore_client.manticore) - manticore_client.multi_tx_analysis() - manticore_client.manticore.finalize() - manticore_client.logger.info("Results are in %s" % manticore_client.manticore.workspace) - ETHENO.shutdown() - elif not ETHENO.clients and not ETHENO.plugins: + if not ETHENO.clients and not ETHENO.plugins: ETHENO.logger.info("No clients or plugins running; exiting...") ETHENO.shutdown() diff --git a/etheno/manticoreclient.py b/etheno/manticoreclient.py deleted file mode 100644 index 0c3355d..0000000 --- a/etheno/manticoreclient.py +++ /dev/null @@ -1,193 +0,0 @@ -import logging -import time - -import builtins -import sys - -# ####BEGIN#### -# Horrible hack to workaround Manticore's global logging system. -# This can be removed after https://github.com/trailofbits/manticore/issues/1369 -# is resolved. -from . import manticorelogger - -oldimport = builtins.__import__ -def manticoreimport(name, *args, **kwargs): - if name == 'manticore.utils.log': - manticorelogger.__name__ = 'manticore.utils.log' - sys.modules[name] = manticorelogger - return manticorelogger - else: - return oldimport(name, *args, **kwargs) - -builtins.__import__ = manticoreimport -try: - import manticore.utils.log - import manticore.utils -finally: - builtins.__import__ = oldimport - -manticore.utils.log = manticorelogger -# ####END#### - -from manticore.ethereum import ManticoreEVM -from manticore.exceptions import NoAliveStates -import manticore - -from . import logger -from . import threadwrapper -from .client import EthenoClient, jsonrpc, DATA, QUANTITY -from .etheno import _CONTROLLER -from .manticoreutils import manticore_is_new_enough - -def encode_hex(data): - if data is None: - return None - elif isinstance(data, int) or isinstance(data, long): - encoded = hex(data) - if encoded[-1] == 'L': - encoded = encoded[:-1] - return encoded - else: - return "0x%s" % data.encode('hex') - -class ManticoreClient(EthenoClient): - def __init__(self, manticore=None): - self._assigned_manticore = manticore - self._manticore = None - self.contracts = [] - self.short_name = 'Manticore' - self._accounts_to_create = [] - - @property - def manticore(self): - if self._manticore is None: - if self._assigned_manticore is None: - # we do lazy evaluation of ManticoreClient.manticore so self.log_directory will be assigned already - if self.log_directory is None: - workspace = None - else: - workspace = self.log_directory - self._assigned_manticore = ManticoreEVM(workspace_url=workspace) - self._manticore = threadwrapper.MainThreadWrapper(self._assigned_manticore, _CONTROLLER) - self._finalize_manticore() - return self._manticore - - def _finalize_manticore(self): - if not self._manticore: - return - for balance, address in self._accounts_to_create: - self._manticore.create_account(balance=balance, address=address) - self._accounts_to_create = [] - self.reassign_manticore_loggers() - self.logger.cleanup_empty = True - - def create_account(self, balance, address): - self._accounts_to_create.append((balance, address)) - self._finalize_manticore() - - def reassign_manticore_loggers(self): - # Manticore uses a global to track its loggers: - manticore.utils.log.ETHENO_LOGGER = self.logger - manticore_loggers = (name for name in logging.root.manager.loggerDict if name.startswith('manticore')) - logger_parents = {} - for name in sorted(manticore_loggers): - sep = name.rfind('.') - if sep > 0: - path = name[:sep] - parent = logger_parents[path] - displayname = name[len(path)+1:] - else: - parent = self.logger - displayname = name - m_logger = logger.EthenoLogger(name, parent=parent, cleanup_empty=True, displayname=displayname) - m_logger.propagate = False - logger_parents[name] = m_logger - - @jsonrpc(from_addr = QUANTITY, to = QUANTITY, gas = QUANTITY, gasPrice = QUANTITY, value = QUANTITY, data = DATA, nonce = QUANTITY, RETURN = DATA) - def eth_sendTransaction(self, from_addr, to = None, gas = 90000, gasPrice = None, value = 0, data = None, nonce = None, rpc_client_result = None): - if to is None or to == 0: - # we are creating a new contract - if rpc_client_result is not None: - tx_hash = rpc_client_result['result'] - while True: - receipt = self.etheno.master_client.post({ - 'id' : "%s_receipt" % rpc_client_result['id'], - 'method' : 'eth_getTransactionReceipt', - 'params' : [tx_hash] - }) - if 'result' in receipt and receipt['result']: - address = int(receipt['result']['contractAddress'], 16) - break - # The transaction is still pending - time.sleep(1.0) - else: - address = None - contract_address = self.manticore.create_contract(owner = from_addr, balance = value, init=data) - self.contracts.append(contract_address) - self.logger.info(f"Manticore contract created: {encode_hex(contract_address.address)}") - #self.logger.info("Block number: %s" % self.manticore.world.block_number()) - else: - self.manticore.transaction(address = to, data = data, caller=from_addr, value = value) - # Just mimic the result from the master client - # We need to return something valid to appease the differential tester - return rpc_client_result - - @jsonrpc(TX_HASH = QUANTITY) - def eth_getTransactionReceipt(self, tx_hash, rpc_client_result = None): - # Mimic the result from the master client - # to appease the differential tester - return rpc_client_result - - def multi_tx_analysis(self, contract_address = None, tx_limit=None, tx_use_coverage=True, args=None): - if contract_address is None: - for contract_address in self.contracts: - self.multi_tx_analysis( - contract_address=contract_address, - tx_limit=tx_limit, - tx_use_coverage=tx_use_coverage, - args=args - ) - return - - tx_account = self.etheno.accounts - - current_coverage = 0 - tx_no = 0 - if manticore_is_new_enough(0, 3, 0): - shutdown_test = 'is_killed' - else: - shutdown_test = 'is_shutdown' - - while (current_coverage < 100 or not tx_use_coverage) and not getattr(self.manticore, shutdown_test)(): - try: - self.logger.info("Starting symbolic transaction: %d" % tx_no) - - # run_symbolic_tx - symbolic_data = self.manticore.make_symbolic_buffer(320) - symbolic_value = self.manticore.make_symbolic_value() - self.manticore.transaction(caller=tx_account[min(tx_no, len(tx_account) - 1)], - address=contract_address, - data=symbolic_data, - value=symbolic_value) - if manticore_is_new_enough(0, 3, 0): - # TODO: find the equivalent functions to get state counts in v0.3.0 - pass - else: - self.logger.info("%d alive states, %d terminated states" % (self.manticore.count_running_states(), self.manticore.count_terminated_states())) - except NoAliveStates: - break - - # Check if the maximun number of tx was reached - if tx_limit is not None and tx_no + 1 >= tx_limit: - break - - # Check if coverage has improved or not - if tx_use_coverage: - prev_coverage = current_coverage - current_coverage = self.manticore.global_coverage(contract_address) - found_new_coverage = prev_coverage < current_coverage - - if not found_new_coverage: - break - - tx_no += 1 diff --git a/etheno/manticorelogger.py b/etheno/manticorelogger.py deleted file mode 100644 index 2245223..0000000 --- a/etheno/manticorelogger.py +++ /dev/null @@ -1,26 +0,0 @@ -# This is a horrible hack that is used to replace manticore.utils.log -# Remove this once https://github.com/trailofbits/manticore/issues/1369 -# is resolved. - -ETHENO_LOGGER = None - -@property -def manticore_verbosity(): - return ETHENO_LOGGER.log_level - -@property -def DEFAULT_LOG_LEVEL(): - return ETHENO_LOGGER.log_level - -def set_verbosity(setting): - pass - #global manticore_verbosity - #manticore_verbosity = min(max(setting, 0), len(get_levels()) - 1) - #for logger_name in all_loggers: - # logger = logging.getLogger(logger_name) - # # min because more verbosity == lower numbers - # # This means if you explicitly call setLevel somewhere else in the source, and it's *more* - # # verbose, it'll stay that way even if manticore_verbosity is 0. - # logger.setLevel(min(get_verbosity(logger_name), logger.getEffectiveLevel())) - -all_loggers = set() diff --git a/etheno/manticoreutils.py b/etheno/manticoreutils.py deleted file mode 100644 index 22455fb..0000000 --- a/etheno/manticoreutils.py +++ /dev/null @@ -1,148 +0,0 @@ -import inspect -import itertools -import pkg_resources - -# Import manticoreclient before we load any actual Manticore classes. -# We don't need it here, but we do rely on it to hook in the Manticore loggers: -from . import manticoreclient -del manticoreclient - -from manticore.core.smtlib.operators import AND -from manticore.ethereum import ManticoreEVM, Detector -import manticore.ethereum.detectors - - -def manticore_version(): - return pkg_resources.get_distribution('manticore').version - - -def manticore_is_new_enough(*required_version): - """Checks if Manticore is newer than the given version. Returns True or False if known, or None if uncertain.""" - if required_version is None or len(required_version) == 0: - required_version = (0, 2, 2) - try: - version = manticore_version() - version = list(map(int, version.split('.'))) - for v, required in itertools.zip_longest(version, required_version, fillvalue=0): - if v < required: - return False - elif v > required: - return True - except Exception: - return None - return True - - -"""Detectors that should not be included in the results from `get_detectors()` (e.g., because they are buggy)""" -if manticore_is_new_enough(0, 2, 3): - # At some point after Manticore 0.2.2, these all stopped working: - DETECTOR_BLACKLIST = { - manticore.ethereum.detectors.DetectDelegatecall, - manticore.ethereum.detectors.DetectEnvInstruction, - manticore.ethereum.detectors.DetectExternalCallAndLeak, - manticore.ethereum.detectors.DetectIntegerOverflow, - manticore.ethereum.detectors.DetectInvalid, - manticore.ethereum.detectors.DetectRaceCondition, - manticore.ethereum.detectors.DetectReentrancyAdvanced, - manticore.ethereum.detectors.DetectReentrancySimple, - manticore.ethereum.detectors.DetectSuicidal, - manticore.ethereum.detectors.DetectUninitializedMemory, - manticore.ethereum.detectors.DetectUninitializedStorage, - manticore.ethereum.detectors.DetectUnusedRetVal - } -else: - DETECTOR_BLACKLIST = set() - - -def get_detectors(): - for name, obj in inspect.getmembers(manticore.ethereum.detectors): - if inspect.isclass(obj)\ - and issubclass(obj, manticore.ethereum.detectors.Detector)\ - and obj != manticore.ethereum.detectors.Detector\ - and obj not in DETECTOR_BLACKLIST: - yield obj - - -def register_all_detectors(manticore): - for detector in get_detectors(): - try: - manticore.register_detector(detector()) - except Exception as e: - manticore.logger.warning(f"Unable to register detector {detector!r}: {e!s}") - - -class StopAtDepth(Detector): - """This just aborts explorations that are too deep""" - - def __init__(self, max_depth): - self.max_depth = max_depth - - stop_at_death = self - - def will_start_run_callback(*args): - with stop_at_death.manticore.locked_context('seen_rep', dict) as reps: - reps.clear() - - # this callback got renamed to `will_run_callback` in Manticore 0.3.0 - if manticore_is_new_enough(0, 3, 0): - self.will_run_callback = will_start_run_callback - else: - self.will_start_run_callback = will_start_run_callback - - super().__init__() - - def will_decode_instruction_callback(self, state, pc): - world = state.platform - with self.manticore.locked_context('seen_rep', dict) as reps: - item = (world.current_transaction.sort == 'CREATE', world.current_transaction.address, pc) - if item not in reps: - reps[item] = 0 - reps[item] += 1 - if reps[item] > self.max_depth: - state.abandon() - - -class ManticoreTest: - def __init__(self, state, expression): - self.state = state - self.expression = expression - - def __bool__(self): - return self.can_be_true() - - def can_be_true(self): - return self.state.can_be_true(self.expression) - - def _solve_one(self, *variables, initial_state): - with initial_state as state: - state.constrain(self.expression) - for v in variables: - value = state.solve_one(v) - yield value - state.constrain(v == value) - - def solve_one(self, *variables): - """Finds a solution to the state and returns all of the variables in that solution""" - return self._solve_one(*variables, initial_state=self.state) - - def solve_all(self, *variables): - """Enumerates all solutions to the state for the given variables""" - with self.state as state: - while state.can_be_true(self.expression): - solution = tuple(self._solve_one(*variables, initial_state=state)) - if len(solution) < len(variables): - break - yield solution - state.constrain(AND(*(v != s for v, s in zip(variables, solution)))) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - pass - - -if __name__ == '__main__': - print('Available Manticore Detectors:') - for detector in get_detectors(): - print(" %s" % detector) From 7828352b49d70ee58e73e69838cb6425ea81ad51 Mon Sep 17 00:00:00 2001 From: ggrieco-tob Date: Fri, 3 Sep 2021 09:35:37 +0200 Subject: [PATCH 2/7] more stuff removed --- README.md | 41 +----------- .../ExploitMetaCoinManticoreScript.py | 31 --------- examples/BrokenMetaCoin/LICENSE | 22 ------- examples/BrokenMetaCoin/README.md | 9 --- .../BrokenMetaCoin/contracts/ConvertLib.sol | 8 --- .../BrokenMetaCoin/contracts/MetaCoin.sol | 48 -------------- .../BrokenMetaCoin/contracts/Migrations.sol | 23 ------- .../migrations/1_initial_migration.js | 5 -- .../migrations/2_deploy_contracts.js | 8 --- examples/BrokenMetaCoin/run_etheno.sh | 16 ----- examples/BrokenMetaCoin/test/TestMetacoin.sol | 25 -------- examples/BrokenMetaCoin/test/metacoin.js | 63 ------------------- examples/BrokenMetaCoin/truffle.js | 9 --- setup.py | 3 - 14 files changed, 3 insertions(+), 308 deletions(-) delete mode 100644 examples/BrokenMetaCoin/ExploitMetaCoinManticoreScript.py delete mode 100644 examples/BrokenMetaCoin/LICENSE delete mode 100644 examples/BrokenMetaCoin/README.md delete mode 100644 examples/BrokenMetaCoin/contracts/ConvertLib.sol delete mode 100644 examples/BrokenMetaCoin/contracts/MetaCoin.sol delete mode 100644 examples/BrokenMetaCoin/contracts/Migrations.sol delete mode 100644 examples/BrokenMetaCoin/migrations/1_initial_migration.js delete mode 100644 examples/BrokenMetaCoin/migrations/2_deploy_contracts.js delete mode 100755 examples/BrokenMetaCoin/run_etheno.sh delete mode 100644 examples/BrokenMetaCoin/test/TestMetacoin.sol delete mode 100644 examples/BrokenMetaCoin/test/metacoin.js delete mode 100644 examples/BrokenMetaCoin/truffle.js diff --git a/README.md b/README.md index 2f3a70b..6a231e4 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@
-Etheno is the Ethereum testing Swiss Army knife. It’s a JSON RPC multiplexer, analysis tool wrapper, and test integration tool. It eliminates the complexity of setting up analysis tools like [Manticore](https://github.com/trailofbits/manticore/) and [Echidna](https://github.com/trailofbits/echidna) on large, multi-contract projects. In particular, custom Manticore analysis scripts require less code, are simpler to write, and integrate with Truffle. +Etheno is the Ethereum testing Swiss Army knife. It’s a JSON RPC multiplexer, analysis tool wrapper, and test integration tool. It eliminates the complexity of setting up analysis tools like [Echidna](https://github.com/trailofbits/echidna) on large, multi-contract projects. If you are a smart contract developer, you should use Etheno to test your contracts. If you are an Ethereum client developer, you should use Etheno to perform differential testing on your implementation. For example, Etheno is [capable of automatically reproducing](examples/ConstantinopleGasUsage) the Constantinople gas usage consensus bug that caused a fork on Ropsten. @@ -19,13 +19,8 @@ Etheno is named after the Greek goddess [Stheno](https://en.wikipedia.org/wiki/S * API for filtering and modifying JSON RPC calls * Enables differential testing by sending JSON RPC sequences to multiple Ethereum clients * Deploy to and interact with multiple networks at the same time -* **Analysis Tool Wrapper**: Etheno provides a JSON RPC client for advanced analysis tools like [Manticore](https://github.com/trailofbits/manticore/) - * Lowers barrier to entry for using advanced analysis tools - * No need for custom scripts to set up account and contract state - * Analyze arbitrary transactions without Solidity source code * **Integration with Test Frameworks** like Ganache and Truffle * Run a local test network with a single command - * Use Truffle migrations to bootstrap Manticore analyses * Symbolic semantic annotations within unit tests ## Quickstart @@ -35,10 +30,6 @@ Use our prebuilt Docker container to quickly install and try Etheno: ``` docker pull trailofbits/etheno docker run -it trailofbits/etheno - -# Run one of the examples -etheno@982abdc96791:~$ cd examples/BrokenMetaCoin/ -etheno@982abdc96791:~/examples/BrokenMetaCoin$ etheno --truffle --ganache --manticore --manticore-max-depth 2 --manticore-script ExploitMetaCoinManticoreScript.py ``` Alternatively, natively install Etheno in a few shell commands: @@ -52,7 +43,7 @@ pip3 install --user etheno # Use the Etheno CLI cd /path/to/a/truffle/project -etheno --manticore --ganache --truffle +etheno --ganache --truffle ``` ## Usage @@ -70,7 +61,7 @@ etheno https://client1.url.com:1234/ https://client2.url.com:8545/ http://client * `--port` or `-p` allows you to specify a port on which to run Etheno’s JSON RPC server (default is 8545) * `--run-publicly` allows incoming JSON RPC connections from external computers on the network * `--debug` will run a web-based interactive debugger in the event that an internal Etheno client throws an exception while processing a JSON RPC call; this should _never_ be used in conjunction with `--run-publicly` -* `--master` or `-s` will set the “master” client, which will be used for synchronizing with Etheno clients like Manticore. If a master is not explicitly provided, it defaults to the first client listed. +* `--master` or `-s` will set the “master” client, which will be used for synchronizing with Etheno clients. If a master is not explicitly provided, it defaults to the first client listed. * `--raw`, when prefixed before a client URL, will cause Etheno to auto-sign all transactions and submit then to the client as raw transactions ### Geth and Parity Integration @@ -123,17 +114,6 @@ By default, Echidna deploys a generic fuzz testing contract to all clients, enum * `--fuzz-limit` limits the number of transactions that Echidna will emit * `--fuzz-contract` lets the user specify a custom contract for Echidna to deploy and fuzz -### Manticore Client - -Manticore—which, by itself, does not implement a JSON RPC interface—can be run as an Etheno client, synchronizing its accounts with Etheno’s master client and symbolically executing all transactions sent to Etheno. -``` -etheno --manticore -``` -This alone will not run any Manticore analyses; they must either be run manually, or automated through [the `--truffle` command](#truffle-integration); - -* `--manticore-verbosity` sets Manticore’s logging verbosity (default is 3) -* `--manticore-max-depth` sets the maximum state depth for Manticore to explore; if omitted, Manticore will have no depth limit - ### Truffle Integration Truffle migrations can automatically be run within a Truffle project: @@ -141,20 +121,6 @@ Truffle migrations can automatically be run within a Truffle project: etheno --truffle ``` -When combined with the `--manticore` option, this will automatically run Manticore’s default analyses on all contracts created once the Truffle migration completes: -``` -etheno --truffle --manticore -``` - -This requires a master JSON RPC client, so will most often be used in conjunction with Ganache. If a local Ganache server is not running, you can simply add that to the command: -``` -etheno --truffle --manticore --ganache -``` - -If you would like to run a custom Manticore script instead of the standard Manticore analysis and detectors, it can be specified using the `--manticore-script` or `-r` command. - -This script does not need to import Manticore or create a `ManticoreEVM` object; Etheno will run the script with a global variable called `manticore` that already contains all of the accounts and contracts automatically provisioned. See the [`BrokenMetaCoin` Manticore script](examples/BrokenMetaCoin/ExploitMetaCoinManticoreScript.py) for an example. - Additional arguments can be passed to Truffle using `--truffle-args`. ### Logging @@ -174,7 +140,6 @@ saved: ## Requirements * Python 3.6 or newer -* [Manticore](https://github.com/trailofbits/manticore/) release 0.2.2 or newer * [Flask](http://flask.pocoo.org/), which is used to run the JSON RPC server ### Optional Requirements diff --git a/examples/BrokenMetaCoin/ExploitMetaCoinManticoreScript.py b/examples/BrokenMetaCoin/ExploitMetaCoinManticoreScript.py deleted file mode 100644 index ad512a3..0000000 --- a/examples/BrokenMetaCoin/ExploitMetaCoinManticoreScript.py +++ /dev/null @@ -1,31 +0,0 @@ -# global variables `logger`, `manticore`, and `manticoreutils` are provided by Etheno - -# No need to set up accounts or contracts the way we usually do with Manticore alone! -# They are already pre-provisioned in the `manticore` object -# and we can simply access them from there: - -# The Truffle migrations deploy three contracts: [Migrations contract, ConvertLib, MetaCoin] -contract_account = list(manticore.contract_accounts.values())[2] - -# The contract was loaded from bytecode, so we need to manually set the ABI: -contract_account.add_function('setMetadata(uint256,uint256)') - -# Create symbolic variables for which Manticore will discover values: -key1 = manticore.make_symbolic_value(name='key1') -value1 = manticore.make_symbolic_value(name='val1') -key2 = manticore.make_symbolic_value(name='key2') - -# Make two calls to the `setMetadata` function of the `MetaCoin` contract -# using the symbolic variables: -contract_account.setMetadata(key1, value1) -contract_account.setMetadata(key2, 1) - -for st in manticore.all_states: - # The value we want to overwrite is the `balances` mapping in storage slot 0 - balances_value = st.platform.get_storage_data(contract_account.address, 0) - with manticoreutils.ManticoreTest(st, balances_value == 1) as test: - for k1, v1, k2 in test.solve_all(key1, value1, key2): - result = f"\nFound a way to overwrite balances! Check {manticore.workspace}\n" - result += f" setMetadata({hex(k1)}, {hex(v1)})\n" - result += f" setMetadata({hex(k2)}, 0x1)\n" - logger.info(result) diff --git a/examples/BrokenMetaCoin/LICENSE b/examples/BrokenMetaCoin/LICENSE deleted file mode 100644 index bb98c92..0000000 --- a/examples/BrokenMetaCoin/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 Truffle - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/examples/BrokenMetaCoin/README.md b/examples/BrokenMetaCoin/README.md deleted file mode 100644 index 02c0fc2..0000000 --- a/examples/BrokenMetaCoin/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Broken MetaCoin Truffle and Manticore Example - -This example is the same `MetaCoin` project used in the Truffle -documentation and tutorials, however, we have added common Solidity -errors to the code, including some that are quite subtle. - -A [`run_etheno.sh`](run_etheno.sh) script is provided to give some -examples of how one might use Etheno and Manticore to automatically -discover the bugs in this project. diff --git a/examples/BrokenMetaCoin/contracts/ConvertLib.sol b/examples/BrokenMetaCoin/contracts/ConvertLib.sol deleted file mode 100644 index 5d83fa9..0000000 --- a/examples/BrokenMetaCoin/contracts/ConvertLib.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity ^0.5.0; - -library ConvertLib{ - function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount) - { - return amount * conversionRate; - } -} diff --git a/examples/BrokenMetaCoin/contracts/MetaCoin.sol b/examples/BrokenMetaCoin/contracts/MetaCoin.sol deleted file mode 100644 index 25494f2..0000000 --- a/examples/BrokenMetaCoin/contracts/MetaCoin.sol +++ /dev/null @@ -1,48 +0,0 @@ -pragma solidity ^0.5.0; - -import "./ConvertLib.sol"; - -/** - * This is a simple example token with several vulnerabilities added. - */ -contract MetaCoin { - mapping (address => uint) balances; - uint256[] metadata; - - event Transfer(address indexed _from, address indexed _to, uint256 _value); - - constructor() public { - balances[tx.origin] = 10000; - } - - function setMetadata(uint256 key, uint256 value) public { - if (metadata.length <= key) { - metadata.length = key + 1; - } - metadata[key] = value; - } - - function getMetadata(uint256 key) public view returns (uint256) { - return metadata[key]; - } - - function backdoor() public { - selfdestruct(msg.sender); - } - - function sendCoin(address receiver, uint amount) public returns(bool sufficient) { - if (balances[msg.sender] < amount) return false; - balances[msg.sender] -= amount; - balances[receiver] += amount; - emit Transfer(msg.sender, receiver, amount); - return true; - } - - function getBalanceInEth(address addr) public view returns(uint){ - return ConvertLib.convert(getBalance(addr),2); - } - - function getBalance(address addr) public view returns(uint) { - return balances[addr]; - } -} diff --git a/examples/BrokenMetaCoin/contracts/Migrations.sol b/examples/BrokenMetaCoin/contracts/Migrations.sol deleted file mode 100644 index 89b4d5c..0000000 --- a/examples/BrokenMetaCoin/contracts/Migrations.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity ^0.5.0; - -contract Migrations { - address public owner; - uint public last_completed_migration; - - modifier restricted() { - if (msg.sender == owner) _; - } - - constructor() public { - owner = msg.sender; - } - - function setCompleted(uint completed) public restricted { - last_completed_migration = completed; - } - - function upgrade(address new_address) public restricted { - Migrations upgraded = Migrations(new_address); - upgraded.setCompleted(last_completed_migration); - } -} diff --git a/examples/BrokenMetaCoin/migrations/1_initial_migration.js b/examples/BrokenMetaCoin/migrations/1_initial_migration.js deleted file mode 100644 index 4d5f3f9..0000000 --- a/examples/BrokenMetaCoin/migrations/1_initial_migration.js +++ /dev/null @@ -1,5 +0,0 @@ -var Migrations = artifacts.require("./Migrations.sol"); - -module.exports = function(deployer) { - deployer.deploy(Migrations); -}; diff --git a/examples/BrokenMetaCoin/migrations/2_deploy_contracts.js b/examples/BrokenMetaCoin/migrations/2_deploy_contracts.js deleted file mode 100644 index b3dc3e9..0000000 --- a/examples/BrokenMetaCoin/migrations/2_deploy_contracts.js +++ /dev/null @@ -1,8 +0,0 @@ -var ConvertLib = artifacts.require("./ConvertLib.sol"); -var MetaCoin = artifacts.require("./MetaCoin.sol"); - -module.exports = function(deployer) { - deployer.deploy(ConvertLib); - deployer.link(ConvertLib, MetaCoin); - deployer.deploy(MetaCoin); -}; diff --git a/examples/BrokenMetaCoin/run_etheno.sh b/examples/BrokenMetaCoin/run_etheno.sh deleted file mode 100755 index b77bcb4..0000000 --- a/examples/BrokenMetaCoin/run_etheno.sh +++ /dev/null @@ -1,16 +0,0 @@ -# First, remove the Truffle build directory. -# This shouldn't be necessary, but Truffle will often fail with -# confusing error messages if it is upgraded between builds. -# So, we just rebuild everything from scratch each time to ensure -# that it always works. -rm -rf build - -echo "Running the custom Manticore script ExploitMetaCoinManticoreScript.py" -# Set the max depth for Manticore to 2 because this script only needs to -# find a sequence of two transactions to exploit the bug -etheno --manticore --truffle --ganache --manticore-max-depth 2 -r ExploitMetaCoinManticoreScript.py - -echo "Running a full Manticore analysis with standard vulnerability detectors (this can take roughly 30 minutes)" -# Set the max depth for Manticore to 2 because we can get ~98% coverage -# with that setting, and it drastically reduces compute time -etheno -m -t -g --manticore-max-depth 2 diff --git a/examples/BrokenMetaCoin/test/TestMetacoin.sol b/examples/BrokenMetaCoin/test/TestMetacoin.sol deleted file mode 100644 index 7af110c..0000000 --- a/examples/BrokenMetaCoin/test/TestMetacoin.sol +++ /dev/null @@ -1,25 +0,0 @@ -pragma solidity ^0.4.2; - -import "truffle/Assert.sol"; -import "truffle/DeployedAddresses.sol"; -import "../contracts/MetaCoin.sol"; - -contract TestMetacoin { - - function testInitialBalanceUsingDeployedContract() public { - MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin()); - - uint expected = 10000; - - Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially"); - } - - function testInitialBalanceWithNewMetaCoin() public { - MetaCoin meta = new MetaCoin(); - - uint expected = 10000; - - Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially"); - } - -} diff --git a/examples/BrokenMetaCoin/test/metacoin.js b/examples/BrokenMetaCoin/test/metacoin.js deleted file mode 100644 index c61c093..0000000 --- a/examples/BrokenMetaCoin/test/metacoin.js +++ /dev/null @@ -1,63 +0,0 @@ -var MetaCoin = artifacts.require("./MetaCoin.sol"); - -contract('MetaCoin', function(accounts) { - it("should put 10000 MetaCoin in the first account", function() { - return MetaCoin.deployed().then(function(instance) { - return instance.getBalance.call(accounts[0]); - }).then(function(balance) { - assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account"); - }); - }); - it("should call a function that depends on a linked library", function() { - var meta; - var metaCoinBalance; - var metaCoinEthBalance; - - return MetaCoin.deployed().then(function(instance) { - meta = instance; - return meta.getBalance.call(accounts[0]); - }).then(function(outCoinBalance) { - metaCoinBalance = outCoinBalance.toNumber(); - return meta.getBalanceInEth.call(accounts[0]); - }).then(function(outCoinBalanceEth) { - metaCoinEthBalance = outCoinBalanceEth.toNumber(); - }).then(function() { - assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, "Library function returned unexpected function, linkage may be broken"); - }); - }); - it("should send coin correctly", function() { - var meta; - - // Get initial balances of first and second account. - var account_one = accounts[0]; - var account_two = accounts[1]; - - var account_one_starting_balance; - var account_two_starting_balance; - var account_one_ending_balance; - var account_two_ending_balance; - - var amount = 10; - - return MetaCoin.deployed().then(function(instance) { - meta = instance; - return meta.getBalance.call(account_one); - }).then(function(balance) { - account_one_starting_balance = balance.toNumber(); - return meta.getBalance.call(account_two); - }).then(function(balance) { - account_two_starting_balance = balance.toNumber(); - return meta.sendCoin(account_two, amount, {from: account_one}); - }).then(function() { - return meta.getBalance.call(account_one); - }).then(function(balance) { - account_one_ending_balance = balance.toNumber(); - return meta.getBalance.call(account_two); - }).then(function(balance) { - account_two_ending_balance = balance.toNumber(); - - assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender"); - assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver"); - }); - }); -}); diff --git a/examples/BrokenMetaCoin/truffle.js b/examples/BrokenMetaCoin/truffle.js deleted file mode 100644 index aed5f84..0000000 --- a/examples/BrokenMetaCoin/truffle.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - networks: { - development: { - host: "127.0.0.1", - port: 8545, - network_id: "*" - } - } -}; diff --git a/setup.py b/setup.py index bde9f4c..f1154bd 100644 --- a/setup.py +++ b/setup.py @@ -19,9 +19,6 @@ 'pycryptodome>=3.4.7,<4.0.0', 'setuptools' ], - extras_require={ - 'manticore': ['manticore>=0.2.2'] - }, entry_points={ 'console_scripts': [ 'etheno = etheno.__main__:main' From c41574fe8f374bb1814e550d57a505a439794c8d Mon Sep 17 00:00:00 2001 From: Anish Naik Date: Fri, 27 May 2022 06:18:06 -0400 Subject: [PATCH 3/7] removed manticore-based if statement in main.py --- README.md | 1 - etheno/__main__.py | 7 ++++--- setup.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6a231e4..e0ddb73 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ Etheno is named after the Greek goddess [Stheno](https://en.wikipedia.org/wiki/S * Deploy to and interact with multiple networks at the same time * **Integration with Test Frameworks** like Ganache and Truffle * Run a local test network with a single command - * Symbolic semantic annotations within unit tests ## Quickstart diff --git a/etheno/__main__.py b/etheno/__main__.py index 5a1bc96..e424e0d 100644 --- a/etheno/__main__.py +++ b/etheno/__main__.py @@ -20,7 +20,7 @@ from . import truffle def main(argv = None): - parser = argparse.ArgumentParser(description='An Ethereum JSON RPC multiplexer and Manticore wrapper') + parser = argparse.ArgumentParser(description='An Ethereum JSON RPC multiplexer, differential fuzzer, and test framework integration tool.') parser.add_argument('--debug', action='store_true', default=False, help='Enable debugging from within the web server') parser.add_argument('--run-publicly', action='store_true', default=False, help='Allow the web server to accept external connections') parser.add_argument('-p', '--port', type=int, default=GETH_DEFAULT_RPC_PORT, help='Port on which to run the JSON RPC webserver (default=%d)' % GETH_DEFAULT_RPC_PORT) @@ -314,8 +314,9 @@ def truffle_thread(): thread = Thread(target=truffle_thread) thread.start() - if args.run_differential and (ETHENO.master_client is not None) and \ - next(filter(lambda c: not isinstance(c, ManticoreClient), ETHENO.clients), False): + # Without Manticore integration the only client types are geth, parity, and command-line raw/regular clients. + # So checking len() >= 2 should be sufficient. + if args.run_differential and (ETHENO.master_client is not None) and len(ETHENO.clients) >= 2: # There are at least two non-Manticore clients running ETHENO.logger.info("Initializing differential tests to compare clients %s" % ', '.join( map(str, [ETHENO.master_client] + ETHENO.clients) diff --git a/setup.py b/setup.py index f1154bd..65a3bda 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='etheno', - description='Etheno is a JSON RPC multiplexer, Manticore wrapper, differential fuzzer, and test framework integration tool.', + description='Etheno is a JSON RPC multiplexer, differential fuzzer, and test framework integration tool.', url='https://github.com/trailofbits/etheno', author='Trail of Bits', version='0.2.4', From 7ebf1175f48e888dc4e3f042b0ce6d0303c9551c Mon Sep 17 00:00:00 2001 From: Anish Naik Date: Fri, 27 May 2022 06:21:16 -0400 Subject: [PATCH 4/7] fix if statement bc master_client is also technically a client --- etheno/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etheno/__main__.py b/etheno/__main__.py index e424e0d..10b8dc5 100644 --- a/etheno/__main__.py +++ b/etheno/__main__.py @@ -316,7 +316,7 @@ def truffle_thread(): # Without Manticore integration the only client types are geth, parity, and command-line raw/regular clients. # So checking len() >= 2 should be sufficient. - if args.run_differential and (ETHENO.master_client is not None) and len(ETHENO.clients) >= 2: + if args.run_differential and (ETHENO.master_client is not None) and len(ETHENO.clients) >= 1: # There are at least two non-Manticore clients running ETHENO.logger.info("Initializing differential tests to compare clients %s" % ', '.join( map(str, [ETHENO.master_client] + ETHENO.clients) From 8cf9e8aa97a5fd60c3461232da4327017bb4d3c0 Mon Sep 17 00:00:00 2001 From: Anish Naik Date: Fri, 27 May 2022 06:37:30 -0400 Subject: [PATCH 5/7] added small todo --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 09123b8..2f0bd86 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,6 +59,7 @@ WORKDIR /home/etheno COPY --chown=etheno:etheno LICENSE setup.py etheno/ COPY --chown=etheno:etheno etheno/*.py etheno/etheno/ +# TODO: get rid of manticore --user flag here. will need to validate that it doesn't break anything. RUN cd etheno && \ pip3 install --no-cache-dir --user '.[manticore]' && \ cd .. && \ From 6fb7cca57b96d816219eb5032996f0fabd9aa519 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 17 Jun 2022 15:23:42 -0400 Subject: [PATCH 6/7] Add a `pip-audit` workflow --- .github/workflows/pip-audit.yml | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/pip-audit.yml diff --git a/.github/workflows/pip-audit.yml b/.github/workflows/pip-audit.yml new file mode 100644 index 0000000..3d3c1ac --- /dev/null +++ b/.github/workflows/pip-audit.yml @@ -0,0 +1,37 @@ +name: Scan dependencies for vulnerabilities with pip-audit + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: "0 12 * * *" + +jobs: + pip-audit: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Install project + run: | + python -m venv /tmp/pip-audit-env + source /tmp/pip-audit-env/bin/activate + + python -m pip install --upgrade pip + python -m pip install . + + + - name: Run pip-audit + uses: trailofbits/gh-action-pip-audit@v0.0.4 + with: + virtual-environment: /tmp/pip-audit-env + From 9d69e66b0381732f6b15392b685f8a7152dd7dde Mon Sep 17 00:00:00 2001 From: Anish Naik Date: Mon, 27 Jun 2022 17:04:35 -0400 Subject: [PATCH 7/7] final removal of manticore references --- Dockerfile | 4 ++-- etheno/__main__.py | 2 +- etheno/logger.py | 7 ------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2f0bd86..c902f44 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,9 +59,9 @@ WORKDIR /home/etheno COPY --chown=etheno:etheno LICENSE setup.py etheno/ COPY --chown=etheno:etheno etheno/*.py etheno/etheno/ -# TODO: get rid of manticore --user flag here. will need to validate that it doesn't break anything. + RUN cd etheno && \ - pip3 install --no-cache-dir --user '.[manticore]' && \ + pip3 install --no-cache-dir && \ cd .. && \ rm -rf etheno diff --git a/etheno/__main__.py b/etheno/__main__.py index 10b8dc5..e526c24 100644 --- a/etheno/__main__.py +++ b/etheno/__main__.py @@ -315,7 +315,7 @@ def truffle_thread(): thread.start() # Without Manticore integration the only client types are geth, parity, and command-line raw/regular clients. - # So checking len() >= 2 should be sufficient. + # So checking len() >= 1 should be sufficient. if args.run_differential and (ETHENO.master_client is not None) and len(ETHENO.clients) >= 1: # There are at least two non-Manticore clients running ETHENO.logger.info("Initializing differential tests to compare clients %s" % ', '.join( diff --git a/etheno/logger.py b/etheno/logger.py index 10298f0..8594bec 100644 --- a/etheno/logger.py +++ b/etheno/logger.py @@ -108,13 +108,6 @@ def getLogger(name: str): ret = ETHENO_LOGGERS[name] else: ret = _LOGGING_GETLOGGER(name) - # ####BEGIN#### - # Horrible hack to workaround Manticore's global logging system. - # This can be removed after https://github.com/trailofbits/manticore/issues/1369 - # is resolved. - if name.startswith('manticore'): - ret.propagate = False - # ####END#### return ret