【翻訳記事】時のオカリナにおける任意コード実行について
はじめに
本記事は、先日実現された「ゼルダの伝説時のオカリナ」における任意コード実行(Arbitrary Code Execution, 通称ACE)についての解説記事です。 本記事の内容はFig氏による解説動画をもとにしています。元動画と異なる点についてはご指摘いただけると幸いです。
任意コード実行という性質上ゲーム内部のシステムに関わる内容が含まれ、専門的な知識が必要となる場面もありますが、できるだけわかりやすく解説できればと思います。
「メモリ」と「アドレス」について
前提知識として、任意コード実行において頻出となる単語「メモリ」と「アドレス」について説明します。
メモリ
広義にはコンピュータにおいてデータを格納するためのものですが、ここではニンテンドー64内の、ゲームデータを格納する入れ物を指します。
ゲームが動作するには、ゲームソフト(ROM)の中のデータを読み込み、ゲーム本体で使えるように保持しておく必要があります。 ここでいう「データ」は、リンクや敵キャラのステータスはもちろん、画面に表示されているグラフィックス自体、ゲームが動作するための処理プログラムやそのための数値など様々です。
メモリにはデータを格納できる量が決まっており、必要に応じてデータをメモリに格納したり、メモリからデータを解放したりしてうまくやり繰りしています。 想定されていないゲームの動作によってメモリの限界量を超えるデータを読み込もうとすると、フリーズしてしまいます。
ニンテンドー64の中にゲームのデータを入れるための大きな箱があり、必要なものを都度出し入れしているのをイメージしていただいてよいかと思います。
アドレス
メモリはデータを詰めるための大きな箱ですが、ゲーム自体はメモリの中のどこに欲しいデータがあるかを理解する必要があります。
そこでメモリには細かく区間分けがされており、区間ごとに住所のような一意の数値が割り振られています。この住所が「アドレス」と呼ばれるものです。
ゲームはメモリの中のデータを読み込む際に、このアドレスを使ってデータの位置を特定します。かなりシンプルな例を挙げると、リンクの残りハート数をアドレス12
に入れておくと決めておけば、ダメージを受けたり回復するたびにアドレス12
の値を変更するような処理が動き、リンクの残りハートを管理することができます。
今回の任意コード実行においては、このアドレスを利用する場面が多く出てきます。メモリ内のデータの場所を指す番号・住所とご理解ください。
また、あるアドレスのデータをゲーム側の処理で使っていることを、「アドレスを参照する」といった言い方で表現します。
静的なメモリと動的なメモリ
本ゲームで利用しているメモリ空間として、静的なメモリと動的なメモリというものがあります。
静的なメモリというのは、格納するデータが予め定められているメモリ空間です。イメージとしては、例えばリンクのハート数等のいかなる状況でも利用するものは静的なメモリに保持されるでしょう。
対して動的なメモリというのは、ゲームの状況に応じて必要になったデータを随時保持していくメモリ空間です。例えばダンジョン内のギミックやオブジェクトはリンクがその部屋に入ったときにのみ必要になるため、動的なメモリに必要になったときにだけ保持されます。
ファイルネーム設定
動画冒頭ではまず、ファイルネームの設定を行っています。設定したファイルネームが今後の任意コード実行に重要な役割を持つのですが、ここではファイルネームを設定する先のアドレスとその値だけ理解していただければOKです。
動画下に表示されている0x8011A5F4 08 01 28 50 - A3 B6 B6 3D
は64に保存されているメモリを可視化したもので、アドレスが8011A5F4
(0x
は16進数であることを表しており、数値としての意味はありません)で、値が08 01 28 50 - A3 B6 B6 3D
です。ゲーム上、どのようなデータもメモリには16進数で格納されます。重要なのは、この数値をプレイヤーが自由に操作できるということです。
SRM(Stale Reference Manipulation)について概要
続いて、Stale Reference Manipulation(以下SRM)というバグ技について触れています。SRMも最近見つかったバグ技で、任意コード実行を実現する大きな要素となりました。
SRMについては以下のドキュメントで解説していますので、ご参照いただければと思いますが、動画と同様に概要を解説いたします。
https://gist.github.com/cma2819/34c9b00f12573316f9b6becc2c0ae948
アクター
時のオカリナを構成する様々なオブジェクトは、「アクター」と呼ばれています。それぞれのアクターの情報はゲームにロードされたときに動的メモリに保持され、位置情報やそのアクターの状態がデータとして格納されています。動画中でリンクがツボを持ち上げるとメモリ内のデータが書き換わりますが、リンクがツボを動かすことでツボのアクター情報が書き換わっているためです。
リンクがアクターを持ち上げる際、ゲーム内ではどのアクターを持ち上げているかをアドレスで管理しています。リンクが動くのに合わせて参照先のアドレスの位置情報のデータを変更することで、リンクに合わせてアクターも動く、というわけです。
参照の保持
ダンジョンは複数の部屋で構成されており、その部屋はロードゾーンで仕切られています。リンクが何かを持っている状態でロードゾーンを跨いでも、アクターごと次の部屋に保持されます。
しかし、バグ技を利用したり少し工夫をすることで、アクターは次の部屋に保持することなく、リンクがアクターを持っているという状態だけをキープすることができます。
その状態では、リンクはまだアクターを持っていることになっているので、元々持っていたアクターのアドレスのデータを常に更新しつづけます。そのアドレスに入っていた、元々持っていたアクターは動的メモリから解放され、次の部屋のアクターが新たにロードされます。すると、新たにロードされた別のアクターの情報を書き換えることができます。
このようにして、動的メモリを操作して、参照の保持を利用して関係ないアクターの情報を書き換えてしまうのがSRMというバグ技です。
実践デモ
動画内では6分ごろになりますが、ここから任意コード実行の実践になります。記事投稿時点では、任意コード実行はゴロンシティでのみ実現しています。
SRMを発動させ、ツボのアクターを配置する
任意コード実行の準備として、さっそくSRMを発動させます。ネールの愛やバクダン、ボムチュウを利用して異空間を動き回っていますが、これはSRMのためのセットアップです。記事投稿時点ではこの方法も改善されているため詳細なやり方は割愛します。
まず異空間から再度中央の部屋に戻ってくることで中央の部屋のアクターをロードしなおすことで、持ち運び可能なアクターであるツボが読み込まれるアドレスを操作しています。ツボを利用してダルニアの部屋にスーパースライドし、ダルニアの部屋でスライドを解除するとツボの参照を保持したままツボのアクターを解放します。この状態では、リンクが持ち上げているツボのグラフィックが表示されなくなります。
さらに持ち上げたツボの参照を保持した後にも、岩迷路の部屋に入ってから中央の部屋に戻ることで再度部屋の情報をロードしなおし、動的メモリの内容を操作します。最終的にツボの角度情報のアドレスが、SRM後に任意コード実行に利用可能なアドレスになるように操作することがセットアップの目的です。
結果的にはアドレス801FA0B0
の中にツボの角度情報が保持されるように調整します。アドレス801FA0B0
の値は80 1F 51 50 - 80 1F 8A 08
となり、そのうち51 50
の部分はツボの角度情報です。
パチンコアクターの調整(パチンコ玉の発射角度)
続いて、パチンコのアクターを利用した調整を行います。パチンコのアクターも動的なメモリ空間を使うものですが、どのアドレスにどのアクターが展開されるかはここまでのセットアップで固定されています。
そのため、パチンコのアクターに含まれる様々なデータもどのアドレスに展開されるかが確定します。今回使うデータはアドレス801F5150
に格納されるパチンコ玉の発射角度です。
パチンコ玉を発射するX角度とY角度を調整して、アドレス801F5150
の1~8桁目を08 07 21 2D
に調整します。
動画ではメモリの実値をみながらなのでちょうどよい角度に調整できていますが、非常に細かい単位での調整です。このパチンコアクターの情報は、リンクがパチンコを再度使うまで維持されるので、次にパチンコを使うまでこのアドレスの値は変わりません。
3Pコントローラの入力
ここまではアクター情報を操作していましたが、次に調整する値は特殊です。
時のオカリナは1Pコントローラの入力を利用するゲームですが、ゲーム内部では3Pコントローラの入力も受け付けています。任意コード実行のためのメモリ操作として、3Pコントローラの入力を記録するメモリを利用します。
3Pコントローラの入力値はアドレス801C84E4
に記録されます。スティックの入力も固定しなければいけない上に、通常のコントローラでは届かない範囲までスティック入力をしなければいけません。
動画ではエミュレータを使って、ゲーム内の数値単位に入力しています。スティックはX軸105
、Y軸125
、加えて十字キー上とCボタン下を入力した状態にしておきます。
3Pコントローラの入力によって、アドレス80AC84E4
の値を08 04 69 7D - 00 00 08 04
に調整します。
任意コードの実行
ここまでで任意コード実行の準備が整いました。ダルニアとの会話を引き金に、準備した任意コードを実行します。
今回実行する任意コードは、「所持しているフックショットを消す」というコードです。ここではまだポーズ画面にフックショットがあることがわかります。フックショットの所持状態は動画でも表示されているアドレス8011A64D
の1, 2桁目のメモリに記録されています。
任意コードの実行には、剣を取り出している状態で、Z注目しながらダルニアに話しかけます。ダルニアに話しかけると、先ほどまで0A
だった値がFF
となります。任意コード実行の副作用として、リンクやナビィの状態が変化します。これは操作キャラであるリンクの内部ステータスがダークリンクのものになっており、自分をZ注目できたり、ナビィの見た目がおかしかったりします。
実行したコードはフックショットを消すという実にシンプルなものであり、副作用もありますが、ゲーム内の操作で任意のコードを実行することができました。
任意コード実行に関しての詳細
それでは、ゲーム内部ではどのような処理が行われて任意コード実行が可能になっているのかをご紹介していきます。
動画で表示されているウィンドウについて
内部処理の解説のために、ツールを利用してゲーム内のメモリや記憶領域の情報を表示しています。画面左下の表では、左側のアドレスに対して実際に格納されている値が右側に表示されています。ここまでで解説しているアドレス〇〇に値△△が格納されている、といった情報が一覧表示されています。
中央に表示されているウィンドウは、64内部で保持されている、CPU(コンピュータの構成要素のうち、計算を司る部分)への命令を一覧表示したものです。ゲーム内での様々な処理は、「命令」という単位で実現されています。この命令は、あるメモリに値を足すであったり、処理するアドレスをいくつに飛ばすのような単純なものです。これらの命令を瞬間的・連続的に一気に実行することでゲームの動きが実現されています。
これらの命令の内容も専用のメモリ領域に保持されています。命令の内容は他のメモリ領域と同様に16進数で表現できる数値ですが、64はこの数値を決まったルールに従って解釈することで命令を理解し、その命令を実行しています。中央のウィンドウでは、メモリ内の数値を同じルールに従って解釈し、より人にとって読みやすい形で表示しています。Command
列では命令の種類(加算、ジャンプ、分岐など)、Parameters
列は命令に渡す数値やアドレス情報になっています。
右部に表示されているCPU General Purpose Registers
は、CPUが命令を処理する際に利用する数値情報を保存する領域ですが、今までのメモリとは異なるものです。CPUの処理を行うための専用領域と考えていただいてOKです。AT
、T0
のような固有の番号で管理、参照します。
SRMが命令の呼び出し先を捻じ曲げる
ここからは実際の動きに関する解説です。
ダルニアに話しかける際、ゲーム内部ではどのようなアイテムを手に持っているか(あるいは何も持っていないか)によって、異なるアドレスの命令を実行しようとします。剣を手に持っている場合、アドレス801FA0B0
に格納されている値80 1F 8A 08
を実行しようとします。
実際の命令の動きを見てみると、
- ダルニアに話しかけた後
801F8970
の命令でレジスタT6
に80 1F 8A 08
が保存される 801F8974
の命令でレジスタT6
に保存されているアドレスへのジャンプが行われる- 命令が`80 1F 8A 08'に飛ぶ となっているのがわかります。これが通常の動きです。
SRMを利用してメモリを書き換えると事情が変わります。今回はツボを利用したSRMで、アドレス801FA0B0
の値を書き換えています。通常80 1F 8A 08
だった値は80 1F 51 50
となっているため、処理は以下のように変わります。
- ダルニアに話しかけた後
801F8970
の命令でレジスタT6
に80 1F 51 50
が保存される 801F8974
の命令でレジスタT6
に保存されているアドレスへのジャンプが行われる- 命令が
80 1F 51 50
に飛ぶ
SRMによって、呼び出す命令の参照先が変化したことがわかります。これが、SRMが任意コード実行を可能にした所以です。
ファイルネームの値を「任意コード」として読み込むまで
命令を操作することで801F5150
というアドレスを命令として読み込ませることができたので、ここから任意コードの実行を目指します。SRMによって書き換えることができたのはアドレスの後半部分51 50
のみであるため、ここから任意コード実行を行うまでにも様々なメモリ操作が必要になります。
ジャンプした先のアドレス801F5150
付近の情報は、ゲームにプログラミングされている命令コードではなく、ゲーム内のアクターデータを保持する動的メモリ空間です。通常はこれらの領域を命令として読み込むことはありません。しかし前述したとおり、命令コードであろうと何であろうとメモリの中には16進数の値が入っているだけなので、ゲームはそれぞれの値を命令として解釈しようとします。
そしてアドレス801F5150
は、セットアップ内でパチンコアクターのXY角度を格納したメモリです。パチンコ弾の発射角度を利用して、アドレス801F5150
の値は08 07 21 2D
になっています。
この08 07 21 2D
を命令として解釈すると、「アドレス801C84B4
にジャンプ」という命令になります。ジャンプ命令は次の命令を処理した後に指定されたアドレスの命令に飛ばしますが、今回の801F5150
の次の命令801F5154
でリンクがダークリンク状態になる副作用が発生しています。
アドレス801F5154
の命令は「レジスタS2
の値のアドレス(801DAA30
)にレジスタF13
の値をコピーする」というものです。これによって、操作キャラであるリンクのアクター情報のうち「アクタータイプ」と呼ばれているものが書き換わり、操作キャラのリンクがさもダークリンクであるような状態になってしまいます。この状態ではマップを移動するロードゾーンが無効化されてしまうため、セーブリセットによってこの状態をリセットすることになります。
ジャンプ命令によって、アドレス801C84B4
に飛びます。このアドレスのメモリはコントローラによる入力を記憶するメモリ空間です。
801C84B4
と801C84B8
の値は、ダルニアに話しかける際の1Pコントローラの入力が影響しています。ダルニアに話しかける際の入力はZ(L)トリガーとAボタンが入力されている状態でしたが、この入力値を命令として解釈すると、「レジスタR0
の値下2桁を特定のメモリに代入」「レジスタS4
をレジスタR0
の値(0)+レジスタR0
の値(0)にする」となっています。これらはゲームの処理に影響しない無害な命令のため、問題なく命令が進みます。
その後はNOP
「なにもしない」命令が続きますが、3Pコントローラの入力値のアドレス801C84E4
、のメモリに到達します。3Pコントローラの入力を細かく調整したことで、アドレス801C84E4
の値が08 04 69 7D
になっています。これは命令として「8011A5F4
にジャンプ」に解釈されます。次の命令もコントローラの入力によるものですが、これも無害な命令のため問題なくジャンプが行われます。
さて、ここでジャンプする先の8011A5F4
ですが、動画冒頭に設定したファイルネームの値が格納されているメモリがまさしく8011A5F4
です。ファイルネームは2命令分の値が格納できる長さを持っているため、ファイルネームで2つの命令を自由に設定できるということです。
ファイルネームによる2つの命令
ファイルネームによって設定したメモリの値は以下の2つです。
8011A5F4
:08 01 28 50
(81まば)8011A5F8
:A3 B6 B6 3D
(べLLっ)
この2つを命令として解釈すると以下のようになります。
8011A5F4
:「アドレス8004A140
にジャンプ」8011A5F8
:「レジスタS6
の値下2桁(FF)を所持アイテムのフックショット情報のメモリに代入」
1つめのジャンプ命令は、任意コード実行後に、その先に続くメモリを読み込ませないための処理です。これによって、フリーズを回避して処理を戻すことができる命令のアドレスにジャンプさせます。
2つめの命令が、今回実現した「フックショットを消す」コード本体です。ファイルネームの後半部分が任意コードの実際の中身となっています。
後は今まで通り、2つめの命令を実行したあとにアドレス8004A140
にジャンプして、任意コード自体は完了します。
後片付け
ここまで散々命令コードを操作してきましたが、正常に処理を戻してあげないとゲームが予期せぬ動作によってフリーズしてしまいます。ここからは、正常にゲームを再開させるための後処理になります。
ジャンプ命令で飛んだ先のアドレス8004A140
には、想定と異なる動作をしたゲームの処理を復帰させるコードを呼び出す処理が定義されています。これは、任意コード等ではなく、ゲームにプログラミングされているものです。これを利用して、SRMで書き換わった命令コードのデータも元に戻した上で元々の命令呼び出しに復帰させ、ゲームをフリーズさせることなく任意コード実行を終了させます。
おまけ
動画では、フックショット消去以外にも複数の任意コード実行を紹介しています。
- 81まばELLサ:「オカリナ」画面欄がすべて埋まる
- 81まばべEee:言語が海外版仕様になる(Aアクションもよく見たら英語)
- 81まばプELら:リンクの服が青色+ホバーブーツみたいに滑る+盾が上下逆さま
- 81まばべHLり:ミラーシールド装備
- 81まばべLLら:リンクの服が黒、リンクが一切落下しない(Figさん大好きモード)
おわりに
今回はFigさんによる解説動画ですが、SRMの発展、任意コード実行には多くのプレイヤーの尽力がありました。また、将来これがRTAで利用できるかどうかはまだわかりませんが、時オカの歴史において非常に大きな出来事であったことには変わりありません。
日本の方向けということで日本語解説を書かせていただきましたが、特にMIPS関連では不足している知識もあり、理解できていない部分があるかもしれません。お気軽にこちらへのコメントやTwitter(@cma2819)でご指摘、ご質問いただければ幸いです。
2019年はゼルダRTAが爆発的に進化した年になりました。来年にも期待ですね。
以上