オフセット−アドレス変換の原理とCDチェック解除への応用
目次
△手順解説
初めに
EXEファイルをヘキサエディタで開いていると、文字列が格納されたオフセットに対応する、プロセスメモリにロードされたEXEモジュール上でのアドレスを知りたくなることがよくあります。ここではそのオフセット−アドレス変換の方法とその応用を説明していきたいと思います。
なお、分かりやすく言えばメモリイメージ上のアドレスを「メモリアドレス」、ディスクイメージ上のアドレスを「ディスクアドレス」と表記すべきなのですが、現状では前者を「アドレス」、後者を「オフセット」と表記するのが一般的ですので、以下でもそのように表記しています。
ちなみに「オフセット」とは「差し引き」等相対的な値を意味します。また、「オフセットアドレス」というのは「(ある地点からの)相対的な番地」という意味で、もともとメモリ上でのセグメントベースからの相対的なアドレスを示すために使われていたものです。また、相対的でなく絶対的な番地を「物理アドレス」と呼ぶケースもありますが、現状では一般的な呼称ではありません。
オフセット−アドレス変換の仕組み
EXEファイルが起動時にどのようにプロセスメモリにロードされるかは、総てEXEファイルのPEヘッダで指定してあります。そのため、先ずはそこに注目してみましょう。ちなみにPEヘッダとは、実行ファイルがOS上で実行されるために必要な情報が総て格納された箇所のことです。解析例は何でも良いのですが、とりあえず「QOH98」にしてみました。
「PeRdr」で逆アセンブルコードリストを作成して、最初の「File header information」をみていくと、EXEファイルのプロセスメモリへのロードに関連する情報が得られます。
Image base 00400000 や Section 2 Name .data PhysicalAddress 000698B4 VirtualAddress 00075000 //Virtualoffsetと同義 Size of raw data 00007E00 Pointer to raw data 00073600などです。 この「Image base」というのは、EXEファイルがプロセスメモリにロードされるスタート地点のことです。一般的にこのアドレスは0x00400000で、例外はPCゲームではまず無いので考慮しなくて良いと思います。ちなみに、例外としては、改造対策目的で起動後段階的にコードを本来とは別のメモリ領域に自己展開を行なうケースが知られています(注:パッカー使用ではない)。
次のセクションですが、これはプログラムのコードを格納した部分や、プログラムが使用する文字列や変数を格納した部分等のことで、一般的に初期化されたデータである文字列や数値は「.data」という名前のセクションに格納されます。例外についてはBlueAshさんがチュートリアルの中で説明しているのでそちらをどうぞ。また、プログラムはセクション名以外で各セクションの格納内容を指定しているので、実際にはセクション名が重要なのではありません。
このセクション情報で重要なのは「VirtualAddress」と「Pointer to raw data」です。「VirtualAddress」はこのセクションがプロセスメモリ上にロードされる、Image baseからの相対的なアドレスのことです。「Pointer to raw data」は「ディスクイメージ上の実行ファイル内で実際にこのセクションがあるオフセット」です。ちなみに、このようなImage baseからの相対的なアドレスを「RVA」(相対仮想アドレス)と呼びます。
このケースのように「VirtualAddress」と「Pointer to raw data」の値が異なることがあるため、そのケースでのアドレス補正を含めたオフセット−アドレス変換の公式は下記のようになります。
「対象オフセット」+「Image base」+「VirtualAddress」-「Pointer to raw data」=「該当アドレス」となります。「VirtualAddress」から「Pointer to raw data」引くことでアドレスのずれを計算しています。このずれは主に未初期化データのプロセスメモリ上でのマッピングに伴い発生するものです。
例えばヘキサエディタでQoh98.exeを開いて「Debug」で文字列検索すると、オフセット7A1C4でヒットします。このオフセットは「Pointer to raw data」を先頭とする「Size of raw data」(ディスクイメージ上のセクションサイズ)の範囲にあるので「.data」セクション内ということが分かります。 そして、この文字列がメモリ上にロードされるアドレスは、
0x07A1C4+0x400000+0x075000-0x073600=0x47BBC4となります。もちろんプロセスメモリエディタで実際にこのアドレスを見れば文字列が確認できます。
さらに、もしこの文字列をプログラムが参照しているなら、逆アセンブルコードリスト上には必ず「0047BBC4」というアドレスが見つかる筈です。この場合、実行ファイルのバイナリデータとしては、リトルエンディアン方式という一般的なCPUのデータ格納方式により「C4BB4700」という形でこのアドレスが格納されているので、ヘキサエディタ上で「C4BB4700」で16進データ検索すればすぐに改造すべきルーチンに対応するオフセットに辿り着くことができる訳です。
ただし、目的のアドレスが別のアドレスに格納された値という形で経由されているケースもあります。ヘキサエディタでは、その経由するアドレスに目的のアドレスを格納するコードに対応するオフセットでヒットするので、経由するアドレスで検索しなおします。逆アセンブルコードリストでは「dword ptr [00456789]」という形で、このアドレスに格納された値が目的の文字列のアドレスとなっているケースです。一部の参照文字列表示可能な逆アセンブラや文字列ダンプツールは、このような経由アドレス方式に対応していませんので注意して下さい。
なお、CDチェック警告メッセージのような特有の文字列でない場合は、文字列参照箇所が複数見つかることがあります。この場合は、ヒット数が少なければ下記のヘキサエディタによる操作を行い、ヒット数が多ければOllyDbgを用いた解析に切り替えた方が良いと考えられます。
オフセット−アドレス変換の実際
ここでは説明のためアドレス−オフセットの変換をPeRdrで取り出したPEヘッダ情報を元に行なっていますが、実際にはそのような手間をかける必要はありません。EXEファイルのPEヘッダから直接値を読むことをお奨めします。例としてQoh98.exeのPEヘッダをヘキサエディタで開いてみました。ちなみに私はヘキサエディタに「He」を使っています。なお、下のテキスト形式ダンプにはPeRdrの「--raw-dump」オプションを使いました。
00000080 50 45 00 00 4C 01 06 00-FB 2F 27 37 00 00 00 00 PE..L..../'7.... //以下PEヘッダ 00000090 00 00 00 00 E0 00 0A 01-0B 01 03 0A 00 28 07 00 .............(.. 000000A0 00 4A 07 00 00 00 00 00-70 E2 06 00 00 10 00 00 .J......p....... 000000B0 00 40 07 00 00 00 40 00-00 10 00 00 00 02 00 00 .@....@......... //B4h=Image base 000000C0 04 00 00 00 00 00 00 00-04 00 00 00 00 00 00 00 ................ 000000D0 00 A0 0E 00 00 04 00 00-00 00 00 00 02 00 00 00 ................ 000000E0 00 00 10 00 00 10 00 00-00 00 10 00 00 10 00 00 ................ 000000F0 00 00 00 00 10 00 00 00-00 00 00 00 00 00 00 00 ................ 00000100 00 F0 0D 00 C8 00 00 00-00 00 0E 00 B0 06 00 00 ................ 00000110 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 00000120 00 10 0E 00 78 81 00 00-00 40 07 00 54 00 00 00 ....x....@..T... 00000130 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 00000140 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 00000150 00 00 00 00 00 00 00 00-D8 F2 0D 00 FC 01 00 00 ................ 00000160 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 00000170 00 00 00 00 00 00 00 00-2E 74 65 78 74 00 00 00 .........text... //コードセクション 00000180 76 27 07 00 00 10 00 00-00 28 07 00 00 04 00 00 v'.......(...... 00000190 00 00 00 00 00 00 00 00-00 00 00 00 20 00 00 60 ............ ..` 000001A0 2E 72 64 61 74 61 00 00-2F 09 00 00 00 40 07 00 .rdata../....@.. 000001B0 00 0A 00 00 00 2C 07 00-00 00 00 00 00 00 00 00 .....,.......... 000001C0 00 00 00 00 40 00 00 40-2E 64 61 74 61 00 00 00 ....@..@.data... //データセクション 000001D0 B4 98 06 00 00 50 07 00-00 7E 00 00 00 36 07 00 .....P...~...6.. //ここが重要 000001E0 00 00 00 00 00 00 00 00-00 00 00 00 40 00 00 C0 ............@... 000001F0 2E 69 64 61 74 61 00 00-E2 0C 00 00 00 F0 0D 00 .idata.......... 00000200 00 0E 00 00 00 B4 07 00-00 00 00 00 00 00 00 00 ................ 00000210 00 00 00 00 40 00 00 C0-2E 72 73 72 63 00 00 00 ....@....rsrc... 00000220 B0 06 00 00 00 00 0E 00-00 08 00 00 00 C2 07 00 ................ 00000230 00 00 00 00 00 00 00 00-00 00 00 00 40 00 00 40 ............@..@ 00000240 2E 72 65 6C 6F 63 00 00-0A 8E 00 00 00 10 0E 00 .reloc.......... 00000250 00 90 00 00 00 CA 07 00-00 00 00 00 00 00 00 00 ................ 00000260 00 00 00 00 40 00 00 42-00 00 00 00 00 00 00 00 ....@..B........ //以下は省略 「.data」セクション各項目のオフセット。 以下のアドレスとオフセットはダブルワード(4バイト)でリトルエンディアン方式で格納。 1C8h: Name 1D0h: PhysicalAddress 1D4h: VirtualAddress 1D8h: Size of raw data 1DCh: Pointer to raw data
CDチェック解除への応用
アドレス−オフセット変換の仕組みが理解できたら次は実践で使ってみましょう。使用ツールとしては「He」等簡易逆アセンブル機能がついているヘキサエディタが良いと思います。なお、改造初心者の方はCDチェックの処理について、その仕組みを良く理解してから下記の手法を使って下さい。ゲーム解析スキルの向上という点を考えると、初心者の方にこのような手抜きのやり方は奨められません。
また、実際には以下で説明する方法では対処できないケースもあるので、一般的にCDチェックの解析はデバッガでGetDriveTypeA関数へのブレークポイント設定から行ないます。
- CD無しでゲームを起動してCDチェックの警告メッセージを確認。「ゲームCDをCDドライブに・・・」等。
- 「He」で「CD」を文字列データ検索し、該当オフセットを確認。
- PEヘッダを見てこの警告メッセージがどのセクションにあるか、またVirtualAddress等のアドレスやオフセットを確認。
- 「CD」文字列該当オフセットをオフセット−アドレス変換。
- 算出したアドレスをリトルエンディアン方式に変換して16進数データ検索。
- 検索がヒットしたオフセット周辺を簡易逆アセンブル。大抵PUSH命令によるMessageBoxA関数呼び出し用パラメータ設定部でヒットします。もし経由アドレスへ目的のアドレスを格納するコードでヒットしたならば、その経由アドレスで再検索して下さい。
- 簡易逆アセンブルコードリストで周辺の処理を見渡して、CreateFileA関数やFindFirstFileA関数などのCALL後の条件ジャンプ書き換えや、GetDriveTypeA関数の戻り値確認部分を見つけて05から03に変更すれば完了です。PUSH命令でパラメータを設定しCALL命令での関数呼び出しや、戻り値の判定と条件ジャンプ命令そしてMOV命令でのフラグ設定といった、一般的なCDチェックに見慣れていれば処理内容は理解できると思います。もしこのやり方で上手くいかなければ、素直にPeRdrで逆アセンブルコードリストを作成した方が良いでしょう。ダミールーチンの可能性や関数呼び出し以外のコール命令からサブルーチンでさらにチェックしているケース等が考えられます。
なお、CD−DAが再生できない環境の方には_inmm.dllのパッチを当ててMCIエラーを回避するよう奨めれば問題無いと思います。ちなみに一般的にはMCIエラー回避の解析もデバッガで行ないます。
以上のやり方に慣れれば、ケースによっては大体2〜3分程度でCDチェックを解除することができるようになります。特に「VirtualAddress」と「Pointer to raw data」が同じ値の場合は、大抵アドレス0x00400000となる「Image base」だけ考えれば良いので簡単です。
もちろん、今回解説したオフセット−アドレス変換は、デバッグモード探索や単純な警告ダイアログ表示回避などの、CDチェック解析以外のゲーム解析にも応用できますので、色々と試されてはいかがでしょうか。
ちなみに『スペシャルねこまんま57号』にはアドレス−オフセットコンバータを実装しています。