Skip to content

Token sale

pragma solidity ^0.4.21;

contract TokenSaleChallenge {
  mapping(address => uint256) public balanceOf;
  uint256 constant PRICE_PER_TOKEN = 1 ether;

  function TokenSaleChallenge(address _player) public payable {
    require(msg.value == 1 ether);
  }

  function isComplete() public view returns (bool) {
    return address(this).balance < 1 ether;
  }

  function buy(uint256 numTokens) public payable {
    require(msg.value == numTokens * PRICE_PER_TOKEN);

    balanceOf[msg.sender] += numTokens;
  }

  function sell(uint256 numTokens) public {
    require(balanceOf[msg.sender] >= numTokens);

    balanceOf[msg.sender] -= numTokens;
    msg.sender.transfer(numTokens * PRICE_PER_TOKEN);
  }
}

Goal

Make the ETH balance in the contract less than 1 ether

Exploit

As per the CTF description, this token contract allows you to buy and sell tokens at an even exchange rate of 1 token per ether.

The TokenSaleChallenge starts with balance of 1 ether, every time we buy new tokens we increase the contract ether balance, and every time we sell tokens we decrease the balance.

In order to leave the contract with less than 1 ether, we must first find a way to buy more tokens, to increase our balance of tokens, so that the balance of our tokens will be quite bigger than the balance of ETH stored in the contract.

We can do this with the help of arithmetic overflow. The place where we can make the overflow is in the buy() function.

The numTokens variable is of type uint256, the maximum value it can store is 115792089237316195423570985008687907853269984665640564039457584007913129639935

We can't possibly call the buy() function with this value, because we don't have enough ether balance in our account to buy that much tokens.

First we need to think in terms of wei and not ether. We know that 1 ether = 10^18 wei

From the following code:

require(msg.value == numTokens * PRICE_PER_TOKEN);

we can draw a conclusion, to pass this require statement, the amount of tokens (numTokens) will be multiplied by 1 ether, and as we said earlier the EVM will interpret 1 ether as 10^18 or 1000000000000000000 wei.

And if we try to buy the following amount:

(115792089237316195423570985008687907853269984665640564039457584007913129639935 / 10^18) + 1 = 115792089237316195423570985008687907853269984665640564039458

Multiplying it by 10^18 will result in an overflow of 415992086870360064, a little bit below half an ether. So, sending that amount of wei as the msg.value will pass the require statement and give us a lot of tokens.

To exploit the contract we need to:

  1. call the buy() function with 115792089237316195423570985008687907853269984665640564039458 as numTokens and 415992086870360064 wei as msg.value.
  2. call the sell() function with 1 token as parameter