こんにちは、CryptoGamesエンジニアのナツキです。
先日Tornado Cashに対して行われたハッキングが話題だったので解説します。ブロックチェーン周りの基礎知識がある人を対象にした記事です。
※既に英語で丁寧に解説されたツイートや動画があるので、この記事では日本語での説明やその補足を主に行っていきます。
この記事でカバーする内容
・今回行われたTornado Cashハッキングの再現手順
・同じアドレスへ別のスマートコントラクトをデプロイする方法
・CREATE2とselfdestruct
この記事でカバーしない内容
・Tornado Cashのサービスやガバナンスの仕組み
・今回のハッキングで起きた被害やその対策
Tornade Cash Hacking事件
Tornado Cashとは?
Tornado Cashは、分散型プライバシープロトコルです。
Tornado Cashを使うことほぼ匿名な送金を行うことができます。
マネロン目的で使われることもあるので、日本の取引所では厳しくチェックされている可能性があります。利用する際は自己責任でお願いします。
ハッキングについて
2023/05/20 にTornado Cashでハッキングが起きました。
予めガバナンスを通して承認されていたコントラクトが悪意のあるコントラクトに差し替えられるという内容のものです。
スマートコントラクトのアドレスは一意であり、そのアドレスが参照するスマートコントラクトの内容は不変である(upgradableなコントラクトを除く)というのが一般的な認識なので、驚いた人も多いと思います。
ハッキング手法について
このツイートで詳しくわかりやすく説明されています。
簡易的な手順
※上記の参考ツイートより画像を引用。
①攻撃者は、コントラクト(=Proposal)をデプロイして、Tornade Cashに承認してもらいます。この時点でContractは、悪意のない正しいコントラクトです。
③不正なコントラクトを同じアドレスに向けてデプロイして、ハッキング完了です。
Tornade Cashは、アドレスを参照してコントラクトを実行するので、そのまま不正なコントラクトと疎通しています。
一度承認済みのコントラクトアドレスに向けて、不正なコントラクトを再デプロイするというハッキングでした。では今度は、もう少し詳細の手順を説明します。
詳細の手順
※CREATE、CREATE2、selfdestructについては後述
・Deployer DeployerがCREATE2を利用して, DeployerAをデプロイ。
・DeployerAがコントラクト(Proposal, 以降ContractA)をデプロイ。
・ContractAがTornade Cashに承認された後、DeployerAとContractAをselfdestructで削除。
・Deployer Deployerは、CREATE2でDeployerBをデプロイ。CREATE2を利用しているので、DeployerAとDeployerBのアドレスは同じ。そしてDeployerBのnonceは0にリセットされる。
・DeployerBが不正なコントラクト、ContractBをデプロイ。
CREATE2ではなく、CREATEによるデプロイですが、DeployerBのnonceはリセットされているので、ContractAとContractBは同じアドレスになります。
この手順で承認後のスマートコントラクトを、悪意のあるコードが含まれたスマートコントラクトに差し替えることができます。
解説動画(英語):
重要な点
・通常の方法でスマートコントラクトをデプロイすると、nonceをもとにコントラクトのアドレスが決定される。
・CREATE2というデプロイ方法を採用すると、特定のアドレスに向けてスマートコントラクトをデプロイできる。
・selfdestructを実行するとスマートコントラクトを削除することができる。
CREATE2
Solidity の CREATE2 は、Ethereum Virtual Machine (EVM) の opcode の一つで、新しいスマートコントラクトをデプロイするための方法です。
CREATE と CREATE2 の違い=新しいスマートコントラクトのアドレスの決定方法
CREATE の場合:
新しいコントラクトのアドレスはデプロイするアカウントと、そのアカウントの nonce(アカウントが行ったトランザクションの数)から計算されます。したがって、新しいコントラクトのアドレスは決定的ですが、一度しか生成できません。
keccak256(senderAddress, nonce)
CREATE2 の場合:
コントラクトのアドレスを計算するために異なる方法を使用します。
具体的には、アドレスはデプロイするアカウント、任意の「salt」(ランダムなデータ)、そしてコントラクトのバイトコード自体から計算されます。
これにより、同じアカウントが同じコントラクトを同じアドレスに向けて何度もデプロイできるようになりました。さらに、CREATE2 を使用すると、コントラクトがデプロイされる前にそのアドレスを予測することが可能です。
keccak256(0xFF, senderAddress, salt, bytecode)
selfdestruct関数
selfdestruct関数を利用すると、一度デプロイしたコントラクトを削除することができます。
脆弱性が発見されたり、不正なトランザクションがあった場合にオーナー権限を持つアドレスがコントラクトを削除して被害を最小限に抑える手段として導入されることもあります。
ただし、CREATE2 を併用すると、潜在的に「self-destructした後でもコントラクトを復活できる」可能性があるため、セキュリティ上の懸念があります。今回は、このCREAT2とself-destrctの組み合わせを使ったハッキングでした。
参考記事:
https://www.alchemy.com/overviews/selfdestruct-solidity
気になった事
CREATE2を使えば、同じアドレスに対して別のコントラクトをデプロイできることがわかりました。
なのでDeployerを挟まずにDeployer Deployerから直接コントラクトをデプロイすることも可能だと思います。Deployerを挟んだ理由が気になりました。
↑こちら解決しました!
CREATE2では、bytecodeを使ってアドレスを決定するのでスマートコントラクトの中身を変えたい場合、攻撃者にとって不都合だったようです。中身の違うスマートコントラクトに差し替えたい場合は、CREATE、CREATE2、selfdestruct全てを活用することが必要です。