-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 8a94b94
Showing
28 changed files
with
2,211 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.sol linguist-language=Solidity |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*~ | ||
*.pyc | ||
build | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
FROM ubuntu:18.04 | ||
MAINTAINER Evan Sultanik | ||
|
||
RUN apt-get -y update | ||
|
||
RUN apt-get install -y npm bash-completion | ||
|
||
RUN npm install -g ganache-cli truffle | ||
|
||
# BEGIN Requirements for Manticore: | ||
|
||
RUN DEBIAN_FRONTEND=noninteractive apt-get -y install python3 python3-pip git | ||
|
||
RUN apt-get install -y build-essential software-properties-common && \ | ||
add-apt-repository -y ppa:ethereum/ethereum && \ | ||
apt-get update && \ | ||
apt-get install -y solc ethereum | ||
|
||
RUN useradd -m etheno | ||
USER etheno | ||
WORKDIR /home/etheno | ||
ENV HOME /home/etheno | ||
ENV PATH $PATH:$HOME/.local/bin | ||
ENV LANG C.UTF-8 | ||
|
||
RUN git clone https://github.com/trailofbits/manticore.git | ||
# Etheno currently requires the dev-account-address-provider-m1 branch; | ||
# We can remove the `git checkout` once https://github.com/trailofbits/manticore/pull/1054 is merged | ||
RUN cd manticore && git checkout dev-account-address-provider-m1 && pip3 install --user . | ||
|
||
# END Requirements for Manticore | ||
|
||
RUN mkdir -p /home/etheno/etheno/etheno | ||
|
||
COPY LICENSE /home/etheno/etheno | ||
COPY setup.py /home/etheno/etheno | ||
|
||
COPY etheno/*.py /home/etheno/etheno/etheno/ | ||
|
||
RUN mkdir -p /home/etheno/examples | ||
COPY examples /home/etheno/examples/ | ||
|
||
# Comment out the requirement for manticore in setup.py because we already manually installed it | ||
# Once the Etheno branch is merged into a Manticore release, we can get rid of this and | ||
# just install Manticore from pip. | ||
RUN sed -i '/manticore/s/^/#/g' /home/etheno/etheno/setup.py | ||
|
||
RUN cd etheno && pip3 install --user . | ||
|
||
USER root | ||
|
||
RUN chown -R etheno:etheno /home/etheno/ | ||
|
||
USER etheno | ||
|
||
CMD ["/bin/bash"] |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
# Etheno | ||
<p align="center"> | ||
<img src="logo/etheno.png?raw=true" width="256" title="Etheno"> | ||
</p> | ||
<br /> | ||
|
||
|
||
Etheno is a JSON RPC multiplexer, analysis tool wrapper, and test integration tool. | ||
* Etheno makes running [Manticore](https://github.com/trailofbits/manticore/) easy | ||
* Etheno eliminates the complexity of setting up analysis tools with a large, multi-contract project | ||
* Etheno does not require Solidity source code | ||
* Etheno simplifies writing custom Manticore scripts | ||
|
||
It is named after the Greek goddess [Stheno](https://en.wikipedia.org/wiki/Stheno), sister of Medusa, and mother of Echidna—which also happens to be the name of [our EVM property-based fuzz tester](https://github.com/trailofbits/echidna). | ||
|
||
## Features | ||
|
||
* **JSON RPC Multiplexing**: Etheno runs a JSON RPC server that can multiplex calls to one or more clients | ||
* API for filtering and even 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 with no need for 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 | ||
|
||
Use Docker to quickly install and try Etheno. This is the easiest way to get up and running until Etheno support is included in the next release of Manticore. | ||
|
||
``` | ||
# Clone the Etheno repo | ||
git clone https://github.com/trailofbits/etheno.git && cd etheno | ||
# Build the Docker image | ||
docker build . -t etheno | ||
# Start a new Etheno Docker container | ||
docker run -it etheno:latest | ||
# 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, install and try Etheno in a few shell commands: | ||
|
||
``` | ||
# Install system dependencies | ||
sudo apt-get update && sudo apt-get install python3 python3-pip -y | ||
# Install Manticore | ||
# Note: This will not work until the dev-account-address-provider-m1 is merged | ||
# pip3 install manticore --user | ||
# Clone and install Etheno | ||
git clone https://github.com/trailofbits/etheno.git | ||
cd etheno | ||
pip3 install -e '.' | ||
# Use the Etheno CLI | ||
cd /path/to/a/truffle/project | ||
etheno --manticore --ganache --truffle | ||
``` | ||
|
||
## Usage | ||
|
||
Etheno can be used in many different ways and therefore has numerous command line argument combinations. | ||
|
||
### JSON RPC Server and Multiplexing | ||
|
||
This command starts a JSON RPC server and forwards all messages to the given clients: | ||
|
||
``` | ||
etheno https://client1.url.com:1234/ https://client2.url.com:8545/ http://client3.url.com:8888/ | ||
``` | ||
|
||
* `--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. | ||
|
||
### Ganache Integration | ||
|
||
A Ganache instance can automatically be run within Etheno: | ||
``` | ||
etheno --ganache | ||
``` | ||
|
||
* `--ganache-port` will set the port on which Ganache is run; if omitted, Etheno will choose the lowest port higher than the port on which Etheno's JSON RPC server is running | ||
* `--ganache-args` lets you pass additional arguments to Ganache | ||
* `--accounts` or `-a` sets the number of accounts to create in Ganache (default is 10) | ||
* `--balance` or `-b` sets the default balance (in Ether) to seed to each Ganache account (default is 100.0) | ||
* `--gas-price` or `-c` sets the default gas price for Ganache (default is 20000000000) | ||
|
||
### Manticore Client | ||
|
||
Manticore—which, by itself, does not implemnent 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: | ||
``` | ||
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 -t -m -g | ||
``` | ||
|
||
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 isntantiate 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`. | ||
|
||
## Requirements | ||
|
||
* Python 3.6 or newer | ||
* [Manticore](https://github.com/trailofbits/manticore/) (Note: Use the docker image or wait for Manticore 0.2.3) | ||
* [Flask](http://flask.pocoo.org/), which is used to run the JSON RPC server | ||
* [Truffle and Ganache](https://truffleframework.com/) for their associated integrations | ||
|
||
## Getting Help | ||
|
||
Feel free to stop by our [Slack channel](https://empirehacking.slack.com/) for help on using or extending Etheno. | ||
|
||
Documentation is available in several places: | ||
|
||
* The [wiki](https://github.com/trailofbits/etheno/wiki) contains some | ||
basic information about getting started with Etheno and contributing | ||
|
||
* The [examples](examples) directory has some very minimal examples that | ||
showcase API features | ||
|
||
## License | ||
|
||
Etheno is licensed and distributed under the [AGPLv3](LICENSE) license. [Contact us](mailto:opensource@trailofbits.com) if you're looking for an exception to the terms. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .etheno import Etheno, EthenoClient, SelfPostingClient, RpcProxyClient |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import argparse | ||
from threading import Thread | ||
import time | ||
import sys | ||
|
||
from .etheno import app, EthenoView, GETH_DEFAULT_RPC_PORT, GanacheClient, ManticoreClient, RpcProxyClient, ETHENO | ||
from . import Etheno | ||
from . import ganache | ||
from . import manticoreutils | ||
from . import truffle | ||
|
||
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') | ||
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) | ||
parser.add_argument('-a', '--accounts', type=int, default=10, help='Number of accounts to create in Ganache (default=10)') | ||
parser.add_argument('-b', '--balance', type=float, default=100.0, help='Default balance (in Ether) to seed to each Ganache account (default=100.0)') | ||
parser.add_argument('-c', '--gas-price', type=int, default=20000000000, help='Default gas price for Ganache (default=20000000000)') | ||
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('--manticore-verbosity', type=int, default=3, help='Manticore verbosity (default=3)') | ||
parser.add_argument('-t', '--truffle', action='store_true', default=False, help='Run the truffle migrations in the current directory and exit') | ||
parser.add_argument('--truffle-args', type=str, default='migrate', help='Arguments to pass to truffle (default=migrate)') | ||
parser.add_argument('-g', '--ganache', action='store_true', default=False, help='Run Ganache as a master JSON RPC client (cannot be used in conjunction with --master)') | ||
parser.add_argument('--ganache-args', type=str, default=None, help='Additional arguments to pass to Ganache') | ||
parser.add_argument('--ganache-port', type=int, default=None, help='Port on which to run Ganache (defaults to the closest available port to the port specified with --port plus one)') | ||
parser.add_argument('-v', '--version', action='store_true', default=False, help='Print version information and exit') | ||
parser.add_argument('client', type=str, nargs='*', help='One or more JSON RPC client URLs to multiplex; if no client is specified for --master, the first client in this list will default to the master (format="http://foo.com:8545/")') | ||
parser.add_argument('-s', '--master', type=str, default=None, help='A JSON RPC client to use as the master (format="http://foo.com:8545/")') | ||
|
||
if argv is None: | ||
argv = sys.argv | ||
|
||
args = parser.parse_args(argv[1:]) | ||
|
||
if args.version: | ||
print(VERSION_NAME) | ||
sys.exit(0) | ||
|
||
if args.ganache and args.master: | ||
parser.print_help() | ||
sys.stderr.write('\nError: You cannot specify both --ganache and --master at the same time!\n') | ||
sys.exit(1) | ||
elif args.ganache: | ||
if args.ganache_port is None: | ||
args.ganache_port = ganache.find_open_port(args.port + 1) | ||
|
||
ganache_instance = ganache.Ganache(args = ['-a', str(args.accounts), '-g', str(args.gas_price), '-e', str(args.balance)], port=args.ganache_port) | ||
|
||
ETHENO.master_client = GanacheClient(ganache_instance) | ||
|
||
ganache_instance.start() | ||
elif args.master: | ||
ETHENO.master_client = RpcProxyClient(args.master) | ||
elif args.client: | ||
ETHENO.master_client = RpcProxyClient(args.client[0]) | ||
args.client = args.client[1:] | ||
|
||
for client in args.client: | ||
ETHENO.add_client(RpcProxyClient(client)) | ||
|
||
manticore_client = None | ||
if args.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)) | ||
manticore_client.manticore.verbosity(args.manticore_verbosity) | ||
|
||
if args.truffle: | ||
truffle_controller = truffle.Truffle() | ||
def truffle_thread(): | ||
if ETHENO.master_client: | ||
ETHENO.master_client.wait_until_running() | ||
print("Etheno Started! Running Truffle...") | ||
ret = truffle_controller.run(args.truffle_args) | ||
if ret != 0: | ||
# TODO: Print a warning/error | ||
pass | ||
|
||
if manticore_client is not None: | ||
if args.manticore_script is not None: | ||
exec(args.manticore_script.read(), {'manticore' : manticore_client.manticore}) | ||
else: | ||
manticoreutils.register_all_detectors(manticore_client.manticore) | ||
manticore_client.multi_tx_analysis() | ||
manticore_client.manticore.finalize() | ||
print(manticore_client.manticore.global_findings) | ||
print("Results are in %s" % manticore_client.manticore.workspace) | ||
ETHENO.shutdown() | ||
|
||
thread = Thread(target=truffle_thread) | ||
thread.start() | ||
|
||
etheno = EthenoView() | ||
app.add_url_rule('/', view_func=etheno.as_view('etheno')) | ||
|
||
etheno_thread = ETHENO.run(debug = args.debug, run_publicly = args.run_publicly, port = args.port) | ||
truffle_controller.terminate() | ||
|
||
if __name__ == '__main__': | ||
main() |
Oops, something went wrong.