DEV Community

Cover image for Token - Level 05
Stefan Alfbo
Stefan Alfbo

Posted on

Token - Level 05

Problem statement

The goal of this level is for you to hack the basic token contract below.

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.

Things that might help:

  • What is an odometer?
// 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

Solution

Start with creating a new contract for the current level by clicking on the button, Get new instance. Remember to have enough eth in the connected wallet and that it's connected to the Sepolia network.

Open up the developer tool in your browser (F12) and make a transfer, where _to is equal to '0x95D34980095380851902ccd9A1Fb4C813C2cb639' (any valid address will work, and _value equal to 21.

await contract.transfer('0x95D34980095380851902ccd9A1Fb4C813C2cb639', 21)
Enter fullscreen mode Exit fullscreen mode

Check that you have more than 20 tokens.

await contract.balanceOf(player)
Enter fullscreen mode Exit fullscreen mode

If the balance is more than 20, then finish up the challenge by clicking on the button, Submit instance, to commit and update the progress on the ethernaut contract.

Explanation

The Token contract only has one function that modifies the state and therefore the vulnerability of the contract probably lies in that function.

  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

As we can see there are only arithmetic operations made in this function, however these kind of operations are classical causes to software bugs. It boils down to how we represent numbers in computers and programming languages.

The input variable _value and the mapping variable balances use the type uint. This means that these variables can only represent numbers between 0 to (2^256)-1, inclusive, in simpler terms, "only positive values".

If you make an arithmetic operation between two uint's the result has to be of the type uint also.

This call to the transfer function

await contract.transfer('0x95D34980095380851902ccd9A1Fb4C813C2cb639', 21)
Enter fullscreen mode Exit fullscreen mode

will look like this when substituting the values in the Solidity code.

// original code
require(balances[msg.sender] - _value >= 0);

// substitute the variables to actual values used
require(20 - 21 >= 0);

// after arithmetic calculation
require(115792089237316195423570985008687907853269984665640564039457584007913129639935 >= 0);
Enter fullscreen mode Exit fullscreen mode

The expression 20 - 21 can't be -1 since the result has to be a positive number (uint), therefore an overflow is occurring and producing the big number, and there is the vulnerability.

It's important to know that this behavior has changed with recent version of Solidity (from v0.8.0), but this contract is on version

pragma solidity ^0.6.0;
Enter fullscreen mode Exit fullscreen mode

so that's an important factor when looking for vulnerabilities in a contract. To prevent this problem in older contracts you may use SafeMath from OpenZeppelin.

The conclusion from the author of this level.

Conclusion

Resources

  • Token - This challenge
  • Remix - Web based IDE for Solidity
  • Solidity - Solidity documentation

Top comments (0)