Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ESultanik committed Oct 7, 2018
0 parents commit 8a94b94
Show file tree
Hide file tree
Showing 28 changed files with 2,211 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.sol linguist-language=Solidity
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*~
*.pyc
build
dist
56 changes: 56 additions & 0 deletions Dockerfile
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"]
661 changes: 661 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

154 changes: 154 additions & 0 deletions README.md
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.
1 change: 1 addition & 0 deletions etheno/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .etheno import Etheno, EthenoClient, SelfPostingClient, RpcProxyClient
104 changes: 104 additions & 0 deletions etheno/__main__.py
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()
Loading

0 comments on commit 8a94b94

Please sign in to comment.