Ethernaut solution 5: Token

Ethernaut solution 5: Token

How to Solve the Ethernaut Token challenge?

Let's take a look into the contract

// 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];
  }
}

This is the basic structure of an ERC20 token

We have:

  • a mapping to store the balance of each individual address who have tokens
mapping(address => uint) balances;
  • A uint256, which is used to track the total supply of the token

  • The constructor which set the initial supply and gives whoevers deploys the contract all the inicial supply

  • And a transfer function that allows us to transfer a specified amount of tokens to a specific address.

  • A balanceOf function which allows us to know how many tokens a specific address has.

The challenge

You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens.

Let's start thinking;

This transfer function has a require statement that checks whether the balance of tokens of whoever is calling the function, minus the _value parameter, is equal or greater than zero.

This statement will always be true, since no matter what is the actual value evaluating on the left side it always will be greater or equal than zero. Remember that uint means unsigned integer, those integers which don't have a sign, so they are always positive. So, we don't have to worry about this statement because it will always be true.

But, If balances[msg.sender] - _value always would be a positive number, since those values are both uint256, what would happen when balances[msg.sender] is < _value

For example

Balances[msg.sender] = 25

Value = 40

25-40 = -15

So, WTF?????

This is when we find a great problem of older versions of solidity, in versions <.0.8.0 this problem is handle by default, overflow and underflow

Underflow & Overflow

As I said before the difference between two uint is another unit, so the result of an arithmetical operation like (30-31) instead of be -1, will be 2^256 - 1.

We can explain this with an example:

Let's say you have a clock, the typical clock with 12 items that goes from 1 to 12, let's call those points.

image.png

And let's imagine that you are in the 3rd position.

image.png

But we want to move forward 13th position, so we move from 3 to … oh we have a problem, we only have 12 positions, but 3 +14 is 16 WTF, Kevin >>:(

wait….

Okay , it's 16, but it's a clock. If we reach 12, we can start counting again from it. So, we can start moving from 3 to 12

image.png

And here we already moved 9 positions, so 13 - 9 = 4, we still have to move 4 positions, so we can simply start again from 12

image.png

And that's it, you are in the 4 position now.

NOW, imagine that clocks as a uint12 (take into account that, in reallity, that number defines the total of bytes that can be stored, but here we are using just the normal number, I’m not referencing bytes)

A unit that can only store 12 positions, but you want to store 16 positions, like in the example, what would happen? It happens that the variable will restart once it takes the upper bound and start again from 1 to add the rest of the numbers; we can call this an -overflow, because we are storing a quantity that is unhandle for that variable.

Back to our example; now let's imagine that instead of add, we subtract 13 positions, so 3 - 13 = -10, but in our clock example would be equal to 2, (here we are counting opposite clockwise, since we are subtracting).

in our uint12, since it starts at 1, and there is no such a thing like cero (let's imagine) the only thing that could do this variable is start counting from 12 to one (once the lower bound is reached), we can call this an underflow, because we are storing a quantity that cannot be computed since is negative, and the only thing that left is start from the last number to the first (12 - 1)

So, this is why the required statement will always be true, because it is suffering from this underflow problem.

Back to the challenge, now the balances are stored in a uint256 (remember that uint is 256 bytes by default). So there can be stored a really huge number stored there.

Then, we just have two simple arithmetic operations

one that subtracts the _value we passed as parameter from our token account balance

and another that adds that amount to the _to address you specified as parameter during the invocation of the function.

balances[msg.sender] -= _value;
balances[_to] += _value;

The subtract operation could also suffer an underflow

Since we are subtracting _vaue from our balance, we can underflow that operation to allow us to steal a big, a really big amount of tokens. Something like

_value = 21

And since our balance is 20 tokens

20 - 21 will provoke an underflow, and will give to us a really huge amount of tokens (2**256-1)

Hacking

So let's write our script, you can follow the structure from the previous post.

ACCOUNT = accounts.add(config["wallets"]["from_key"])

ADDRESS_TO_SEND = "0x468F2866Cd6aEcf640644885F6Cad63Ff4f9BC4c"

def hack():
    # grab the Token contract Object
    instance_contract = interface.IToken(INSTANCE_ADDRESS)

    # hack the contract
    tx = instance_contract.transfer(ADDRESS_TO_SEND, 21, {"from": ACCOUNT})
    tx.wait(1)

    print(f"Contract has been hacked. Now we have {instance_contract.balanceOf(ACCOUNT)} tokens")

def main():
    hack()

image.png

And that's it, we hacked the contract. :)

Now you can submit the contract instance, you can follow the steps from our previous post.

image.png

But hey! Could I overflow it? You could, in this case the third line of code will be affected balances[_to] += _value; since you are using addition, you could do something like 2**256 + 21, that should give you an overflow. If you are using the brownie framework it will throw you an error

How can we prevent this problem?

Underflow and Overflow problems are handle by default in the latest versions of solidity, since version 0.8.0, but is always recommended that you check for those problems to avoid unexpected behaviors, you can use the safeMath library of OpenZeppelin that prevents this kind of problem in your arithmetic operation)


You can grab the complete solution in this github repo

If you have a comment or a suggestion please leave it in the section comments, also if you see any problem with the code feel free to make PR

You can follow me on my twitter @_bravoK and DM me, I’m always happy to talk and know more people of this amazing community

Stay tuned for the next Ethernaut solution.