// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
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];
}
}
The goal of this level is for you to hack the basic token contract below.
You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens.
Things that might help:
What is an odometer?
더 많은 토큰을 얻으면 된다.
코드를 처음 봤을 때 드는 생각은 유명한 더 다오 해킹사건이다. 무한 루프를 통해 이더리움을 계속 환전해간 사건. 현재 transfer()
함수는 인출하고자 하는 _value
값을 송금하기 전에 0으로 만들지 않기 때문에 반복적으로 transfer()
함수를 호출하면 되지 않을까?
현재 내가 가지고 있는 토큰의 양은 20개.
총 발행량은 2100만개다.
반복문을 돌려서 발행된 토큰을 모두 가져오게 했다.(다 가져오는 건 너무했나..?) attack
함수를 호출한 뒤 다시 balance를 확인했더니 부자가 돼있었다. 다시 확인해보니 0을 하나 빠트려서 총 발행량의 10%만 가져왔다. 그래도 통과!
그런데 내가 생각했던 게 출제의도와는 다를 수 있다. 다른 사람은 어떻게 해결했는지 알아보자. 해결 과제 마지막 부분에 odometer
는 계기판을 의미한다. 이와 관련해서 batchOverflow 공격이 있는데, 0000
을 가리키는 계기판에서 1을 더하면 0001
이 되지만 반대로 1을 빼면 1111
이 된다. 즉, 어떤 주소의 balance를 저장하는 mapping에서 1을 빼준다면 해당 주소는 엄청난 양의 토큰을 갖는 것으로 저장된다.
balances[msg.sender] -= _value;
balances[_to] += _value;
처음 상황을 가정했을 때, 위 코드에서 우리 주소의 balances는 20개다. 여기서 20개를 transfer 요청한다면 당연히 balances는 0개로 될 것이다. 그런데 처음부터 20개가 아니라 21개를 요청한다면 balances 상에서 마이너스가 되는 게 아니라 엄청난 크기의 양수로 변하게 된다. 따라서 아래 코드를 입력하고 balances를 확인하면 토큰을 쓸어담을 수 있다.
await contract.transfer("메타마스크주소", 20 +1)