On may 21-2023, attacker deployed malicious code which was able to withdraw 100,000 TORN tokens worth nearly 1M $.
Let's breakdown how the hacker was able to exploit.
The hacker proposed changes to the Tornado cash governance smart contract and provided with a legitimate code which the community accepted. But later the hacker removed the legitimate code and deployed malicious code to the same address which was approved by the community, but the community members were not aware of this code change and the hacker exploited.
Let's understand how the hacker was able to deploy the smart contract to the same address
The hacker created a root contract which deployes a deployer contract with the help of create2 opcode, the opcode is used to determine the address of the smart contract before deploying and the address of this contract is independent of nonce(no. of previous transactions made from the account).
The normal create opcode uses address of the deployer and nonce to create the address of the smart contract
contract_address = sha3(RLP(deployer address, nonce))
Deployer address and nonce is RLP(recursive length prefix) encoded and hashed.Here the contract address is dependent on the nonce, if the nonce is different the contract address is also different
create2 opcode is independent of nonce and uses salt(random hexadecimal) of user choice to compute the contract address.
contract address is calculated as
contract_address = sha3(0xFF, sender, salt, bytecode)
- 0xFF, a constant that prevents collisions with CREATE.
- The sender’s own address.
- salt(random value)
- bytecode of root contract
so the deployer contract is deployed at 0xABC
after that with the help of the deployer contract the hacker deploys a legitimate governance contract to 0xFFF address using create opcode and now the nonce of the deployer contract is set to 1, because a transaction is made, now the commuunity members approved this contract and its address.
Now hacker deleted the deployer contract and legitimate contract. Later he deployed the deployer contract again with create2 opcode using root contract, so now the nonce of the deployer contract is again set to 0 and deployed at the same address(0xABC).
With the help of the deployer contract the hacker deployed malicious contract to the approved address(0xFFF).
The malicious contract had functionalities where it was able to grant the hacker 1,200,000 votes and got access to the withdraw function of the Tornado cash smart contract and was able to withdraw the funds using delegate call
Delegatecall is a low-level function in Solidity that allows a contract to call another contract's function, but with the context of the original contract. This means that the original contract's storage, msg.sender, and msg.value are used when the called contract is executed.
Let's take an example of how delegate call can be used to withdraw funds from other contract
pragma solidity ^0.8.0;
contract Malicious {
address payable public contractB;
constructor(address payable _contractB) {
contractB = _contractB;
}
function withdrawFromContractB() public {
(bool success, ) = contractB.delegatecall(abi.encodeWithSignature("withdraw()"));
require(success, "Withdraw failed");
}
}
The malicious contract has a delegatecall which calls the withdraw function in the Tornado cash governance contract.
pragma solidity ^0.8.0;
contract Governance {
address public owner;
uint public balance = 10 ether;
constructor() {
owner = msg.sender;
}
function withdraw() external {
require(msg.sender == owner, "Only the owner can withdraw");
require(balance > 0, "Insufficient balance");
uint amount = balance;
balance = 0;
payable(msg.sender).transfer(amount);
}
}
The code used here is just for educational purpose.
In conclusion, the recent DeFi hack serves as a stark reminder of the risks and vulnerabilities that exist within the decentralized finance ecosystem. The breach not only resulted in a significant financial loss but also raised concerns about the security of smart contracts and the potential for exploitation.
Top comments (0)