講座10 ディスクチェック解除 中編

〜前回に続き〜

ディスクチェック解除方法の中編です♪
「えぇ〜? ってことはまだ終わらないんか〜〜???」
と思われるかもしれませんが、まあそうです(苦笑
逆アセンブルリストを読みながら、解析するコツを覚えていきましょう。

ドライブチェックの後…

GetDriveTypeAの後の判定分岐処理を書き替えるところまでやりましたので、
今度はその後に待っているディスク判定処理を解除しましょう。
ここがディスクチェックのメイン部分とも呼べるところですが、解析前に一つ言っておきます。

プログラマの人の趣味が出ます(爆

これは、言いかえれば「ゲームごとにアルゴリズムが違う」ということです。
例えばAさんというプログラマが「CD内のファイル構成を元にしたチェック」を作ったら、
逆アセンブルリストにはファイルチェックをベースとしたディスクチェック処理がありますし、
Bさんというプログラマが「俺は、CDのボリュームラベルをチェックに使う」としたら、
逆アセンブルリストにはボリュームラベルチェックをベースとしたディスクチェック処理があります。

つまり、ディスクチェックには数多くのパターンがある…ということです。
(実はほとんどのチェックに「共通部分」があるんですけど、それはまた別の機会で…)
まあ、1回成功すれば後は自力で何とかできると思いますので、今回はとりあえず、
前回使ったサンプルを続行して解析しましょう。

なお、このサンプルに使用されている方法はボリュームラベルチェックです。
この形式のチェックは結構よく見かけますので、参考になると思いますよぅ(某T社とか…

ボリュームラベルを取得する処理を探す!

…と言っても、簡単です(笑
そもそも、前回すでにチェック第1段階である GetDriveTypeA を見つけているのですから、
すぐ後に見つかるのは当然だったりします。

というわけで、リストの GetDriveTypeA の下の方を見てゆくと〜…
:0040107E FF1558504000      call dword[00405058]
              ;;call KERNEL32.GetVolumeInformationA
:00401084 6838604000       push 00406038
           (StringData)"486"

…案の定でしたね(笑
GetVolumeInformationA…読んで字のごとく「ボリューム情報の取得」です。
VisualC++でプログラムを組む場合(ボリュームのみ取得したい場合)には…
GetVolumeInformation("A:\\",LabelName,sizeof(LabelName),NULL,NULL,NULL,NULL,NULL);
といった感じにします。
(上記の例では、Aドライブのボリュームラベル名を、LabelNameに格納します)
で、LabelNameに格納されたボリュームラベル名をチェックして、正しければスタート〜…
という処理を作れば、それがボリュームラベルチェックをベースとした、
ディスクチェック処理になります。簡単ですね。

さらに処理を見てゆく…

ボリュームラベルを取得したあと、以下のような処理が続きます。
:00401090 E87B010000              call 00401210                     //strcmp(文字列判定)コール
:00401095 83C408                  add esp, 00000008
:00401098 85C0                    test eax, eax                     //正常でなければ
:0040109A 750A                    jne 004010A6                      //(ラベルが不正)分岐。
:0040109C C785FCFEFFFF01000000    mov dword[ebp+FFFFFEFC], 00000001 //フラグを01(正常)にする。
--------- // 現在のドライブの検索を終了したので、次のドライブ検索に移行。
:004010A6 E971FFFFFF              jmp 0040101C //次のドライブを対象に再びGetDriveTypeに戻る。
--------- // 全てのチェックが終わったら、ここに来る(アドレス00401034の[jg 004010AB]で分岐)
:004010AB 83BDFCFEFFFF01          cmp dword[ebp+FFFFFEFC], 00000001 // もしフラグが01(正常)..
:004010B2 7502                    jne 004010B6                      // でなければ分岐
:004010B4 EB2D                    jmp 004010E3                      // (正常ならば)分岐
--------- // 全ドライブを検索した結果、不正だった場合はここにくる
:004010B6 6A04                    push 00000004
:004010B8 683C604000              push 0040603C
                      (StringData)"error"
:004010BD 6844604000              push 00406044 //[キーディスクを〜..]のメッセージ格納アドレス
:004010C2 6A00                    push 00000000
:004010C4 FF15B0504000            call dword[004050B0]
                            ;;call USER32.MessageBoxA // 警告メッセージ表示
:004010CA 83F807                  cmp eax, 00000007
:004010CD 7507                    jne 004010D6        // [はい]を押したら分岐
:004010CF 6A01                    push 00000001
:004010D1 E856000000              call 0040112C       // [いいえ]を押したらここにくる。
--------- // [はい]の処理。
:004010D6 BA01000000              mov edx, 00000001
:004010DB 85D2                    test edx, edx
:004010DD 0F8530FFFFFF            jne 00401013        // 最初からチェックやり直し。
--------- // フラグが01(正常)だった場合に来る処理。
:004010E3 6A00                    push 00000000
:004010E5 6868604000              push 00406068 //[チェック完了]メッセージ格納アドレス
:004010EA 6878604000              push 00406078 //[正常に終了しました]メッセージ格納アドレス
:004010EF 6A00                    push 00000000
:004010F1 FF15B0504000            call dword[004050B0]
                            ;;call USER32.MessageBoxA
:004010F7 33C0                    xor eax, eax
:004010F9 8BE5                    mov esp, ebp
:004010FB 5D                      pop ebp
:004010FC C21000                  ret 0010            // プログラム終了

「なんやねん、これ〜〜〜???」
と思うかもしれませんが、ここがボリュームラベルチェック後の処理です。
つまり「警告を表示するか、正常に起動するか…」です。
一応、コメントは付け加えておきましたので、解説を見る前に目を通しておくと良いですよ。

ただ、これを一気に理解しようとすると、まず間違い無く挫折してしまうと思います。
なので、主要な部分をピックアップしてみましょう。

フラグという概念

上記の逆アセンブルリストを見ていると、コメントのところどころに「フラグを〜…」という文章が見られますが、
これが、このフラグがプログラムのポイント部分です。
プログラムの最後には「フラグが01(正常)だった場合に来る処理。」もありますし、
この処理は全て、このフラグによって制御されていると言えます。
(なお、アセンブラのフラグレジスタとは違うものですので、混合しないよう気をつけてください)

つまり、このフラグを強制的に「ディスクが入った状態と同じ値」にしてしまえば、
プログラムはディスクが入っていると認識し、チェックを通過するはずです。
では、やってみましょう。

フラグをON/OFFしている部分を探す

まあ、コメント内で書いていますので気づいたと思いますが、以下の処理がそれです。
:0040109A 750A          jne 004010A6
:0040109C C785FCFEFFFF01000000  mov dword[ebp+FFFFFEFC], 00000001

上の条件分岐があり、その下に mov という命令が使われていますが、
これがディスクチェックフラグをONにするのに使用される命令です。

mov命令

上記の例で使用されている mov dword[ebp+FFFFFEFC], 00000001 には、
「アドレス ebp+FFFFFEFC の数値を、dword型(4バイト)の1(01 00 00 00)にする」
という効果があります。
もちろん、これが mov dword[00435000], FFFFFFFF であれば、
「アドレス 00435000 を FFFFFFFF にする」という効果があります。
(正しくはアドレス 00435000 に FFFFFFFF をストアする…などと言います)

「アドレス ebp+FFFFFEFC ???」と思われる方も多いと思いますが、このebpはレジスタです。
このebpにどんな数値が入っているかは、デバッガで確認しないと分かりませんが、
私の環境でデバッガで調べた結果、ebp=0063FDAC でした。

では、ウインドウズに標準で入っている「電卓」を起動してください。
(インストール時に変更しなければ、普通は「アクセサリ」内にあります)。
起動したら、メニューから「電卓の種類」を選び、これを「関数電卓」にします。
そして、そこで以下の計算をします。
0063FDAC+FFFFFEFC
これで、計算結果が 63FCA8 となればOKです。
「FFFFFEFC(4294967036)と足したのに、足す前より減ってる???」
と思った方はなかなか観察力がありますね。
まあ、この辺りは情報処理技術者資格の参考書でも読んでください。
ひょっとしたら、情報処理検定2〜3級の参考書にも書いてあるかもしれませんです。
「2進数の計算」とか「補数によるマイナスの扱い方」などのところを見てくださいな。

つまり、これにより、mov dword[ebp+FFFFFEFC], 00000001 とは、
mov dword[0063FCA8], 00000001と同じだということが分かります。
(つまり、アドレス 0063FCA8 に 01 00 00 00 を書き込むってわけです)

※注意 Windows2000やWindowsNTだとメモリ配置が異なるらしいので、動作は保証しかねます。
 必ずそのアドレスの数値を確認してから作業を行ってください。
 なお、ヒープ1の範囲が00410000〜0064FFFFでなければ、確実に失敗するはずです。

手動でフラグをいじってみる

では、キーディスクを入れずに、サンプルプログラム CHK_VOL.EXE を実行してみてください。
警告メッセージが表示されますが、そこでDBxSTANDなどのプロセスエディタでCHK_VOLを開きます。
そして、先ほどの計算で求めたアドレス 0063FCA8 を見ると〜… 00 00 00 00 となっています。
そこでキーディスクを挿入して「はい」をクリックしてディスクチェックをし、
正常終了メッセージを表示させたまま再びプロセスエディタに戻ります。
すると… 01 00 00 00 。つまり、フラグがONになっていることが確認できます。

では1度サンプルプログラムを終了し、再びキーディスク無しで起動してプロセスエディタで開きます。
警告メッセージが表示されますが、その状態でアドレス 0063FCA8 の数値を 01 00 00 00 にします。
そして、「はい」をクリックすると……もう言わなくても分かりますね。
これがフラグを変更した場合に可能なディスクチェック解除です。

次回に続く!

です。
先ほどの方法では手動でフラグを変更しましたが、次はプログラムそのものを改造し、
「必ずフラグがONになる」という処理にしてみたいと思います。
それではお楽しみに!

>>NEXT STEP