Flujos de tokens, NFT y contratos inteligentes · Lección 6 de 6
Análisis de exploits y hackeos
11 min de lectura
Cuando hackean un protocolo o un exchange, en cuestión de horas se hacen las mismas preguntas: qué ocurrió, cuánto se sustrajo, dónde está ahora y quién lo hizo. Las respuestas se obtienen leyendo con gran detalle un pequeño número de transacciones y, después, siguiendo el rastro durante mucho tiempo. Esta lección reúne las habilidades de decodificación del módulo (tokens, proxies, swaps, préstamos flash y puentes) y las aplica a casos públicos.
El ciclo de vida de un exploit en los datos
La mayoría de los incidentes, sea cual sea su causa raíz, atraviesan las mismas fases, y cada fase deja distintas evidencias:
| Fase | Qué hace el atacante | Evidencia que se debe recopilar |
|---|---|---|
| Financiamiento | Obtiene fondos para pagar el gas de direcciones nuevas | Primera transferencia entrante a cada dirección del atacante y su origen |
| Preparación | Despliega contratos de ataque, hace pruebas y, a veces, primero en otras cadenas | Creaciones de contratos, intentos fallidos y sus motivos de reversión, bytecode |
| Ejecución | Ejecuta una o más transacciones de ataque | Trazas, registros, cambios de saldo por dirección y token |
| Consolidación | Intercambia tokens congelables o ilíquidos por ETH u otros activos neutrales y reúne los fondos | Swaps de DEX con gran impacto en el precio, transferencias entre direcciones del atacante |
| Dispersión | Divide los fondos, los pasa por puentes, usa mezcladores, intercambia por Bitcoin y los convierte en efectivo | Patrones de distribución, depósitos en puentes, depósitos en mezcladores, direcciones de depósito de exchanges |
Las fases se superponen y, en los casos de compromiso de claves, la preparación ocurre fuera de la cadena. Aun así, organizar las evidencias de esta manera evita que la investigación se centre de inmediato en la transacción más visible y pase por alto el financiamiento que podría identificar al atacante.
Lectura de la transacción de ataque
Empieza por la traza, no por los registros. Los registros documentan lo que los contratos decidieron anunciar; la traza registra cada llamada, su valor y si tuvo éxito. Un orden de trabajo práctico:
- Identifica a los actores: la cuenta de propiedad externa que envió la transacción, el contrato de ataque al que llamó, los contratos de la víctima y cualquier dirección que termine obteniendo ganancias.
- Calcula los cambios de saldo por dirección y por token para toda la transacción (la consulta de neteo de la lección Swaps de DEX y flujos de liquidez sirve en este caso). La ganancia neta del atacante y la pérdida neta de la víctima dan el contexto para todo lo demás.
- Recorre el árbol de llamadas desde el inicio y marca los préstamos flash (consulta la lección Préstamos, puentes y contabilidad de protocolos) para no contar el principal prestado como una pérdida.
- Encuentra el paso en el que el estado del protocolo dejó de ser coherente. Esa es la causa raíz y, por lo general, corresponde a una sola llamada a una función.
Un resumen de cambios de saldo para una transacción de ataque ficticia muestra el resultado del paso 2:
{
"tx_hash": "0x7c0d…e91a",
"block_number": 21000417,
"balance_changes": [
{"address": "0xa77a…c0de", "role": "attack contract", "token": "DAI", "delta": "+2400000.00"},
{"address": "0x5af3…71b2", "role": "victim pool", "token": "DAI", "delta": "-2401000.00"},
{"address": "0x87f1…4e2a", "role": "flash-loan lender", "token": "DAI", "delta": "+1000.00"},
{"address": "0x3e9d…02f7", "role": "sender EOA", "token": "ETH", "delta": "-0.041"}
]
}
El préstamo flash de 2.000.000 DAI del prestamista no aparece, porque salió y volvió en la misma transacción; solo queda su prima de 1.000 DAI (0,05 %). La víctima perdió 2.401.000 DAI, el contrato de ataque retuvo 2.400.000 y el remitente pagó 0,041 ETH en gas.
El exploit de Euler Finance del 13 de marzo de 2023 muestra el método. Los análisis publicados por investigadores de seguridad sobre una de las transacciones de ataque la reducen a estos pasos:
Contrato de ataque, una de varias transacciones de ataque (pool de DAI)
1. Préstamo flash de 30.000.000 DAI de Aave
2. Depositar 20.000.000 DAI en Euler -> tokens de colateral (eDAI)
3. mint(): apalancamiento propio -> deuda de 200.000.000 dDAI, 195.600.000 eDAI
4. Reembolsar 10.000.000 DAI -> se queman 10.000.000 dDAI
5. Volver a ejecutar mint() -> otros 200.000.000 dDAI y 195.600.000 eDAI
6. donateToReserves(100.000.000 eDAI) -> sin comprobación de solvencia: la cuenta queda insolvente
7. Un segundo contrato de ataque liquida el primero con un descuento del 20 %
8. Retirar 38.900.000 DAI, reembolsar el préstamo flash más la prima y quedarse con el resto
El paso 6 es la causa raíz: una función que reducía el colateral sin comprobar la salud de la cuenta. Los pasos 1 y 8 anulan el capital prestado. Al repetirse en varios pools, el ataque sustrajo alrededor de 197 millones de dólares en DAI, WETH, WBTC, stETH y USDC. El atacante intercambió la mayor parte por ETH y DAI, envió 100 ETH a un mezclador y luego negoció con el protocolo mediante mensajes en cadena; para el 3 de abril de 2023 había devuelto todos los fondos recuperables. Vale la pena decodificar los mensajes en cadena: son texto en los datos de entrada de transferencias simples y forman parte del registro.
Financiamiento, preparación e imitadores
El financiamiento de una dirección del atacante suele ser la mejor pista para atribuir el ataque. Entre las fuentes habituales están los retiros de mezcladores (los pools de Tornado Cash usaban denominaciones fijas como 0,1, 1, 10 y 100 ETH), los puentes desde otra cadena, los servicios de exchange instantáneo y, en ocasiones, un retiro de un exchange, que puede investigarse mediante un proceso legal (consulta la lección Seguimiento del valor a través de exchanges y servicios). Comprueba también lo siguiente:
- El desplegador de cada contrato de ataque y otros contratos que haya desplegado, incluidas versiones de prueba en otras cadenas.
- La similitud del bytecode con contratos de ataque anteriores, que puede vincular incidentes.
- Los intentos fallidos anteriores al exitoso: sus motivos de reversión muestran qué estaba probando el atacante y cuándo empezó.
No todas las direcciones que obtienen ganancias pertenecen al atacante original. Los bots de MEV copian transacciones pendientes rentables, y algunos grupos de hackers de sombrero blanco se adelantan a los ataques para rescatar fondos. En el caso de Euler, una dirección que afirmó que su bot había copiado accidentalmente el primer ataque terminó transfiriendo los DAI al atacante de todos modos, porque el contrato copiado tenía codificada de forma fija la dirección del atacante. En el incidente del puente Nomad de agosto de 2022, cientos de direcciones simplemente volvieron a ejecutar la transacción del primer atacante sustituyendo la dirección por la suya. Clasifica a estos actores por separado, indica el nivel de confianza y nunca publiques la identidad de una persona privada basándote únicamente en esos vínculos (consulta la lección Atribución y etiquetado).
Cuando el error no está en el código
Algunos de los robos más grandes no implicaron ningún fallo en los contratos inteligentes. En el robo del puente Ronin de marzo de 2022, los atacantes controlaban cinco de las nueve claves de validadores que exigía el puente, suficientes para firmar retiros de alrededor de 173.600 ETH y 25,5 millones de USDC; el robo pasó inadvertido durante seis días. Las autoridades estadounidenses atribuyeron el robo al grupo Lazarus de Corea del Norte en abril de 2022. En la cadena, los retiros tenían firmas válidas; solo el contexto (el tamaño, el destinatario y el momento inusuales) los delató.
El robo de Bybit del 21 de febrero de 2025, de alrededor de 1.500 millones de dólares, fue el mayor robo de criptoactivos registrado hasta ese momento y constituye una lección sobre cómo leer lo que los firmantes aprobaron realmente. La billetera fría del exchange era un proxy multisig Safe. Los investigadores descubrieron que la interfaz de la billetera había sido comprometida a través de una máquina de desarrollador del proveedor de la billetera, de modo que los firmantes vieron una transferencia rutinaria mientras sus billeteras de hardware firmaban otra cosa. Una vez decodificada, la transacción firmada se ve así:
SafeProxy.execTransaction(...) -- selector 0x6a761202, tres firmas válidas
to = contrato del atacante
data = transfer(...) -- nombre de función que parece inofensivo
operation = 1 -- DELEGATECALL, no CALL
DELEGATECALL attacker contract.transfer(...) -- se ejecuta con el almacenamiento del proxy
SSTORE slot 0 := implementación maliciosa -- se reemplaza el puntero al singleton de Safe
Luego: se llaman sweepETH(...) y sweepERC20(...) en el proxy, sin necesidad de firmas
Como un proxy Safe conserva su puntero de implementación en la ranura de almacenamiento 0 (consulta la lección Decodificación de interacciones con contratos inteligentes), un delegatecall que escribe en la ranura 0 entrega el control de toda la billetera. Las transacciones de vaciado movieron entonces alrededor de 401.346 ETH, 90.375 stETH, 15.000 cmETH y 8.000 mETH. El 26 de febrero de 2025, el FBI atribuyó el robo a actores norcoreanos a los que denomina TraderTraitor, publicó una lista de direcciones e informó que los fondos se estaban convirtiendo a Bitcoin y distribuyendo entre miles de direcciones en varias cadenas. Conclusiones para los analistas: decodificar cada campo operation en las transacciones multisig, activar alertas ante cualquier delegatecall a un contrato desconocido y supervisar las ranuras de implementación de los proxies de alto valor.
Seguimiento después del incidente
Las primeras horas determinan cuánto se puede congelar o marcar. Una secuencia típica:
- Crea el conjunto semilla: todas las direcciones controladas por el atacante que se encuentren en las transacciones de ataque, el financiamiento y los vínculos con el desplegador.
- Vigila los activos congelables. Algunos emisores de monedas estables pueden congelar saldos, razón por la cual los atacantes los intercambian primero; informa cuanto antes las direcciones.
- Comparte direcciones y hashes de transacciones con exchanges, puentes y otros servicios que puedan recibir los fondos, mediante sus canales de reporte establecidos.
- Sigue la consolidación y dispersión hacia adelante, salto por salto y a través de las cadenas (consulta las lecciones Seguimiento de fondos hacia delante y hacia atrás, Seguimiento entre cadenas y Mezcladores, CoinJoin y ofuscación en grafos).
-- Dos primeros saltos desde las direcciones semilla de un incidente, ordenados primero por los flujos más grandes
WITH seeds AS (
SELECT address FROM incident_addresses WHERE incident_id = 'INC-0142'
), hop1 AS (
SELECT t.to_address, SUM(t.amount_usd) AS usd, MIN(t.block_time) AS first_seen
FROM value_transfers t JOIN seeds s ON t.from_address = s.address
WHERE t.to_address NOT IN (SELECT address FROM seeds)
GROUP BY t.to_address
)
SELECT h.to_address AS hop1, t.to_address AS hop2, SUM(t.amount_usd) AS usd
FROM hop1 h
JOIN value_transfers t
ON t.from_address = h.to_address AND t.block_time >= h.first_seen
GROUP BY h.to_address, t.to_address
ORDER BY usd DESC;
La consulta supone que existe una tabla value_transfers que ya combina transferencias nativas de las trazas con transferencias de tokens, cada una valorada en el momento de la transferencia. La primera CTE encuentra las direcciones que recibieron fondos de las semillas (excluye los movimientos entre semillas); el paso final sigue a cada una un salto más, contando solo las transferencias posteriores a la primera recepción. Los grandes flujos del segundo salto hacia servicios conocidos son los que podrían tener registros fuera de la cadena.
Redacción del informe de un incidente
Clasifica la causa raíz, porque determina qué evidencias son importantes:
| Clase de causa raíz | Ejemplo público | Firma típica en los datos |
|---|---|---|
| Fallo lógico | Euler, 2023 | Préstamo flash más una secuencia de llamadas inusual que deja un estado imposible |
| Inicialización o control de acceso | Nomad, 2022 | Muchas personas que llaman tienen éxito con los mismos datos de llamada |
| Verificación de firmas | Wormhole, 2022 | Tokens emitidos sin un depósito correspondiente |
| Compromiso de claves | Ronin, 2022 | Retiros con firmas válidas, sin error en el código |
| Interfaz o cadena de suministro | Bybit, 2025 | Transacción con firma válida que contiene un delegatecall inesperado |
| Gobernanza | Beanstalk, 2022 | Poder de voto prestado, propuesta ejecutada en la misma transacción |
Calcula las pérdidas según los precios del bloque de cada transacción de ataque, enumera todas las transacciones de ataque con la altura del bloque y mantén separados los importes recuperados y las pérdidas. Las lecciones Redacción del informe analítico y Protección de datos, ética y divulgación responsable explican cómo presentar y compartir estos hallazgos.
Enfoque del examen: las cinco fases de un exploit y las evidencias que deja cada una; lectura de la transacción de ataque a partir de la traza, cálculo de cambios de saldo y exclusión del principal del préstamo flash mediante el neteo; pasos del ataque a Euler y su causa raíz (donateToReserves sin una comprobación de salud); fuentes de financiamiento, vínculos con el desplegador y el bytecode, intentos fallidos e imitadores como pistas para atribuir el ataque; compromisos de claves e interfaces (Ronin, Bybit) y el delegatecall de Safe que reescribió la ranura 0; prioridades de las primeras horas (conjunto semilla, activos congelables, notificación a servicios); consultas de saltos sobre transferencias nativas y de tokens combinadas; clases de causas raíz y valoración de pérdidas según los precios del bloque.