講座06 〜実戦その1〜

〜はじめに〜

今回から、逆アセンブラを用いた改造を紹介してゆきたいと思います。
旧・改造講座と同じく、一発目はCDチェック解析!!
……と行きたいところですが、いかんせん最近の作品で、なおかつCDチェックのついてる
という条件を満たすものが、自分の持ってるゲームに全然見当たりません。
今更、「誰彼〜たそがれ〜」を扱っても、他の人とネタがぶつかる可能性もありますし…。

というわけで

第1回のネタはメッセージボックスの消去です。
メッセージボックスとは…
モロにディスクチェック
とまぁこんな感じに、重要な事柄やエラーなどを画面に表示するのに使われるものです。
そして、CDチェックの警告にももちろん使われています。
つまり、今回はCDチェックを外す前準備なお話です。

C++ソースと逆アセンブルリストとの比較

簡単なメッセージボックスを表示するなら、以下のようなC++プログラムになります。
#include <windows.h>

int WINAPI WinMain(HINSTANCE, HINSTANCE,LPSTR,int)
{
  MessageBox(NULL,"メッセージボックスの内容","タイトル",MB_OK);
  return 0;
}
※Borland C++ Compiler 5.5(Free)で動作確認済み
 上記のC++ソースと、プログラム本体(以下サンプルプログラム)はここからダウンロードできます。

これは、実行するとメッセージを表示し、OKを押すと終了…というシンプルなものです。
それでは、このプログラムを逆アセンブルしてみます。
DIS K06_MES.EXE > LIST.ASM
で、 メッセージボックス表示に関する処理 は、リストの以下の部分です。
:00401153 6A00          push 00000000
:00401155 6841914000       push 00409141
:0040115A 6828914000       push 00409128
:0040115F 6A00          push 00000000
:00401161 E8A0740000       call 00408606
              ;;call USER32.MessageBoxA
いきなりこんなもの見せられても、分からない人にはサッパリですね(汗
でも、「call USER32.MessageBoxA」はメッセージボックスに関するものということは分かりますよね。

で、C++のプログラムで指定した「メッセージボックスの内容」「タイトル」のメッセージがありませんが、
これは、メッセージ内容をアドレスから参照しているためです。

アドレス 00401155 や 0040115A で「push 004091...」といった命令が使用されていますが、
このpush命令のあとにくっついてる数値のアドレスにメッセージがあります。
例えば、アドレス 00409141 の内容を見れば、「タイトル」とありますし、
アドレス 00409128 の内容を見れば、「メッセージボックスの内容」という
メッセージを確認することができます。

メッセージを見るには

実行中のプログラムから確認するのであれば、DBxSTANDなどのプロセスエディタを使えば、
そのアドレスに移動 するだけで簡単に見れます。
で、実行していない状態であるファイルそのものからメッセージを参照する場合、
拙作の、CSBという文字列検索ツールがありますので、それをダウンロードしましょう。
そして、今回のサンプルプログラムを例にすると、以下の手順を行います。

1.CSBを起動し、[ExeFileOpen]をクリックして、サンプルプログラム K06_MES.EXE を選択。
2.ウインドウ右下に[STDOUT]という欄があるので、そこの[FileOut]をクリック。
3.出力ファイル名を指定します(適当に K06.TXT とでも入力してください)。
4.出力したファイルをテキストエディタで開く。

よく分からないぐちゃぐちゃな文字が記されたテキストが表示されますが、
とりあえずひたすら画面を下にスクロールしてください。
しばらくすると、以下のような記述が見られるはずです。
Address 409128:メッセージボックスの内容
Address 409141:タイトル
もう分かったと思いますが、CSBのSTDOUT機能を用いると、メッセージボックスで表示する内容や、
そのメッセージの格納されているアドレスをテキスト形式で得ることができます。

つまり、STDOUT機能で出力したテキストでメッセージ内容とアドレスを確認し、
逆アセンブルリストでそのアドレスを検索…という手順を行えば、プログラム内の
どの部分でメッセージボックスを表示しているか? を素早く知ることができます。

メッセージボックスの消去

今度は、今回の題材でのメッセージボックス処理を見ると、
push命令4つとMessageBoxAが1つという形式になっています。
これらを無効化してしまえば、当然、メッセージボックスを消去できます。
その際に用いる命令が nop です。
これは、そのまんま何もしないという意味のある命令です。
(何もしないとはいえ命令ですので、実行時間ゼロ秒…なんてことはありません)
で、メッセージボックス処理を全部 nop にしてしまえば、メッセージボックスを非表示に…というわけです。

その方法ですが、処理のデータを全て 90 で埋めるだけです。
逆アセンブルリストを見ると、アドレス 00401153 〜 00401161(命令長含めて 00401165)までが
メッセージボックス表示処理となっていますが、ここを全て 90 にしてしまいましょう。

書き替え方法1 アドレスを書きかえる

そのまんまです。
DBxSTANDで、アドレス 00401153 〜 00401165 までの間を全て 90 で埋めればおしまいです。
ただし、今回のサンプルでは「開始の瞬間、メッセージボックスを表示」という仕様なので、
うまくいったかどうか確認できません(苦笑

書き替え方法2 ファイルを書き替える

今回は、こちらの方法のほうが良いですね。
というわけでバイナリエディタでファイルを書き替える…のですが、いかんせん、
第2回で説明したように、アドレスとオフセットには互換性がありません。
なので、メモリ上のどこに展開されたかヘッダを確認…と、作業が必要になったりしますが、
その場合は色々と面倒ですので、もっと気軽な方法を紹介します。

例え、アドレスとオフセットに互換性が無いと言っても、メモリ上でもファイルでも、データは全く同じ
ですので(当然ですねぇ)バイナリエディタのサーチ機能を用いて、
「メッセージボックス表示処理のバイナリデータを検索」します。
ただし、「だったら、アドレス 00401153 のデータが 6A 00 だから、これをサーチ〜〜」
なんてことをやると、該当件数が面白いことになります(ぉ
この方法で気をつけなければならないのは「他に見当たらないようなデータをサーチしなければならない」
ということです。
例えば、call USER32.MessageBoxA命令のバイナリデータである E8A0740000 をサーチすれば、
一発でヒットしますし、アドレス 00401153 〜 00401160 までのデータをまとめて
6A00684191400068289140006A00 とサーチしても、一発でヒットします(ここまでしなくても良いけど…

そしてヒットした位置の付近から、書き替え対象アドレス 00401153 〜 00401165 の
バイナリデータと同じものを探しましょう。
詳細は省きますが、アドレスとオフセットは「下2桁は必ず同じ値になる」ので、
それをヒントにしても良いでしょう。

てわけで、オフセット 00000753 〜 00000765 に該当しますので、ここを全て 90 にします。

実行!

で、実行すると〜…何も起こりません。
実は、何も起こらないのではなくメッセージボックスを表示しないので、そのまま終わったんです。
つまり、メッセージボックスの消去に成功したわけですね。

ところで、サンプルプログラムではpush命令4つとMessageBoxAが1つという構成になっていますが、
プログラムによっては「pushとMessageBoxAとの間に別の命令(movなど)がある」や、
「push eaxなど、数値ではなくアルファベット(正式にはレジスタと言います)のものが指定されている」
のような別パターンもありますが、上記の構成はそのままです。
push命令4つとMessageBox命令の部分を 90 にすれば消去することができます。

最後に

「じゃあ、CDチェックの警告もこの方法で外してやれ!!」
と挑戦しても、大多数のものには通用しないと思います(苦笑
メッセージボックスを消去したとはいえ、処理が「警告表示後に終了」へと流れていたら、
警告が消えただけで、プログラムはそのまま閉じられてしまいます。
(もし、「再試行処理」へ流れていたら、そのままフリーズすることも…)

そう、CDチェックを外す場合はCDが入っているかどうか?を判定、
そして、最後のCDチェック後、警告を表示するか? 正常に起動するか?を判定…
のような分岐処理があります。

次回は、この分岐処理の改造方法を紹介します。
それでは!

>>NEXT STEP