WEB3DEV Español

Gabriella Alexandra Martinez Viloria
Gabriella Alexandra Martinez Viloria

Posted on

Desempaquetando el ataque de Gobernanza de Tornado Cash

Desempaquetando el ataque de Gobernanza de Tornado Cash

Este artículo es una traducción de ZAN, hecha por Gabriella Martínez. Puedes encontrar el artículo original aquí
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_es en Twitter.

ZAN
Twitter
Github

Image description

Vistazo del Evento

El 20 de mayo de 2023, a las 07:25>11 (UTC), Tornado.Cash fue atacado por una propuesta maliciosa. El atacante drenó exitosamente 473.000 tokens TORN. En un período de tiempo corto, el atacante intercambió 100.000 TRON por 54 ETH. Luego, el atacante depositó 372 ETH a Tornado Cash para blanquear sus ganancias.

Línea de Ataque

Paso 1: Proponer una Propuesta

El atacante, en primer lugar, desplegó una propuesta de contrato normal para engañar a la comunidad y alentarlos a que la voten.

El tx id es:

0x34605f1d6463a48b818157f7b26d040f8dd329273702a0618e9e74fe350e6e0d.

El contenido de la propuesta es:

Image description

Esta propuesta apunta a castigar ciertas direcciones y usa la lógica de la Propuesta 16. Aún así, a diferencia de la Propuesta 16, esta propuesta de contrato contiene una trampa oculta, la cual se auto destruye con una función llamada emergencyStop().

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

La comunidad no reconoció la amenaza potencial de la función que se auto destruye y votó por apoyar la Propuesta 20.

Image description

Paso 2: Destruye la propuesta

Luego que la propuesta del atacante haya recogido suficiente votos, el atacante llama la función emergencyStop() para que se auto destruya.

El tx id es:

0xd3a570af795405e141988c48527a595434665089117473bc0389e83091391adb

Image description

Image description

De esta forma, ambas la propuesta del contrato y el contrato que crearon, fueron destruidos.

Paso 3: Redespliega el contrato

Hemos llegado al momento crucial del ataque. El atacante desplegó una nueva propuesta de contrato en la misma dirección como en el paso 1, pero con contenidos totalmente distintos. ¿Cómo lo lograron?

Veamos las instrucciones usadas por el atacante en la transacción del despliegue.

Primero, el atacante usó create2 para desplegar el contrato en la dirección 0x7dc86183274b28e9f1a100a0152dac975361353d. Luego, el contrato 0x7dc8 usó create para desplegar la propuesta del contrato en la dirección 0xc503893b3e3c0c6b909222b45f2a3a259a52752d.
Image description

¿Por qué la dirección del contrato recién desplegado es la misma que la del paso 1, pero el contenido de la propuesta del contrato es totalmente distinta?

Es porque create y create2 tienen diferentes métodos de cálculos.

create new_address = keccak256(sender, nonce);
create2 new_address = keccak256(0xFF, sender, salt, bytecode);
Enter fullscreen mode Exit fullscreen mode

El atacante usó create2 para desplegar el contrato en la dirección 0x7dc8 y, siempre y cuando el remitente, sal y bytecode son exactamente los mismos, la misma dirección del contrato puede ser obtenida, es decir, 0x7dc86183274b28e9f1a100a0152dac975361353d. Luego, el contrato en 0x7dc8 fue usado para desplegar la propuesta del contrato usando create, el cual solo depende del remitente y del nonce. Claramente, en ambos pasos 1 y 3, el remitente es el contrato en el que 0x7dc8 y el nonce es 1, así que la dirección de la propuesta del contrato también serán la misma, sin importar el bytecode de la propuesta del contrato. A través de este método, el atacante pudo actualizar la propuesta del contrato a una versión maligna.

Paso 4: Obtén la recompensa

El atacante llamó la función execute() al contrato de la gobernanza para ejecutar la propuesta del contrato. Como se ve en la línea 8, la función executeProposal() de la propuesta del contrato, se llama usando delegatecall;

El tx id es:

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

Así que, ¿cómo el atacante lucró desde este ataque?

El uso de delegatecall es la llave, y el atacante también hizo algunos trabajos preliminares.

El atacante creó alrededor de 100 contratos zombies usando otra cuenta en la siguiente transacción.

0x1417e2408a890fd8bc41014d2448490abc8e9981c88cae3c20d455781ae9c0f6

Luego de crear cada cuenta, el atacante bloqueó 0 TORN al contrato de la Gobernanza.

Image description

Cuando se ejecuta la propuesta, por el uso de la instrucción de delegatecall, la propuesta del contrato malicioso solo necesita modificar su propio espacio para, síncrónicamente, modificar el almacenamiento del espacio del contrato de la Gobernanza. Por lo tanto, la propuesta maliciosa cambió el balance de cada contrato zombie a 10.000 y el almacenamiento del espacio del contrato de la Gobernanza también fue modificado. De esta forma, el atacante se garantizó a sí mismo 1.000.000 de votos falsos. Aquí, comparto una visión diferente del número de votos falsos con samczsun, el cual cree que hay 1.200.000 votos falsos.
El tx id es:

0x3274b6090685b842aca80b304a4dcee0f61ef8b6afee10b7c7533c32fb75486d

Los cambios de estados causados por la transacción, pueden ser vistos a continuación:

Image description

La ganancia real aún va a los trabajos preliminares, mencionados anteriormente. Ya que el balance ha sido modificado, el atacante solo necesitó volver a la otra cuenta y retirar el dinero.

El id de la transacción de la retirada es:

0x13e2b7359dd1c13411342fd173750a19252f5b0d92af41be30f9f62167fc5b94

En esta transacción, el atacante retiró 473.000 TORN.

¿Regresar el dinero o un gigatroll?

El atacante luego propuso otra propuesta, el cual regresaría 483.000 TRON y borraría el poder de voto falso de las 100 cuentas zombies. Aquí están los detalles de la propuesta.

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

Esta comunidad ha discutido esta propuesta y muchos ya han votado a favor, con un conteo total de 517 mil votos a favor.

Image description

¿Es esta propuesta realmente sobre regresar los fondos robados? Probablemente, no es tan simple. El atacante ya ha cambiado algunos TRON por ETH y depositado 372 ETH como depósito en Tornado Cash. Así que, ¿de dónde vienen estos 483 mil TRON para pagar los fondos?

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

Como 0xdeadf4ce sospecha, es posible que todo esto sea un “gigatroll” para manipular el precio del token.

Lecciones aprendidas

Mirando atrás a todo el proceso, parece ser difícil prevenir estos ataques. Aunque el mecanismo de gobernanza de una DAO es descentralizada, no todos en la comunidad son auditores profesionales y pueden ser fácilmente engañados y manipulados para que voten de cierta manera. Siempre hay factores incontrolables en cualquier proceso que dependa de humanos.

Sin embargo, aún podemos aprender una lección importante que deberíamos ser vigilantes contra las propuestas que contengan acciones auto destructivas.

Discussion (0)