『おまけモードの武将カード800人分ALL化をプロセスメモリエディタとデバッガで簡単に達成してみる』 [使用ツールollydbg/うさみみハリケーン] 私自身がそうだったのですが、およそプログラミングの基礎知識等の無いままにとにかく改造・解析を始めてみようと考えると、最初に触れるのが 所持金パラメータ等のワークコードかCGモード等によく用いられるフラグコードのサーチ、及び構成かと思います。後者の場合、多くの方が空の環境フラグデータ等を用いプロセスメモリエディタのメモリサーチャ 機能による見当・変動サーチ等にて絞りこみを行い、最終候補よりフラグの構成を判別。予測乃至メモリ上他ファイルから必要データを調べ、補完するケースが比較的多めだと思います。 実際、私もそのような解決方法を辿る場合が少なからずありますが、このような手法は簡易にとりかかれる反面、頻繁にアドレス変動して しまうものにはとても効率が悪く、予測され得るフラグ構成が暗号化を始めとする判別阻害要素を含んでいた場合、その時点で解析作業が一時中断してしまう可能性があるかと思います。 前記の手法は基本的なものですが、その時点における視点を少し変えることで効率良く大きな結果を出せる場合があります。 ここで上記の手法に存在する概念を抽出し、整理してみると大体次のようになることがわかります。 ◇サーチ候補から得られたフラグ値の実態は、あくまで個々の解析者の経験上に基づく予想の域を越えない ◇件のフラグ型コードの本旨は、あくまで解析者の「補完」によって構成される 「視点を変える」と先に述べたので、それに併せて各々を反転し、次の概念に基づく手法を模索することにしてみます。 ◇デバッガによる解析からフラグ値の実態を判断し、仕様に沿った完全に正しいフラグ値を得る ◇件のフラグ型コードの実態をプログラムルーチンから抽出する 以上を踏まえて、題材である太閤立志伝5ver1.31につき、対象ソフトのフラグコード中最も難解であろう武将カード800人分ALL化について考察してみます。 これは結果としてアドレスは変動しないものの、フラグ成立時の値が特殊な法則に沿った値になる上(復号できないため暗号化とは微妙に異なる)、1人1バイトとして計800バイトサイズ のコードになるものです。ちなみに他カード種類に関しては、対象アドレス中の各ビットがフラグになっているに過ぎない基本的なものなので、それらに関する叙述は省略します。また、以後の叙述にあたっては、 デバッガとプロセスメモリエディタの併用を前提としたうえで進めます。 →<初期状態はこのようになります>[画像] デフォルトで選べる主人公候補のところだけフラグが成立しています。一見単純な暗号化っぽく思えますが、 少しゲームを進めていくと対象武将が違うのに、フラグ値が同じ状況が複数出現してきます。 解析の前段階にあたっては、初期のアドレスのあたり(任意の武将カード入手で成立するフラグの存在箇所 )は普通に変動サーチで求めておきました。対象ソフトの開発元であるコーエーは汎用的なパラメータ・フラグデータをモジュールエリア内で構成する傾向があるので、これまでの経験を参考として従うのにも 試す価値があると判断できたためです。実際に検索できたので、次に最初の確認事項として以後の解析にあたっての後慮を取り除きます。 つまりは、アドレスのあたりが正しいか、及び安定しているか(アドレス変動しないか)を確認すること。であり、そのために ◇ヒットした候補を初期値(否成立状態)に書き換え、効果が消えるか(あたりが正しいか)を調べる ◇前記成立後、該当アドレスにフラグ存在状況を発生させるフラグストアコードをデバッガで検出する Module=TAIKOU5.EXE 00506F57 81FE 20030000 CMP ESI,320 //武将IDが最終番以降か、同時に書き込み範囲がオーバーしていないかの判定 00506F87 889E 18307500 MOV BYTE PTR DS:[ESI+753018],BL //IDを基に設定されたアドレスにフラグをストア ESIレジスタ=相対オフセット並びに、800人からなる武将のID番号[0000-031Fh/4byte] BL レジスタ=該当武将カードの存在を示すフラグ値[01h-FEh/1byte]先の後者要件に関し、注目すべきコードは上記の2つ。ここから判断できる状況は、 ◇武将カード1人分につき1byteのフラグと同サイズのストア範囲をもつ ◇IDの最終番号は800。ひいてはフラグエリアは都合800byteサイズになる ◇該当フラグエリアはアドレス753018hをベースとし、アドレス変動を起こさない ◇アドレス506F87hにブレークすると、対象武将のIDと対応するフラグ値が簡単に判明する 最終目標は、全武将IDから対応アドレスを知り、フラグとなる値を調査することですので、アドレス506f87よりblレジスタ への干渉を溯るのも一つの手段になりますが、はっきりと全容を知るために、ここでアドレス506f87のコードの要素を纏めて みます。 A.)該当コードアドレスは、ゲーム内でフラグ成立要因が発生した時点でしか通過せず、一度に一人分のデータしかわからない 当然といえば当然です。そこで、この時点で発想の反転を行います。 B.)フラグ成立要因が発生しない時点でも通過するコードアドレスで、一度に武将全員分のデータがわかりそうな箇所 ゲーム内の仕様を良く分析したうえで、以下の結論が出ます。 C.)おまけモード入室時の存在フラグ判定時は、フラグ成立要因が発生しない時点でも通過するコードアドレスがあり、 一度に全員分のカード存在判定が行われる D.)つまり、この時点での比較対象を利用することで一度に全員分のフラグ値を知ることが可能である 今ページ開設の骨子は、この発想A.〜D.への変遷を柔軟に行うことと言えます。 では、先の発想C.〜D.相当の処理群を探します。既にアドレス506F87hにて、ベースアドレスが753018hであることが判明しているうえ、 フラグエリアのアドレスの扱い方がわかっているので、容易に検索できると思います。 Module=TAIKOU5.EXE 00507000 8A8E 18307500 MOV CL,BYTE PTR DS:[ESI+753018] //該当フラグアドレスから判定用パラメータをロード 00507009 33C0 XOR EAX,EAX //成立判定フラグ用レジスタの初期化 0050700C 3ACB CMP CL,BL //ロードされた値と該当フラグ値の比較 0050700F 0F94C0 SETE AL //ゼロフラグが成立した(CL=BLである)場合、ALレジスタに正判定成立フラグ01hをセット ESIレジスタ=相対オフセット並びに、800人からなる武将のID番号[0000h-031Fh/4byte] AL レジスタ=成立結果判定用フラグ[00or01/1byte] BL レジスタ=該当武将のフラグ値[01-FEh/1byte] CL レジスタ=フラグアドレスからロードされた値[00orフラグ値/1byte]上記コード群から得られる第1の情報は、 ◇最終的に成立判定に必要なのは、コードアドレス50700FhでセットされるALレジスタのみ であり、ALレジスタに常時01hが入っている場合は、全ての成立判定に無条件で正判定を返した状況になります。 0050700F B001 MOV AL,1 //成立結果判定用フラグに強制的に正判定相当の値を格納 00507011 90 NOP50700F-B00190 [0F94C0] … 当方で公開したSSGの「主人公札・プレイ選択制限解除」機能相当のコードとなります。 メモリ上、セーブデータ共にデフォルトのままであるにもかかわらず、全ての武将が主人公候補になります。 さて、この時点で「セーブデータや該当フラグエリアの内容を問はずALL化させるコード」ができましたので、今度はセーブデータ や該当フラグエリアそのものをALL化させる方法を探します。 とはいっても、先の処理群を見ればなんとなく気付いた方もおられるのではないでしょうか? この処理群には致命的な「隙」が存在するのです。その隙を概念分けすると… 1)フラグエリアの構成は1byte単位の数珠繋ぎ状態であること 2)おまけモード入室時には律儀に全員分の判定800回分を行ってくれること 3)フラグアドレスから値をロードするコード00507000hの時点で、既にBLレジスタには比較用の値が 格納されていること 4)フラグアドレス指定コードやフラグ範囲の限界判定箇所から、該当処理はあくまで武将カードの存在フラグ確認判定にのみ利用されていること 上記4を踏まえると3は致命的な構成なので(一応コーエー製品なのですが)、ここでその隙を突きます。 Module=TAIKOU5.EXE 00507000 889E18307500 MOV BYTE PTR DS:[ESI+753018], BL //パラメータロードアドレス=対象フラグ格納アドレスに、比較用の正当なフラグ値を直接ストア 00507009 33C0 XOR EAX,EAX //成立判定フラグ用レジスタの初期化 0050700C 3ACB CMP CL,BL //本来であれば、フラグ格納アドレスからロードされる筈だった値とフラグ値の比較 0050700F B001 MOV AL,1 //成立判定用フラグに強制的に正判定相当の値を格納 00507011:90 NOP ESIレジスタ=相対オフセット並びに、800人からなる武将のID番号[0001h-031Fh/4byte] AL レジスタ=成立結果判定用フラグ[00or01/1byte] BL レジスタ=該当武将のフラグ値[01-FEh/1byte] CL レジスタ=フラグアドレスからロードされた値[00orフラグ値/1byte] *0050700Fh以降は00507000h改竄によるCLレジスタが放置されたことへの補填、 つまり処理整合用の対処です。既にEAXレジスタの中身が00507009hにてクリアされているので 連続nop命令でもよいのですが、結果としてALL化されるため上記のようにしました。507000-889E [8A8E] 50700F-B00190 [0F94C0] ということで、上記2つの改造コードを実行した状態でタイトル画面からおまけモードに入りましょう。 →<上記コードによる最終効果がこちら>[画像] バグってるわけではないですよ?いかにもALL化な気分を出すために武将カード以外はSSGで埋めちゃいましたけどw ◇デバッガによる解析から実態を判断された仕様に沿った完全に正しいフラグ値を… ◇わざわざ800byte分調べて補填するのは大変なので、プログラムルーチン本体に代理して埋めてもらいました こうして最初の方で述べた方針を双方満たせたというわけです。 あとはメモリ上のフラグエリアをコード化すれば完成。以上をもって、当ページの解説は終了です。このページ で語りたかったことは、前記のA.〜D.への変遷を柔軟に行いそれを利用するということなのですが、これを概念付けすると、 プログラマに構築された該当ゲームの仕様と自分の対処意図を十ニ分に理解しつつ逆流用するということであり、 今回用いたアプローチの実態でもあります。 ゲーム解析において多角的・柔軟な発想が良しとされるのは、その解析対象が自分で築いたものではないからであり、 それらが解析対象の核心に近づける数ある有効手の中の一つであるからだと考えます。 構築者本人が特定の事象をどのように判断するかを慮ることは、全てのゲーム解析において重要な視点の一つとなり得るのでは ないでしょうか? 今回の解析結果の捕捉: 先にも述べましたが、このようにプログラムルーチンを逆流用するようなことをしなくても、正確なフラグ値を調べていけば、 自力でALL化することは可能です。つまりコードアドレス506F87の時点でのBLレジスタへの干渉を溯っていく方法です。実際に試みてみると コードアドレス6AF272から始まる一連の変換処理用サブルーチンが本体であると判断することができます。具体的な変換手順を纏めると以下、 //1)まずID番号を用意する(IDは0000h-0320hまでの連番で格納レジスタは4byteサイズ) 006AF277 69C0 6589076C IMUL EAX,EAX,6C078965 //2)このID番号に6C078965hを乗算 006AF27D 05 39300000 ADD EAX,3039 //3)2)の結果に3039hを加算 006AF287 C1E8 10 SHR EAX,10 //4)3)の結果を16ビット右論理シフト(10000hで除算) 006AF28E F7F9 IDIV ECX //ECX:000000FFh //5)4)の結果をFFhで除算 006AF290 66:8BC2 MOV AX,DX 006AF294 C3 RETN //→506FF5h 00506FF5 66:8BD8 MOV BX,AX 00506FF9 FEC3 INC BL //6)5)の「余り」に+01hを加算したものが目的の値になります。 *IDが205h(羽柴秀吉)の場合、フラグ格納アドレスは0x753018+0x205=75321Dh 存在フラグの値は、(0x205*0x6C078965+0x3039)/0x10000/0xFFの余り+0x01=0x64→<デフォルトのアドレス75321Dhが64hか確認>[画像] 一見して法則性のなさそうな値が出た場合、初心者の方であればこれは「暗号化」ではないかと考え、ある程度の経験のある方は出現した値に対し、どのような「論理算」 が行われたかという発想が出易いと思いますが、実際は該当フラグエリアは各武将ID番号を基にした「チェックサムの集合体」です。状況次第では2バイト要するIDデータを一律1バイトに変換、 また見かけ上暗号化のようにも見え、他種類のカードフラグと性質を違えるため、隠蔽要素としてはそこそこ考えたのかもしれませんが、既に提示した解析結果に裏付けられたように、 それを使いこなす処理の方に欠陥があればそちらを解析対象にすればよいだけなので、折角の良案も宝の持ち腐れに終ってしまいます。 ■今ページ開設のきっかけを与えて下さった有志の開発者・解析者の皆様に深く感謝いたします。 2004/04/22 REDCAT記
|