Innovation 智能合约题目合集(上)

 


前言:

最近找到的一个比较有趣的区块链的challenge合集。

https://blockchain-ctf.securityinnovation.com/#/dashboard

题目感觉是比最原来的那几个传统漏洞要难一些。其中加入了一些有趣的tips。

所以尝试来做一下这些题目

非常适合初学者来锻炼一下。

我会给出详细的做题解法以及细节处理,

如果有任何问题欢迎评论留言。

此篇介绍前5道题目


Donation

所有题目完成的标志是把钱转回。

而这里面就是withdrawxxxxxx函数可以实现这个功能

并且function 类型为external 可以为外部所调用。

但是他的每一个题目都有一个CtfFramework.sol

此合约限制了能偶调用题目合约的地址。

如果需要使用攻击合约或者其他外部账户地址。

需要提前添加。

也就是这里的ctf_challenge。

此题目合约给了源码

但是有很多库是我们所没有的。

所以我选择了全部集成于一个合约来写。

最后点击withdraw调用函数即可。


Lock Box

题目利用now 进行一种随机数的取值。

但是此种随机数在constructor中创建被保存于 storage里面

通过反汇编我们发现此处存于stor1

我们只需要在链上找storage1 赋值处即可。

在Transcation Details 里面即可发现

他的js页面已经内置了 web3.eth调用所以可以选择remix 或者页面内直接输入。

输入成功


Piggy bank

这道题目比较巧妙。

给出部分源代码.

contract PiggyBank is CtfFramework{

    using SafeMath for uint256;

    uint256 public piggyBalance;
    string public name;
    address public owner;

    constructor(address _ctfLauncher, address _player, string _name) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        name=_name;
        owner=msg.sender;
        piggyBalance=piggyBalance.add(msg.value);
    }

    function() external payable ctf{
        piggyBalance=piggyBalance.add(msg.value);
    }


    modifier onlyOwner(){
        require(msg.sender == owner, "Unauthorized: Not Owner");
        _;
    }

    function withdraw(uint256 amount) internal{
        piggyBalance = piggyBalance.sub(amount);
        msg.sender.transfer(amount);
    }

    function collectFunds(uint256 amount) public onlyOwner ctf{
        require(amount<=piggyBalance, "Insufficient Funds in Contract");
        withdraw(amount);
    }

}


contract CharliesPiggyBank is PiggyBank{

    uint256 public withdrawlCount;

    constructor(address _ctfLauncher, address _player) public payable
        PiggyBank(_ctfLauncher, _player, "Charlie") 
    {
        withdrawlCount = 0;
    }

    function collectFunds(uint256 amount) public ctf{
        require(amount<=piggyBalance, "Insufficient Funds in Contract");
        withdrawlCount = withdrawlCount.add(1);
        withdraw(amount);
    }

}

这里我们可以看到

piggybank这个合约里面 如果我们想要调用

withdraw 或者 collectfunds都是需要 是合约的owner才可以。

但是我们并不是。而我们可以发现 charliesPiggyBank 这个合约里面 以同样的命名定义了一个collectfunds

所以这里实际是会把piggybank的同名函数覆盖掉。

所以我们只需要调用 合约的collectfunds(0.15eth)即可。

SI Token Sale

代码稍微长一些

pragma solidity 0.4.24;

import "../CtfFramework.sol";

// https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v1.8.0/contracts/token/ERC20/StandardToken.sol
import "../StandardToken.sol";

contract SIToken is StandardToken {

    using SafeMath for uint256;

    string public name = "SIToken";
    string public symbol = "SIT";
    uint public decimals = 18;
    uint public INITIAL_SUPPLY = 1000 * (10 ** decimals);

    constructor() public{
        totalSupply_ = INITIAL_SUPPLY;
        balances[this] = INITIAL_SUPPLY;
    }
}

contract SITokenSale is SIToken, CtfFramework {

    uint256 public feeAmount;
    uint256 public etherCollection;
    address public developer;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        feeAmount = 10 szabo; 
        developer = msg.sender;
        purchaseTokens(msg.value);
    }

    function purchaseTokens(uint256 _value) internal{
        require(_value > 0, "Cannot Purchase Zero Tokens");
        require(_value < balances[this], "Not Enough Tokens Available");
        balances[msg.sender] += _value - feeAmount;
        balances[this] -= _value;
        balances[developer] += feeAmount; 
        etherCollection += msg.value;
    }

    function () payable external ctf{
        purchaseTokens(msg.value);
    }

    // Allow users to refund their tokens for half price ;-)
    function refundTokens(uint256 _value) external ctf{
        require(_value>0, "Cannot Refund Zero Tokens");
        transfer(this, _value);
        etherCollection -= _value/2;
        msg.sender.transfer(_value/2);
    }

    function withdrawEther() external ctf{
        require(msg.sender == developer, "Unauthorized: Not Developer");
        require(balances[this] == 0, "Only Allowed Once Sale is Complete");
        msg.sender.transfer(etherCollection);
    }

}

可以发现这里没有使用safemath库,考虑溢出,找溢出点。

可能是purchaseTokens这个函数中可以自由操控value。

那么 这里就很明显了 我们通过合约下溢 来操控balance

然后查看collection 我这里是300000000000

那么他每次Transfer 一半

那我们自动×2就行。

给出exp

contract exp{
    address target=challenge address;
    constructor() payable{}
    function exp1()public payable{
        target.call.value(1000 wei)();
       SITokenSale A=SITokenSale(target);
        A.refundTokens(600000000000000000);
        selfdestruct(Your own address);
    }
    function()payable{

    }
}

Secure Bank

给出合约源码

pragma solidity 0.4.24;

import "../CtfFramework.sol";

contract SimpleBank is CtfFramework{

    mapping(address => uint256) public balances;

    constructor(address _ctfLauncher, address _player) public payable
        CtfFramework(_ctfLauncher, _player)
    {
        balances[msg.sender] = msg.value;
    }

    function deposit(address _user) public payable ctf{
        balances[_user] += msg.value;
    }

    function withdraw(address _user, uint256 _value) public ctf{
        require(_value<=balances[_user], "Insufficient Balance");
        balances[_user] -= _value;
        msg.sender.transfer(_value);
    }

    function () public payable ctf{
        deposit(msg.sender);
    }

}

contract MembersBank is SimpleBank{

    mapping(address => string) public members;

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

    function register(address _user, string _username) public ctf{
        members[_user] = _username;
    }

    modifier isMember(address _user){
        bytes memory username = bytes(members[_user]);
        require(username.length != 0, "Member Must First Register");
        _;
    }

    function deposit(address _user) public payable isMember(_user) ctf{
        super.deposit(_user);
    }

    function withdraw(address _user, uint256 _value) public isMember(_user) ctf{
        super.withdraw(_user, _value);
    }

}

contract SecureBank is MembersBank{

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

    function deposit(address _user) public payable ctf{
        require(msg.sender == _user, "Unauthorized User");
        require(msg.value < 100 ether, "Exceeding Account Limits");
        require(msg.value >= 1 ether, "Does Not Satisfy Minimum Requirement");
        super.deposit(_user);
    }

    function withdraw(address _user, uint8 _value) public ctf{
        require(msg.sender == _user, "Unauthorized User");
        require(_value < 100, "Exceeding Account Limits");
        require(_value >= 1, "Does Not Satisfy Minimum Requirement");
        super.withdraw(_user, _value * 1 ether);
    }

    function register(address _user, string _username) public ctf{
        require(bytes(_username).length!=0, "Username Not Enough Characters");
        require(bytes(_username).length<=20, "Username Too Many Characters");
        super.register(_user, _username);
    }
}

这里比较明显的一点是 Secure 和 Members 中的withdraw参数不同

这样两个函数的整个意义就不同了 是完全不同的两个函数。 不过他们都会使用super.withdraw来调用Simple中的withdraw

所以我们这里直接去调用 合约创建者的address 加上我们存入的0.4eth即可。

所以我们找寻一下合约创建者

并且基于他一个register.

这样就可以了


小结:

前五道题目主要以合约的基础知识为主,不需要编写过多的程序。主要是教会我们如何在链上查询关于合约的各种相关信息等。
接下来的 5道题目是主要考察我们的脚本编写合约交互能力的。

(完)