[SCH] Arithmetic Overflow

frenchkebab·2023년 4월 24일
0

Web3

목록 보기
2/8

Arthmetic overflow

value를 보내지 않고 require문 뚫기1

  function buy(uint256 numTokens) public payable {
    require(block.timestamp <= startTime + SALE_PERIOD, "ICO is over");

    // 1 ETH = 10 Tokens (1 Token = 0.1 ETH)
    require(msg.value == (numTokens * 10) / 100, "wrong ETH amount sent");

    token.mint(msg.sender, numTokens);
  }

  function refund(uint256 numTokens) public {
    require(block.timestamp < startTime + SALE_PERIOD, "ICO is over");

    token.burn(msg.sender, numTokens);

    // 1 ETH = 10 Tokens (1 Token = 0.1 ETH)
    payable(msg.sender).call{value: (numTokens * 10) / 100}("");
  }

잠시동안 곰곰히 고민을 했는데... 답이 안나와서 해설을 봤다.
Arithmetic Overflow/Underflow에 대해서는 잘 알고 있다고 생각했는데 생각지도 못한 방법이 있었다.

require(msg.value == (numTokens * 10) / 100, "wrong ETH amount sent");

여기서 당연히 msg.value를 이빠이 넣어서 overflow를 발생시킬 것이라고 생각했으나..
(numTokens * 10) / 100를 0으로 만들 수 있는 방식이 있었다.

numTokens 자리에 MAX_UINT / 10 + 1값을 넣는 것이였다.
(MAX_UINT = 2 ** 256 - 1)

MAX_UINT / 10 + 1 에 10을 곱하면 MAX_UINT + 10이 된다.
이 값은 MAX_UINT + 1 (= 0) + 9로, 즉 최종적으로 분자의 값은 9이다.

마지막으로 9 / 100이 연산되면 0으로 버려져서 최종적으로

msg.value == 0을 만족하게 된다.

value를 보내지 않고 require문 뚫기2

마지막 4번 문제였는데 결국 혼자 풀지를 못했는데...
동일한 매커니즘이였다.

    function transfer(address _to, uint256 _value) external returns (bool) {
        require(balances[msg.sender] >= _value, "Not enough balance");
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        return true;
    }

    function batchTransfer(address[] memory _receivers, uint256 _value) external returns (bool) {
        uint256 totalAmount = _receivers.length * _value;
        require(_value > 0, "Value can't be 0");
        require(balances[msg.sender] >= totalAmount, "Not enough tokens");

        balances[msg.sender].sub(totalAmount);

        for (uint256 i = 0; i < _receivers.length; i++) {
            balances[_receivers[i]] = balances[_receivers[i]].add(_value);
        }

        return true;
    }
uint256 totalAmount = _receivers.length * _value;

여기서 totalAmount를 0으로 만들면 되는 것이였다.

_receivers 배열의 길이를 2로 두고, _valueMAX_UINT / 2 + 1로 두면 된다.

처음에는 해설을 보고 '이렇게 두면 2를 곱했을 때 MAX_UINT + 2가 되는 것 아닌가?' 하는 생각을 했었다.

이해하는데 한참 걸렸다.

MAX_UINT/2 + 1의 값이 2**255가 된다는 것을 이해하는 것이 중요하다.

2**256 - 1 / 22**255 - 0.5이다.

그래서 나는 당연히 - 0.5 부분을 날려버리고 생각했는데, 여기서부터 이해가 잘못되었었다.

2**255 + 0.5라면 날리는 것이 맞지만, 빼게 되면 소숫점 앞의 정수 부분이 아예 달라지게 된다.

5 + 0.55.5로 소숫점을 버리면 5가 되지만,
5 - 0.54.5로 소숫점을 버리면 4가 된다.

최종적으로는 5 - 1과 같아지는 상황이다.

다시 위의 예시로 돌아가면, (2**256 - 1) / 22**255 - 0.5가 되고, 이것은 2**255 - 1과 같은 값이다.

따라서 여기에 + 1을 해주니, 2**255와 같은 값이 되는 것이다.

최종적으로 2를 곱해주게 되면 2**256이 되어 Overflow가 발생하여 0이 되게 된다.

profile
Blockchain Dev Journey

0개의 댓글