Blockchain hacking : Ethernaut

In this article, I propose a few solutions to the Ethernaut challenge : https://ethernaut.openzeppelin.com/. I will release more solutions progressively

Run the challenge at : https://ethernaut.openzeppelin.com/

It is an initiative from OpenZeppelin, which is a well known library for secure smart contract development. It proposes Ethereum based, open source challenges, written by several contributors

There are already several solutions available. My goal in this article is to provide a step by step walkthrough, that should help the most beginners among us. Please note that I have done the challenge using Google Chrome

Before starting, it is usefull to have an overview about the Web3 process, in comparison to the usual Web2, as it will help understand why we use Javascript, Metamask, Solidity and other such features

https://bit.ly/3PphGCC

Level 0 : Hello Ethernaut

In this chapter, which is an introduction, we will set up MetaMask, get test Ether, and start our first interactions with the smart contract

Set up MetaMask

First of all, we need to install the Metamask browser extension : https://metamask.io/, and then create a password and a wallet key. We need to select the Sepolia test network, which we will use for our hacking scenarios

Open the browser’s console

The modern browsers provide hands-on tools for the web developers, such as the console, in which one can input “live” Javascript instructions, check the data in memory, explore available functions and variables

In the Ethernaut browser session, let’s go in Tools -> Developer tools (or just press F12). In the console view, we find the following informations

You may get some errors, so please make sure to solve them before continuing. If needed, you may check the following actions :

  • use Chrome
  • install Metamask after you reached the challenge page
  • select the Sepolia test network
  • refresh the page as per the need

We get an Ethernaut address : 0xa3e7317E591D5A0F1c605be1b3aC4D2ae56104d6

My player number is : 0xe62f9927b2198669b5988993E52d3093854898b0. In fact, this is my Metamask public address

You can check mine here in Etherscan, the well known ethereum blockchain explorer : https://bit.ly/3aP37JO. It corresponds, in my specific case, to the Ethereum Name Service “forensicxs.eth”

Level 0 : Hello Ethernaut

We click on the level “00” box

Use the console helpers

We are invited to type the help() command in the console. Here is the output :

We can check the balance of our account, with the function getBalance(address). We need to input our actual address

We see that the “Promise” has been fulfilled, and that our balance is at zero, as expected. You can find informations about Promises here : https://javascript.info/promise-basics

The ethernaut contract

Let’s look into the smart contrat. The ethernaut command provides a lot of informations about the contract itself

We can see the public methods available, such as owner

We can see that the smart contract has an abi connector at the following address : 0xa3e7317E591D5A0F1c605be1b3aC4D2ae56104d6

The abi, or Application Binary Interface, gives a contract the ability to communicate and interact with external applications and other smart contracts. Here is the overall mechanism

https://bit.ly/3zkRymT

Here we are more interested in reading contracts FROM the Ethereum blockchain, but you can check the following link to get an overview how to deploy a contrat TO the blockchain : https://bit.ly/3OlkD5V

If we try to check the ethernaut owner, the query to the blockchain will need some gas to be able to run. In my case, the Metamask wallet balance is not sufficient, so I get an error message

Get test ether

You can request test Ether with a Google account : https://tinyurl.com/5xe8r23z

This method is limited to 0.05 ETH per day, so it’s not quite enough to get started with the Ethernaut challenge

You can get more token on Alchemy (requires a login and a small amount of real ETH on your metamask wallet)

https://www.alchemy.com/faucets/ethereum-sepolia

Getting a level instance

As indicated by the guide, let’s request our level instance, by pressing the “Get new instance” button. We are prompted by Metamask to authorize the transaction. In my case, the gas fee to pay is 0.0228 Sepolia ETH (= test ETH)

The transaction is duly handled, and my instance is created, with a smart contract at the address 0x3A99561FA3026041B8647bdae4E7339c2Ebc45E9

Inspecting the contract

Let’s inspect briefly this contract instanciation, using the contract command

We have access to several public functions, including several ones that look interesting : info, info1, info2, info42, password. Let’s try to find additional informations about these functions

Interact with the contract to complete the level

In this paragraph, we take advantage of Solidity being an OOP language (object oriented programming), so that we can get informations about the functions by calling them, with their respective methods and arguments

Let’s run the contract.info() command. We find a valuable text information “You will find what you need in info()”

We continue with the contract.info1() command. We find another text information “Try info2(), but with \”hello\” as a parameter.”

Therefore, we continue with the contract.info2(“hello”) command.

You get the idea. We have to follow a chain of information…let’s continue with the infoNum() method. We see in the PromiseResult, the word 42

Let’s look for contract.info42()

We have to look for “theMethodeName” as a method

Let’s look into contract.method7123949

We are invited to find the password. We have noticed the function “password” in the contract. Let’s check it in contract.password()

We get the password “ethernaut0“. Now, we can authenticate. We are prompted to accept the transaction by Metamask, a gas fee of 0.023 Sepolia ETH is applied

The transaction is successfull, we can check it on Etherscan

To complete the level, you need to press “submit instance“. Metamask prompts you to accept again the transaction as the blockchain consumes gas for any request

Our transaction is approved, we get a very graphic confirmation 🙂

We get a notification on the Ethernaut website

Just below, OpenZeppelin provides the contract code we just interacted with, where we can confirm the steps and logic we just followed above

This level is finished, let’s move to the next one

From now on, we will come closer to a realistic hacking environment

Level 1 : Fallback

We are requested to inpect a smart contract code (here below), take ownership of it, and withdraw the available balance

In Solidity, a fallback function does not take arguments and does not return any value. But a fallback function can receive ether and therefore be “payable”

Let’s get the instance of the contract by clicking “Get new instance”. We need to pay some gas

Our instance address is 0xB386A21D3aAe67a9dAe0144aF2226b86c8EB6960

We can analyse the contract code. The first flaw is in this code, as anyone can :

  • call this function
  • send Ether
  • if the contribution gets higher than the one of the contract owner, take ownership of the contract

Then, the “owner” can withdraw the funds

Therefore, the fallback contract allows users to contribute small amounts of Ether, and the maximum contributor becomes the owner of the contract

But to take ownership of the contract, you would need to get more than 1000 ETH, as per the constructor, because the owner has an initial contribution of 1000 ETH

Fortunately for us, there is a second flaw to take ownership of the contract, whereby any sender can become the owner if :

  • he sends a positive value of Ether (msg.value > 0)
  • he has already contributed (contributions[msg.sender] > 0)

We can deposit Ether on the contract by using the necessary arguments of the function contract.contribute()

We have to deposit less than 0.001 ETH, so we can choose 0.0009 ETH, just a decimal below. The help() function provides us some guidance about the method to use, to send our transaction to the contract

We also need to send our transaction in Wei (see here a converter, for information -> https://eth-converter.com/), as generally required by smart contracts

So in summary, the arguments to provide to the contract.contribute() function are :

  • from : our address is given automatically by our player address
  • value : the value of our deposit in Wei

Here is the full transaction. It gets fullfilled, after we paid some smart contract gas fee

We can check our ether balance with the function getBalance

Then, as we have already contributed to the contract, we can take ownership by sending a positive deposit

We can check the contract owner. This is my player address, so I have claimed ownership

Then we can drain the funds with the function contract.withdraw()

We need to press the “submit instance” button to validate this level. We get to this screen, we can press the “Go to the next level” button

Level 2 : Fallout

The full smart contract code is below

To start with a quick analysis, I notice that the contract owner has a null value, which is the default address

At the same time, this address has a huge amount of ETH

I notice also a discrepancy between the contract name Fallout and the constructor Fal1out, which means that the owner is not initiated properly (leading to the owner being the null address)

So anyone can claim ownership of the contract. I just call the contract with contract.Fal1out() and here we go !

We can check that I’m now the contract owner. The Promise result confirms this is my player address

I submit the instance. Level is completed

Level 3 : Coin Flip

As per the description, this is a coin flipping game where we need to guess the outcome of a coin flip. To complete the level we need to guess the correct outcome 10 times in a row

The full code is provided here below

This code leverages the randomness of the blockchain’s state (specifically, the last block’s hash of Ethereum) to simulate a coin flip

By dividing a large pseudo-random value (the block hash) by a large constant and interpreting the outcome, the contract creates an unpredictable result for the two possible outcomes of a coin flip (true or false)

It is usefull to read this article about Ethereum, and get to understand block number, block hash : https://bit.ly/3U878Mu

In the code, blockhash is a global variable that takes block.number and returns the hash of the given block

block.number -1 returns the block number of the previous block, as the current block is not yet mined

On Ethereum, the block hash changes every 12 seconds or so, as shown on this chart

https://ycharts.com/indicators/ethereum_average_block_time

It means that within these 12 seconds, the blockhash will not change and the coinFlip will not be random at all. So we can somehow repeat the flip without any change to the result. However, there’s a protection against re-using the same blockhash several times, so we need to find another way

We need the consecutiveWins to reach 10. At the moment, it is stuck to zero

We can perform a Coin flip by first getting the contract address

Then we can flip the Coin, but it does not get fulfilled as the revert() function cancels it

We can use another contract to hack the CoinFlip code, that we are going to call HackCoinFlip

We can guess the calculation of the hash of the CoinFlip contract, to generate the random number that we know this function is using. We can “intercept” this result and pass it on to our HackCoinFlip contract. The code of this other contract is below

The contract calls the flip function of the CoinFlip contract with the calculated guess

The constructor initializes the coinFlipContract instance with the address of the deployed CoinFlip contract, to intercept the result

The CoinFlip contract’s outcome is determined by the block hash of the last block when it is mined. So, by accessing the same block hash used by the CoinFlip, the HackCoinFlip contract can precisely calculate what the coin flip result will be

Because the hash of the last block is used to generate the random outcome and is publicly available as part of the Ethereum blockchain, this contract can effectively “guess” the flip outcome before it happens

We need to deploy our code in our environnement. For that, we are going to use the Remix IDE : https://remix.ethereum.org/

In Remix, we need to copy the code of our two contracts : contract HackCoinFlip, contract CoinFlip

Then we can compile the code, correct any issues (with the help of the built-in AI)

Afterwards, we deploy the code by selecting the Metamask environnement and our instance adress 0x2B483868AD9862d8ab1EdE67BC2f3f4A4962C7f9

First, we deploy the HackCoinFlip contract

By pressing the Deploy button, we need to pay some gas in Metamask

Then we deploy the CoinFlip contract (we need to pay gas again)

Then we can use our contracts to “flip and guess”. At the beginning, ConsecutiveWins is set to zero

We need to execute makeGuess for 10 times, and check the consecutiveWins counter going to 10

Then we can press the Submit instance button, and we pass the level

Lessons learned : this example highlights the importance of proper random number generation within smart contracts, as relying on block hashes for randomness can be easily exploited. In production, developers should use reliable sources of randomness, such as Oracles or other decentralized solutions (like Chainlink VRF : https://chain.link/vrf)