Appearance
奪われた DVT トークンを取り戻せ! Wallet Mining 挑戦記
ある日、あなたは謎めいた CTF チャレンジ「Wallet Mining」に直面しました。そこには、ユーザーが Safe ウォレットをデプロイするたびに 1 DVT トークンが付与されるという、興味深い仕組みが存在していました。しかし、このシステムには深刻な問題が潜んでいました。
事態の概要:
- インセンティブ付きウォレットデプロイ: 特定の「Ward」と呼ばれるデプロイ担当者のみが、指定されたデプロイメントに対して報酬を受け取れるように設計された、アップグレード可能な認証メカニズムが組み込まれています。
- Safe ファクトリーの利用: ウォレットデプロイメントコントラクトは、Safe ファクトリーとコピーコントラクトを利用しており、すでに Safe のシングルファクトリーはデプロイ済みです。
- 消失した Nonce: 2,000 万 DVT トークンが、本来ユーザーの 1-of-1 Safe ウォレットがデプロイされるはずだったアドレス
0xCe07CF30B540Bb84ceC5dA5547e1cb4722F9E496に転送されました。しかし、デプロイに必要な Nonce が失われてしまったのです。 - 不穏な噂: システムの脆弱性に関する噂が広まり、関係者はパニックに陥っています。
- あなたの役割: あなたは、ユーザーから彼女の秘密鍵へのアクセス権限を託されました。限られた時間の中で、ウォレットデプロイメントコントラクトから全てのトークンを回収し、Ward へ送金する必要があります。さらに、ユーザーの資金も安全に回収しなければなりません。
- 制約: 全ての操作を 単一のトランザクション で実行する必要があります。
事態を紐解く鍵:
このチャレンジの肝は、WalletDeployer コントラクト、AuthorizerFactory、そして Safe ウォレットのデプロイメントメカニズムに隠されています。
WalletDeployer.dropメソッドの分析: このメソッドは、mom(AuthorizerUpgradeableコントラクトのアドレス)が設定されておらず、かつcan(msg.sender, aim)がtrueの場合にのみ、Safe ウォレットのデプロイを許可します。しかし、momが設定されている場合でも、canメソッドはアセンブリで実装されており、巧妙にAuthorizerUpgradeableのwardsマッピングを参照しています。AuthorizerFactory.deployWithProxyとAuthorizerUpgradeable.init:AuthorizerFactoryは、TransparentProxyを介してAuthorizerUpgradeableをデプロイします。AuthorizerUpgradeableのinitメソッドは、wardsとaimsの配列を受け取り、needsInitを 0 に設定して初期化を完了します。ここで重要なのは、AuthorizerUpgradeableのconstructorはneedsInitを 0 に設定するため、initメソッドは一度しか呼び出せないという点です。TransparentProxy.setUpgraderと_fallback:TransparentProxyは、upgraderロールを持つアカウントからのupgradeToAndCallメソッドの呼び出しを特別に処理します。これは、プロキシのロジックをアップグレードできることを示唆しています。Safe ウォレットのデプロイメント:
WalletDeployerは、cook(Safe ファクトリー)とcpy(Safe シングルコピー)を使用して Safe ウォレットをデプロイします。dropメソッドのwatとnumパラメータは、Safe ウォレットの初期化データと Nonce に相当します。
攻撃シナリオと解決策:
失われた Nonce を見つけることは困難です。しかし、WalletDeployer.drop メソッドの can メソッドに注目すると、アセンブリコード内で sload(0) を使用している ことがわかります。これは、ストレージの 0番地の値をロードしています。このストレージには、AuthorizerFactory のデプロイ時に AuthorizerUpgradeable のアドレスが格納されていると推測できます。
以下が、この問題を解決するためのステップです:
AuthorizerUpgradeableのアドレス特定:AuthorizerFactoryのデプロイコードからAuthorizerUpgradeableのアドレスを推測し、WalletDeployer.canメソッドで使われるストレージの 0番地の値を特定します。AuthorizerUpgradeableの初期化:AuthorizerFactory.deployWithProxyを使用して、AuthorizerUpgradeableをデプロイします。この際、wardsに 攻撃者自身のアドレス を、aimsに ユーザーがウォレットをデプロイするはずだったアドレス (USER_DEPOSIT_ADDRESS) を指定します。さらに、upgraderに 攻撃者自身のアドレス を指定します。これにより、攻撃者自身がUSER_DEPOSIT_ADDRESSへのデプロイを「Ward」として認可されるようになります。WalletDeployer.dropの呼び出し: 攻撃者はWalletDeployer.dropメソッドを呼び出します。この際、aimにはUSER_DEPOSIT_ADDRESSを、watには 1-of-1 Safe ウォレットをデプロイするための初期化コードを、numには 適切な Nonce を指定します。Nonce の特定は、SafeProxyFactory.createProxyWithNonceのデプロイメントパターンを分析することで可能になります。WalletDeployerのcook(SafeProxyFactory) とcpy(Safe singleton) を利用して、USER_DEPOSIT_ADDRESSが意図した Nonce でデプロイされるように計算します。Safe ウォレットからの資金回収: デプロイされた Safe ウォレットは、ユーザーの秘密鍵で署名することで操作可能になります。まず、Safe ウォレットから
WalletDeployerに転送された報酬トークン(1 DVT)を攻撃者自身に転送します。ユーザー資金の回収: 次に、Safe ウォレットからユーザーの本来の資金(2,000 万 DVT)を攻撃者自身に転送します。
Ward への報酬送金: 最後に、
WalletDeployerが本来 Ward に支払うはずだった報酬トークンを、指定された Ward のアドレスに送金します。
攻撃コードの例 (MyContract):
上記のステップを Solidity コードで実装したのが、提供された MyContract です。このコントラクトは、WalletDeployer、AuthorizerUpgradeable、ユーザー情報、そして Vm インスタンスを受け取り、一連の攻撃処理を実行します。特に、Nonce の特定と Safe ウォレットのトランザクション実行部分が重要となります。
まとめ:
この「Wallet Mining」チャレンジは、スマートコントラクトの内部構造、特にアップグレード機構、アセンブリコードの理解、そして Safe ウォレットのデプロイメントメカニズムを深く理解することを要求します。巧妙に隠された Nonce の問題と、認可メカニズムの穴を突くことで、失われた DVT トークンを全て回収し、ユーザーを救済することができるのです。この経験は、より堅牢なスマートコントラクト開発のための貴重な教訓となるでしょう。