Question & Suggestion
- 以下の解説はPCゲーム解析の初心者の方を対象として執筆しました
- 以下の回答は匿名希望のリバースエンジニア有志とうさぴょんで行っています
| デバッガ・逆アセンブラ・プロセスメモリエディタ・ヘキサエディタ関連 | ExGAME関連 | その他 |
ご質問・ご意見一覧
デバッガ・逆アセンブラ・プロセスメモリエディタ・ヘキサエディタ関連
- Windows VistaでOllyDbgなどのプログラム解析ツールは動作しますか?
- 新規購入したパソコンでプログラム解析ツールが実行できません
- 逆アセンブラやデバッガを使ったゲーム解析を行うためには、どの様な知識から身に付ければよいでしょうか?
- ゲーム解析以前のコンピュータ関連基礎知識を身につけたい
- プログラム解析ツールの操作を簡単に行いたい
- 初心者はどのツールを使うべきですか?
- やはりプログラミングの勉強をしておいた方がゲームの解析に役立つのでしょうか?
- ファイルパッチコードの形式について教えてください
- 『TASM』ではAPI定義済みのインクルードファイルは使えますか?
- API関数の書式や引数に指定する値等をすばやく探し出す方法はありませんか?
- 逆アセンブルコードリスト上で文字列参照箇所をすばやく見つけるには?
- PE(Portable Executable)ヘッダの詳細情報を簡単に知りたい。
- コード無効化のメモリパッチへNOP命令を用いるべきではないと聞いたことがありますが...
- 『PeRdr』の解説文書でレジストリへの逆アセンブラ登録について書かれていますが、レジストリをいじるのはちょっと怖いです。
- ヘキサエディタとバイナリエディタの違いを教えて下さい。
- MOV命令の意味の言い方が人によって違うのですが...
- ある方が「TwinWay」のCDチェック解析で、FindFirstFileA関数を使ってCDの中身が全部揃っているかチェックしていると説明していますが、そのようなチェックをする必要性があるのでしょうか?
- 某PCゲームなのですが、改造ツールを起動するとゲームが強制終了します。
- 某PCゲームの2重起動(多重起動)制限を解除したいのですが、どうアプローチすれば良いか分かりません。
- 改造済の実行ファイルを配布したいのですが。
- プロセスメモリエディタの検索で判明したパラメータのアドレスが変なメモリ領域にあります。
- 「環境依存型変動アドレス」について教えてください
- 相互修復型パラメータの書き換え方法を教えてください。
- プログラムの参照文字列を解析の端緒とする場合の注意点を教えて下さい。
- ゲームのシナリオ表示等、文字列を画面に「描画」する処理箇所を特定するためのアプローチ方法が分かりません。
- タスク切り替えが出来ないゲームの対処法を知りたい。
- フルスクリーンのゲームをウィンドウモードでプレイする方法を教えて下さい。
- PrintScreenキーでスナップショットが取れないゲームの仕組みを教えてください。
- ゲームが自己プロセスを隠蔽することは可能ですか?
- ゲームの当たり判定解析のアプローチが分かりません
- 「逆アセンブルコードの再利用」について教えてください
- 日本語版のOSでは起動させないゲームの仕組みを教えてください
- PCゲーム解析関連情報の管理はどのようにするべきですか
- ゲーム実行PCの固有情報を鍵として暗号化されるセーブデータを別のPCで使用したい
- アプリケーションの処理実行速度を変更するソフトウェアの仕組みを教えて下さい
- ゲーム上でランダムに設定されるデータ(出現アイテムの種類など)を固定化したい
- 「倍直」とは何ですか
- ゲームなどで実行時に作成される「一時ファイル」を残したい
ExGAME関連
△デバッガ・逆アセンブラ・プロセスメモリエディタ・ヘキサエディタ関連
Windows VistaでOllyDbgなどのプログラム解析ツールは動作しますか?
(注)この回答は、基本機能への限定的な動作確認に基づくものであり、詳細な検証を行った結果ではありません。基本的にWindows Vistaでは、Windows XP SP2で正常動作するほとんどのプログラム解析・改造ツールは動作可能と見られます。ただし、処理をネイティブAPIやカーネルに強く依存するといった特殊なツールではまったく動作しないものもあります。動作上致命的な問題が生じるケースについては、次の質問の回答も参照してください。
扱いに注意が必要なのは、デバッガやプロセスメモリエディタなどプロセスへの操作を行うツールです。Windows VistaではUAC(User Access Control:ユーザーアカウント制御)というセキュリティ関連機能により、管理者としてログインしていても通常のプログラム実行の権限は標準ユーザー扱いとなります。そのため、エクスプローラ上でEXEファイルをダブルクリックするような通常の起動では、自動的に権限のフィルタリングが行われ、デバッガとして十分な実行権限が与えられません。ここでもし解析対象プロセスも標準ユーザーの権限で実行されているものならば、デバッガやプロセスメモリエディタによる解析は、レジストリの操作やプロセス関連情報取得などに制限はあるものの一応問題なく行えます。しかし、この場合はなにぶんデバッガなどに本来必要な権限がないだけに、解析にあたり何らかの不具合が発生してもおかしくない状況といえます。
Windows Vistaでは、管理者としてプログラムを実行する際には、その都度確認のダイアログが表示されとても煩雑です。このことを併せて勘案すれば、デバッガやプロセスメモリエディタに関しては、まず通常どおり(標準ユーザーとして)実行してプログラム解析を行い、なんらかの問題が発生したら管理者として実行するという方法が、利便性と安全性の面からみた妥協案になると考えられます。また、UACの制御を簡易化し、権限昇格確認のダイアログ表示を抑制するツール『TweakUAC』を利用するという方法もあります。
Windows Vistaでは、管理者の権限でプログラム解析ツールを実行するためには以下の方法が使用できます。
- エクスプローラ上でプログラム解析ツールのEXEファイルを右クリックし、表示されるポップアップメニューから「管理者として実行」で実行させる
- Vista専用のマニフェストファイルを使用する(使用法など詳細はマニフェストファイル同梱テキストにあります)
- エクスプローラ上でプログラム解析ツールのEXEファイルかそのショートカットを右クリックして、ポップアップメニューの「プロパティ」→「互換性」タブ→「管理者としてこのプログラムを実行する」チェックボックスを有効にする(マニフェスト使用よりも若干権限が低くなります)
- UAC(ユーザーアカウント制御)の設定を無効にする(セキュリティ確保の面からお奨めしません)
OllyDbgでは、ジャスト・イン・タイム デバッガとしてOllyDbgを登録したり、エクスプローラが表示するポップアップメニューにOllyDbgを登録する際には、レジストリの書き込み処理を成功させるために管理者として実行する必要があります。この登録処理は権限の不足により失敗してもエラーメッセージは表示されません。
Windows Vistaが、実行されるプログラムの発行元を認識する際に参照する、実行可能ファイルに付加されるデジタル署名は、認証元の資料によると法人組織ではないプログラマ個人では取得ができません。つまり、個人製作のフリーウェアなどでは、発行元が認識不能という警告メッセージの表示を回避することはできません。ただし、実状としてWindows Vista用カーネルドライバならば、アメリカのGlobalSign社に認証を依頼すれば、プログラマ個人が身分証明を行う等の煩雑な手続きを経ることで、デジタル署名を取得可能です。
Windows Vistaでの「OllyDbg」や「うさみみハリケーン」のインストール先フォルダは、既定の「Program Files」フォルダ内とは別の場所に新規作成することをお奨めします。これは、両ソフトで各種設定保存に使用するINIファイルが、Vistaで実装されたUACのファイル/レジストリ仮想化機能(VirtualStore)の影響を受けないようにするためです。これにより、従来どおり関連ファイル一式がインストール先フォルダ内のみに集約され、関連ファイルが他のフォルダに分散することを回避できます。
VirtualStoreにより、Program FilesフォルダやWINDOWSフォルダ内で行われたファイル作成・読み書きアクセスは、VirtualStore専用フォルダにリダイレクトされます。このフォルダは、Vistaのスタートメニュー下部「検索の開始」箇所に以下のいずれかの文字列を貼り付けることで、当該フォルダをエクスプローラで開くリンクが表示されます。
%USERPROFILE%\AppData\Local\VirtualStore
%LocalAppData%\VirtualStore
解析ツールがキーフックによる各種操作などで、内部的にメッセージフックを用いる場合には注意が必要です。この場合、解析ツールから解析対象アプリケーションを起動することなどにより、解析する側とされる側の実行権限を同じレベルに合わせないと、メッセージフックが正常に動作しません。ちなみに、同様の制限が一部のプロセス間通信にも適用されます。
●<参考>当サイト管理人の動作確認環境と動作確認時実行手順
・Windows Vista Home Premium 32bit クリーンインストール
・UAC(ユーザーアカウント制御)は有効
・インストール先フォルダは「C:\APP\UsaMimi」や「C:\APP\Olly」
・起動時はエクスプローラ上でEXEファイル右クリックから[管理者として実行]
新規購入したパソコンでプログラム解析ツールが実行できません
パソコンの買い替え等に伴い、実行環境がWindows XPからVistaに変更された場合、XP上では問題なく動作する一部のプログラム解析ツール(以下「解析ツール」)が実行できないケースがあります。以下に想定される原因を列挙しました。- DEP
OSに実装されているDEP(データ実行防止)機能により、プログラムの処理上でDEPを想定していない一部の解析ツールは、実行不能となります。ただし、OSのDEP初期設定では、システムに関わる重要なプロセスのみがDEPの対象として設定されています。そのため、設定を自分で変更し、全てのプロセスをDEPの対象にした場合に、表題のような実行不能になるケースが生じます。他のケースとしては、一部のショップ製パソコンで、出荷時にDEPの設定を変更しているケースが挙げられます。
- OSバージョンチェック
- API関数の仕様変更
- その他
通常、DEPによりプロセスの実行が中断された場合は、OSがそのことをユーザーに警告します。しかし、Windows Vista Home Premiumで確認した限りでは、一切の警告なしにプロセスの実行が中断され強制終了するケースもあります。
もし、DEPが解析ツール実行不能の原因と推測される場合は、DEPの設定を確認し、解析ツールのEXEファイルを対象から除外してみてください。 VistaでのDEPの設定は、以下の手順で行います。
1.スタートメニューから、[コントロールパネル]→[システムとメンテナンス]→[システム]→[システムの詳細設定]をクリックします。あるいは、スタートメニュー下部「検索の開始」箇所に以下の実行可能ファイル名文字列を貼り付け、表示されるリンクをクリックします。
SystemPropertiesAdvanced.exe
2.管理者としてのアクセス許可の後、[パフォーマンス]欄の[設定]ボタンをクリックします。
3.[データ実行防止]タブをクリックし、オプション[次に選択するのものを除くすべてのプログラムおよびサービスについてDEPを有効にする]が選択されているならば、[追加]ボタンで、解析ツールの実行可能ファイルを除外対象に追加します。設定変更内容を反映させるため、必要に応じて再起動させます。
一般的なソフトウェアならば、OSに実装されている「互換モード」により対処可能なケースもあります。しかし、特にプロセスを対象とする解析ツールでは、一部のプロセス操作用API関数でOSによって仕様が異なるため、「互換モード」適用では対処できないケースがあります。
望ましい対処策として、作者に対して状況と当該解析ツールの必要性を説明し、Vistaへの対応を打診してみることをお勧めします。
たいていの場合、プログラムからOSのバージョン情報を取得する際には、GetVersionEx関数を使用します。もし、作者側での対処が望めない場合は、ユーザーの自己責任においてバージョン情報取得処理にパッチをあてるという対処策があります。
実例として、拙作の汎用プロセスメモリエディタ兼デバッガ「うさみみハリケーン」では、Vista対応にあたり、複数のプロセス関連処理箇所でソースコードを書き替えました。
このようなケースでは、作者にVistaへの対応を希望することで、最も適切な対処が行われると考えられます。
なお、解析ツールの作者側としては、上記のケースとは逆に、Vistaでの正常動作をもってWindows 2000/XPで正常に動作すると判断すべきではないことに、注意が必要です。
関連して、Windows OSに含まれる、DbgHelp.dllがエクスポートしている一部のAPI関数では、内部処理に用いられる構造体がXP SP2とVistaで異なります。そのため、この構造体の変更を想定していない一部の解析ツールにおいて、XP SP2では正常に動作するが、Vistaではクラッシュするというケースもあります。この場合は、作者の動作確認環境に含まれるDbgHelp.dllを、ツールに同梱して再配布することで対処可能です。DbgHelp.dllは実行環境におけるバージョン差異に対処するために、再配布が許可されているDLLです。
逆アセンブラやデバッガを使ったゲーム解析を行うためには、どの様な知識から身に付ければよいでしょうか?
逆アセンブラやデバッガを使う前に、先ずヘキサエディタやプロセスメモリエディタ等の基本的な改造ツールの操作を身に付けて下さい。それからゲーム解析チュートリアルに加えて、インテルのアーキテクチャマニュアル、プラットフォームSDK等を揃えてアセンブリ言語とWindowsAPIの基本事項(レジスタ、条件ジャンプ等各種命令、各種API関数他)を理解し、その上で逆アセンブラやデバッガを用いたゲーム解析を始めれば良いかと思います。また、上に挙げた情報ソースは、面倒でも疑問点があれば何度でも参照して正しい知識を身に付けて下さい。また、基本事項をより理解するために、上記基本的な改造ツールやデバッガのヘルプには十分に目を通し、さらに必要に応じてネット上で検索サイトや用語集サイトを利用したり、当サイトで紹介している解析参考書籍を参照することをお奨めします。
なお、知識が不十分である事柄について他の方に教える場合は、「自分も詳しくは無いのですが」等の一文を加えて知識が不十分であることを明確にしておけば、よほどの勉強不足で無い限り、もし内容に予期しない間違いがあっても非難されることは無いと思います。
過去の実例を見る限り、アセンブリ言語やWindowsAPIに関する基本的な知識を身に付けないまま逆アセンブラやデバッガを使ったゲーム解析に挑んだ方々が、解析の方向性を定められず無意味な解析に時間を浪費し、最終的に挫折してしまうケースが少なくありませんでした(勿論私のケースを多々含みます)。
しかし、上記のアセンブリ言語とWindowsAPIの基本事項に加えて、解析対象のゲームに対して「自分がどのような解析・改造を行いたいか」を明確にした上で、その解析に必要な正しい知識をチュートリアル等を通じて身に付けてから解析に臨めば、大抵のゲーム解析は特に迷うこともなくスムーズに解析できるはずです。勿論逆に、現在の自分のスキルでは対処できないケースという判断も的確かつ迅速に行えます。
必要な知識が欠如していると、「闇雲に書き換え」等、あまりにも非効率的な解析を行うことになります。このような解析は、私を含めた複数のゲーム解析関連の方の経験から言えば、ゲーム解析スキル向上には殆ど役に立ちませんので、十分にご注意下さい。
以上が簡略化した「一般的な」回答ですが、実際には質問された方のスキルの程度や改造目的等に応じてケース・バイ・ケースで詳細に回答しています。
ゲーム解析以前のコンピュータ関連基礎知識を身につけたい
当サイトで紹介しているプログラム解析の参考書籍でもこのような基礎知識は解説しています。しかし、それとは別に、後々ゲーム解析にスムーズにリンクすることを踏まえて、私や他のリバースエンジニアの方々が超初心者の方に勧めている解説書は以下が挙げられます。・プログラムはなぜ動くのか
・はじめて読む8086
なお、『はじめて読む8086』と同じ筆者による『はじめて読む486』『はじめて読むPentium マシン語入門編』も出版されており、できればこの3冊を読み比べて、自分が求める内容に一番近いものを選択されることをお勧めします。
上記の解説書に記述されている情報は、ネット上で入手できない訳ではありません。実際、この様な解説書に頼らず、ネット上で資料を収集して学ばれた方もいます。しかし、超初心者の方が短時間で体系的にコンピュータ関連基礎知識を身につけるには、定評のある上記の解説書を用いるのが適切と考えられます。 ただし、これらの解説書に書いてあることだけにとらわれず、疑問に感じた点はネット上で調べる等、自分で知識を拡げていくという自己努力の姿勢も大切です。「読んだ」だけで満足しないよう注意して下さい。
プログラム解析ツールの操作を簡単に行いたい
一般的なプログラム解析ツールは、ユーザーが簡単に操作できるようにするために、色々な工夫をこらしています。基本的に、このような操作簡易化のための仕組みは、プログラム解析ツールに同梱されているヘルプで解説されています。しかし、たとえば、プログラム解析ツールの作者が、ユーザーには基本的なツール操作やプログラミングの知識があるはずと考えるケースでは、ヘルプでの説明が省略されていることもあります。以下では、特に解析初心者の方が知っておくと役立つ、操作簡易化のための仕組みを列挙しました。以下のうさみみハリケーンに関する記述については、ヘルプに詳細な解説がありますので、参照されることをお勧めします。
●16進数の指定方法
プログラム解析ツールによっては、10進数での数値指定とは別に、16進数での数値指定もできるようにしています。指定方式にはいくつかのパターンがありますが、「0x12345678」というように「0x」をつけるケースが多いといえます。うさみみハリケーンでのプロセスメモリ検索機能・一括書き込み機能では、「0x」で16進数指定が可能です。
●バイナリデータの指定方法
一部のプログラム解析ツールには、検索などでバイナリデータを指定する際に、「シーケンス」や「マスク」という方法を使用可能なものもあります。シーケンスとは「1 0x1234 86 120 0xABCDEF00」というように、指定された複数のデータを連結して一つのデータとして扱う方法です。うさみみハリケーンのプロセスメモリ検索機能・一括書き込み機能はシーケンスに対応しています。マスクは指定するバイナリデータの一部を無視して処理を行う方法で、「ワイルドカード」と表記されることもあります。マスクの使用例としては、検索対象のバイナリデータに「7FFF****05*0」と指定して、「*」部分は無視して検索を行うケースが挙げられます。うさみみハリケーンでは、範囲検索(64Bit Mode)でバイト列を検索する際にマスクを使用可能です。
●各種クリックへの機能割り当て
プログラム解析ツールでは、リストやバイナリデータ表示画面などで選択した項目に対して、左クリックでの選択以外に各種クリックが使用可能なケースもあります。このようなケースではたいてい、選択項目に対して、ダブルクリック、右クリックあるいは、ホイールボタンクリックに、その選択項目に応じた処理を行うための機能を割り当てています。そのため、画面上で何かを選択したら、まず右クリックなど各種クリックを試してみることをお勧めします。また、リスト上で項目選択後に、ダブルクリックにならない間隔で同じ項目を選択すると、項目表示部分の文字列を編集できるようになるケースもあります(リストビューの「ラベル編集」)。この場合、項目文字列の右クリックにより、項目文字列を簡単にコピーすることが可能です。
初心者はどのツールを使うべきですか?
ツールの選択基準としては、解析者のスキル・解析対象・アプローチ手法・ツールの実装機能・その他の要素があり、一概にどれを使うべきとはいえません。そのため、ここに列挙した選択基準等を参考に、自分にあうかどうか色々なツールを解析の実践で試されることをお奨めします。このことは、自分なりの解析スタイルを模索するということにおいても、とても重要な要素といえます。サイト管理者としては、当サイトで紹介しているツールを使って頂ければ幸いです。
やはりプログラミングの勉強をしておいた方がゲームの解析に役立つのでしょうか?
アセンブリ言語やC言語のプログラミングの知識がゲーム解析に役立つのは事実ですが、必須とまでは言えません。個人的にはアセンブリ言語でダイアログベースのゲーム改造ツールを製作可能な程度のスキルは身に付けた方が良いかと思います。またC言語については、直接的にWindowsAPIを用いないMFCベースでプログラムを組めてもゲーム解析にはさほど役に立ちません。SDKベースで簡単なプログラムを組めるようにしておくことをお奨めします。アセンブリ言語でのプログラミングは当サイトの「改造初心者向け練習用プログラム No1」、また、MFC(Microsoft Foundation Classes)及びSDK(Software Development Kit)の意味等についてはリンク先の「日経ソフトウェア用語集」等を参照して下さい。端的に述べれば、SDKベースの方がWindowsAPIとその機能の実際を理解する上で大いに役立ちます。
なお、アセンブリ言語に関する基礎知識とアセンブリ言語によるプログラミングの参考書としては、『アセンブリ言語の教科書』をお奨めします。アセンブリ言語を使用して自分でレジスタ・スタック・API関数などをどう使うか考えながらプログラムを組み、さらにそのプログラムの実際の処理をデバッガで解析することは、PCゲームの解析において大切な「処理の流れを理解する」力をつける上で役立ちます。特に超初心者の方は、PCゲームで解析を実践するよりも先に、処理内容を把握しており解析しやすい自作プログラムで解析の練習を行っておくと、後々PCゲームの解析へのステップアップがスムーズになります。
C言語については、フリーで配布されている『Borland C++ Compiler 5.5 』で簡単なアプリケーション作成から始めてみては如何でしょうか。
関連して、フリーで入手できるコンパイラ『Delphi 6 Personal』のインラインアセンブラ機能がアセンブリ言語の勉強になるのではというご意見を頂いています。『Delphi』が使用する言語は「Object Pascal」で、PCゲームの大半の開発に用いられているC/C++言語でないものの、試してみる価値は十分にあると思います。今後「Object Pascal」で開発されたPCゲームが増えれば、当サイトで解析手法等を解説したいと考えています。なお、『Delphi 6 Personal』には『TASM』は付属していません。
ファイルパッチコードの形式について教えてください
バイナリファイルのパッチコードについては、コマンドプロンプトでのfcコマンド(/bオプション)の出力形式をベースにした差分形式と、単純にオフセットと書き込むバイナリデータを指定したり、書き換え対象オフセットの範囲とその範囲を埋めるバイナリデータを指定するといった(ゲーム用)改造コード形式が一般的です。なお、この差分形式は、アンダーグラウンドでメジャーなバイナリファイル書き換えソフトの名称からFireFlower形式とも呼ばれています。また、この改造コード形式は、アドレスとバイナリデータを指定してプロセスメモリの書き換え用改造コードにも使われています。オフセットとアドレスを混同しないよう注意して下さい。
差分形式の場合は、「*」の後がコメント、「FILENAME 」の後に書き換え対象ファイル名、オフセットは8桁で設定するのが一般的です(書き換えソフトによって異なるケースあり)。下記の場合、3行目ではオフセット0x2054のバイナリデータが「74」ならば「EB」に書き換えることを意味します。
*Ver7.4用 FILENAME Target.exe 00002054: 74 EB 000051AC: 55 C3改造コード形式の場合は以下の形式が一般的といえます。1行目ではオフセット0x010000から5バイトを「E703646401」で書き替えることを意味します。2行目はオフセット0x0100から0x01FFを指定バイナリデータ「E703」を繰り返し書き込んで埋めるという意味です。いずれも、差分形式のような書き換え前データの照合は行いません。
010000-E703646401 0100>01FF-E703上記差分形式及び改造コード形式のバイナリファイル用パッチコードの実行と、差分形式パッチコードの作成については、『スペシャルねこまんま57号』の「簡易バイナリファイル書き換え」機能が対応していますので、ご利用頂ければ幸いです。『スペシャルねこまんま57号』ではファイルマッピングを使用しており、書き換え対象ファイル全体をメモリ上に読み込みませんので、巨大なバイナリファイルでもフリーズすることなく高速に書き換えを行うことが可能です。
『TASM』ではAPI定義済みのインクルードファイルは使えますか?
使用可能です。ただし、当サイト公開ソースコードでは逆アセンブルコードリストとの様式の乖離を避け読解し易くするため、意図的にインクルードファイルを用いていません。ちなみに関数や構造体定義の例は下記の様になります。WriteFile PROCDESC WINAPI :HANDLE, :LPCVOID, :DWORD, :LPDWORD, :LPOVERLAPPED FILETIME struct ft_dwLowDateTime DWORD 0 ;low-order 32 bits ft_dwHighDateTime DWORD 0 ;high-order 32 bits FILETIME ends FILETIME_ equ 4+4
API関数の書式や引数に指定する値等をすばやく探し出す方法はありませんか?
まず、プラットフォームSDKの最新版を入手してください。API関数の書式についてはプラットフォームSDKでの関数名検索がお奨めです。他にもMSDNサイトあるいは各種サーチエンジンで関数名を使って検索するという選択肢があります。なお、API関数にはその書式から使用例のC言語ソースまで掲載している解説本も複数出版されていますが、それを用いる場合は必ずMSDNサイト等でAPI関数の仕様変更他、最新の情報にも触れておいた方が良いと思われます。例えば、同じ関数でもWindows9xと2000/XPで仕様(挙動)が異なるものが少なくありません。引数については、プラットフォームSDKで目的の関数に用いるインクルードファイルと引数に指定する定数名を確認した上で、同インクルードファイルをテキストエディタで開いて定数名を検索すれば定数の実際の値を得られます。このインクルードファイルは、プラットフォームSDKだけでなく各種コンパイラにも付属しています。実際に頻用するインクルードファイルは、「Kernel32.DLL」等に対応する「winbase.h」や「User32.DLL」に対応する「winuser.h」等です。なお、アセンブラでのコーディング用に主要な関数が用いる定数名と実際の値を定義している汎用インクルードファイルも公開されていますので、それを参照するという選択肢もあります。
逆アセンブルコードリスト上で文字列参照箇所をすばやく見つけるには?
『OllyDbg』にはプログラムが参照する文字列一覧をアドレス付きで表示する機能や、対象文字列先頭アドレスを指定して参照コード一覧を表示する機能があります(「OllyDbg Q&A」参照)。『OllyDbg』を使用しない場合は、『W32Dasm』の様に、参照文字列一覧を表示しワンタッチで逆アセンブルコードリスト上での参照箇所を確認できるツールを使うと良いと思います。『W32Dasm』」以外では『lmn』に同様の機能があります。しかし、この様な参照文字列抽出は文字列認識アルゴリズムとの関係上完璧に行われない場合も少なくないため、「DATA」または「.data」セクションを『OllyDbg』上でダンプしてその内容を視認しておくことをお奨めします。
なお、対象実行ファイルが『Delphi』または『C++ Builder』でコンパイルされている場合は、コードセクション内(「CODE」や「text」)にダイアログの設定フォームに関する文字列等が含まれます。特に『Delphi』では警告メッセージ文字列等もコードセクション内に含めることが可能です。しかし、現状では実行ファイルが『Delphi』または『C++ Builder』でコンパイルされているゲームはごくまれですので、参考程度に考えておいてください。ちなみに、『Delphi』または『C++ Builder』で用いられるダイアログの設定フォーム本体はリソースセクションに格納され、『eXeScope』や『OllyDbg』でその内容を確認することができます。
また、「DATA」セクション等に格納された、プログラムが認識する文字列は、値00hを文字列終端としていることに注意が必要です。
PE(Portable Executable)ヘッダの詳細情報を簡単に知りたい。
まずPEヘッダについて書かれた資料を読んでおくことをお奨めします。これはファイルフォーマット情報を扱うサイトで資料を入手可能です。詳細については「Microsoft(R) Portable ExecutableおよびCommon Object File Format仕様書」が参考になります。実際のPEヘッダ詳細情報は、まず『OllyDbg』で目的の実行ファイルをオープンし、メニューの「View」から「Memory」でメモリマップを表示します。この時点で「CODE」や「DATA」等の各セクションの簡単な情報を得ることができます。さらに、メモリマップの一番上の列にある、「Contains」が「PE header」となっている列を選択・右クリックから「Dump」でPEヘッダの詳細情報が表示されます。
なお、同様の機能が『eXeScope』にも実装されています。
コード無効化のメモリパッチへNOP命令を用いるべきではないと聞いたことがありますが...
結論からいえば、コード無効化のメモリパッチへNOP命令を用いても構いません。ただし、NOP命令は「何もしない」のではなく、実際には「(E)AXレジスタと(E)AXレジスタの内容を入れ替える」という処理を行っており、当然ながら処理に相応の時間を要します。そのため、メモリパッチでコードを無効化したい範囲があれば、ジャンプ命令で無効化したい範囲の先頭と終端を結ぶバイパス書き換えで対処するのが「美しいコーディング」といえます。この際、メモリパッチ箇所の視認性や可読性を高めるためのパディング(不要箇所への詰め物)に、「実行されないコードとして」NOP命令を用いるのは問題ありません。なお、コードを無効化したい範囲あるいはコード書き換え後の余剰範囲が4バイト以下程度であれば、NOP命令による対象範囲上書きでも適切といえます。ちなみに、質問にあるNOP命令使用への否定的な意見は、決して誤りではありません。過去に海外で発売された一部のゲームは、改造対策として「連続NOP処理検出機能」を実装していたためです。ただし、現在では改造対策に「連続NOP処理検出機能」よりも優れた多くの方法が編み出されており、今後「連続NOP処理検出機能」が即時ゲーム終了フラグや時限起動型の改造トラップ用フラグ設定に用いられる可能性は低いと考えられます。そのため、個人的には「コード無効化のメモリパッチへNOP命令を用いても問題ない」と判断しました。
なお、一部のコンパイラは「Code Align」機能により、コーディングに関係なくコンパイルされた実行ファイルにはNOP命令が多用される仕様となっています。
『PeRdr』の解説文書でレジストリへの逆アセンブラ登録について書かれていますが、レジストリをいじるのはちょっと怖いです。
『PeRdr』に同梱されているREGファイルを使えば、手動でレジストリを書き替える必要はありません。また、このREGファイルにより変更される箇所は拡張子との関連付けを設定しており、Windowsの中核であるシステムには関与していないため、たとえ同梱REGファイルを使わず手動で少々間違えて書き替えても、深刻な状況を招くことはまずありません。また、レジストリの書き換えに不安のある方は、あらかじめ書き換え箇所の部分的なバックアップを作成することをお奨めします。例えば、上記REGファイルではレジストリの「HKEY_CLASSES_ROOT\exefile\shell\」以下を書き替える訳ですから、レジストリエディタでこの「shell」の部分を選択して、メニューから「レジストリ」→「レジストリファイルの書き出し」でレジストリ該当部分のバックアップが作成できます。これにより、「HKEY_CLASSES_ROOT\exefile\shell\」以下の必要以外の部分を間違って書き替えても、上記REGファイル同様に、バックアップのREGファイルをエクスプローラ上でダブルクリックすればレジストリの該当箇所がバックアップで上書きされます。
また、Vector等で配布されている、フリーウェアのレジストリバックアップツールを用いるという選択肢もあります。
ヘキサエディタとバイナリエディタの違いを教えて下さい。
正確な表記か否かです。正式名称:ヘキサデシマルエディタ
略称:ヘキサエディタ
テキストファイルエディタとの対比用:バイナリファイルエディタ
誤り:バイナリエディタ
いわゆる「バイナリ」という単語には「2進法の」という本来の意味と、それから派生した、「(データが)0と1で構成される」という2つの意味があります。上記を見て頂ければ分かると思いますが、「ヘキサエディタ」との対比という点で「バイナリエディタ」とは前者の意味であり、「2進法即ち0と1のみを用いた書き換えによるファイル編集ソフト」を指すことになります。また、「バイナリ」を「0と1で構成される」の意味で用いる場合は「バイナリデータ」、更に「バイナリデータ」で構成されるテキスト形式以外のファイルは、上記の通り「バイナリファイル」と表記すれば「バイナリ」という単語の意味の正しい使い分けができますので、この様な表記が適切と言われています。
実際のところ、日本では上記の2つの意味を混同した、ヘキサエディタを「バイナリエディタ」とする表記が定着しており、その表記が問題にされることは現在では殆どありませんし、私は「ヘキサエディタ」という表記を強要するつもりもありません。しかし、英語のネイティブスピーカーであるリバースエンジニアに対しては、「バイナリエディタ」は一応意味は通じるものの「変な英語」に聞こえているようです。 当然ながら海外では「Hex editor」あるいは「Binary file editor」と表記するのが一般的です(リバースエンジニアのサイトでは特に)。ちなみに、海外のソフトで本物の「バイナリエディタ」(0と1のみを用いた書き換えによるファイル編集ソフト)があります。
個人的には、日常使うに当たっては特に「バイナリエディタ」でも構わないと考えています。しかし、さすがにチュートリアル等で人様に解説するとなれば、「ヘキサエディタ」という正しい表記を用いるのが、そのチュートリアル等を読んで頂く方に対して失礼が無いのではと思われます。現在は1つも公開していませんが、私が過去に執筆した総てのチュートリアルでは「正しい表記」を心がけています。
MOV命令の意味の言い方が人によって違うのですが...
MOV命令は、本来の意味はデータの「転送」ですが、その使い方によって表記が異なります。1.メモリからメモリへ→「転送」
2.レジスタからレジスタへ→「転送」
3.メモリからレジスタへ→「ロード」
4.レジスタからメモリへ→「ストア」
なお、ここでの「転送」は転送元データが消去される訳ではないので要注意です。意味としては「コピー」と考えた方が良いでしょう。このような誤解を避けるために「代入」と表記しているケースもあります。
(注)上記1のメモリ間転送は、MOV命令を2回用いてロードとストアを連続で行うことを意味します。メモリ間での直接転送は、アドレッシングモード(レジスタ・セグメントレジスタ・メモリ・データのデータ転送可能組み合わせ)に含まれていません。
mov ax,word ptr [00450000]
mov word ptr [0045048C],ax
メモリ間転送の転送対象メモリサイズが4バイトならば、スタックを利用するという選択肢もあります。
push dword ptr [00450000]
pop dword ptr [0045048C]
同転送対象がメモリブロックならば、ストリング操作命令とリピートプリフィックス命令を組み合わせます。ここでは処理の前後でレジスタの内容を変化させないように、pushad及びpopad命令で汎用レジスタの退避と復旧も行っています。
pushad
cld
mov ecx,20h
mov esi,00450000h
mov edi,0045048Ch
rep movsd
popad
ある方が「TwinWay」のCDチェック解析で、FindFirstFileA関数を使ってCDの中身が全部揃っているかチェックしていると説明していますが、そのようなチェックをする必要性があるのでしょうか?
必要性はありません。製品版でCDの中身が全部揃っていないケースは通常考えられないからです。なお、このCDチェックではゲームCD認証ファイルである「bgm15.wav」というファイル1つだけを検索しており、CDの内容を総て確認している訳ではありません。FindFirstFileA関数はフォルダから指定の検索ファイル名と名前が一致する最初のファイルを検索する関数ですので間違わないよう注意して下さい。また、私はこのゲームは購入していませんので未確認ですが、このゲームのCDチェックの場合、コードに不可解な部分があり、そこが一番説明すべきポイントだと言われています。
(注)ある方の連絡先は非公開とのことなので、この件に関してお知らせすることができませんでした。
某PCゲームなのですが、改造ツールを起動するとゲームが強制終了します。
このケースは、「スペシャルねこまんま57号」を含む国産の改造ツール9種類を、ウィンドウタイトルあるいはウィンドウクラス名で探索し、該当ウィンドウが存在した場合は即座に終了するという、改造対策の典型的なケースでした。このようなケースでは、大抵FindWindow関数か、EnumWindows関数とGetWindowText関数・GetClassName関数併用等の手法が用いられます。「スペシャルねこまんま57号」の場合、このようなケースではウィンドウタイトルの変更で対処します(クラス名はダイアログ標準なのでウィンドウタイトル変更だけで対処可能)。近年、デバッガ検出・メモリ改竄検出・実行ファイルの改竄検出・独自のパッカー使用等、改造対策の多様化が進んでいます(「OllyDbg Q&A 参照」)。 また、改造ツールを検出すると、自分でなく改造ツールを強制終了させるゲームも現れましたので注意が必要です。
某PCゲームの2重起動(多重起動)制限を解除したいのですが、どうアプローチすれば良いか分かりません。
一般的な2重起動制限解除アプローチとしては、CreateMutex関数呼び出し後にGetLastError関数戻り値を定数ERROR_ALREADY_EXISTS(B7h)と比較しているコードを探し出し、その後に続く条件ジャンプを書き換えます。なお、2重起動の余地がある不完全な手法ですが、OpenMutex関数を使用するケースもあります。特定名のミューテックスをこの関数でオープンして、戻り値がゼロでなければ2重起動と判断します。
他の2重起動検出手法としては、自分が既に起動していることを、自分と同じウィンドウタイトルやウィンドウクラス名を持つウィンドウの存在を探索した結果で判断するケースもあります。この場合は、FindWindow関数か、EnumWindows関数とGetWindowText関数・GetClassName関数併用等の手法が用いられます。
さらに、SetProp関数でウィンドウのプロパティリストに識別用の文字列を設定しておき、EnumWindows関数等でウィンドウを列挙しながらGetProp関数でその文字列を持つウィンドウが存在するか確認することも可能です。GetProp関数の戻り値がゼロでなければ2重起動と判断します。
また、CreateFileMapping関数で特定名のファイルマッピングオブジェクトを作成し、GetLastError関数の戻り値を定数ERROR_ALREADY_EXISTS(B7h)と比較することで2重起動を検出可能です。
当方では未確認ですが、CreateEvent関数を使用したケースもあるようです。2重起動検出手法はCreateFileMapping関数のケースと同じになります。
基本的に、どうアプローチすれば良いか分からない場合は、思いつくキーワードで検索することから始めると良いでしょう(このケースでは「2重起動防止」等)。
改造済の実行ファイルを配布したいのですが。
日本以外の東アジア諸国や東南アジア諸国のゲーム解析系サイトでは、CDチェック解除済等の改造済ゲーム実行ファイルが普通に配布されています。しかし、日本では著作権法と不正競争防止法に抵触すると言われており、配布は控えた方が良いと思われます。プロセスメモリエディタの検索で判明したパラメータのアドレスが変なメモリ領域にあります。
このケースでは、プロセスメモリ上のゲームのEXEモジュールに属するメモリエリアではなく、EXEモジュールが使用する、ゲームと共にインストールされたDLLモジュールに属するメモリエリアでパラメータを管理していました。このようなケースでは、DLLファイルの拡張子を変更していることもありますので、解析時には必ず対象プロセスのモジュールリストを確認してください。また、このゲーム専用のDLLファイルが読み込まれるアドレスは、環境により異なることがありますので注意が必要です。『うさみみハリケーン』や『スペシャルねこまんま57号』の改造コードを用いた、DLLモジュールに属するアドレスの書き換えについては、ヘルプを参照願います。
なお、EXEモジュールやゲームが使用する専用のDLLモジュールに属するメモリエリアとは別のメモリエリアで、パラメータやフラグを管理することは不可能ではありません。
「環境依存型変動アドレス」について教えてください
●アドレス変動の仕組み
例えば、PCゲームのパラメータ等のアドレスが、プロセスメモリ上に『***.exe』といった解析対象アプリケーションのメインモジュールがロードされたエリアであるモジュールエリア内(基本的にアドレス0x400000〜のメモリエリア)にあるならば、そのアドレスは実行環境等に依存しない固定アドレスとなります。これは、メインモジュールのロード処理は実行環境に影響を受けず、また、メインモジュール内のデータ格納用セクションには、基本的にプログラム側で使用することやサイズ上限が『確定している』データを格納するためです。
しかし、解析対象アプリケーションが起動後に自分で動的に確保したメモリブロックおよび、そのメモリブロックに格納されたパラメータ等のアドレスは固定とはなりません。これは動的に行うメモリ確保では、状況に応じて確保されるメモリブロックのアドレスが変化するためです。 動的なメモリブロック確保について、ヒープ(プログラムが実行中に動的にメモリを割り当てるためにOSが用意している、あるいはアプリケーションが自分でHeapCreate関数を使用して作成したメモリ領域)から動的に必要なサイズのメモリブロックを確保するHeapAlloc,GlobalAlloc, LocalAlloc関数のうち、GlobalAlloc, LocalAlloc関数は速度面等の理由により使用可能ではあるが現在では推奨されていません。ちなみに、WindowsNT系OS上でGlobalAlloc関数を使用して確保されたメモリブロックのアドレスは、WindowsOSにおけるメインモジュールの開始アドレス0x00400000よりも低いアドレスになります。 また、プログラミング手法として大きなメモリブロックを確保する場合には、WindowsOSのバージョンに関わらずパフォーマンス面で適しているVirtualAlloc関数の使用が推奨されています。VirtualAlloc関数はヒープからの確保ではなく、簡単に言えばプロセスの仮想アドレス空間内のページ(システムがメモリ管理に用いるメモリ単位で、x86コンピュータでのサイズは0x1000バイト)に使用可能属性を設定して割り当てるAPI関数です。なお、1つのアプリケーションだが実際には2つ以上のプロセスが同時に実行されるケースでは、CreateFileMapping関数で第1引数に定数INVALID_HANDLE_VALUE(0xFFFFFFFF)を指定して作成したファイルマッピングオブジェクトを、プロセス間の共有メモリとして使用することもあります。 これらのメモリ確保用API関数を用いて確保されたメモリブロック上のアドレスは、基本的にOSその他実行環境によって変化する環境依存型変動アドレスとなります。使用OSが同じ場合にプロセスヒープ(OSが各プロセス用に作成する既定のヒープで、スタックとは別物)から確保されたメモリブロックのアドレスが同一になるケースもありますが、確実とはいえません。なお、解析対象アプリケーションのプログラマがメモリ確保の設定やタイミング等を変化するように設計すれば、実行環境に依存するアドレス変動に加え、アプリケーション起動ごとのアドレス変動も生じさせることが可能です。また、起動ごとのアドレス変動は、常駐ソフト等の環境要因によっても起こりえます。おおむね、大きなメモリブロックの確保が必要になるPCゲームに加え、家庭用ゲーム機のエミュレータでもパラメータ等のアドレスは環境依存型変動アドレスになるといえます。 さらに、パラメータ等の管理を解析対象アプリケーション専用のDLLがそのモジュールエリア内で行う場合、このようなDLLがプロセスメモリ上にロードされるアドレスは実行環境によって必ずしもWindowsOSにおける基本的なDLLロード先アドレスである0x10000000とはならないため、この場合も環境依存型変動アドレスとなります。このような解析対象アプリケーション専用のDLLは、解析対象アプリケーションがLoadLibrary関数を使用して動的にロードすることもできますが、『***.exe』といったメインモジュールからリンク(実行ファイルの設定で実行に必須のDLLとして指定)することで、アプリケーションの起動時に自動的にプロセスメモリ上にロードされるようにすることもできます。また、プログラムの処理としては、プロセスメモリ上で特定のDLLがロードされたアドレスを取得するのは容易です。 アドレス変動に関しては、解析対象アプリケーションが、データを格納するための必要最小限のサイズのメモリブロックを確保し、必要に応じてより大きなサイズのメモリブロックを確保してデータを格納し直すことがアドレス変動の原因となるケースもあります。このようなケースではHeapReAlloc関数かメモリブロック再確保用の自作関数を用い、格納するデータの増加に応じてより大きなサイズのメモリブロックを確保し、元のメモリブロックの内容を新しく確保したメモリブロックにコピーしたうえで元のメモリブロックを開放します。 HeapReAlloc関数はメモリ確保対象ヒープ内の使用状況や再確保するメモリブロックのサイズに応じて、元のメモリブロックを拡張あるいは、別途メモリブロックを確保してそこに元のメモリブロックの内容をコピーします。元のメモリブロックを拡張する場合はアドレス変動は生じません。 環境依存型変動アドレスは、プログラム側でそのアドレスにアクセスする以上、対象アドレス(群)の基準となるアドレスをポインタ(アドレスを指定するために特定アドレスに格納された値を使うやり方)として格納する必要があります。また、プログラム側では確保したメモリブロックが不要になった時に開放するため指定するパラメータとして、確保したメモリブロックの先頭アドレスを保持しなければなりません。そのため、データを格納するために確保したメモリブロックの先頭アドレスを格納するポインタは、基本的にEXEモジュールのモジュールエリア内の固定アドレスあるいは、DLLモジュールのモジュールエリア内でDLLロード先アドレスからみて相対値が固定のアドレスに配置することになります。なお、ヒープから確保されたメモリブロックの先頭アドレスはページ境界(0x1000単位)上にはならず、0x00831E90といった半端なアドレスとなります。一方、VirtualAlloc関数で割り当てられたページの先頭アドレスは64KB境界(0x10000単位)上となります。 |
●アプローチ
これら環境依存型変動アドレスの解析においては、パラメータ等のアドレスにデバッガで読み書きまたは書き込みブレークポイントを設定してブレークさせ、ブレーク箇所周辺の逆アセンブルコードリストを読解してプログラムの処理上どのようにアドレスを管理しアクセスしているかを把握するというアプローチが基本となります。この際、逆アセンブルコードリスト上で処理をさかのぼって観察し、特にパラメータ等のアドレスを指定するためにレジスタに格納される値の出所に注目して下さい。また、パラメータ等のアドレスが含まれるメモリブロックの、API関数による確保処理とその後のデータ格納処理を追いかけていくというアプローチもあります。
システム情報表示ツールなどが表示する、解析対象プロセスに属するヒープの一覧をアプローチの参考にする場合は、そのヒープ一覧はあくまである瞬間でのスナップショットであり永続的な情報ではないこと、各ヒープ内の確保済みメモリブロックにはプロセスのメインモジュールだけではなくシステムDLL他別のモジュールが確保したものも含まれること及び、基本的にヒープ内のメモリブロックのアドレスは環境に依存する変動アドレスであることに十分注意して下さい。 環境依存型変動アドレスを改造コードで指定する際には、アドレスの直接指定ができないため、ポインタを利用することになります。解析対象アプリケーション側が複数のポインタを連ねてデータのアドレスを管理しアクセスしているケースでは、改造コードは多重ポインタ対応のものを使用することで対処します。 また、多重ポインタ解析の手間を省くために、解析対象アプリケーションの改造対象パラメータ等操作処理箇所をデバッガで特定後、その処理箇所でパラサイトルーチンへ繋がるようコードを書き替えて、パラサイトルーチンの処理として固定アドレスにポインタを自作する方法もあります。この自作ポインタはプロセスメモリ上で解析対象アプリケーションのメインモジュールのモジュールエリア内にある未使用領域に配置します。 ポインタに関する解析結果の公開にあたっては、予期せぬアドレス変動要因を考慮して、公開前にOSを再起動させて解析対象アプリケーションも再起動させたうえで、ポインタを用いた改造コード等の動作を再確認されることをお奨めします。 このようなポインタの解析を含む、環境依存型変動アドレス解析のスマートなアプローチについては、REDCATさんのサイト『猫缶Index』にて公開されている解析結果や、DANAさんのサイト『DANAの部屋』の実践改造講座が大いに参考になります。 |
●構造体
PCゲームにおけるキャラクター情報等、メモリブロックを確保して複雑なデータを格納する場合は、そのデータは色々な要素を格納できる構造体(複数のデータを一つに集めたデータ構造)の形式をとるケースが多いといえます。構造体の中にさらに別の構造体を格納することも可能です。基本的に、構造体を使用する場合のプログラムの処理としては、複数のポインタを連携させた効率的なデータ管理・アクセス手法が使われます。
<参考>構造体を用いたデータ格納例 以下は構造体の要素を大幅に省略して簡略化したモデルです。さらに、プロセスメモリ上へのデータの格納方法はプログラマのコーディングによって変化するため、以下はあくまで1つの例に過ぎないと考えて下さい。 [構造体のヘッダ] 正義の味方のキャラ数(0x2バイト) 悪の組織のキャラ数(0x2バイト) ---他の色々なデータは省略--- [正義の味方] キャラ1名前(0x10バイト) HP(0x4バイト) MP(0x4バイト) 所持金(0x4バイト) 所持アイテム数(0x2バイト) 所持アイテム1レベル(0x2バイト) 所持アイテム2レベル(0x2バイト) ---その他の要素が続く--- キャラ2名前 HP MP 所持金 所持アイテム数 所持アイテム1レベル 所持アイテム2レベル ---その他の要素が続く--- 以下キャラ3以降に続く [悪の組織の一員] キャラ1名前 HP MP 所持金 所持アイテム数 所持アイテム1レベル 所持アイテム2レベル ---その他の要素が続く--- 以下キャラ2以降に続く解析対象アプリケーションが、ソースコードのメンテナンス性向上等を目的として構造体の形式でデータを格納している場合、プログラム上でのデータの管理とアクセスは、アドレスの直接指定ではなくポインタを使用することで処理の効率化を図ります。上の例で各キャラクターのデータのサイズが0x100ならば、例えば正義の味方キャラ2の所持金データは、構造体の先頭アドレスである「正義の味方のキャラ数」のアドレスからみて、+(0x2+0x2) +(0x100) +(0x10+0x4+0x4)のアドレスに格納されています。ここで、正義の味方データの先頭アドレスとなる「正義の味方キャラ1名前」のアドレスがポインタとして別の場所に格納されていれば、「正義の味方キャラ2の所持金」のアドレスはプログラム上の処理では「ポインタの格納値+0x100*1+0x18」として効率的に処理され、同様に他のキャラの各要素へアクセスすることも容易になります。 逆アセンブルコードリスト上でのこのようなポインタに関するアドレスの演算処理は、「LEA EDX,[ECX*4+00425180] 」といった形で特にLEA命令やレジスタを使って処理を高速化することが可能であり、また、対象アドレス格納値の取得や変更にはMOV命令で「MOV EAX, DWORD PTR[EBX+ECX*4+1470]」、「MOV DWORD PTR[EBX+ECX*2+1470], EAX」といった効率的な処理を行うことも可能です。 構造体のデータにアクセスするためのポインタは構造体に含めることも、全く別のメモリエリアに配置することもできます。また、上の例のように構造体でデータを管理する場合は、一般的に各ポインタを連携させて使用するため、プログラムの処理の効率上、使用する複数のポインタを特定のメモリエリアに固めて格納していることもあります。 |
相互修復型パラメータの書き換え方法を教えてください。
この「相互修復型パラメータ」とは、ひとつのパラメータをプロセスメモリ上で複数箇所に格納し、高頻度でその複数のパラメータをチェックして、複数箇所のパラメータのうちひとつがプロセスメモリエディタで書き換えられた時に、他の箇所に格納されたパラメータを使って元の値に修復するケースを指します。いわゆるメモリ改竄検出・防御手法のひとつです。このようなケースでは大抵の場合、複数箇所のパラメータを、複数の改造コードを一括実行してほぼ同時に書き換えれば対処可能です。しかし、それで対処できない場合は、デバッガでパラメータ格納アドレスへ書き込みメモリアクセスのブレークポイントを設定することで、相互修復ルーチンを特定し無効化する必要があります。また、『うさみみハリケーン』や『スペシャルねこまんま57号』のデバッガで対象プロセスにアタッチして「実行一時停止」機能を用いる、対象プロセスの全スレッドを停止させた状態で複数箇所のパラメータを書き換える手法も有効です。
プログラムの参照文字列を解析の端緒とする場合の注意点を教えて下さい。
まず、プログラムが実行される際に参照する文字列は、必ずしもANSI(日本語ならシフトJIS)やUNICODE形式の文字列として実行ファイル中に予め用意される訳ではない点に注意が必要です。つまり、プログラム実行中に必要に応じて文字列をスタックあるいはデータセクション内に動的に生成して参照し、さらに不要になれば消去することも可能です。そのため、OllyDbg等で表示されるプログラムの参照文字列が「総ての」参照文字列とは限りません。たいていの場合、プログラムが参照する予め用意された文字列はデータセクション内に格納されていますが、Delphi等コンパイラによってはコードセクション内に参照文字列が格納されることもあります。また、API関数は関数名の接尾辞が「A(ANSI)」か「W(Wide:UNICODE)」かで、参照される文字列がANSIかUNICODE形式かを判断することが可能です。
なお、参照する文字列をキャラクターコード(文字コード)に演算を施すことで暗号化した上で予め用意しておき、必要時に復号化することも可能です。この方法は、CDチェック時のオリジナルCD認証用ファイル名や、多重起動防止用ミューテックス名等に使用されることがあります。キャラクターコード表はプラットフォームSDKや『うさみみハリケーン』のヘルプで見ることが出来ます。
さらに、ゲームの解析においては、パラメータの数値を文字列に変換する際に使用されることがあるwsprintf関数等についても、プラットフォームSDK等を参照して理解しておくことを強くお勧めします。例えばwsprintf関数の第2引数に使用可能な文字列「%d」は指定した符号付き10進整数を文字列に、「%u」は指定した符号無し整数を文字列に変換する書式指定文字列です。具体例としては、パラメータ表示用文字列作成のために、「体力:%d, お金:%u」という書式指定文字列をwsprintf関数の第2引数として指定し、第3、第4引数には体力とお金の値を指定します。この場合、第3、第4引数に指定した値の出所がどこにあるか、逆アセンブルコードリスト上で辿っていくことがポイントになります。なお、数値を文字列に変換する方法は他にもあり、必ずしもwsprintf関数あるいは書式指定文字列を使う訳ではないので注意して下さい。
ゲームのシナリオ表示等、文字列を画面に「描画」する処理箇所を特定するためのアプローチ方法が分かりません。
デバッガで逆アセンブルコードリスト上のテキスト描画用API関数呼び出し箇所を把握し、同箇所の実行にブレークポイントを設定して実際の描画処理を追いかけます。代表的なテキスト描画用API関数は以下。DrawText,DrawTextEx,GrayString,TextOut(使用されることが多い),ExtTextOut,TabbedTextOut
また、表示済み文字列のメモリサーチを行い、その文字列格納アドレス周辺のバイナリデータを視認して、「これから描画されるはずのテキスト」が発見された場合は、そのテキスト内のアドレスの読み込みにブレークポイントを設定してブレークさせるという方法もあります。
タスク切り替えが出来ないゲームの対処法を知りたい。
まず、汎用のタスク切り替え手法による対処法は、拙作『うさみみハリケーン』のヘルプの「基礎用語解説」→「フルスクリーン」で、特定キーの押し下げや『スペシャルねこまんま57号』のキーフック等を用いた手法を解説していますのでご覧下さい。次に、解析による対処法を考える場合には、「タスク切り替えが出来ない」という状況をよく考察する必要があります。例えば、タスク切り替えが出来ないゲームはDirectXを使用したゲームで散見されますが、MicrosoftはDirectX 9を用いたアプリケーションでのタスク切り替えの無効化を禁止しています。そのため、キーフック他によるタスク切り替えの無効化ではなく、タスク切り替えを検出してアプリケーション強制終了等の処理を行うケースが少なくありません。
さらに、解析によって対処する場合の注意点として、タスク切り替え検出ルーチンを探し出すというアプローチは工数が嵩む可能性が挙げられます。これは、タスク切り替えの検出手法が、仮想デバイスドライバを使用したり、メインウィンドウに送られてくるWM_ACTIVATE・WM_ACTIVATEAPP等のWindowメッセージで自分のウィンドウがアクティブか否か確認する方法や、GetForegroundWindow関数等のAPI関数を用いて自分のウィンドウが最前面にあるかを高頻度で確認する方法等、色々な手法があるためです。そのため、強制終了等のタスク切り替えに伴うアクションを手掛かりにデバッガを用いてアプローチする手法が効率的と考えられます。
その他の対処法としては、タスク切り替えが出来ないゲームのプロセスを停止・再開等制御することで、タスク切り替えを可能にするソフトウェア『AAT(Anti Alt-Tab)』を使用するという選択肢もあります(当方では動作未確認)。 また、DirectX汎用窓化ツール『DXWnd』に実装されている、タスク切り替えをゲームに通知させない機能を利用する対処法もあります。
フルスクリーンのゲームをウィンドウモードでプレイする方法を教えて下さい。
まず最初に、対象ゲームが開発者用のウィンドウモードを実装している可能性を考慮して下さい。この場合、ウィンドウモードのトリガーは、iniファイルやレジストリの「Window=1」といったキーの値や、「xxx.exe -win」等のコマンドラインオプションであることが少なくありません。また、「Alt+Enter」他の特定キーを起動時に押すことでウィンドウモードになるケースもあります。これらは、対象ゲームの参照文字列、起動時のiniファイルあるいはレジストリ読み込み関数(GetPrivateProfileInt関数,RegQueryValueEx関数他)の呼び出し、またはキー入力認識関数(GetAsyncKeyState関数等)の呼び出しを元にアプローチを行います。次に、対象ゲームがインポートするDLL等から判断してDirectX 9(または8)を用いているならば、ウィンドウ作成およびDirectX初期化ルーチンにパラサイトルーチンを使用して処理を変更する方法があります。最も基本的なアプローチとしては、まずCreateWindow関数呼び出しの時点で第3引数に適切なウィンドウスタイル、第6,第7引数にウィンドウサイズを指定させます。それからd3d9.dll(d3d8.dll)のDirect3DCreate9(Direct3DCreate8)関数呼び出し後のD3DPRESENT_PARAMETERS構造体初期化部分でメンバ変数WindowedをTRUE(値1)に設定し、さらに同構造体のメンバ変数BackBufferFormatを、予めGetAdapterDisplayMode関数で取得されたD3DDISPLAYMODE構造体のメンバ変数Formatの値か、D3DFMT_UNKNOWN(値0)に設定します。なお、古いゲームでddraw.dllをインポートしてDirectDrawを使用しているならば、DirectDrawCreate関数呼び出し直下のディスプレイとの協調設定部で、SetCooperativeLevel関数の第3引数をDDSCL_NORMAL(値8)に変更する必要があります。以上が最も基本的なアプローチですが、これで総てのゲームでウィンドウモード化が可能になる訳ではなく、他に何箇所も変更しなくてはならないケースも多々あります。
参考:D3DPRESENT_PARAMETERS構造体初期化例(ウィンドウモード) 00401000 /$ 55 PUSH EBP 00401001 |. 8BEC MOV EBP,ESP 00401003 |. 83EC 44 SUB ESP,44 00401006 |. 6A 78 PUSH 78 00401008 |. E8 BD020000 CALL <JMP.&d3d8.Direct3DCreate8> ;D3Dオブジェクト作成 0040100D |. A3 00784000 MOV DWORD PTR DS:[407800],EAX ;D3Dオブジェクトのポインタ 00401012 |. 833D 00784000 00 CMP DWORD PTR DS:[407800],0 00401019 |. 75 07 JNZ SHORT dxtest.00401022 0040101B |. B8 05400080 MOV EAX,80004005 00401020 |. EB 78 JMP SHORT dxtest.0040109A 00401022 |> 8D45 F0 LEA EAX,DWORD PTR SS:[EBP-10] ;D3DDISPLAYMODE構造体のアドレス 00401025 |. 50 PUSH EAX 00401026 |. 6A 00 PUSH 0 00401028 |. 8B0D 00784000 MOV ECX,DWORD PTR DS:[407800] 0040102E |. 8B11 MOV EDX,DWORD PTR DS:[ECX] 00401030 |. A1 00784000 MOV EAX,DWORD PTR DS:[407800] 00401035 |. 50 PUSH EAX 00401036 |. FF52 20 CALL DWORD PTR DS:[EDX+20] ;GetAdapterDisplayMode関数 00401039 |. 85C0 TEST EAX,EAX 0040103B |. 7D 07 JGE SHORT dxtest.00401044 0040103D |. B8 05400080 MOV EAX,80004005 00401042 |. EB 56 JMP SHORT dxtest.0040109A 00401044 |> 6A 34 PUSH 34 00401046 |. 6A 00 PUSH 0 00401048 |. 8D4D BC LEA ECX,DWORD PTR SS:[EBP-44] ;D3DPRESENT_PARAMETERS構造体のアドレス 0040104B |. 51 PUSH ECX 0040104C |. E8 7F020000 CALL dxtest.004012D0 ;ZeroMemory関数 00401051 |. 83C4 0C ADD ESP,0C 00401054 |. C745 D8 01000000 MOV DWORD PTR SS:[EBP-28],1 ;D3DPRESENT_PARAMETERS構造体のメンバ変数Windowed 0040105B |. C745 D0 01000000 MOV DWORD PTR SS:[EBP-30],1 00401062 |. 8B55 FC MOV EDX,DWORD PTR SS:[EBP-4] ;D3DDISPLAYMODE構造体のメンバ変数Format 00401065 |. 8955 C4 MOV DWORD PTR SS:[EBP-3C],EDX ;D3DPRESENT_PARAMETERS構造体のメンバ変数BackBufferFormat 00401068 |. 68 04784000 PUSH dxtest.00407804 0040106D |. 8D45 BC LEA EAX,DWORD PTR SS:[EBP-44] ;D3DPRESENT_PARAMETERS構造体のアドレス 00401070 |. 50 PUSH EAX 00401071 |. 6A 20 PUSH 20 00401073 |. 8B4D 08 MOV ECX,DWORD PTR SS:[EBP+8] 00401076 |. 51 PUSH ECX 00401077 |. 6A 01 PUSH 1 00401079 |. 6A 00 PUSH 0 0040107B |. 8B15 00784000 MOV EDX,DWORD PTR DS:[407800] 00401081 |. 8B02 MOV EAX,DWORD PTR DS:[EDX] 00401083 |. 8B0D 00784000 MOV ECX,DWORD PTR DS:[407800] 00401089 |. 51 PUSH ECX 0040108A |. FF50 3C CALL DWORD PTR DS:[EAX+3C] ;CreateDevice関数 0040108D |. 85C0 TEST EAX,EAX 0040108F |. 7D 07 JGE SHORT dxtest.00401098 00401091 |. B8 05400080 MOV EAX,80004005 00401096 |. EB 02 JMP SHORT dxtest.0040109A 00401098 |> 33C0 XOR EAX,EAX 0040109A |> 8BE5 MOV ESP,EBP 0040109C |. 5D POP EBP 0040109D \. C3 RETNまた、上記DirectX初期化ルーチンからd3d9.dll(d3d8.dll)内にステップインして、D3DPRESENT_PARAMETERS構造体のメンバ変数判定部を特定した上で、このDLLを直接書き換える方法もありますが、これは対象ゲーム以外のDirectXを使用するアプリケーションに影響を及ぼしかねないという問題があります。
このように、パラサイトルーチンやDirectX関連DLL書き換えによるアプローチは汎用性等に問題があるため、近年では対象ゲームのDLLインポートをダミーDLLでフックしてCreateDevice関数処理部等を自作したものに置き換えるアプローチを取るケースが増えています。現在DirectX初期化処理のフック手法は日本や海外のゲーム専門リバースエンジニアの間で改良が進められており、いずれ多くのゲームでウィンドウモード化を可能にする汎用性の高い手法が公開されると予想されます。
なお、現在DirectX使用アプリケーションのウィンドウモード化機能を持つソフトウェアとして、『DXWnd』や『3D-Analyze』 が公開されています。いずれも、すべてのゲームで完璧なウィンドウモード化を実現できる訳ではありませんが、試す価値は十分にあると思われます。また、対象ゲームによっては、『VMWare』や『Virtual PC』による仮想マシン上で実行させることによる擬似的なウィンドウモード化が可能なケースもあります。今後これらの仮想マシン構築ソフトがDirect3Dに完全対応すれば、大抵のゲームは問題なく仮想マシン上で実行可能になると考えられます。
PrintScreenキーでスナップショットが取れないゲームの仕組みを教えてください。
スナップショットが作成できないのは、PrintScreenキー押し下げを監視して、キー押し下げのタイミングでクリップボードの内容をクリアしているためと考えられます。キーフックを用いてPrintScreenキー押し下げを監視する場合は、キー入力のウィンドウメッセージを取得して、このキーの仮想キーコードの値(2Ch)と比較した上で処理を分岐させることになります。また、クリップボードの内容のクリア処理は、OpenClipboard関数呼び出し後に第1引数を08h(CF_DIB)にしてGetClipboardData関数を呼び出してクリップボードの内容が画像データかどうか確認し、画像データの場合はEmptyClipboard関数を使ってクリップボードのデータをクリアします。
なお、クリップボードの内容のクリア処理は、対象ゲームのEXEファイルではなく、対象ゲーム専用のロードされたDLLファイル(拡張子変更可能)内のコードで実行されている可能性が高いことに注意が必要です。このようなケースに限らず、ゲームの解析においては必ずプロセスのモジュールリストを確認しておくことをお奨めします。
ゲームが自己プロセスを隠蔽することは可能ですか?
いわゆるプロセスメモリエディタやOS付属のタスクマネージャが表示するプロセスリストにリストアップされないよう、アプリケーションが自己プロセスを隠蔽する手法は実在します。自己プロセスを隠蔽する例としては、一部のマルウェアや、商用プロテクトが施されたオンラインゲームが挙げられます。しかし、プロセス隠蔽はセキュリティ上の問題を招きかねないため、オンラインゲームではない、一般的なPCゲームに実装されるケースはほとんど皆無であり、懸念する必要はないと考えられます。
その他のケースとしては、プロセスの隠蔽ではありませんが、「game.exe」といったゲーム開始用の実行ファイルが、別ファイルとなっているゲーム本体の実行ファイル(拡張子はEXEとは限らない)の実行及び改竄検出用のローダーであるケースが挙げられます。この場合、大抵「game.exe」は本体の実行ファイルを実行後に終了するため、プロセスリストに「game.exe」はリストアップされません。このようなケースでは、ゲーム本体の実行ファイルに改造対策が施されている可能性を考慮して下さい。
なお、「うさみみハリケーン」のキーフック機能を使えば、プロセスリストには表示されないプロセスをウィンドウ情報を元に特定してプロセスリストに追加することが可能です。
ゲームの当たり判定解析のアプローチが分かりません
API関数を用いた当たり判定としては、2つの矩形の交差を判断するIntersectRect関数の使用が挙げられます。また、補助的な関数として、指定の点が指定の矩形の境界の内側にあることを判断するPtInRect関数が挙げられます。ただし、関数を使うのではなく、複数の矩形の集合体との重なりを、座標の演算による比較で求めるケースもあります。この場合は、 自機座標の格納アドレスを変動サーチで探し出し、メモリアクセスへのブレークポイント設定で処理を追いかけることになります。この際、自機や敵機の座標が浮動小数点数で格納されている可能性と、座標は暗号化されており増減で絞り込まない方がよい可能性を考慮して下さい。なお、座標の格納アドレスへのブレークポイントは、無用なブレークを避けるためにスレッドを指定して設定する方が効率的と考えられます(『うさみみハリケーン』ではスレッドを指定してブレークポイント設定可)。
座標の更新のトリガーとなるWindowメッセージは、矢印キー押し下げ時のWM_KEYDOWNあるいは、WM_MOUSEMOVEやWM_RBUTTONDOWN(lparam)などが考えられます。
なお、当たり判定に伴う結果である自機数やHP等の減算処理のコードを特定し無効化するという代替アプローチもあります。
「逆アセンブルコードの再利用」について教えてください
たとえば、暗号化されたセーブデータを復号化する目的で、セーブデータ復号化ツールを製作する場合、ゲームのプログラムに実装されているセーブデータ復号化処理をそのまま復号化ツールに流用できれば、コーディングが容易になります。(注:ゲームのプログラムを書き換えて暗号化しないセーブデータを出力させるというアプローチもあります)実際の処理としては、ゲームのプログラムの復号化処理箇所をデバッガで特定し、その逆アセンブルコードリストを整形すれば、C言語コンパイラ等が実装しているインラインアセンブラを用いることで、復号化ツールのソースコードにほとんどそのまま流用することが可能になります。
この場合、処理の流れは以下のようになります。
1.デバッガでセーブデータ読み込み時の復号化処理箇所を特定
2.特定した復号化処理箇所の逆アセンブルコードリストをテキストエディタにコピー
3.テキストエディタ上でマクロ等を用いて逆アセンブルコードリストを整形
4.3.を復号化ツールのソースコードにインラインアセンブラの形式で挿入
5.復号化ツールをコンパイルして動作を確認
この手法は、暗号化されたゲームの画像データ用Susie Pluginの製作等にも有用です。ただし、この様な手法で製作したSusie Pluginの公開が原因で、ソフトハウス側とトラブルになったケースもあります。
この様な逆アセンブルコードの再利用には、逆アセンブルコードリスト上での処理の流れと、スタックやレジスタに格納されている値との関係について理解する必要があります。特に目的の処理箇所が関数の場合、スタックフレーム内に格納されている引数やローカル変数等がどのように使われているかに注目してください。なお、スタックへのデータ出し入れは、最後に入れたデータが最初に取り出される、FILO(First In, Last Out、先入れ後出し)方式で行われます。
●<参考:関数とスタックフレーム> ;関数呼び出し部 push 2 ;引数2 push 1 ;引数1 call function add esp, 8 ;引数分スタック修正 ;関数本体(関数の処理は省略) push ebp ;ebpレジスタを新しいフレームポインタとして使うため、元の値を退避 mov ebp, esp ;スタックポインタespレジスタの値をebpレジスタに転送 sub esp, 8 ;ローカル変数(ここではDWORDx2)の設定 ;スタックフレームの内容 ;dword ptr [ebp+C] = 2(引数2) ;dword ptr [ebp+8] = 1(引数1) ;dword ptr [ebp+4] = 関数のリターンアドレス ;dword ptr [ebp] = ebpレジスタの元の値(前のフレームポインタ) ;dword ptr [ebp-4] = ローカル変数1 ;dword ptr [ebp-8] = ローカル変数2 (関数の処理) mov esp, ebp ;スタックポインタ復帰 pop ebp ;フレームポインタ復帰 ret ;関数呼び出し元に戻る ●<参考:x86 スタックフレームのレイアウト> ・関数パラメータ ・関数のリターン アドレス ・フレーム ポインタ ・例外ハンドラ フレーム ・ローカルに宣言した変数やバッファ ・呼び出し側のレジスタの保存 (必ず上記全項目が格納される訳ではない) ●<参考:インラインアセンブラ使用例> _asm{ ;引数の設定(レジスタ渡しにすることも可能) push 2 ;引数2 push 1 ;引数1 push 0 ;ダミー ;元の逆アセンブルコードリストをなるべくそのまま使用する push ebp mov ebp, esp ;スタックポインタespレジスタの値をebpレジスタに転送 sub esp, 8 ;ローカル変数(ここではDWORDx2)の設定 ;dword ptr [ebp+C] = 2(引数2) ;dword ptr [ebp+8] = 1(引数1) ;dword ptr [ebp-4] = ローカル変数1 ;dword ptr [ebp-8] = ローカル変数2 (関数の処理) ;API関数の呼び出しは関数ポインタを用意しておいてcall ;ジャンプ元やジャンプ先には、クロスリファレンス情報を元にラベルを指定 mov esp, ebp ;スタックポインタ復帰 pop ebp ;ebpレジスタ復帰 add esp, C ;引数とダミー分のスタック修正 }なお、逆アセンブルコードリストを読解した上で再利用するだけではなく、逆アセンブルコードリストを自分が得意とする開発言語のコードに置き換える練習をしておくこともお勧めします。
日本語版のOSでは起動させないゲームの仕組みを教えてください
一部の海外のゲームでは、デバッグ環境がない等の理由により、起動時にOSの使用言語を調べて、製作環境と異なる言語のOS上では起動させないケースがあります。このようなケースでは、OSの使用言語をGetSystemDefaultLangID関数を用いて言語IDで判断することが多いようです。ただし、この手法ではコントロールパネルで「地域と言語のオプション」を変更した場合に正確な使用言語が判別できないため、「User.exe 」等のシステムファイルのバージョン情報にある使用言語をGetFileVersionInfo関数とVerQueryValue関数で取得するケースもあります。
他にはレジストリのロケール情報を取得してOSの使用言語を判断する方法もありますが、正確に言語IDが取得できない場合があるため、この手法を用いることはほとんどないようです。
<参考:言語ID例>
0x0000 Language Neutral
0x0404 Chinese (Taiwan)
0x0804 Chinese (PRC)
0x0409 English (United States)
0x0809 English (United Kingdom)
0x040c French (Standard)
0x0407 German (Standard)
0x0411 Japanese
0x0412 Korean
0x0419 Russian
なお、日本語版のOSでも起動可能だが文字化けするという場合には、『Microsoft AppLocale Utility』を使用することで、文字化けが解決する可能性があります。
PCゲーム解析関連情報の管理はどのようにするべきですか
デバッガ等を用いたPCゲームの解析に伴い得られた、特許取得済み商用プロテクトに関わる情報や、PCゲーム解析の参考資料として入手したウイルスのソースコードなどは、当然ながら外部に公開すべきではありません。このような情報を公開せず、参考資料として自分のパソコンに保存しておくことは問題ないと思われますが、その場合はパソコンの盗難等による、予期せぬパソコン内の保存データの流出に対し、セキュリティ上の対策を講じておくべきでしょう。具体的には、PCゲーム解析に伴い得られた情報全ての暗号化が挙げられます。昨今では色々なファイル/フォルダ暗号化ソフトが公開されていますが、利便性の問題から、ファイル操作時の暗号化と復号化を自動で行う暗号化仮想ドライブ構築ソフトがお勧めです。
私の場合はフリーウェアの『TrueCrypt』(日本語ランゲージパックあり)を使用しています。AES、Blowfish、CAST5、Serpent、Triple-DES、Twofish等の暗号化アルゴリズムに対応しており、ユーザーが設定する暗号化仮想ドライブマウント用パスワードがよほど短くない限り、このソフトで情報漏洩は防止できると考えられます。ただし、暗号化仮想ドライブマウント中のトロイ等による情報漏洩まではこのソフトで対処できる訳ではないので、過信は禁物です。
なお、パソコンの盗難時等にファイル復元ソフトを使用されるケースに対処するため、『TrueCrypt』の使用に加えて『Eraser』などのファイル完全削除ソフトの併用をお勧めします。
ゲーム実行PCの固有情報を鍵として暗号化されるセーブデータを別のPCで使用したい
これには色々なパターンがあり一概に対処法は述べられません。まず、暗号化と復号化の仕組みを解析して理解するところから始めると良いと思われます。アプローチとしては、 セーブデータ書き込み時の暗号化や読み込み時の復号化のためにどのようなデータ(鍵)を参照しているか。そのデータはすでにセーブデータあるいは別のファイルやレジストリに保存されているものか、それともセーブデータ読み書き時に動的に生成しているものか。どのような要因でセーブデータ読み込みエラーを出すように設計されているか。コードを書き換えて暗号化と復号化処理をスキップさせることは可能か。などを解析していくと解決の糸口がつかめることもあります。
ただし、復号化に必要なデータを、セーブデータ読み込み時にPC固有情報を元に動的に生成している場合など、別のPC上だけでの対処が困難なことも少なくありません。この場合は、元のPC上と別のPC上でゲームのインストール時/初回起動時/セーブデータ読み書き時などに生成される暗号化・復号化用データを同じものにさせる必要があります。
具体的には、ゲーム側が暗号化・復号化用データを生成するために、Windowsのユーザー名や各種ハードウェア情報といった、PC固有の情報を取得するコードを、そのまま書き換えたり、ラッパーDLLやAPIフックで改変して環境に関わらず同じ情報を取得させ、どのPC上でも同じ暗号化・復号化用データを生成させることで対処しますが、「PC固有の情報」が広範に渡るだけに必ずしも対処が容易とはいえません。
なお、 ラッパーDLLやAPIフックによる、API関数呼び出し時に特定の動作を行わせたり特定の値を返させたりする対処法は、上記のケース以外にも色々応用可能ですので、有効な1手法として覚えておくことをお勧めします。
アプリケーションの処理実行速度を変更するソフトウェアの仕組みを教えて下さい
『Speed Hack Tool』などとも呼ばれるこの種のソフトウェアが、アプリケーションの処理実行速度を変更する基本的な仕組みは以下と推測されます。あくまで推測であり、必ずしも以下の方法を使用しているとは限らないことに留意願います。- 自作のアプリケーションから、対象プロセスをCreateProcess関数のProcess Creation Flags->CREATE_SUSPENDEDを使って、エントリーポイント実行前にメインスレッドを停止させた状態で起動します。ただし、この状態でのプロセスメモリ読み書き以外のプロセス操作は、メインスレッド再開後にアクセス違反による対象プロセスの強制終了を招くことがあります。これに対処するためには、まず停止状態で起動後すぐにエントリーポイントから2バイトを自分自身にジャンプするニーモニック"EBFE"に書き換えてからResumeThread関数でメインスレッドを再開させます。次に、エントリーポイントで無限ループに陥らせた状態のままSuspendThread関数でメインスレッドを再度停止させ、さらにエントリーポイントから2バイトをオリジナルのバイナリデータで書き戻し、FlushInstructionCache関数を実行します。これにより、メインスレッドをエントリーポイントで停止させることが可能になります。また、単純に起動後すぐに全スレッドをSuspendThreadで停止させることもできます。なお、対象プロセスのエントリーポイントのアドレスは、あらかじめ対象プロセスの実行ファイルのPEヘッダを参照することで特定しておきます。
- CreateRemoteThread関数とVirtualAllocEx関数・WriteProcessMemory関数を使って、対象プロセスが実行する処理としてLoadLibrary関数を呼び出し、対象プロセス内に自作DLLをロードさせます。この自作DLLロード(DllMain関数内でfdwReason==DLL_PROCESS_ATTACH)をトリガーとして何らかの処理を自作DLL側に行わせることも可能です。さらに、処理実行速度変更に関する設定内容をプロセスメモリ上の自作DLLモジュールエリア内か、メモリマップトファイルといった共有メモリなどに書き込みます。
- 対象プロセスのメインモジュールのモジュールエリアで、PEヘッダの情報からIAT(インポートアドレステーブル)のアドレスを特定します。それからIATを解析し、IAT内に設定されているSetTimer関数、QueryPerformanceCounter関数、(必要に応じて)timeGetTime関数、GetTickCount関数などの開始アドレスを、ロード済み自作DLL内のダミー関数の開始アドレスに書き換えて、SetTimer関数などのAPI関数呼び出し処理が必ずダミー関数を経由するようにします。IAT解析時は、パッカー使用など対象プロセスのIATが異常な場合の対処が必須となります。
- ResumeThread関数を使って、停止している対象プロセスの全スレッドの実行を再開させます。すでに自作DLLを対象プロセス内にロードさせているため、この時点で自分自身のプロセスを終了させることも可能です。
- 以降はダミー関数の処理により、SetTimer関数などのAPI関数呼び出し時の引数や戻り値を変更することで、それらを元に対象アプリケーションが設定する各種処理の実行速度も変更されます。ダミー関数ではQueryPerformanceCounter関数などが最初に呼ばれた際の戻り値を保存しておき、次に呼ばれた際には、最初の戻り値と新しい戻り値との差を元に、その差を定数倍などして修正した値を戻り値として対象アプリケーション側に渡します。
上記アプリケーションの処理実行速度変更を既に起動済みのプロセスに対しても行えるようにするには、WM_TIMERメッセージへの対応などから、GetMessage関数・DispatchMessage関数・PeekMessage関数等のメッセージループ関連API関数もダミー関数で処理させる必要があると考えられます。また、上記の方法はWindowsNT系OSでのみ可能であり、これをWindows9x系OSにも対応させるにはさらに複雑な操作が必要です。 上記の方法では、対象プロセスがLoadlibrary関数かGetModuleHandle関数呼び出し後にGetProcAddress関数を用いて取得した関数アドレスを使用するケースには対応していませんが、アプリケーションの処理実行速度設定に関連するAPI関数に関してそのような処理を行うことはまずありません。
なお、このIATを直接書き換える方法は、対象プロセスの特定API関数呼び出しに併せて自作コードを実行させる方法として有用です。特に、コードセクションには一切変更(改ざん)を加えていない点に注目して下さい。また、対象プロセスへの自作DLLロード及び自作コードの実行については、以下のページが参考になります。
別のプロセスにコードを割り込ませる3つの方法
プロセスメモリ上にロードされたモジュールにおける、PEヘッダやセクション及びIATなどが、API関数呼び出しなどプロセスの処理実行に関してどのような役割を果たしているかを理解することは、プログラム解析において重要な要素の一つといえます。この理解を深めるために、プロセスメモリ上の各モジュールのPEヘッダ情報及びインポート・エクスポート関数一覧を表示するソフトウェアの自作や、公開されているパッカーやアンパッカーのソースコードの読解をお勧めします。
ゲーム上でランダムに設定されるデータ(出現アイテムの種類など)を固定化したい
プログラムではこの様なランダムにデータを設定する場合、擬似乱数(あらかじめ用意された演算等のアルゴリズムにより生成した乱数)を使用します。C言語では標準ライブラリ関数として擬似乱数を生成するrand関数がありますが、生成した擬似乱数の重複など精度面ほかで問題があるため、最近では「Mersenne Twister」等の優れた疑似乱数生成アルゴリズムが奨められています。基本的に擬似乱数は「種」と呼ばれる任意の数値に特定の処理を施して生成します。そのため、たいていの擬似乱数生成アルゴリズムにおいて、プログラム側が設定する種の数値さえ固定化できれば、生成される擬似乱数は常に同じ値になります。
PCゲームにおける擬似乱数を生成するための種の設定には、特定のAPI関数によって得られた、常に変化し種として適切な「現在時刻」や「システムの実行経過時間に連動する値」が使用されるケースが少なくありません。用いられる可能性があるAPI関数のリストは下記。なお、これらのAPI関数の戻り値は、各種処理におけるタイミングを調整するための処理時間制御に用いられることもありますが、種を取得する場合は戻り値を他の数値と大小比較する必要が無い点に注目してください。また、一回の擬似乱数生成で複数の擬似乱数からなる配列を作成するアルゴリズムならば、擬似乱数が必要となる毎に種を取得する必要がありません。
timeGetTime
GetTickCount
GetTickCount64(Vistaで実装された)
QueryPerformanceCounter
GetSystemTimeAsFileTime
NtQuerySystemTime(ネイティブAPI)
GetSystemTimes(XP SP1で実装された)
timeGetSystemTime(timeGetTimeよりオーバーヘッド大)
GetProcessTimes(プロセスの実行時間を取得、9x/Me不可)
アプローチとしては、これらのAPI関数を用いて種を取得・変更する処理を無効化することになります。もしも生成された擬似乱数とその前に生成した擬似乱数の重複をチェックし、重複時には擬似乱数を再生成する処理があるならば、必ずその処理も無効化しないと無限ループに陥ります。
他のアプローチとしては、サブルーチンとなっている擬似乱数生成処理そのものの無効化や、生成した擬似乱数を任意の数値範囲内にするための演算処理箇所を書き換えて、生成した擬似乱数を固定値に置き換える手法も考えられます。
参考までに、もしMersenne Twister(普及しているMT19937バージョンの32ビットマシン用)を用いて擬似乱数を生成している場合は、逆アセンブルコードリスト上に同アルゴリズムが使用する定数0x9908B0DFや調律用定数0x9D2C5680および0xEFC60000が見つかります(プログラマの意図的操作がなければ)。
種の設定に関連して、現在時刻の取得には以下のAPI関数も使用可能です。ただし、これらのAPI関数は取得する時刻情報の形式が暦型であるため、種の設定よりは、セーブデータに保存時刻情報を書き込むケースや、定期型CD/DVDチェックおよび、イベント出現等でプレイする月/日/時/分/秒/曜日などをトリガーとする場合の条件判定などに用いられる可能性が高いといえます。
GetLocalTime
GetSystemTime
なお、C言語の標準ライブラリ関数のうち、プログラム実行開始からの消費したプロセッサ時間を取得するclock関数はGetSystemTimeAsFileTime関数、現在時刻を取得するtime関数はGetLocalTime関数とGetSystemTime関数を使用しています(Visual C++ 6で確認)。
さらに、インラインアセンブラを用いてRDTSC命令(オペコード:0F31)を実行することで、システムの実行経過時間に連動するプロセッサのタイムスタンプ・カウンタを取得して種とすることも可能です。インテルPentiumで実装されたこの命令を実行すると、EDX:EAXの形式で64ビットのタイムスタンプ・カウンタが同レジスタにロードされます。
「倍直」とは何ですか
「倍直」はクラッキング関連用語です。意味としては、「シェアウェアの実行可能ファイルをバイナリエディタでオープンすることで、ユーザー登録用シリアルコードが直接視認可能なケース」です。倍直のケースが生じる原因は、ソースコード上でのプログラムの処理において、シリアルコードの固定文字列をそのまま参照することです。これにより、コンパイラが生成する実行可能ファイルのデータセクションには、プログラムが使用するデータとして、その固定文字列が最初から用意されることになります。もし、シリアルコード文字列をプログラムの処理として動的に生成するならば、倍直にはなりえません。
近年では、このようなクラッキング手法も広く知られており、プログラミング初心者が製作したシェアウェアであっても、たいていは倍直対策を施します。現在では、解析対策に注力しない一部のシェアウェアを除けば、倍直はまれなケースだといえます。ちなみに、1990年代半ばでは、あるPC誌の付属CDに収録されたシェアウェアの約3割が、倍直ということもありました。
倍直に関して、たとえシェアウェアの不正使用が目的でなくとも、安易にシリアルコードと推測される文字列でユーザー登録を試さないでください。過去に実在したケースとして、あるシェアウェアでは偽のシリアルコードを倍直になるよう用意しておき、そのシリアルコードでユーザー登録を行うと、システム破壊を試みるロジックボムを発動させました。システム破壊とまではいかなくとも、個人情報が送信されるといった可能性は考慮すべきでしょう。
ゲームなどで実行時に作成される「一時ファイル」を残したい
アプリケーションによっては、実行時の任意のタイミングで「一時ファイル(テンポラリファイル)」を作成し、プロセス終了時までに同ファイルを削除するケースがあります。ゲームが作成するこのような一時ファイルには、暗号化されていたデータを復号したものといった、解析の手がかりになりえるデータが格納されることもあります。このような、プログラムで使用する一時ファイルの作成には、CreateFile関数が使用可能です。第6引数で、一時ファイル作成に関連するフラグを論理和で指定します。
●C言語でのCreateFile関数使用例 CreateFile(FileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,//一時ファイル指定 NULL ); ●逆アセンブルコードリスト例 PUSH 0 PUSH 4000100 PUSH 2 PUSH 0 PUSH 0 PUSH C0000000 PUSH EAX CALL DWORD PTR DS:[<&KERNEL32.CreateFileA>] 0x04000100 = FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSEこの場合は、第6引数を定数FILE_ATTRIBUTE_NORMAL(0x00000080)に書き換えることで対処可能です。なお、一時ファイルの作成場所は、必ずしもゲーム等アプリケーションのインストール先フォルダとは限りません。GetTempPath関数を使用して、一時ファイル専用のフォルダに格納するケースも想定されますので注意が必要です。
さらに、CreateFile関数の引数指定ではなく、DeleteFile関数を使用して対象ファイルを削除している可能性も考慮してください。
△ExGAME関連
ExGAMEのVol5、Vol6中のBlueAshさんが寄稿した記事で、付属している逆アセンブルコードリストのレイアウトが変です。
当該原稿はExGAME側がDTPを含む編集全般を担当しており、その過程でレイアウトが変更されています。ExGAMEでBlueAshさんが公開している『ExSTAND』用ゲーム改造コードには、「%」の自動更新マクロを使った方が良いものがあります。
この自動更新マクロは、メモリ容量の少ない環境等で使用した場合に、ゲームの動作を不安定にさせるケースが過去に複数報告されています(「D+VINE[LUV]」他)。そのような報告があったケースにおいて、自動更新マクロでなく『ExSTAND』の「改造コード予約機能」で対処できるのであれば、公開用改造コードとしてはそちらを用いることをお勧めします。当該ケースで自動更新マクロを用いるか否かは、ユーザーの方の判断に委ねた方が良いでしょう。なお、「スペシャルねこまんま57号」や「うさみみハリケーン」でキーフック機能を使えば、改造コード実行の自動更新を動的に設定/解除することが可能です。