# Node quest (Vanity) ## Part 1 ## Story A wizard is trying to brew a strength potion, but doesn’t have the right ingredients. Your job is to find acidic ingredients for his potion. ## Problem contract Reference of difficulty. If I try to brute force address with 5 fixed letters, then the difficulty will be approximately = (16)^6 = (2)^ 24 = ~17 million Vanity generator takes around 6 mins for this with 50% probability. If we increase it to 7 letters, time taken will be increased to 16 times. For each increase in letter, difficulty is increased by 16 times making it very difficult to break vanity address with large keyword. ![](https://i.imgur.com/2sLLJLn.png) Vanity keyword(suffix): `ac1d1c` corresponding number: `11279644` Sample address: `0xe445fD0626b5F61aBC1F22d2f86abbb3e4ac1d1c` Private key: `0x2093d5a64254eca2312e8f46c516df3154a677f176cde4f6248081690d515274` How to check? Check if last 6 letter of caller( `msg.sender`) matches the vanity keyword. Sample code to check: ``` pragma solidity 0.8.15; import "forge-std/Test.sol"; contract NodeGuardians { uint256 public constant VANITY_NUMBER = 11279644; bool public isAcidic; /* * @notice Checks if the function is called by valid vanity address */ function testIngredient() external { //check last 6 letters uint256 suffixNumber = uint160(msg.sender) & uint160(0xFFFFFF); isAcidic = suffixNumber == VANITY_NUMBER; } } ``` Sample test: ``` // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import "forge-std/Test.sol"; import "../src/NodeGuardians.sol"; contract CounterTest is Test { NodeGuardians victim; function setUp() external { victim = new NodeGuardians(); } function test_part1() external { vm.startPrank(0xe445fD0626b5F61aBC1F22d2f86abbb3e4ac1d1c); victim.testIngredient(); assert(victim.isAcidic()); } } ``` Solution JS code: ``` const ethWallet = require('ethereumjs-wallet').default; for(let index=0; index < 20000000; index++) { let addressData = ethWallet.generate(); const privateKey = addressData.getPrivateKeyString(); const address = addressData.getAddressString(); if(address.substring(address.length-6).toLowerCase() == '00ac1d') { console.log(privateKey); console.log(address); } } ``` How the hack works? Here, we are taking the last 3 bytes(6 letter in hex) and doing `and` operation of 3 bytes with `FFFFFF`. This will give us last 3 bytes of `caller`. If it matches the vanity number, `isAcidic` will become true and challenge is passed. ## Part 2 ## Story A wizard is able to find the right acidic ingredients for his potion. Now, your job is to help him in building acidic potion which can behave like potion ingredient. You have some secret info that right potion ingredient have `heatLevel` of more than `5000`. ## Problem contract Deploy a contract at vanity address ending with vanity keyword which has method named `heatLevel` returning value more than `5000`. Also, the contract deployed should have the functionality to call our CTF contract. Vanity keyword(suffix): `ac1d1c` corresponding number: `11279644` Sample address: `0xe445fD0626b5F61aBC1F22d2f86abbb3e4ac1d1c` Private key: `0x2093d5a64254eca2312e8f46c516df3154a677f176cde4f6248081690d515274` required function signature: `heatLevel()` = `0x08cdd247` required value: more than `5000` EOA address: `0xa00efCD04c6bc0749ce21E753ADE3e3E80c02c3B` Nonce of EOA: `0` Deployed address using EOA: `0xC2BC4D50905B16250EB2cDC659773e9CB69aFa0b` Salt:`14350630` Attacker contract: `0x2209719ccef5ba5b8056cee1eac6abb541ac1d1c` Creation code:`0x608060405234801561001057600080fd5b50610260806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806308cdd2471461003b578063ab5016bf14610050575b600080fd5b6115b360405190815260200160405180910390f35b61006361005e36600461011f565b610065565b005b6000826001600160a01b03168260405161007f91906101ef565b6000604051808303816000865af19150503d80600081146100bc576040519150601f19603f3d011682016040523d82523d6000602084013e6100c1565b606091505b50509050806101045760405162461bcd60e51b815260206004820152600b60248201526a18d85b1b0819985a5b195960aa1b604482015260640160405180910390fd5b505050565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561013257600080fd5b82356001600160a01b038116811461014957600080fd5b9150602083013567ffffffffffffffff8082111561016657600080fd5b818501915085601f83011261017a57600080fd5b81358181111561018c5761018c610109565b604051601f8201601f19908116603f011681019083821181831017156101b4576101b4610109565b816040528281528860208487010111156101cd57600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b6000825160005b8181101561021057602081860181015185830152016101f6565b8181111561021f576000828501525b50919091019291505056fea2646970667358221220ffcc9c47d7ed2e9bafd5f9846d933b63bd4da7cdd74c545416bb87227122c06b64736f6c634300080f0033` How to check? * Check if caller address is vanity. * Check if caller has bytecode deployed at the address. * Call the required function and check if it returns the require value. Sample code to check: ``` pragma solidity 0.8.15; import "forge-std/Test.sol"; interface HeatInterface { function heatLevel() external returns (uint256); } contract NodeGuardiansPartTwo { uint256 public constant VANITY_NUMBER = 11279644; uint256 public constant REQUIRED_HEAT_VALUE = 5000; bool public hasRequiredHeat; /* * @notice Check if caller address is vanity. * Check if caller has bytecode deployed at the address. * Call the required function and check if it returns the require value. */ function checkHeatLevel() external { uint256 suffixNumber = uint160(msg.sender) & uint160(0xFFFFFF); uint256 sizeOfContract; assembly { sizeOfContract := extcodesize(caller()) } uint256 heatLevel = HeatInterface(msg.sender).heatLevel(); hasRequiredHeat = suffixNumber == VANITY_NUMBER && sizeOfContract > 0 && heatLevel > REQUIRED_HEAT_VALUE; } } ``` Sample JS script to generate salt for following bytecode: ``` const { ethers } = require("ethers"); const ethWallet = require('ethereumjs-wallet').default; for(let index=0; index < 20000000; index++) { const hashedCode = ethers.keccak256("0x608060405234801561001057600080fd5b50610260806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806308cdd2471461003b578063ab5016bf14610050575b600080fd5b6115b360405190815260200160405180910390f35b61006361005e36600461011f565b610065565b005b6000826001600160a01b03168260405161007f91906101ef565b6000604051808303816000865af19150503d80600081146100bc576040519150601f19603f3d011682016040523d82523d6000602084013e6100c1565b606091505b50509050806101045760405162461bcd60e51b815260206004820152600b60248201526a18d85b1b0819985a5b195960aa1b604482015260640160405180910390fd5b505050565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561013257600080fd5b82356001600160a01b038116811461014957600080fd5b9150602083013567ffffffffffffffff8082111561016657600080fd5b818501915085601f83011261017a57600080fd5b81358181111561018c5761018c610109565b604051601f8201601f19908116603f011681019083821181831017156101b4576101b4610109565b816040528281528860208487010111156101cd57600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b6000825160005b8181101561021057602081860181015185830152016101f6565b8181111561021f576000828501525b50919091019291505056fea2646970667358221220ffcc9c47d7ed2e9bafd5f9846d933b63bd4da7cdd74c545416bb87227122c06b64736f6c634300080f0033"); const packed = ethers.solidityPacked(["bytes1", "address", "uint256", "bytes32"], ["0xff", "0xC2BC4D50905B16250EB2cDC659773e9CB69aFa0b", index, hashedCode]); const hashedPacked = ethers.keccak256(packed); const address = `0x` + hashedPacked.slice(-40).toLowerCase(); if(address.substring(address.length-6).toLowerCase() == 'ac1d1c') { console.log(index, address); } } ``` Sample Code to be deployed at Vanity address: ``` pragma solidity 0.8.15; contract PartTwoSolutionContract { function heatLevel() external pure returns (uint256) { return 5555; } function callChallengeContract(address to, bytes memory data) external { (bool ok, ) = to.call(data); require(ok, "call failed"); } } ``` Part2 vanity contract deployer: ``` pragma solidity 0.8.15; import "./PartTwoSolutionContract.sol"; contract PartTwoSolutionContractDeployer { bytes32 public constant SALT = bytes32(uint256(14350630)); //address of this contract should be 0xC2BC4D50905B16250EB2cDC659773e9CB69aFa0b function deployUsingCreate2() external returns (PartTwoSolutionContract) { return new PartTwoSolutionContract{salt: SALT}(); } } ``` Foundry test for validation: ``` // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import "forge-std/Test.sol"; import "../src/NodeGuardiansPartTwo.sol"; import "../src/PartTwoSolutionContractDeployer.sol"; contract PartTwo is Test { NodeGuardiansPartTwo victim; PartTwoSolutionContract attacker; PartTwoSolutionContractDeployer deployer; function setUp() external { victim = new NodeGuardiansPartTwo(); console.logBytes(type(PartTwoSolutionContract).creationCode); } function test_part2() external { vm.startPrank(0xa00efCD04c6bc0749ce21E753ADE3e3E80c02c3B); deployer = new PartTwoSolutionContractDeployer(); attacker = deployer.deployUsingCreate2(); console.log(address(attacker)); attacker.callChallengeContract( address(victim), abi.encodeWithSignature("checkHeatLevel()") ); assert(victim.hasRequiredHeat()); } } ```