Skip to content
On this page

奪われた 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 ウォレットのデプロイメントメカニズムに隠されています。

  1. WalletDeployer.drop メソッドの分析: このメソッドは、momAuthorizerUpgradeable コントラクトのアドレス)が設定されておらず、かつ can(msg.sender, aim)true の場合にのみ、Safe ウォレットのデプロイを許可します。しかし、mom が設定されている場合でも、can メソッドはアセンブリで実装されており、巧妙に AuthorizerUpgradeablewards マッピングを参照しています。

  2. AuthorizerFactory.deployWithProxyAuthorizerUpgradeable.init:AuthorizerFactory は、TransparentProxy を介して AuthorizerUpgradeable をデプロイします。AuthorizerUpgradeableinit メソッドは、wardsaims の配列を受け取り、needsInit を 0 に設定して初期化を完了します。ここで重要なのは、AuthorizerUpgradeableconstructorneedsInit を 0 に設定するため、init メソッドは一度しか呼び出せないという点です。

  3. TransparentProxy.setUpgrader_fallback:TransparentProxy は、upgrader ロールを持つアカウントからの upgradeToAndCall メソッドの呼び出しを特別に処理します。これは、プロキシのロジックをアップグレードできることを示唆しています。

  4. Safe ウォレットのデプロイメント:WalletDeployer は、cook(Safe ファクトリー)と cpy(Safe シングルコピー)を使用して Safe ウォレットをデプロイします。drop メソッドの watnum パラメータは、Safe ウォレットの初期化データと Nonce に相当します。

攻撃シナリオと解決策:

失われた Nonce を見つけることは困難です。しかし、WalletDeployer.drop メソッドの can メソッドに注目すると、アセンブリコード内で sload(0) を使用している ことがわかります。これは、ストレージの 0番地の値をロードしています。このストレージには、AuthorizerFactory のデプロイ時に AuthorizerUpgradeable のアドレスが格納されていると推測できます。

以下が、この問題を解決するためのステップです:

  1. AuthorizerUpgradeable のアドレス特定: AuthorizerFactory のデプロイコードから AuthorizerUpgradeable のアドレスを推測し、WalletDeployer.can メソッドで使われるストレージの 0番地の値を特定します。

  2. AuthorizerUpgradeable の初期化: AuthorizerFactory.deployWithProxy を使用して、AuthorizerUpgradeable をデプロイします。この際、wards攻撃者自身のアドレス を、aimsユーザーがウォレットをデプロイするはずだったアドレス (USER_DEPOSIT_ADDRESS) を指定します。さらに、upgrader攻撃者自身のアドレス を指定します。これにより、攻撃者自身が USER_DEPOSIT_ADDRESS へのデプロイを「Ward」として認可されるようになります。

  3. WalletDeployer.drop の呼び出し: 攻撃者は WalletDeployer.drop メソッドを呼び出します。この際、aim には USER_DEPOSIT_ADDRESS を、wat には 1-of-1 Safe ウォレットをデプロイするための初期化コードを、num には 適切な Nonce を指定します。Nonce の特定は、SafeProxyFactory.createProxyWithNonce のデプロイメントパターンを分析することで可能になります。WalletDeployercook (SafeProxyFactory) と cpy (Safe singleton) を利用して、USER_DEPOSIT_ADDRESS が意図した Nonce でデプロイされるように計算します。

  4. Safe ウォレットからの資金回収: デプロイされた Safe ウォレットは、ユーザーの秘密鍵で署名することで操作可能になります。まず、Safe ウォレットから WalletDeployer に転送された報酬トークン(1 DVT)を攻撃者自身に転送します。

  5. ユーザー資金の回収: 次に、Safe ウォレットからユーザーの本来の資金(2,000 万 DVT)を攻撃者自身に転送します。

  6. Ward への報酬送金: 最後に、WalletDeployer が本来 Ward に支払うはずだった報酬トークンを、指定された Ward のアドレスに送金します。

攻撃コードの例 (MyContract):

上記のステップを Solidity コードで実装したのが、提供された MyContract です。このコントラクトは、WalletDeployerAuthorizerUpgradeable、ユーザー情報、そして Vm インスタンスを受け取り、一連の攻撃処理を実行します。特に、Nonce の特定と Safe ウォレットのトランザクション実行部分が重要となります。

まとめ:

この「Wallet Mining」チャレンジは、スマートコントラクトの内部構造、特にアップグレード機構、アセンブリコードの理解、そして Safe ウォレットのデプロイメントメカニズムを深く理解することを要求します。巧妙に隠された Nonce の問題と、認可メカニズムの穴を突くことで、失われた DVT トークンを全て回収し、ユーザーを救済することができるのです。この経験は、より堅牢なスマートコントラクト開発のための貴重な教訓となるでしょう。

Built with AiAda