Type: #metamorphic
Project: Tornado DAO
Date: 20/05/23
Blockchain: ETH
Problem: Proposal can be updated as a metamorphic contract.
Changing the bytecode of a fixed account in Ethereum is possible using a sequence of CREATE/CREATE2 and SELFDESTRUCT calls.
CREATE, and CREATE2 are unique EVM operations that create new accounts. A contract can only be created if the account is "empty," where "empty" means codesize == 0 and nonce == 0. A newly created smart-contract has codesize > 0 and nonce == 1. Although this information is well-known, it is worth mentioning the following details about CREATE/CREATE2:
* CREATE - calculates address of new contract from keccak256(_deployer_addr, deployer_nonce_)(actually: keccak256(rlp([_deployer_addr, deployer_nonce_]))[12:])
* CREATE2 - calculates address of new contract from keccak256(_0xFF, deployer_addr, salt, bytecode)(actually: keccak256(_0xFF, deployer_addr, salt, bytecode)[12:]).
But how was it possible to deploy different contract at the same address in case of Tornado DAO? The hacker deployed factory contract with CREATE2 opcode, which deployed the proposal with the CREATE opcode. After the hacker deployed a proposal, the nonce of the factory became 1, so if the next proposal is executed with CREATE opcode - the address will differ. BUT: When the contract is selfdesctructed, the nonce of the contract also becomes zero.
The Hacker:
1) Deploys a proposal contract. Tornado governance approves proposal contract.
2) Deletes the proposal contract. Deletes the factory of the proposal contract making nonce = 0.
3) Attacker redeploys a factory, and then deploys different contract at the same address as the proposal contract.
4) Attacker executes malicious code. Since the initial proposal was already passed by voters, the attacker managed to grant himself the fake votes.
Discoverer: NaN. was hacked
Harm: 1 M $
link
Project: Tornado DAO
Date: 20/05/23
Blockchain: ETH
Problem: Proposal can be updated as a metamorphic contract.
Changing the bytecode of a fixed account in Ethereum is possible using a sequence of CREATE/CREATE2 and SELFDESTRUCT calls.
CREATE, and CREATE2 are unique EVM operations that create new accounts. A contract can only be created if the account is "empty," where "empty" means codesize == 0 and nonce == 0. A newly created smart-contract has codesize > 0 and nonce == 1. Although this information is well-known, it is worth mentioning the following details about CREATE/CREATE2:
* CREATE - calculates address of new contract from keccak256(_deployer_addr, deployer_nonce_)(actually: keccak256(rlp([_deployer_addr, deployer_nonce_]))[12:])
* CREATE2 - calculates address of new contract from keccak256(_0xFF, deployer_addr, salt, bytecode)(actually: keccak256(_0xFF, deployer_addr, salt, bytecode)[12:]).
But how was it possible to deploy different contract at the same address in case of Tornado DAO? The hacker deployed factory contract with CREATE2 opcode, which deployed the proposal with the CREATE opcode. After the hacker deployed a proposal, the nonce of the factory became 1, so if the next proposal is executed with CREATE opcode - the address will differ. BUT: When the contract is selfdesctructed, the nonce of the contract also becomes zero.
The Hacker:
1) Deploys a proposal contract. Tornado governance approves proposal contract.
2) Deletes the proposal contract. Deletes the factory of the proposal contract making nonce = 0.
3) Attacker redeploys a factory, and then deploys different contract at the same address as the proposal contract.
4) Attacker executes malicious code. Since the initial proposal was already passed by voters, the attacker managed to grant himself the fake votes.
Discoverer: NaN. was hacked
Harm: 1 M $
link