『無敵化&一撃死化のパラサイトルーチンでラスボスの一撃必殺に挑戦してみる』 [使用ツールollydbg/うさみみハリケーン] <はじめに> このページの骨子となるパラサイトルーチンに関する基礎知識や、基本となるアセンブリ言語等の情報に関しては、 当方もかねてよりお世話になっております Digital Travesia さんに集結していますので、ありがたく参照させて頂く のがよいかと思います。殊に、 ◇IA-32 インテル・アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル [参照リンク] ◇改造初心者向け練習用プログラム No3 [うさぴょんさん製作] 前者は基礎知識の取得という面では必須ともいえ、また後者は実践の基本を学ぶことができるばかりか 内容の充実した参考テキストも添付されており、とても有用です。当ページにはこれらの基礎とある程度の解析経験を 踏まえた方向けといえる部分が少々存在します。では、本題となる 永遠のアセリアDVD版ver1.16 に関し、パラメータ減少回避(無敵化)改造 コードの発展例作成に関する捕捉を綴っていきます。 <本題> まず、最初に題材となる 永遠のアセリアDVD版ver1.16 に関し、基本となるパラメータ管理領域の簡易な説明を しておきます。隠蔽要素らしきものはありませんので、メモリサーチャによって容易にあたりをつけられますから詳細は省略しますが、 下記の画像内のアドレス0x61C920(環境差のよる変動無し変動)を1人目のキャラクタのベースアドレスと見なし、 →<基本パラメータを管理する汎用メモリエリアの参照>[画像] +0x00:キャラクタID/ +0x04:パーティ内存在フラグと配置位置相当のフラグ/ +0x08:クラス/ +0x0A:マインド値/ +0x0C:好感度/ +0x10:LV/ +0x14-0x1B[4byte]:現在HP・最大HP/ +0x1C-0x21[2byte]:攻撃力・防御力・抵抗力/ +0x44-0x55[2byte]:装備スキル/ +0x56-0x67[2byte]:スキル残存数/ ■次のキャラクタまでの相対オフセット+68h というような内容になっています。キャラクタIDに関してはゲーム内には一切情報が供出されませんが、これは該当キャラが誰であるかを 見分ける識別用のフラグであり、当方のSSG内のキャラクタ変換項目でも裏付けられるように、1キャラ毎に1つのIDが以下のように振り分け されています。ちなみにソフト内ではこのID番号は常に2バイトの変数扱いとなっているようです。 0001:高嶺悠人/ 0002:聖賢者悠人[エターナル化]/ 0003:アセリア/ 0004:永遠のアセリア[エターナル化]/ 0005:オルファリル/ 0006:再生の炎オルファリル[エターナル化]/ 0007:エスペリア/ 0008:聖緑のエスペリア[エターナル化]/ 0009:ウルカ/ 000A:深遠の翼ウルカ[エターナル化]/ 000B:岬今日子/ 000C:碧光陰/ 000D:倉橋時深/ 000E:ネリー/ 000F:シアー / 0010:セリア/ 0011:ナナルゥ/ 0012:ヒミカ/ 0013:ハリオン/ 0014:ニムントール/ 0015:ヘリオン/ 0016:ファーレーン …とここまでが味方ユニットの識別用ID番号です。 おまけですがw、敵となるロウエターナル達に関しては以下のごとく 0020:統べし聖剣シュン /0021:法皇テムオリン /0022:黒き刃のタキオス /0023:不浄のミトセマール / 0024:双剣のメダリオ /0025:ントゥシトラ 他の方によるこのソフトの解析結果が公開されだした当時、キャラクタのクラスをエターナル、またはハイエターナルにしたのに専用技が使えません。どうすればいいのでしょう? という質問をよく見かけましたが、その答えは「エターナルというクラスだから使えるのではなく、エターナルという(IDを冠する)キャラクタだから使える」になります。 高嶺悠人(ID:0001)ではクラスを変更しても不可能ですが、聖賢者悠人(ID:0002)であれば専用技コネクティドウィルが繰り出せます。 つまりはアセリア(ID:0003)と永遠のアセリア(ID:0004)はシステム上では外見を使いまわした全くの別人ということですね。 ゲーム解析において留めておくとよいことですが、自分の眼前の事象を根拠無く自身の思い込みの論理で決め付けてしまうと思わぬ落とし穴に陥る ことがあります。でき得る限りプログラム仕様に適った根拠を探し、以って判断するようにすると比較的失敗も減らせることでしょう。 ただ、過度の固執により新しい発想の基点を抑制するほど警戒視することでもないのでしょうけど。 さて件の解析に着手するにあたって、基本パラメータエリアの参照画像より敵味方を区別せず、キャラクタ毎に同種の配列が数珠繋ぎになっていることがわかりますから、 まずは「キャッスルファンタジアシリーズ」や「うたわれるもの」に代表されるようにこれらの配列そのものが戦闘パートにダイレクトに流用されている可能性を考慮する 発想が浮かぶと思います。実際にこのエリアの現在HP相当のアドレスを戦闘パートの進行にあわせて観察してみますと… →<基本パラメータエリアの観察結果>[画像] …容易にわかることですが、戦闘中には現在HP相当のアドレス内は全く変動しませんでした。戦闘終了後に、その時点でのHP相当の値が入ることになります。ですからこの場合、 戦闘中のHP計算は別の箇所で行われているため、そちらに対処しなければ、パラメータ減少回避構築はあり得ないと考えるのが自然でしょう。何故なら、戦闘終了時現在HPが再度格納される 以前にHPが0になっていればキャラクタは死亡扱いになってしまうからであり、条件次第ではゲームオーバー直行ですから(^^; そこで、その戦闘中のHPを管理する箇所はどこにあるのか?ということに焦点が移ります。戦闘システムを実際に眺めたうえで分析するに、はっきりしているのは戦闘中のHP減少に 応じてHPを表す数値と体力バーが変動しているということだけです。ですから、まずはその箇所から探りを入れてみることにします。HPの値はキャラクタ毎に流動的に 変化する以上、それを定める基準となる箇所があるはずですし、キャラクタを複数同時に管理する関係上、プログラマの利便性を考慮した特殊な領域が構成されているであろうことも 推察に難くありません。その箇所が表示以外に実際のパラメータ管理等汎用的に利用されている可能性を考えてみることにします。そこで任意のキャラクタにダメージが及んだ 時点と同時に戦闘処理をブレークさせ、メモリ上に影響の及んだ箇所を検索してみると以下のようになりました。 →<HP減少表示を管理する一過性のパラメータエリア>[画像] 大前提として、この領域は戦闘中にしか作成されず、また頻繁にアドレス変動します。この場合、変動というよりは攻撃種によって対応した新しい領域に再構成されるといった方がより正確かもしれません。 このソフトの戦闘システムは、基本的に敵味方1チーム3人編成で互いに攻撃の応酬を行うのものなので、HPパラメータ等も6人分の領域が用意されています。画像中の最初の栞から次の栞までの都合48バイト分が、 6人の最大HPと現在HPの配列となり、3番目の栞からその次までの都合12バイトが互いのチームを構成するキャラクタIDの配列になります。 ここから必然的にHPパラメータの配置順もこれに従うことが判断できますし、HPパラメータを即値で扱っていることがわかりますが、残念ながらこれはやはりHP表示に関する配列だったので、 手を加えたとしてもキャラクタの無敵化や目標の一撃死等には繋がりませんでした。 ただし、ここで得られた状況から次のように考えられます。先程述べた通り、表示用アドレスではキャラクタの生死判別ができませんので無論この箇所を0にしてもキャラクタは死亡しませんし、 変動後のHPが再度基本領域に格納されるのは少なくとも戦闘終了後です。戦闘中のHPを管理する本体が更に別の箇所にあると考えて間違いないでしょう。そうでなければ、同一戦闘で 複数回の攻撃を受けるキャラクタがいた場合の対処に説明がつきません。予測されるその領域にも、やはり最大HPと現在HPの値が管理されているでしょうから、以下の手順でその本体を見極めてみます。 1)該当エリア初期化にあたり、基本パラメータエリアから値がロードされるであろうアドレスにアクセスブレークポイントを設置 * 該当エリア初期化 = 戦闘開始時の初期時点で行われるであろう該当領域の作成処理(確実にアドレス変動すると想定) 2)1)で得られた情報をもとに、ロードされたパラメータがストアされるコードから該当メモリエリアを発見する *以下、先に簡易解説をした「基本パラメータを管理する汎用メモリエリアの仕様」を併せて追うと理解しやすいです。 00434733 0FBF55 D8 MOVSX EDX,WORD PTR SS:[EBP-28] //EDXレジスタに2バイト長のキャラクタIDを格納 00434737 6BD2 68 IMUL EDX,EDX,68 //IDに0x68を乗算(IDをもとにした相対オフセットの算出) 0043473A 81C2 B8C86100 ADD EDX,ASELIADVD.0061C8B8 //上記結果に(ID:0000時相当の)ベースアドレスを加算 00434740 8955 DC MOV DWORD PTR SS:[EBP-24],EDX //算出された格納対象キャラクタのベースアドレスを待避 0043474A 8B55 DC MOV EDX,DWORD PTR SS:[EBP-24] //EDXレジスタに格納対象キャラクタのベースアドレスを格納 0043474D 8B52 18 MOV EDX,DWORD PTR DS:[EDX+18] //ベースアドレスから相対オフセット+18hにある最大HP値をロード 00434750 899481 1C010000 MOV DWORD PTR DS:[ECX+EAX*4+11C],EDX //目標の管理エリアと見なされる特定アドレスに値をストア 00434765 8B8486 1C010000 MOV EAX,DWORD PTR DS:[ESI+EAX*4+11C] //目標の管理エリアの該当キャラクタの最大HPアドレス相当箇所より値をロード 0043476C 89848A 50010000 MOV DWORD PTR DS:[EDX+ECX*4+150],EAX //目標の管理エリアの別箇所に値をストア。最大HP→現在HPへのコピー処理と思われる→<検出された一過性の戦闘用HP管理領域の本体>[画像] コードアドレス 434765/43476C の ESI/EDXレジスタ:戦闘用HP管理領域のポインタアドレス コードアドレス 434765/43476C の EAX/ECXレジスタ:該当キャラクタの配置箇所を示す変数 以下、その内訳 00/味方チームアタッカー 01/味方チームディフェンダー 02/味方チームサポーター 03/敵チームアタッカー 04/敵チームディフェンダー 05/敵チームサポーター *「HP減少表示を管理する一過性のパラメータエリア」よりの情報との統合判断による 戦闘用HP管理領域のポインタアドレスより相対オフセット+10Eh:該当キャラクタのID格納アドレス 戦闘用HP管理領域のポインタアドレスより相対オフセット+11Ch:最大HP格納アドレス 戦闘用HP管理領域のポインタアドレスより相対オフセット+150h:現在HP格納アドレス さて、戦闘用HPの格納場所が判明したので、あとはこのアドレスからロードされた値に対する減算処理と思しき 箇所を探すことになりますが、ここで現在HP相当のアドレスを観察してみると妙なことに気づきます。確かに 最大HPと同じ値が格納されたはずであるのに、戦闘開始後に眺めてみると、戦闘画面中の現在HPの数値と 全く異なる値に変わっているのです。ですが、ここで暗号化?更に別の空間へ転送?等と考えるのは早計。 何度かサンプルをとってみると希に値の変わらない場合もあることがわかりました。 では何故こうなっているかに関してですが、システムと今までの私の解析経験から状況を分析することで 大体理解することができました。詳細は以下、 ◇この状況の正体を見極めるキーポイント 1)私自身が近似した状況をStudio e.go! のMen at Work 2 で見た経験がある 2)この戦闘システムには戦闘中随時に戦闘演出をスキップする機能が存在している 3)「ランダム性の無い」戦闘システムであることをパッケージ等で謳っている つまり、状況を問わず毎回同じ戦闘結果になるということであり、先の戦闘スキップの場合を除外しない 4)ゲーム内でのエスペリアによる致命的なこの発言 『戦闘には偶然などありません。勝つべき側は、戦う前に周到な準備をしているものです。』 2)3)を踏まえ、この内容を角度を変えて眺めてみると、この戦闘システムの決定的なネタバレと同意になることがわかる *発言内の「戦闘」「勝つべき側」→「この戦闘システム」と置き換えてみましょう ここから導かれる結論: この戦闘システムにおいては、最初に戦闘スキップが受けつけられる時点よりも以前に戦闘結果の算出が終了している。つまり、 戦闘演出に入る前のBGM切り替え及び画面フェードアウトの時点で戦闘結果は既に決定しているということ。プレイヤーはその結果 に従って構成された演出の再現を、リアルタイムに戦闘しあっているかのように眺めさせられているに過ぎません。 ここにも最初の方で述べた「自分の眼前の事象を根拠無く自身の思い込みの論理で決め付けてしまうと思わぬ落とし穴に陥る」の もう一例があったわけです。この場合、戦闘開始後では発見された現在HP相当のアドレスにブレークポイントを設置してもブレークすることはありませんし、 ましてや既に説明した通り、ゲーム中のHP表示を管理するエリアがありますので、この箇所の変動を メモリサーチで検出し、ブレークポイントを設置したとしても望んだ結果への大きな一歩は得られないであろうことは言うまでもありません。 今回はデバッガによる解析から、値の詳細はともかくもアドレス位置の正確性への信頼がありましたので、このような状況も比較的早期に 看破できました。 さて、ここまでで戦闘システムと現在HPアドレスが仮格納されている箇所はわかりましたから、あとは実践あるのみです。ただし先程述べた通り、この 戦闘結果の計算はその開始時期を正確に掴むのが困難であり、またほんの一瞬のうちに終了してしまう(少なくとも体感不可)ので、確実性を得る手順が必要になります。 一例としては以下を参照。 1)戦闘開始時の初期時点で行われるであろう該当領域の作成処理内にて、現在HPがストアされた直後のコード(先の説明内のアドレス43476Cのコード実行直後) にブレークポイントを設置。つまり、この時点で処理を確実に中断させる。 2)格納された現在HP相当のアドレスに改めてメモリーライトブレークポイントを設置してから、1)で定めたコード実行ブレークポイントを解除して処理を再開。 理論上最も早期なブレークポイントの設置になるので必ずブレークすることが保障されます。 2)で最も早期と言ってますが、処理を再開すると1秒と経たぬうちにブレークしますw 0043D132 0FBF45 14 MOVSX EAX,WORD PTR SS:[EBP+14] //該当キャラクタの存在サイドを表すと思われる変数の格納 0043D136 6BC0 03 IMUL EAX,EAX,3 //上記の値を3で乗算 0043D139 0FBF8D 64FFFFFF MOVSX ECX,WORD PTR SS:[EBP-9C] //該当キャラクタの役割を表すと思われる変数の格納 0043D140 03C1 ADD EAX,ECX //前記2種の変数の加算(該当キャラクタの配置箇所を示す変数の算出) 0043D142 8B95 48FFFFFF MOV EDX,DWORD PTR SS:[EBP-B8] //EDXレジスタに該当領域のポインタアドレスを格納 0043D148 8B8482 50010000 MOV EAX,DWORD PTR DS:[EDX+EAX*4+150] //EAXレジスタに現在HP相当値をロード 0043D14F 3B45 B4 CMP EAX,DWORD PTR SS:[EBP-4C] //現在HP相当値とダメージ相当量の比較 0043D152 0F8F 8A000000 JG ASELIADV.0043D1E2 //現在HP相当値の方が大きければ(生き残っていれば)アドレス0x43D1E2に誘導 0043D1D5 C78488 50010000 MOV DWORD PTR DS:[EAX+ECX*4+150],0 //上記判定が成立しなかった場合、現在HP相当値を一律0にする *アドレス[EBP-4C] は攻撃1回によるダメージ総計値の待避箇所 *コードアドレス 0043D140 におけるEAXレジスタの扱いに関しては、既に述べた内訳部分通り 00/味方チームアタッカー 01/味方チームディフェンダー 02/味方チームサポーター 03/敵チームアタッカー 04/敵チームディフェンダー 05/敵チームサポーター *コードアドレス 0043D1D5 は減算結果が0以下の場合のHP表示を意識しての強制的処遇。今回の案件は最終的に無敵化と一撃死化の同時構築にありますので、先の結果を基に生死判定の箇所を最終的なターゲットにすることに しました。実際の減算処理は、この判定ジャンプ後(生存していると判断されて)から改めて行われているようです。ただし、パラサイトルーチンそのものは とても広範囲な時点・状況での作成が可能であり、該当システムに関する深い理解と解析知識を増やすことにより、 より可能性を高めることが可能です。今回の私の観点以外にも対処方法は無数存在することに留意しておいてください。 今回のパラサイトルーチンの作成にあたって考慮しておくべき点は以下。基礎知識的なことは、始めに述べた参考テキスト類等 から御自身で学んでください。 1)無敵化と一撃死化を分別するキャラクタの識別判定を構築する 2)無敵化の場合は現在HPを最大回復、一撃死化の場合は現在HPを一律0にするようにする 3)無敵化の場合はダメージ相当量を0にし、一撃死化の場合はダメージ相当量を最大HPと同じにする 3)に関しては、スタックにパラメータが待避される意味を考慮しておきます。つまり、 ・コール命令に対するリターンアドレスのようなル―ルが存在するからであり、 ・汎用レジスタを一時的に解放するための本来の中身だからであり、 ・汎用サブルーチンの引数相当の存在となり得るからであり、 ・自身が複数のサブルーチンに流用され得るからです ここで、減算処理後にダメージ相当量を待避しておく意味は、ダメージ量表示にこれを流用する余地を残すためであろうと推測されます。 実際にこの値を変化させることで、ダメージ表記が変化できることを確認しておきました。 それでは実際にSSGに搭載したパラサイトルーチンの簡易な解説をしつつ、今回の解析結果の解説を閉じます。先程にも述べましたが、 パラサイトルーチンは、該当ゲームシステムそのものに自身の構築したアセンブラコードを実行させることができますので、その規模・内容・ 効果に関し、作者の裁量のもときわめて多くの可能性を実現させることができます。始めに紹介した「改造初心者向け練習用プログラム No3」 等、現在は新規に解析を志す方々にとっても恵まれた環境にあると思いますし、無論私もその恩恵を頂いた一人です。今まで行ってきた 解析に関し、それに満足しきらず、ほんの一歩ですが先に進もうと考えてみるのも良いのではないでしょうか? 00565460 51 PUSH ECX //当ルーチン内で汎用的に用いるためにECXレジスタの中身を待避し、汎用レジスタとして確保 *他のレジスタの役割を考慮し、このルーチンにおいて役割の低いレジスタを利用 00565461 8D8A 0E010000 LEA ECX,DWORD PTR DS:[EDX+10E] //便宜的にキャラクタID配列の始点アドレスをベースアドレスに設定 00565467 66:833C41 01 CMP WORD PTR DS:[ECX+EAX*2],1 //ダメージを受けたキャラクタが高嶺悠人(ID:0001)以前のものか判定(保険要素) 0056546C 7C 1C JL SHORT ASELIADV.0056548A //条件を満たす(敵である)場合、アドレス0x56548Aへ誘導 0056546E 66:833C41 16 CMP WORD PTR DS:[ECX+EAX*2],16 //ダメージを受けたキャラクタがファーレーン(ID:0016)以降のものか判定 00565473 7F 15 JG SHORT ASELIADV.0056548A //条件を満たす(敵である)場合、アドレス0x56548Aへ誘導 00565475 33C9 XOR ECX,ECX //ECXレジスタを初期化(ECX = 0x00000000) 00565477 894D B4 MOV DWORD PTR SS:[EBP-4C],ECX //ダメージ相当量格納アドレスに先程の値0を格納 0056547A 8B8C82 1C010000 MOV ECX,DWORD PTR DS:[EDX+EAX*4+11C] //ECXレジスタに最大HP相当値をロード 00565481 898C82 50010000 MOV DWORD PTR DS:[EDX+EAX*4+150],ECX //現在HP相当アドレスに先程の値をストア 00565488 EB 13 JMP SHORT ASELIADV.0056549D //整合用合流処理0x56549Dへ誘導 *ここまでが味方を想定した無敵化相当の対処 0056548A 8B8C82 50010000 MOV ECX,DWORD PTR DS:[EDX+EAX*4+11C] //ECXレジスタに最大HP相当値をロード 00565491 894D B4 MOV DWORD PTR SS:[EBP-4C],ECX //ダメージ相当量格納アドレスに先程の値を格納 00565494 33C9 XOR ECX,ECX //ECXレジスタを初期化(ECX = 0x00000000) 00565496 898C82 50010000 MOV DWORD PTR DS:[EDX+EAX*4+150],ECX //現在HP相当アドレスに先程の値0をストア *ここまでが敵を想定した一撃死相当の対処 0056549D 59 POP ECX //当ルーチン内の汎用レジスタを解放 0056549E 8B8482 50010000 MOV EAX,DWORD PTR DS:[EDX+EAX*4+150] //寄生元アドレスに対する補填処理 005654A5 E9 A57CEDFF JMP ASELIADV.0043D14F //本ルーチンへ処理を返すラスボスより強い守護者も99999ダメージで一撃、味方は何度攻撃を食らおうがダメージ0のまま。 ある程度の手順を踏みますが、堅実な解析姿勢によるソフト全般に広範囲な影響を及ぼすコードが作成できました。 <今回解析結果の捕捉> 先のパラサイトルーチンにより効果は保障されましたが、今までの解析結果を考慮すると不自然な点があることに お気づきの方がいらっしゃると思います。即ち無駄が多いということ。それはこのパラサイトルーチンが、単体で構成意図を即座に解し得るレベル まで可読性を高めることだけに徹した結果によるものです。当ソフトの開発チームはかつて「風と大地のページェント」 において10回近くに渡る修正を繰り返した経歴があり(失意もあって次作の「アルティメットハンター」は購入回避)、今回の「永遠のアセリア」でも CD版で5回程度、DVD版以降も1度修正を行うことになります。加えてこの機能公開時CD版にはアルファロムプロテクトがかかっていたため、 究極的に自分自身が不定期な新verへの柔軟な対処のために可読性を必要としていた経緯がありました。今回自身のコードを見直す 機会が得られましたので、パラサイトルーチンの最適化を試みてみることにします。今しばらく修正等が行われないか様子を みてから、いずれSSG収録内容もこちらのものに差し換える予定です。 00565460 8B8C82 1C010000 MOV ECX,DWORD PTR DS:[EDX+EAX*4+11C] 00565467 83F8 02 CMP EAX,2 0056546A 7F 13 JG SHORT ASELIADV.0056547F 0056546C 898C82 50010000 MOV DWORD PTR DS:[EDX+EAX*4+150],ECX 00565473 33C9 XOR ECX,ECX 00565475 894D B4 MOV DWORD PTR SS:[EBP-4C],ECX 00565478 8BD0 MOV EDX,EAX 0056547A E9 C07DEDFF JMP ASELIADV.0043D23F 0056547F 894D B4 MOV DWORD PTR SS:[EBP-4C],ECX 00565482 8BC8 MOV ECX,EAX 00565484 E9 DF7CEDFF JMP ASELIADV.0043D168◇最適化による改善点 1)このソフトのプログラマ自身終始キャラクタIDによる識別判定に徹しているが、今回判定の必要要素は 味方か敵かのいずれかにあるので、ID配列の固定性質を利用した相対オフセットによる識別判定に切り替え、 以って判定処理の軽量化と成した 2)死亡確定処理方面においてはコードアドレス 0043D1D5 を活用することでHPに対する配慮は要らないと判断した。また 、これを踏まえ共通要素となる最大HP相当値を予め用意することで、このルーチン全体の軽量化を行った 3)当ルーチンのパラサイト元は生死判定の箇所にあるので、判定後のそれぞれの返り先にバイパスジャンプを用いた。 その際、当機能により無効化される処理群を、いくらかのレジスタの補填を行うことでスキップさせ、 戦闘計算処理そのものを微弱ながらに高速化させた ■今ページ開設のきっかけを与えて下さった有志の開発者・解析者の皆様に深く感謝いたします。 2004/05/15 REDCAT記
|