WEB3DEV

Cover image for Desvendando o Ataque de Governança do Tornado Cash
Isabela Curado Nehme
Isabela Curado Nehme

Posted on

Desvendando o Ataque de Governança do Tornado Cash

24 de maio de 2023

ZAN: https://zan.top/home/

Twitter: https://twitter.com/zan_team

Github: https://github.com/ZAN-Team/

Avaliação do evento

Em 20 de maio de 2023 às 07:25:11 (UTC), o Tornado.Cash foi atacado por uma proposta maliciosa. O atacante drenou com sucesso 473.000 tokens TORN. Em um curto período de tempo, o atacante trocou 100.000 TRON por 54 ETH. Mais tarde, ele depositou 372 ETH no Tornado Cash para lavar seus lucros.

Linha de Ataque

Etapa 1: sugerir uma proposta

O atacante primeiro implantou uma proposta de contrato normal para enganar a comunidade e incentivá-la a votar nele.

O id tx é:

0x34605f1d6463a48b818157f7b26d040f8dd329273702a0618e9e74fe350e6e0d.

O conteúdo da proposta é:

A proposta visa punir determinados endereços e usa a lógica da Proposta 16. No entanto, ao contrário da Proposta 16, esta proposta de contrato contém uma armadilha escondida — que é uma função de autodestruição chamada emergencyStop().

function emergencyStop() public onlyOwner {
selfdestruct(payable(0));
}
Enter fullscreen mode Exit fullscreen mode

A comunidade não reconheceu a ameaça potencial da função de autodestruição e votou a favor da Proposta 20, dando apoio.

Passo 2: Destruir a proposta

Depois que a proposta do atacante coletou votos suficientes, o atacante chamou a função emergencyStop() para se autodestruir.

O id tx é:

0xd3a570af795405e141988c48527a595434665089117473bc0389e83091391adb

Dessa forma, tanto a proposta do contrato quanto o contrato que a criou foram destruídos.

Etapa 3: reimplantar a proposta

Chegamos à parte crucial do ataque. O atacante implantou uma nova proposta de contrato no mesmo endereço da etapa 1, mas com conteúdo completamente diferente. Como isso foi realizado?

Vamos dar uma olhada nas instruções usadas pelo atacante na transação de implantação.

O atacante primeiro usou a função create2 para implantar o contrato no endereço 0x7dc86183274b28e9f1a100a0152dac975361353d. Em seguida, o contrato 0x7dc8 usou a função create para implantar a proposta de contrato no endereço 0xc503893b3e3c0c6b909222b45f2a3a259a52752d.

Por que o endereço do contrato recém-implantado é o mesmo que o da etapa 1, mas o conteúdo da proposta de contrato é completamente diferente?

É porque as funções create e create2 têm diferentes métodos de cálculo.

create new_address = keccak256(remetente, nonce);

create2 new_address = keccak256(0xFF, destinatário, salt, bytecode);

O atacante usou a função create2 para implantar o contrato no endereço 0x7dc8 e, desde que remetente, salt e bytecode sejam exatamente os mesmos, o mesmo endereço de contrato pode ser obtido, ou seja, 0x7dc86183274b28e9f1a100a0152dac975361353d. Em seguida, o contrato em 0x7dc8 foi usado para implantar a proposta do contrato usando create, que depende apenas do remetente e do nonce. Claramente, em ambas as etapas 1 e 3, o remetente é o contrato em 0x7dc8 e o nonce é 1, portanto, o endereço da proposta de contrato também será o mesmo, independentemente do bytecode da proposta do contrato. Por meio desse método, o atacante foi capaz de atualizar a proposta do contrato para uma versão maliciosa.

Passo 4: Obter recompensa

O atacante chamou a função create do contrato de governança para executar a proposta do contrato. Conforme visto na linha 8, a função executeProposal() da proposta do contrato é chamada usando delegatecall.

O id tx é:

0x3274b6090685b842aca80b304a4dcee0f61ef8b6afee10b7c7533c32fb75486d

function execute(uint256 proposalId) external payable virtual {
require(state(proposalId) == ProposalState.AwaitingExecution, "Governance::execute: invalid proposal state");
Proposal storage proposal = proposals[proposalId];
proposal.executed = true;
​
address target = proposal.target;
require(Address.isContract(target), "Governance::execute: not a contract");
(bool success, bytes memory data) = target.delegatecall(abi.encodeWithSignature("executeProposal()"));
if (!success) {
if (data.length > 0) {
revert(string(data));
} else {
revert("Proposal execution failed");
}
}
​
emit ProposalExecuted(proposalId);
}
Enter fullscreen mode Exit fullscreen mode

Então, como o atacante lucrou com esse ataque?

O uso da delegatecall é a chave, e o atacante também fez algum trabalho de base.

O atacante criou cerca de 100 contratos zumbis usando outra conta na transação seguinte.

0x1417e2408a890fd8bc41014d2448490abc8e9981c88cae3c20d455781ae9c0f6

Depois de criar cada conta, o atacante bloqueou 0 TORN no contrato de governança.

Ao executar a proposta, devido ao uso da instrução delegatecall, a proposta de contrato maliciosa precisou apenas modificar seu próprio espaço de armazenamento para modificar sincronicamente o espaço de armazenamento do contrato de Governança. Portanto, a proposta maliciosa alterou o saldo de cada contrato zumbi para 10.000 e o espaço de armazenamento do contrato de Governança também foi modificado. Dessa forma, o atacante garantiu a si mesmo 1.000.000 de votos falsos. Aqui compartilho uma visão diferente do número de votos falsos em relação à visão do samczsun, que pensa que são 1.200.000 votos falsos.

O id tx é:

0x3274b6090685b842aca80b304a4dcee0f61ef8b6afee10b7c7533c32fb75486d

As mudanças de estado causadas pela transação podem ser visualizadas da seguinte maneira:

O lucro real ainda retorna à parte do trabalho de base mencionado anteriormente. Como o saldo foi modificado, o atacante só precisa voltar para a outra conta e retirar o dinheiro.

O id da transação de retirada é:

0x13e2b7359dd1c13411342fd173750a19252f5b0d92af41be30f9f62167fc5b94

Nessa transação, o atacante retirou 473.000 TORN.

Retorno do dinheiro ou uma grande provocação?

Mais tarde, o atacante realizou outra proposta, que retornaria 483.000 TRON e apagaria o poder da votação falsa das 100 contas zumbis. Aqui estão os detalhes da proposta.

https://etherscan.io/address/0x1fad009ad35689b5a9b91486148f2f32afe31e23#code

A comunidade tem discutido essa proposta e muitos já votaram a favor, com um total atual de 517 mil votos a favor.

Será que esta proposta é realmente sobre a devolução dos fundos roubados? Provavelmente não é tão simples. O atacante já trocou alguns TRON por ETH e depositou 372 ETH como depósito no Tornado Cash. Então, de onde pode vir os 483 mil TRON para reembolsar os fundos?

IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C).transfer(
0x2F50508a8a3D323B91336FA3eA6ae50E55f32185,
483000 ether
);
Enter fullscreen mode Exit fullscreen mode

Como o 0xdeadf4ce suspeitava, tudo isso pode ser um “uma grande provocação (gigatroll)” para manipular o preço do token.

Lições aprendidas

Olhando para todo o processo, parece difícil evitar que tais ataques aconteçam. Embora o mecanismo de governança da DAO seja descentralizado, nem todos na comunidade são auditores profissionais e podem ser facilmente enganados e manipulados para votar de uma determinada maneira. Sempre há fatores incontroláveis ​​em qualquer processo dependente de humanos.

No entanto, ainda podemos aprender uma lição importante de que devemos estar vigilantes contra propostas que contenham ações autodestrutivas.

Esse artigo foi escrito por ZAN e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.

Top comments (0)