Question & Suggestion
- 以下の解説は、PCゲーム解析等プログラム解析の、初心者の方を対象として執筆しました
- 以下の解説は、特に記載のない限り32ビットアプリケーションを対象としています
- 以下の回答は、匿名希望のリバースエンジニア有志とうさぴょん、Lucaで行っています
| デバッガ・逆アセンブラ・プロセスメモリエディタ・ヘキサエディタ関連 | その他 |
ご質問・ご意見一覧
デバッガ・逆アセンブラ・プロセスメモリエディタ・ヘキサエディタ関連
- Windows Vistaや7でOllyDbgなどのプログラム解析ツールは動作しますか?
- 新規購入したパソコンでプログラム解析ツールが実行できません
- プログラム解析環境を構築したい
- 逆アセンブラやデバッガを使ったゲーム解析を行うためには、どの様な知識から身に付ければよいでしょうか?
- プログラム解析以前のコンピュータ関連基礎知識を身につけたい
- プログラム解析ツールの操作を簡単に行いたい
- 初心者はどのツールを使うべきですか?
- やはりプログラミングの勉強をしておいた方がゲームの解析に役立つのでしょうか?
- ファイルパッチコードの形式について教えてください
- 常用している解析ツールをセキュリティソフトがマルウェアと検出しました
- API関数の書式や引数に指定する値等をすばやく探し出す方法はありませんか?
- 逆アセンブルコードリスト上で文字列参照箇所をすばやく見つけるには?
- PE(Portable Executable)ヘッダの詳細情報を簡単に知りたい。
- コード無効化のメモリパッチへNOP命令を用いるべきではないと聞いたことがありますが...
- 『PeRdr』のレジストリへの登録について、レジストリをいじるのはちょっと怖いです。
- ヘキサエディタとバイナリエディタの違いを教えて下さい。
- MOV命令の意味の言い方が人によって違うのですが...
- うさみみハリケーン用のドライバは開発しないのですか?
- 某PCゲームなのですが、改造ツールを起動するとゲームが強制終了します。
- 某PCゲームの2重起動(多重起動)制限を解除したいのですが、どうアプローチすれば良いか分かりません。
- 改造済の実行ファイルを配布したいのですが。
- プロセスメモリエディタの検索で判明したパラメータのアドレスが変なメモリ領域にあります。
- 「環境依存型変動アドレス」について教えてください
- 相互修復型パラメータの書き換え方法を教えてください。
- プログラムの参照文字列を解析の端緒とする場合の注意点を教えて下さい。
- ゲームのシナリオ表示等、文字列を画面に「描画」する処理箇所を特定するためのアプローチ方法が分かりません。
- タスク切り替えが出来ないゲームの対処法を知りたい。
- フルスクリーンのゲームをウィンドウモードでプレイする方法を教えて下さい。
- PrintScreenキーでスナップショットが取れないゲームの仕組みを教えてください。
- ゲームが自己プロセスを隠蔽することは可能ですか?
- ゲームの当たり判定解析のアプローチが分かりません
- 「逆アセンブルコードの再利用」について教えてください
- 日本語版のOSでは起動させないゲームの仕組みを教えてください
- PCゲーム解析関連情報の管理はどのようにするべきですか
- ゲーム実行PCの固有情報を鍵として暗号化されるセーブデータを別のPCで使用したい
- アプリケーションの処理実行速度を変更するソフトウェアの仕組みを教えて下さい
- ゲーム上でランダムに設定されるデータ(出現アイテムの種類など)を固定化したい
- 「倍直」とは何ですか
- ゲームなどで実行時に作成される「一時ファイル」を残したい
- ASLRに影響されないEXEファイル改造を行いたい
- プログラムがユーザーの入力を検出する仕組みを知りたい
△デバッガ・逆アセンブラ・プロセスメモリエディタ・ヘキサエディタ関連
Windows Vistaや7でOllyDbgなどのプログラム解析ツールは動作しますか?
(注)この質問と回答は、もともとWindows XPからVistaへの移行期のものであり、その後Windows 7向けに若干の加筆を行っています。基本的にWindows Vistaや7では、Windows XP SP2で正常動作するほとんどのプログラム解析・改造ツールは動作可能と見られます。ただし、処理をネイティブAPIやカーネルに強く依存するといった特殊なツールではまったく動作しないものもあります。動作上致命的な問題が生じるケースについては、次の質問の回答も参照してください。
扱いに注意が必要なのは、デバッガやプロセスメモリエディタなどプロセスへの操作を行うツールです。Windows Vista以降ではUAC(User Access Control:ユーザーアカウント制御)というセキュリティ関連機能により、管理者としてログインしていても通常のプログラム実行の権限は標準ユーザー扱いとなります。そのため、エクスプローラ上でEXEファイルをダブルクリックするような通常の起動では、自動的に権限のフィルタリングが行われ、デバッガとして十分な実行権限が与えられません。システムで実行されているプロセスの一覧取得も制限され、一部のプロセスしか認識・列挙できなくなります。ここでもし解析対象プロセスも標準ユーザーの権限で実行されているものならば、デバッガやプロセスメモリエディタによる解析は、レジストリの操作やプロセス関連情報取得などに制限はあるものの一応問題なく行えます。しかし、この場合はなにぶんデバッガなどに本来必要な権限がないだけに、解析にあたり何らかの不具合が発生してもおかしくない状況といえます。
Windows Vista以降では、UACにより、管理者としてプログラムを実行する際には、その都度確認のダイアログが表示されとても煩雑です。このことを併せて勘案すれば、デバッガやプロセスメモリエディタに関しては、まず通常どおり(標準ユーザーとして)実行してプログラム解析を行い、なんらかの問題が発生したら管理者として実行するという方法が、利便性と安全性の面からみた妥協案になると考えられます。また、Windows Vistaでは、UACの制御を簡易化し、権限昇格確認のダイアログ表示を抑制するツール『TweakUAC』や『Norton UAC Tool』を利用するという方法もあります。
Windows 7ではUACの警告表示についての設定を簡単に変更可能です。コントロール パネル→[システムとセキュリティ]→[アクションセンター]→[ユーザー アカウント制御設定の変更] か、あるいは、スタートメニュー下部「プログラムとファイルの検索」箇所に以下の実行可能ファイル名文字列を貼り付け、表示されるリンクをクリックします。
UserAccountControlSettings.exe
さらに、レジストリを変更することでより細かい設定変更が可能になります。
参考:UAC設定をレジストリから制御し、スムーズなセキュリティ設定を実現する
Windows Vista以降では、管理者の権限でプログラム解析ツールを実行するためには以下の方法が使用できます。
- エクスプローラ上でプログラム解析ツールのEXEファイルを右クリックし、表示されるポップアップメニューから「管理者として実行」で実行させる
- Vista専用のマニフェストファイルを使用する(使用法など詳細はマニフェストファイル同梱テキストにあります)
- エクスプローラ上でプログラム解析ツールのEXEファイルかそのショートカットを右クリックして、ポップアップメニューの「プロパティ」→「互換性」タブ→「管理者としてこのプログラムを実行する」チェックボックスを有効にする(マニフェスト使用よりも若干権限が低くなります)
- ランチャーをタスクスケジューラに登録する([最上位の特権]を指定、必要に応じ処理優先度を修正)
- UAC(ユーザーアカウント制御)の設定を無効にする(セキュリティ確保の面からお奨めしません)
OllyDbgでは、ジャスト・イン・タイム デバッガとしてOllyDbgを登録したり、エクスプローラが表示するポップアップメニューにOllyDbgを登録する際には、レジストリの書き込み処理を成功させるために管理者として実行する必要があります。この登録処理は権限の不足により失敗してもエラーメッセージは表示されません。
Windows Vista以降での「OllyDbg」や「うさみみハリケーン」のインストール先フォルダは、既定の「Program Files」フォルダ内とは別の場所に新規作成することをお奨めします。これは、両ソフトで各種設定保存に使用するINIファイルが、Vistaで実装されたUACのファイル/レジストリ仮想化機能(VirtualStore)の影響を受けないようにするためです。これにより、従来どおり関連ファイル一式がインストール先フォルダ内のみに集約され、関連ファイルが他のフォルダに分散することを回避できます。
VirtualStoreにより、Program FilesフォルダやWINDOWSフォルダ内で行われたファイル作成・読み書きアクセスは、VirtualStore専用フォルダにリダイレクトされます。このフォルダは、Vistaのスタートメニュー下部「検索の開始」箇所、7では「プログラムとファイルの検索」箇所に以下のいずれかの文字列を貼り付けることで、当該フォルダをエクスプローラで開くリンクが表示されます。
%USERPROFILE%\AppData\Local\VirtualStore
%LocalAppData%\VirtualStore
解析ツールがキーフックによる各種操作などで、内部的にメッセージフックを用いる場合には注意が必要です。この場合、解析ツールから解析対象アプリケーションを起動することなどにより、解析する側とされる側の実行権限を同じレベルに合わせないと、メッセージフックが正常に動作しません。ドラッグ・アンド・ドロップが正常に機能しないケースも権限レベルの不一致が原因として考えられます。ちなみに、同様の制限が一部のプロセス間通信にも適用されます。もし、上記のような問題がおきたときは、プログラム解析ツール、解析対象アプリケーション「両方」を管理者権限で起動させてみて下さい。
●<参考>当サイト管理人の動作確認環境と動作確認時実行手順
・Windows Vista Home Premium 32bit クリーンインストール
・UAC(ユーザーアカウント制御)は有効
・インストール先フォルダは「C:\APP\UsaMimi」や「C:\APP\Olly」
・起動時はエクスプローラ上でEXEファイル右クリックから[管理者として実行]
新規購入したパソコンでプログラム解析ツールが実行できません
(注)この質問と回答は、もともとWindows XPからVistaへの移行期のものであり、その後Windows 7向けに若干の加筆を行っています。パソコンの買い替え等に伴い、実行環境がWindows XPからVistaや7に変更された場合、XP上では問題なく動作する一部のプログラム解析ツール(以下「解析ツール」)が実行できないケースがあります。以下に想定される原因を列挙しました。
- DEP
OSに実装されているDEP(データ実行防止)機能により、プログラムの処理上でDEPを想定していない一部の解析ツールは、実行不能となります。ただし、OSのDEP初期設定では、システムに関わる重要なプロセスのみがDEPの対象として設定されています。そのため、設定を自分で変更し、全てのプロセスをDEPの対象にした場合に、表題のような実行不能になるケースが生じます。しかしながら、一般に販売されているパソコンの中には、一部の大手メーカー製や一部のショップ製パソコンで、出荷時にDEPの設定を変更しているケースもあります。なお、パソコンに不具合が生じた際に、家族・親戚・友人・パソコン修理業者などにパソコンを診てもらった結果、気づかないうちにDEPの設定が変更されていたというケースも実在します。
- OSバージョンチェック
- API関数の仕様変更
- 64ビット版Windows 7
- セキュリティソフト・オンラインゲーム
- (参考)ヘルプが表示されない
通常、DEPによりプロセスの実行が中断された場合は、OSがそのことをユーザーに警告します。しかし、Windows Vista Home Premiumで確認した限りでは、一切の警告なしにプロセスの実行が中断され強制終了するケースもあります。
もし、DEPが解析ツール実行不能の原因と推測される場合は、DEPの設定を確認し、解析ツールのEXEファイルを対象から除外してみてください。 Vista以降でのDEPの設定は、以下の手順で行います。
1.Windows Vistaでは、スタートメニューから、[コントロールパネル]→[システムとメンテナンス]→[システム]→[システムの詳細設定]をクリックします。Windows 7では、スタートメニューから、[コントロールパネル]→[システムとセキュリティ]→[システム]→[システムの詳細設定]をクリックします。
あるいは、Vistaのスタートメニュー下部「検索の開始」箇所、7では「プログラムとファイルの検索」箇所に以下の実行可能ファイル名文字列を貼り付け、表示されるリンクをクリックします。
SystemPropertiesAdvanced.exe
2.管理者としてのアクセス許可の後、[パフォーマンス]欄の[設定]ボタンをクリックします。
3.[データ実行防止]タブをクリックし、オプション[次に選択するのものを除くすべてのプログラムおよびサービスについてDEPを有効にする]が選択されているならば、[追加]ボタンで、解析ツールの実行可能ファイルを除外対象に追加します。設定変更内容を反映させるため、必要に応じて再起動させます。
一般的なソフトウェアならば、OSに実装されている「互換モード」により対処可能なケースもあります。しかし、特にプロセスを対象とする解析ツールでは、一部のプロセス操作用API関数でOSによって仕様が異なるため、「互換モード」適用では対処できないケースがあります。
望ましい対処策として、作者に対して状況と当該解析ツールの必要性を説明し、Vistaや7への対応を打診してみることをお勧めします。
実例として、拙作の汎用プロセスメモリエディタ兼デバッガ『うさみみハリケーン』では、Vista対応にあたり、複数のプロセス関連処理箇所でソースコードを書き替えました。
このようなケースでは、作者にVistaや7への対応を希望することで、最も適切な対処が行われると考えられます。
なお、解析ツールの作者側としては、上記のケースとは逆に、Vistaや7での正常動作をもってWindows 2000/XPで正常に動作すると判断すべきではないことに、注意が必要です。特にネイティブAPIを使用している場合は、OSによって対応する引数の範囲が異なるケースを考慮してください(NtQueryInformationProcess関数の引数ProcessInformationClassなど)。
関連して、Windows OSに含まれる、DbgHelp.dllがエクスポートしているMiniDump系のAPI関数では、内部処理に用いられる構造体がXP SP2とVistaで異なるものがあります。そのため、この構造体の変更を想定していない一部の解析ツールにおいて、XP SP2では正常に動作するが、Vistaではクラッシュするというケースもあります。このような場合は、作者の動作確認環境に含まれるDbgHelp.dllを、ツールに同梱して再配布することで対処可能です。DbgHelp.dllは実行環境におけるバージョン差異に対処するために、再配布が許可されているDLLです。また、DbgHelp.dllは「Known DLLs」ではないので、実行ファイルと同じフォルダに導入したものが優先的にロードされます。
DbgHelp.dllを最新のものにすれば、処理速度の向上や取得できる情報の増加というメリットが増える場合もありますが、解析ツールの作者が想定していない新しいバージョンのDbgHelp.dllでは、上記のようにこのDLLの仕様により不具合を招くケースもあります。他の例として、たとえばWindows 9x対応のためIMAGEHLP.dllとDbgHelp.dllの両方のAPI関数の呼び出しを処理中で混在させている解析ツールで、DbgHelp.dllが新しすぎると不具合を生じることがあります(注:Windows Vista以降でのこれらDLLのファイル名は「dbghelp.dll」、「imagehlp.dll」)。
なお、64ビット版Windows 7では32ビット版と異なり、32ビット版アプリケーションのプロセスメモリ上に読み込まれるGDI32.dll、kernel32.dllといった一部のシステムモジュールは、ReadProcessMemory関数を用いてプロセスメモリ上でのモジュールエリア全体を一度に読み込むことができない仕様となっています。このため、32ビット版Windowsが想定使用環境のプログラム解析ツールでは、プロセスメモリ上からの情報取得時や逆アセンブル時などに、モジュール全体が読めないことでエラーとみなすケースがあります。このことは、実際のゲーム解析といったプログラム解析を行う上で深刻な問題とまではいえないものの、プログラム解析を制約する要素となりえます。
また、セキュリティソフトが実装する「ゲームモード」といった特殊なモードにより、解析ツールのダウンロード時や、その配布ファイルの解凍時に、一切の警告無しに解析ツール本体の実行ファイルが削除されるケースにも注意が必要です。これにより、配布ファイルを解凍すると、「実行ファイルが見つからない」という事態が発生します。「ゲームモード」は本来ユーザーが自分自身で設定するものですが、初心者の方の中には、「ゲームモード」がどのようなものか知らないまま設定している方も少なくないようです。
Windows Vista以降では、既存のHLP形式のヘルプファイルを表示するには、Microsoftが配布する専用のソフトウェアをダウンロードする必要があります。
プログラム解析環境を構築したい
一般論としてプログラム解析環境に用いるOSは、プログラム解析環境における、プログラム解析への制約の少なさという面でみれば、Windows 7やVistaよりもXP、64ビット版よりも32ビット版が望ましいということになります。XP 32ビット版ならば、Windows XP時代に公開された解析関連ツールや解析資料といった、過去の多くの資産を十分に活用できるという利点もあります。ただし、解析対象プログラムがカーネルモードでのAPIフックといったルートキット同様の解析対策を実装しているケースでは、署名なしのドライバへの制約が増加した64ビット版の方がスムーズに解析できることもあります。
また、一般的なPCゲームやアプリケーションならばPC実機で解析を行い、マルウェアや「挙動に問題がある」アプリケーションの解析には「VMware Workstation」や「VMware Player」などで構築した仮想PCを用います。できれば、解析専用にPC1台を割り当てて、たとえ解析対象プログラムのロジックボムなどでシステムに不具合が生じたり起動不可となっても、システム対応のバックアップソフトなどを使って問題なく復旧できるようにしておくと良いでしょう。仮想PCならば、スナップショット機能を使用して問題発生時に元の状態へ戻すか、仮想PCの本体であるファイルのバックアップで対処できます。なお、仮想PC検出・対策機能を実装しているマルウェアも実在しますので、仮想PCという解析環境を過信しないでください。関連して、仮想PCは、挙動が不明なプログラム解析ツールの動作確認にも役立ちます。
解析および、解析結果に基づく改造結果の動作確認について、私の場合は、OSに独自設定が施され色々なソフトウェアがプレインストールされているメーカー製PCは使用しません。解析用PCは自作してWindowsをクリーンインストールし、OSの標準設定のままで解析・動作確認を行っています。また、仮想PCには解析結果に基づく改造時動作のOS差異を考慮して、複数のバージョンのWindowsを用意し、動作確認を行っています。これは、公開する解析結果の汎用性を重視しているためです。
プログラム解析ツールの導入や使用については、当Q&Aの他の質問と回答を参考にしてください。
余談ですが、一部のリバースエンジニアの方々は、解析環境の「構成要素」として、猫とか「Lonely Planet」とかチョコレートとか楽器とかの、思考回路をリフレッシュさせる何かが必須だと主張されています。
逆アセンブラやデバッガを使ったゲーム解析を行うためには、どの様な知識から身に付ければよいでしょうか?
逆アセンブラやデバッガを使う前に、まずヘキサエディタ(バイナリエディタ)やプロセスメモリエディタ等の基本的な改造ツールの操作を身に付けて下さい。それからゲーム解析チュートリアルに加えて、インテルのアーキテクチャマニュアル、プラットフォームSDK等を揃えてアセンブリ言語とWindowsAPIの基本事項(レジスタ、条件ジャンプ等各種命令、各種API関数他)を理解し、その上で逆アセンブラやデバッガを用いたゲーム解析を始めれば良いかと思います。また、上に挙げた情報ソースは、面倒でも疑問点があれば何度でも参照して正しい知識を身に付けて下さい。また、基本事項をより理解するために、上記基本的な改造ツールやデバッガのヘルプには十分に目を通し、さらに必要に応じてネット上で検索サイトや用語集サイトを利用したり、当サイトで紹介している解析参考書籍を参照することをお奨めします。
なお、知識が不十分である事柄について他の方に教える場合は、「自分も詳しくは無いのですが」等の一文を加えて知識が不十分であることを明確にしておけば、よほどの勉強不足で無い限り、もし内容に予期しない間違いがあっても非難されることは無いと思います。
過去の実例を見る限り、アセンブリ言語やWindowsAPIに関する基本的な知識を身に付けないまま逆アセンブラやデバッガを使ったゲーム解析に挑んだ方々が、解析の方向性を定められず無意味な解析に時間を浪費し、最終的に挫折してしまうケースが少なくありませんでした(もちろん私のケースを多々含みます)。
しかし、上記のアセンブリ言語とWindowsAPIの基本事項に加えて、解析対象のゲームに対して「自分がどのような解析・改造を行いたいか」を明確にした上で、その解析に必要な正しい知識をチュートリアル等を通じて身に付けてから解析に臨めば、大抵のゲーム解析は特に迷うこともなくスムーズに解析できるはずです。もちろん逆に、現在の自分のスキルでは対処できないケースという判断も的確かつ迅速に行えます。
必要な知識が欠如していると、「闇雲に書き換え」等、あまりにも非効率的な解析を行うことになります。このような解析は、私を含めた複数のゲーム解析関連の方の経験から言えば、ゲーム解析スキル向上には殆ど役に立ちませんので、十分にご注意下さい。
以上は簡略化した「一般的な」回答です。リバースエンジニアそれぞれ進む方向は異なります。が、「自分は何がしたいか、そのために何が必要か」を自問し続ければ、迷うことは少ないと思います。
プログラム解析以前のコンピュータ関連基礎知識を身につけたい
当サイトで紹介しているプログラム解析の参考書籍でもこのような基礎知識は解説しています。しかし、それとは別に、後々プログラム解析にスムーズにリンクすることを踏まえて、私や他のリバースエンジニアの方々が超初心者の方に勧めている解説書は以下が挙げられます。・プログラムはなぜ動くのか
・はじめて読む8086
なお、『はじめて読む8086』と同じ筆者による『はじめて読む486』『はじめて読むPentium マシン語入門編』も出版されており、できればこの3冊を読み比べて、自分が求める内容に一番近いものを選択されることをお勧めします。
上記の解説書に記述されている情報は、ネット上で入手できない訳ではありません。実際、この様な解説書に頼らず、ネット上で資料を収集して学ばれた方もいます。しかし、超初心者の方が短時間で体系的にコンピュータ関連基礎知識を身につけるには、定評のある上記の解説書を用いるのが適切と考えられます。 ただし、これらの解説書に書いてあることだけにとらわれず、疑問に感じた点はネット上で調べる等、自分で知識を拡げていくという自己努力の姿勢も大切です。「読んだ」だけで満足しないよう注意して下さい。
プログラム解析ツールの操作を簡単に行いたい
一般的なプログラム解析ツールは、ユーザーが簡単に操作できるようにするために、色々な工夫をこらしています。基本的に、このような操作簡易化のための仕組みは、プログラム解析ツールに同梱されているヘルプで解説されています。しかし、たとえば、プログラム解析ツールの作者が、ユーザーには基本的なツール操作やプログラミングの知識があるはずと考えるケースでは、ヘルプでの説明が省略されていることもあります。以下では、特に解析初心者の方が知っておくと役立つ、操作簡易化のための仕組みを列挙しました。以下の『うさみみハリケーン』に関する記述については、同梱ヘルプやオンラインヘルプに詳細な解説がありますので、参照されることをお勧めします。
●ファイルやプロセスの指定方法
解析対象となるファイルあるいはプロセスの指定・オープンには、たいてい、その手順を簡易化する仕組みが設けられます。ファイルならば、メニューに「最近使ったファイル」が表示され、メニューから簡単に指定できるケースがあります。また、コマンドラインオプションに対象ファイルを指定して解析ツールを起動させることで、自動的に指定・オープン処理を行うものもあります。この機能は、エクスプローラ上の右クリックで表示される、ポップアップメニューに解析ツールを登録すると特に役立ちます。
プロセスならば、メインモジュール(EXEファイル)のパスかファイル名を指定することで、プロセスの選択・オープン処理を自動化するものがあります。この機能は、同じゲームを何度も起動して解析するといったケースに役立ちます。『うさみみハリケーン』では、コマンドラインオプションに「C:\APP\UsaMimi\UsaMimi.exe /UsaTest2.EXE」といった形式で解析対象プロセスのEXEファイル名を指定することで、『うさみみハリケーン』起動時にそのプロセスを自動で選択・オープンします(参考)。この場合、オープンしている対象プロセスが終了・再起動しても、「Ctrl+O」キー押し下げだけでその再起動したプロセスを選択・オープンします。このようなコマンドラインオプションの指定は、ショートカットかランチャーで指定するのが簡単です。
●16進数の指定方法
プログラム解析ツールによっては、10進数での数値指定とは別に、16進数での数値指定もできるようにしています。指定方式にはいくつかのパターンがありますが、「0x12345678」というように「0x」をつけるケースが多いといえます。この場合、汎用のプログラム解析チュートリアルで解説しているような、Windows付属の「電卓」を使用した16進数への変換は不要です。なお、「0x」での16進数の入力に対応しているならば、さらに、入力した数値の最初がゼロで、以降を1から7で入力する8進数の入力にも対応しているケースがあります。
『うさみみハリケーン』でのプロセスメモリ検索機能・範囲検索機能・置換機能・一括書き込み機能などでは、「0x」で16進数指定が可能です。また、『うさみみハリケーン』には、テキスト入力ベースの進数変換・式入力計算ソフト「UMEC」を同梱しています。
●バイナリデータの指定方法
一部のプログラム解析ツールには、検索などでバイナリデータを指定する際に、「シーケンス」や「マスク」という方法を使用可能なものもあります。シーケンスとは「1 0x1234 86 120 0xABCDEF00」というように、指定された複数のデータを連結して一つのデータとして扱う方法です。『うさみみハリケーン』のプロセスメモリ検索機能・範囲検索機能(64Bit Mode)・置換機能・一括書き込み機能・10進入出力機能他はシーケンスに対応しています。マスクは指定するバイナリデータの一部を無視して処理を行う方法で、「ワイルドカード」と表記されることもあります。マスクの使用例としては、検索対象のバイナリデータに「7FFF****05*0」と指定して、「*」部分は無視して検索を行うケースが挙げられます。マスクの指定には「*」の他に「?」もよく使われます。『うさみみハリケーン』では、範囲検索(64Bit Mode)やリージョン検索でバイト列を検索する際に、マスクを「?」で指定することができます。
●各種クリックへの機能割り当て
プログラム解析ツールでは、リストやバイナリデータ表示画面などで選択した項目に対して、左クリックでの選択以外に各種クリックが使用可能なケースもあります。このようなケースではたいてい、選択項目に対して、ダブルクリック、右クリックあるいは、ホイールボタンクリックに、その選択項目に応じた処理を行うための機能を割り当てています。そのため、画面上で何かを選択したら、まず右クリックなど各種クリックを試してみることをお勧めします。また、リスト上で項目選択後に、ダブルクリックにならない間隔で同じ項目を選択すると、項目表示部分の文字列を編集できるようになるケースもあります(リストビューの「ラベル編集」)。この場合、項目文字列の右クリックにより、項目文字列を簡単にコピーすることが可能です。
初心者はどのツールを使うべきですか?
ツールの選択基準としては、解析者のスキル・解析対象・アプローチ手法・ツールの実装機能・その他の要素があり、一概にどれを使うべきとはいえません。そのため、ここに列挙した選択基準等を参考に、自分にあうかどうか色々なツールを解析の実践で試されることをお奨めします。このことは、自分なりの解析スタイルを模索するということにおいても、とても重要な要素といえます。サイト管理者としては、当サイトで紹介しているツールを使って頂ければ幸いです。
なお、ひとつのプログラム解析ツールにこだわらず、複数のプログラム解析ツールをうまく組み合わせて併用し、プログラム解析の工数を減らすという創意工夫も大切です。気になったツールを見つけたら、まずは仮想PC上で試してみることをお勧めします。あるプログラム解析ツールが自分に合うか否かというのは、ネット上の評価に依存せず、自分自身で判断しましょう。
やはりプログラミングの勉強をしておいた方がゲームの解析に役立つのでしょうか?
アセンブリ言語やC言語のプログラミングの知識が、ゲーム解析などのプログラム解析に役立つのは事実ですが、必須とまでは言えません。個人的にはアセンブリ言語やC/C++言語で、ダイアログベースのゲーム改造ツールを製作可能な程度のスキルは身に付けた方が良いかと思います。アセンブリ言語でのプログラミング例は、当サイトで公開しているチュートリアル「改造初心者向け練習用プログラム No1」を参照して下さい。また、当Webサイトのチュートリアル欄では、各種開発言語での改造ツールのソースコード配布サイトも紹介しています。
アセンブリ言語でのプログラミングには『MASM32』が導入や学習に適しています。C言語については、無償で配布されている『Visual Studio Express Editions』で簡単なアプリケーション製作から始めてみることをお勧めします。すでにネット上に多くの参考となる解説サイトがあります。これらの開発環境の入手先は当Webサイトのツール欄で紹介しています。
なお、アセンブリ言語に関する基礎知識とアセンブリ言語によるプログラミングの参考書としては、『アセンブリ言語の教科書』をお奨めします。アセンブリ言語を使用して自分でレジスタ・スタック・API関数などをどう使うか考えながらプログラムを組み、さらにそのプログラムの実際の処理をデバッガで解析することは、PCゲームの解析において大切な「処理の流れを理解する」力をつける上で役立ちます。特に超初心者の方は、PCゲームで解析を実践するよりも先に、処理内容を把握しており解析しやすい自作プログラムで解析の練習を行っておくと、後々PCゲームの解析へのステップアップがスムーズになります。
近年では、一部のプログラム解析ツールが機能拡張のためにPythonやLuaを使用するケースが増えています。アセンブリ言語やC/C++言語の基礎を十分理解した後に、これらの言語にも触れてみることをお勧めします。
ただし、PythonやLuaによる、プログラム解析ツールの機能追加用スクリプトを製作するよりは、C/C++言語を使って自作ツールの形で同機能を実現する方が、よりプログラム解析のスキル向上に繋がると考えられます。特にファイルやプロセスメモリのやや複雑な読み込みや書き込みといった処理は、プログラム解析・改造ツール自作の格好の題材といえます。
ファイルパッチコードの形式について教えてください
バイナリファイルのパッチコードについては、コマンドプロンプトでの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号』ではファイルマッピングを使用しており、書き換え対象ファイル全体をメモリ上に読み込みませんので、巨大なバイナリファイルでもフリーズすることなく高速に書き換えを行うことが可能です。
常用している解析ツールをセキュリティソフトがマルウェアと検出しました
そのセキュリティソフト開発元、あるいはアンチマルウェアエンジン提供元に対して、「False Positive Report」(誤検出の報告)を出しましょう。たいていは作者側でもこの誤検出レポートは送信しているのですが、すべてのセキュリティソフトまではカバーできないのが実情です。また、セキュリティソフトによっては、購入者以外の誤検出レポートを一切受け付けないため、Virustotalなどで誤検出の発生が判明しても、作者側では対応できないこともあります。なお、このレポート提出時には、誤検出された実行ファイルのみではなく、挙動確認ができるように「付属DLLも含めた実行ファイル一式」を送るようにしたほうが、よりスムーズに対処されると考えられます。
また、この誤検出レポートへの対応はある程度自動化されているとはいえ、このレポートを人が読む場合を考えて、「Fucking (セキュリティソフト名) !」や「(セキュリティソフト名) sucks!」の多用など、不適切な表現はなるべく控えましょう。
『うさみみハリケーン』が頻繁にセキュリティソフトから「マルウェアと疑わしい(Suspicious)」ソフトウェアだと誤検出されるように、プログラム解析ツールが誤検出の対象となることは珍しいことではありません。このような誤検出の場合、検出名としてはたいてい「Suspicious」や「Generic」・「Gen」といった、マルウェアや亜種ウイルスと疑わしい(マルウェアそのものではない)ことを示す名前がつけられます。
API関数の書式や引数に指定する値等をすばやく探し出す方法はありませんか?
まず、プラットフォームSDKを入手してください。API関数の書式についてはプラットフォームSDKでの関数名検索がお奨めです。他にもMSDNサイトあるいは各種サーチエンジンで関数名を使って検索するという選択肢があります。なお、API関数については、その書式から使用例のC言語ソースまで掲載しているプログラミング系解説サイトが多々あり、プログラム解析を学ぶ上でも参考になります。引数については、プラットフォームSDKで目的の関数に用いるインクルードファイルと引数に指定する定数名を確認した上で、同インクルードファイルをテキストエディタで開いて定数名を検索すれば定数の実際の値を得られます。このインクルードファイルは、プラットフォームSDKだけでなく各種コンパイラにも付属しています。実際に頻用するインクルードファイルは、「Kernel32.DLL」等に対応する「winbase.h」、「User32.DLL」に対応する「winuser.h」および「ntdll.dll」に対応する「WinNT.h」等です。なお、アセンブラでのコーディング用に、主要な関数が用いる定数名と実際の値を定義している汎用インクルードファイルも公開されていますので、それを参照するという選択肢もあります。
逆アセンブルコードリスト上で文字列参照箇所をすばやく見つけるには?
『OllyDbg』にはプログラムが参照する文字列一覧をアドレス付きで表示する機能や、対象文字列先頭アドレスを指定して参照コード一覧を表示する機能があります(「OllyDbg Q&A」参照)。また、『うさみみハリケーン』が実装している逆アセンブラでも、『OllyDbg』の様に、参照文字列一覧を表示しワンタッチで逆アセンブルコードリスト上での参照箇所を確認可能です。しかし、この様な参照文字列抽出は文字列認識アルゴリズムとの関係上完璧に行われない場合もありえるため、「DATA」または「.data」などのデータセクションを『OllyDbg』や『うさみみハリケーン』上でダンプして、その内容を視認しておくことをお奨めします。『うさみみハリケーン』には、プロセスメモリ上の任意の指定範囲から、格納されている文字列を一括抽出する機能も実装しています (メニューの[編集]→[指定メモリエリアから文字列を抽出])。
なお、対象実行ファイルが『Delphi』または『C++ Builder』でコンパイルされている場合は、コードセクション内(「CODE」や「text」)にダイアログの設定フォームに関する文字列等が含まれます。特に『Delphi』では警告メッセージ文字列等もコードセクション内に含めることが可能です。しかし、現状では、特にPCゲームの実行ファイルが『Delphi』または『C++ Builder』でコンパイルされているケースはごくまれですので、参考程度に考えておいてください。
また、「DATA」セクション等に格納された、プログラムが認識する文字列は、値00h(ANSI)や値0000h(Unicode)を文字列終端としていることに注意が必要です。
PE(Portable Executable)ヘッダの詳細情報を簡単に知りたい。
まずPEヘッダについて書かれた資料を読んでおくことをお奨めします。たとえば「Microsoft(R) Portable ExecutableおよびCommon Object File Format仕様書」が参考になります。さらに、現在ではネット上でPEヘッダについての良質な解説ページも複数見つかりますので、必要に応じて検索サイトを利用することをお勧めします。そして、『うさみみハリケーン』付属のPEエディタ『UMPE』を使用することで、PEヘッダの詳細情報の表示や編集が可能です。
『OllyDbg』でのデバッグ中ならば、PEヘッダ詳細情報は、まずメニューの「View」(表示)から「Memory」(メモリ)でメモリマップを表示します。この時点で「CODE」や「DATA」等の各セクションの簡単な情報を得ることができます。さらに、メモリマップの一番上の列(カラム)にある「Contains」項目が、「PE header」となっている列を選択・右クリックから「Dump」でPEヘッダの詳細情報が表示されます。
コード無効化のメモリパッチへNOP命令を用いるべきではないと聞いたことがありますが...
結論からいえば、コード無効化のメモリパッチへNOP命令を用いても構いません。ただし、NOP命令は「何もしない」のではなく、実際には「(E)AXレジスタと(E)AXレジスタの内容を入れ替える」という処理を行っており、当然ながら処理に相応の時間を要します。そのため、メモリパッチでコードを無効化したい範囲があれば、ジャンプ命令で無効化したい範囲の先頭と終端を結ぶバイパス書き換えで対処するのが「美しいコーディング」といえます。この際、メモリパッチ箇所の視認性や可読性を高めるためのパディング(不要箇所への詰め物)に、「実行されないコードとして」NOP命令を用いるのは問題ありません。なお、コードを無効化したい範囲あるいはコード書き換え後の余剰範囲が4バイト以下程度であれば、NOP命令による対象範囲上書きでも適切といえます。ちなみに、質問にあるNOP命令使用への否定的な意見は、決して誤りではありません。過去に海外で発売された一部のゲームは、改造対策として「連続NOP処理検出機能」を実装していたためです。ただし、現在では改造対策に「連続NOP処理検出機能」よりも優れた多くの方法が編み出されており、今後「連続NOP処理検出機能」が即時ゲーム終了フラグや時限起動型の改造トラップ用フラグ設定に用いられる可能性は低いと考えられます。そのため、個人的には「コード無効化のメモリパッチへNOP命令を用いても問題ない」と判断しました。
なお、一部のコンパイラは「Code Align」機能により、コーディングに関係なくコンパイルされた実行ファイルにはNOP命令が多用される仕様となっています。
『PeRdr』のレジストリへの登録について、レジストリをいじるのはちょっと怖いです。
(注)この質問と回答はすでに古いものですが、エクスプローラ上での右クリックにプログラム解析ツールを登録する際の参考になるため残しています。当Webサイトで配布している『PeRdr』用REGファイルを使えば、手動でレジストリを書き替える必要はありません。また、このREGファイルにより変更される箇所は拡張子との関連付けを設定しており、Windowsの中核であるシステムには関与していないため、たとえ同梱REGファイルを使わず手動で少々間違えて書き替えても、深刻な状況を招くことはまずありません。
また、レジストリの書き換えに不安のある方は、あらかじめ書き換え箇所の部分的なバックアップを作成することをお奨めします。例えば、上記REGファイルではレジストリの「HKEY_CLASSES_ROOT\exefile\shell\」以下を書き替える訳ですから、レジストリエディタでこの「shell」の部分を選択して、メニューから「レジストリ」→「レジストリファイルの書き出し」でレジストリ該当部分のバックアップが作成できます。これにより、「HKEY_CLASSES_ROOT\exefile\shell\」以下の必要以外の部分を間違って書き替えても、上記REGファイル同様に、バックアップのREGファイルをエクスプローラ上でダブルクリックすればレジストリの該当箇所がバックアップで上書きされます。
なお、手動またはアプリケーションによるレジストリ書き換え不具合への対処には、Windowsの機能である「復元ポイント」を作成するという選択肢もあります。
ヘキサエディタとバイナリエディタの違いを教えて下さい。
正確な表記か否かです。正式名称:ヘキサデシマルエディタ
略称:ヘキサエディタ
テキストファイルエディタとの対比用:バイナリファイルエディタ
誤り:バイナリエディタ
いわゆる「バイナリ」という単語には「2進法の」という本来の意味と、それから派生した、「(データが)0と1で構成される」という2つの意味があります。上記を見て頂ければ分かると思いますが、「ヘキサエディタ」との対比という点で「バイナリエディタ」とは前者の意味であり、「2進法即ち0と1のみを用いた書き換えによるファイル編集ソフト」を指すことになります。また、「バイナリ」を「0と1で構成される」の意味で用いる場合は「バイナリデータ」、更に「バイナリデータ」で構成されるテキスト形式以外のファイルは、上記の通り「バイナリファイル」と表記すれば「バイナリ」という単語の意味の正しい使い分けができますので、この様な表記が適切と言われています。
実際のところ、日本では上記の2つの意味を混同した、ヘキサエディタを「バイナリエディタ」とする表記が定着しており、その表記が問題にされることは現在では殆どありませんし、私は「ヘキサエディタ」という表記を強要するつもりもありません。個人的には、日常使うに当たっては特に「バイナリエディタ」でも構わないと考えています。しかし、英語のネイティブスピーカーであるリバースエンジニアに対しては、「バイナリエディタ」は一応意味は通じるものの「変な英語」に聞こえているようです。 当然ながら海外では「Hex editor」あるいは「Binary file editor」と表記するのが一般的です(リバースエンジニアのサイトでは特に)。ただ、海外においても「言葉の乱れ・揺らぎ」というものはあります。
ちなみに、海外のソフトで本物の「バイナリエディタ」(0と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
うさみみハリケーン用のドライバは開発しないのですか?
一部のマルウェア(オンラインゲームを含む)の解析対策は、現在では、たいていrootkitの技術を使用してカーネルモードで行っており、ユーザーモードで動作するプロセスメモリエディタやデバッガ単体では対処できません。そのため、プロセスメモリエディタやデバッガといったプログラム解析ツールと、自分で製作したドライバを併用することで解析対策に対処している方が少なくありません。ドライバ併用により、プログラム解析ツールのプロセス隠蔽や、プロセスのオープン処理やプロセスメモリ読み書き処理のカーネルモード化、各種カーネルオブジェクトの操作などが可能になります。
このようなドライバは、公開すれば迅速に対策されてしまうことや、バグがあるとユーザーのシステムに深刻な影響を与えかねないといった理由から、ドライバ自体は公開されず、ドライバ自作のヒントとなる技術情報のみ提供されるというパターンが見受けられます。なお、「セキュリティツール」として、このようなドライバを自作する上で参考となるソースコードを配布しているサイトもあります。
現実的な問題として、もしプログラム解析ツールに上記のようなドライバを同梱した場合は、即座に解析・改造対策を施される可能性や、特にカーネルモードで解析対策が実行されている状態では、プログラム解析ツール側のドライバが安全に動作しない可能性があることから、ドライバの開発は見送っているのが現状です。
さらに、プロセス隠蔽やAPIのフックと処理のフィルタリングを行うドライバは、高確率でセキュリティソフトからマルウェアと認定されます。また、システムの深部を弄るドライバを使用するソフトウェアは、たとえドライバ使用が明示的であっても、好まない方が多いという実情もあります。
某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/DVDチェック解除済等の改造済ゲーム実行ファイルが普通に配布されています。しかし、日本では著作権法と不正競争防止法に抵触すると言われており、配布は控えた方が良いと思われます。日本での実情としては、ファイルパッチコードあるいはプロセスメモリ用パッチコードの形式で、文字列として実行ファイルの改造情報を公開するのが一般的となっています(ゲーム本体の違法コピーを配布するケースは別)。
プロセスメモリエディタの検索で判明したパラメータのアドレスが変なメモリ領域にあります。
このケースでは、プロセスメモリ上のゲームのEXEモジュールに属するメモリエリアではなく、EXEモジュールが使用する、ゲームと共にインストールされたDLLモジュールに属するメモリエリアでパラメータを管理していました。このようなケースでは、DLLファイルの拡張子を変更していることもありますので、解析時には必ず対象プロセスのモジュールリストを確認してください。また、このゲーム専用のDLLファイルが読み込まれるアドレスは、環境により異なることがありますので注意が必要です。『うさみみハリケーン』や『スペシャルねこまんま57号』の改造コードを用いた、DLLモジュールに属するアドレスの書き換えについては、ヘルプの「改造コードの活用例」を参照願います。
なお、ゲームのパラメータなどを格納するメモリエリアは、VirtualAlloc関数による確保時に第3引数に定数MEM_TOP_DOWN(0x100000)を論理和で追加設定することにより、そのアドレスは7FF*****といった可能な限り高いアドレスに確保することができます。この高アドレスのメモリエリアは、プロセスメモリエディタでの通常の指定検索範囲に入らない可能性が生じるため注意が必要です。逆に言えば、プログラムコードを書き換えてVirtualAlloc関数呼び出し時に常にこのフラグを追加すれば、大きなサイズのメモリエリア作成時には必ず高いアドレスとなり、その高いアドレスにゲームで使用するパラメータなどのデータが集中すれば、プロセスメモリエディタでの検索が容易になるケースもあると考えられます。
「環境依存型変動アドレス」について教えてください
●アドレス変動の仕組み|
例えば、PCゲームのパラメータ等のアドレスが、プロセスメモリ上に『***.exe』といった解析対象アプリケーションのメインモジュールがロードされたエリアであるモジュールエリア内(基本的にアドレス0x400000〜のメモリエリア)にあるならば、そのアドレスは実行環境等に依存しない固定アドレスとなります。これは、メインモジュールのロード処理は実行環境に影響を受けず、また、メインモジュール内のデータ格納用セクションには、基本的にプログラム側で使用することやサイズ上限が『確定している』データを格納するためです(注:Windows Vista以降では、EXEモジュールをランダムなアドレスにロードする機能ASLRが実装されています。ASLRの解説と、プログラム解析におけるASLRへの対処については、この質問と回答を参照してください)。
しかし、解析対象アプリケーションが起動後に自分で動的に確保したメモリブロックおよび、そのメモリブロックに格納されたパラメータ等のアドレスは固定とはなりません。これは動的に行うメモリ確保では、状況に応じて確保されるメモリブロックのアドレスが変化するためです。 動的なメモリブロック確保について、ヒープ(プログラムが実行中に動的にメモリを割り当てるために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として指定)することで、アプリケーションの起動時に自動的にプロセスメモリ上にロードされるようにすることもできます。また、プログラムの処理としてはGetModuleHandle関数使用により、プロセスメモリ上で特定の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/DVDチェック時のオリジナルCD/DVD認証用ファイル名や、多重起動防止用ミューテックス名等に使用されることがあります。キャラクターコード表はプラットフォーム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
なお、PCゲームではGetGlyphOutline関数を使うケースもあります。
また、表示済み文字列のメモリサーチを行い、その文字列格納アドレス周辺のバイナリデータを視認して、「これから描画されるはずのテキスト」が発見された場合は、そのテキスト内のアドレスの読み込みにブレークポイントを設定してブレークさせるという方法もあります。
参考までに、PCゲームなどのプログラムの処理中で参照される文字列を監視・出力したいならば、以下のAPI関数群でAPIフックを行い、処理される文字・文字列をAPIフック用DLL内からOutputDebugString関数で出力するやり方があります。出力したデバッグ文字列は、『うさみみハリケーン』の「デバッグ文字列出力を監視」機能などのモニターを使って取得します。ただし、これで全ての参照文字列が取得できるとは限りません。
WideCharToMultiByte
MultiByteToWideChar
GetGlyphOutline
GetCharABCWidths
GetTextExtentPoint32
TextOut
ExtTextOut
DrawText
DrawTextEx
wsprintf
wvsprintf
lstrlen
lstrcpy
lstrcat
StringCbCatなどlstr*関数の互換関数
_mbsincや_mbslenなどのCランタイムライブラリが提供する文字列関連関数
LoadString
タスク切り替えが出来ないゲームの対処法を知りたい。
まず、汎用のタスク切り替え手法による対処法は、拙作『うさみみハリケーン』のヘルプの「基礎用語解説」→「フルスクリーン」で、特定キーの押し下げや『スペシャルねこまんま57号』のキーフック等を用いた手法を解説していますのでご覧下さい。次に、解析による対処法を考える場合には、「タスク切り替えが出来ない」という状況をよく考察する必要があります。例えば、タスク切り替えが出来ないゲームはDirectXを使用したゲームで散見されますが、MicrosoftはDirectX 9を用いたアプリケーションでのタスク切り替えの無効化を禁止しています。そのため、キーフック他によるタスク切り替えの無効化ではなく、タスク切り替えを検出してアプリケーション強制終了等の処理を行うケースが少なくありません。
<参考>Alt + Tab キーおよびその他のタスク切り替えを無効にするにはどうすればよいですか。
さらに、解析によって対処する場合の注意点として、タスク切り替え検出ルーチンを探し出すというアプローチは工数が嵩む可能性が挙げられます。これは、タスク切り替えの検出手法が、仮想デバイスドライバを使用したり、メインウィンドウに送られてくる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』、『DirectXウィンドウ化ツール』、『Window Mode Patch for Game』および『3D-Analyze』があります。これらのソフトにより、多くのゲームでウィンドウモード化が実現可能です。また、対象ゲームによっては、『VMWare』や『Virtual PC』などの仮想マシン上で実行させることによる、擬似的なウィンドウモード化が可能なケースもあります。仮想マシン構築ソフトにはDirect3D対応のものもあり、大抵のゲームは仮想マシン上で実行可能と考えられます。
PrintScreenキーでスナップショットが取れないゲームの仕組みを教えてください。
スナップショットが作成できないのは、PrintScreenキー押し下げを監視して、キー押し下げのタイミングでクリップボードの内容をクリアしているためと考えられます。キーフックを用いてPrintScreenキー押し下げを監視する場合は、キー入力のウィンドウメッセージを取得して、このキーの仮想キーコードの値(2Ch)と比較した上で処理を分岐させることになります。また、クリップボードの内容のクリア処理は、OpenClipboard関数呼び出し後に第1引数を08h(CF_DIB)にしてGetClipboardData関数を呼び出してクリップボードの内容が画像データかどうか確認し、画像データの場合はEmptyClipboard関数を使ってクリップボードのデータをクリアします。
なお、クリップボードの内容のクリア処理は、対象ゲームのEXEファイルではなく、対象ゲーム専用のロードされたDLLファイル(拡張子変更可能)内のコードで実行されている可能性が高いことに注意が必要です。このようなケースに限らず、ゲームの解析においては必ずプロセスのモジュールリストを確認しておくことをお奨めします。
ゲームが自己プロセスを隠蔽(いんぺい)することは可能ですか?
いわゆるプロセスメモリエディタやOS付属のタスクマネージャが表示するプロセスリストにリストアップされないよう、アプリケーションが自己プロセスを隠蔽する手法は実在します。このプロセス隠蔽にはたいてい、カーネルモードで各種操作を行うrootkitの技術が使用されています。自己プロセスを隠蔽する例としては、一部のマルウェアや、商用プロテクトが施されたオンラインゲームが挙げられます。しかし、マルウェア同様にシステムの深部を弄るプロセス隠蔽は、セキュリティ上の問題を招きかねないため、オンラインゲームではない、一般的なPCゲームに実装されるケースは少ないといえます。システム深部にタッチせずに、デバッガやプロセスメモリエディタがゲームのプロセスを認識できなくする手法もありますが、たいていデバッガなどから無効化可能なため、あえて実装するケースはまれと考えられます。ただ、ゲームのプログラマはほとんど無意味な解析対策だと分かっていても、社長やディレクターなどの上司が実装を命令することがあると聞きます。
その他のケースとしては、プロセスの隠蔽ではありませんが、「game.exe」といったゲーム開始用の実行ファイルが、別ファイルとなっているゲーム本体の実行ファイル(拡張子はEXEとは限らない)の実行及び改ざん検出用のローダーであるケースが挙げられます。この場合、大抵「game.exe」は本体の実行ファイルを実行後に終了するため、プロセスリストに「game.exe」はリストアップされません。このようなケースでは、ゲーム本体の実行ファイルに改造対策が施されている可能性を考慮して下さい。
なお、『うさみみハリケーン』のキーフック機能を使えば、プロセスリストには表示されないプロセスを、ウィンドウ情報を元に特定してプロセスリストに追加することが可能です。また、『うさみみハリケーン』には、プロセスID総当りでプロセスを列挙したり、システムが保持するパフォーマンスデータを基にプロセスを列挙するといった、特殊なプロセス列挙機能を備えており、解析対策としてのプロセス隠蔽に対処できるケースがあります。
ゲームの当たり判定解析のアプローチが分かりません
当たり判定の処理にはいろいろな手法があるため、汎用性が高い定型的なアプローチはありません。まず、API関数を用いた当たり判定としては、2つの矩形の交差を判断するIntersectRect関数の使用が挙げられます。また、補助的な関数として、指定の点が指定の矩形の境界の内側にあることを判断するPtInRect関数が挙げられます。
実際のゲームプログラミングとしては、API関数を使うのではなく、自機の座標と、敵弾の座標との演算により矩形の重なりを判定するというケースが多いようです。この場合、当たり判定処理直前にSetRect関数が使用されることがあります。
当たり判定が座標の演算による比較で行われるならば、自機座標の格納アドレスを変動サーチで探し出し、メモリアクセスへのブレークポイント設定で処理を追いかけることになります。この際、自機や敵機の座標が浮動小数点数で格納されている可能性と、座標は暗号化されており増減で絞り込まない方がよい可能性を考慮して下さい。なお、座標の格納アドレスへのブレークポイントは、無用なブレークを避けるためにスレッドを指定して設定する方が効率的と考えられます(『うさみみハリケーン』ではスレッドを指定してブレークポイント設定可)。
座標の更新のトリガーとなるWindowメッセージは、矢印キー押し下げ時のWM_KEYDOWN(0x0100)あるいは、WM_MOUSEMOVEやWM_RBUTTONDOWN(lparam)などが考えられます。たとえば、↑キーが押されたことを検出するためには、ウィンドウプロシージャならばVK_UP(0x26)、DirectInputならばDIK_UP(0xC8)といった、定数を用いた演算処理が行われます。もし、この演算処理を見つけることができれば、その処理の直後にある、座標格納アドレスへアクセスし座標を書き換える処理の解析により、比較的簡単に座標の格納アドレスを見つけることができます。
なお、当たり判定に伴う結果である自機数や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+0C] = 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+0C] = 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, 0C ;引数とダミー分のスタック修正
}
なお、逆アセンブルコードリストを読解した上で再利用するだけではなく、逆アセンブルコードリストを自分が得意とする開発言語のコードに置き換える練習をしておくこともお勧めします。
日本語版のOSでは起動させないゲームの仕組みを教えてください
一部の海外のゲームでは、デバッグ環境がない等の理由により、起動時にOSの使用言語を調べて、製作環境と異なる言語のOS上では起動させないケースがあります。このようなケースでは、OSの使用言語をGetSystemDefaultLangID関数を用いて言語IDで判断することが多いようです。ただし、この手法ではコントロールパネルで「地域と言語のオプション」を変更した場合に正確な使用言語が判別できないため、「user.exe 」等のシステムファイルのバージョン情報にある使用言語をGetFileVersionInfo関数とVerQueryValue関数で取得するケースもあります。 なお、かつてMicrosoftは上記「user.exe」のバージョン情報による言語ID取得を例示していました。しかし、Windows Vista以降では、Windowsの日本語版であっても「USER.EXE」の使用言語は「英語(米国)」になっていますので、これからは「USER32.DLL」といった別のシステムファイルがチェックされると考えられます。
他にはレジストリのロケール情報を取得して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』(Windows Server 2003/Windows XP用)を使用することで、文字化けが解決する可能性があります。
PCゲーム解析関連情報の管理はどのようにするべきですか
デバッガ等を用いたPCゲームの解析に伴い得られた、特許取得済み商用プロテクトに関わる情報や、PCゲーム解析の参考資料として入手したウイルスのソースコードなどは、当然ながら外部に公開すべきではありません。このような情報を公開せず、参考資料として自分のパソコンに保存しておくことは問題ないと思われます。ウイルスのソースコードは、不正指令電磁的記録取得・保管罪の定義において「不正な指令を記述した電磁的記録その他の記録」に該当するものの、学術的研究を目的としてウイルスのソースコードを取得し、自分のパソコンにのみ保管することは、同罪の成立要件を満たしていないため支障はないといえます。
参考:いわゆるコンピュータ・ウイルスに関する罪について(法務省の見解)
ただし、このような情報の管理にあたっては、パソコンの盗難等による、予期せぬパソコン内の保存データの流出に対し、セキュリティ上の対策を講じておくべきでしょう。
具体的には、PCゲーム解析に伴い得られた情報全ての暗号化が挙げられます。昨今では色々なファイル/フォルダ暗号化ソフトが公開されていますが、利便性の問題から、ファイル操作時の暗号化と復号化を自動で行う暗号化仮想ドライブ構築ソフトがお勧めです。
私の場合はフリーウェアの『TrueCrypt』(日本語ランゲージパックあり)を使用しています。AES、Serpent、Twofish等の暗号化アルゴリズムに対応しており、ユーザーが設定する暗号化仮想ドライブマウント用パスワードがよほど短くない限り、このソフトで情報漏洩は防止できると考えられます。ただし、暗号化仮想ドライブマウント中のトロイ等による情報漏洩まではこのソフトで対処できる訳ではないので、過信は禁物です。
なお、パソコンの盗難時等にファイル復元ソフトを使用されるケースに対処するため、『TrueCrypt』の使用に加えて『Eraser』などのファイル完全削除ソフトの併用をお勧めします。
余談ですが、私の失敗経験からいえば、情報を収集しプログラム解析を学ぶより先に、情報を取捨選択し、重要度といった自分なりの指標を元に、情報を整理する技術を学ぶべきです。少なくとも、収集した膨大な資料などの情報の中から、必要な情報をすぐに取り出せるようにする工夫は必須だと思います。
ゲーム実行PCの固有情報を鍵として暗号化されるセーブデータを別のPCで使用したい
これには色々なパターンがあり一概に対処法は述べられません。まず、暗号化と復号化の仕組みを解析して理解するところから始めると良いと思われます。アプローチとしては、 セーブデータ書き込み時の暗号化や読み込み時の復号化のためにどのようなデータ(鍵)を参照しているか。そのデータはすでにセーブデータあるいは別のファイルやレジストリに保存されているものか、それともセーブデータ読み書き時に動的に生成しているものか。どのような要因でセーブデータ読み込みエラーを出すように設計されているか。コードを書き換えて暗号化と復号化処理をスキップさせることは可能か。などを解析していくと解決の糸口がつかめることもあります。
ただし、復号化に必要なデータを、セーブデータ読み込み時にPC固有情報を元に動的に生成している場合など、別のPC上だけでの対処が困難なことも少なくありません。この場合は、元のPC上と別のPC上でゲームのインストール時/初回起動時/セーブデータ読み書き時などに生成される暗号化・復号化用データを同じものにさせる必要があります。
具体的には、ゲーム側が暗号化・復号化用データを生成するために、Windowsのユーザー名や各種ハードウェア情報といった、PC固有の情報を取得するコードを、そのまま書き換えたり、ラッパーDLLやAPIフックで改変して環境に関わらず同じ情報を取得させ、どのPC上でも同じ暗号化・復号化用データを生成させることで対処しますが、「PC固有の情報」が広範に渡るだけに必ずしも対処が容易とはいえません。
なお、 ラッパーDLLやAPIフックによる、API関数呼び出し時に特定の動作を行わせたり特定の値を返させたりする対処法は、上記のケース以外にも色々応用可能ですので、有効な1手法として覚えておくことをお勧めします。
アプリケーションの処理実行速度を変更するソフトウェアの仕組みを教えて下さい
『Speed Hack Tool』などとも呼ばれるこの種のソフトウェアが、アプリケーションの処理実行速度を変更する基本的な仕組みは以下と推測されます。あくまで推測であり、必ずしも以下の方法を使用しているとは限らないことに留意願います。『うさみみハリケーン』が実装している「実行速度調整」機能では、以下のようなIATの書き換えではなく、API関数のエントリーを逆アセンブラで解析してAPIフックを行います。
- 自作のアプリケーションから、対象プロセスを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が異常な場合の対処 (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ヘッダ情報及びインポート・エクスポート関数一覧を表示するソフトウェアの自作や、公開されているパッカーやアンパッカーのソースコードの読解をお勧めします。
上記の処理実行速度変更は基本的にAPIフックで実現可能ですが、API関数の内部処理を自前でエミュレートして、システムが保持する時間関連情報を直接参照するケースには対応できません。
Windows Vista以降では、アドレス0x7FFE0000以降のKUSER_SHARED_DATA構造体先頭と+320hに時間関連の値が格納されています。この値は『うさみみハリケーン』を使って確認することができます。このメモリエリアは、ユーザーモードからも参照可能なカーネルモード用のメモリエリアといえるものであり、この時間関連の値はユーザーモードのプロセスから書き換えることはできません。
//0x7FFE0000以降
ULONG TickCountLowDeprecated;
ULONG TickCountMultiplier;
KSYSTEM_TIME InterruptTime;
KSYSTEM_TIME SystemTime;
KSYSTEM_TIME TimeZoneBias;
//中略
//0x7FFE0320
union
{
KSYSTEM_TIME TickCount;
UINT64 TickCountQuad;
};
//以下略
//KSYSTEM_TIME構造体の定義
typedef struct _KSYSTEM_TIME
{
ULONG LowPart;
LONG High1Time;
LONG High2Time;
} KSYSTEM_TIME, *PKSYSTEM_TIME;
以下の時間関連API関数は内部処理でKUSER_SHARED_DATA構造体を参照しています。GetTickCount
GetSystemTime
GetSystemTimeAsFileTime
GetLocalTime
解析対象アプリケーションが、GetTickCount関数等の代わりに同関数の内部処理をエミュレーションして、上記の固定アドレスを使って経過時間取得を行っているならば、同処理箇所にパラサイトルーチンを打ち込んで取得する値を操作あるいは、本来呼び出されるAPI関数に繋げるというアプローチが想定されます。ドライバを使用してカーネルモードでのカーネルオブジェクト操作で対処するというアプローチもあります。
ゲーム上でランダムに設定されるデータ(出現アイテムの種類など)を固定化したい
プログラムではこの様なランダムにデータを設定する場合、擬似乱数(あらかじめ用意された演算等のアルゴリズムにより生成した乱数)を使用します。C言語では標準ライブラリ関数として擬似乱数を生成するrand関数がありますが、生成した擬似乱数の重複など精度面ほかで問題があるため、近年では「Mersenne Twister」等の優れた疑似乱数生成アルゴリズムが奨められています。なお、一般的にrand関数の処理は、線形合同法と呼ばれる単純な計算式で行われています。以下はその計算例であり、定数0x343FDなどは定型的に使用されます。もし、解析対象アプリケーションがMSVCR90.dllなどmsvcr**.dllという、MicrosoftのCランタイムライブラリを使用しているならば、同DLLが提供し、以下の計算処理を行うrand関数が呼び出される可能性があります。一方、rand関数の改良版といえる新しいrand_s関数は、内部処理で後述のSystemFunction036関数を使用します。
MOV ECX, [EAX+14] IMUL ECX, ECX,343FD ADD ECX, 269EC3 MOV [EAX+14], ECX MOV EAX, ECX SHR EAX, 10 AND EAX, 7FFF RETNまた、API関数を用いた擬似乱数の生成には、Advapi32.dllが提供するCryptGenRandom関数やSystemFunction036関数(「RtlGenRandom関数」と表記されることあり)が使用可能です。
基本的に擬似乱数は「種」と呼ばれる任意の数値に特定の処理を施して生成します。そのため、たいていの擬似乱数生成アルゴリズムにおいて、プログラム側が設定する種の数値さえ固定化できれば、生成される擬似乱数は常に同じ値になります。
PCゲームにおける擬似乱数を生成するための種の設定には、特定のAPI関数によって得られた、常に変化し種として適切な「現在時刻」や「システムの実行経過時間に連動する値」が使用されるケースが少なくありません。用いられる可能性があるAPI関数のリストは下記。なお、これらのAPI関数の戻り値は、各種処理におけるタイミングを調整するための処理時間制御に用いられることもありますが、種を取得する場合は戻り値を他の数値と大小比較する必要が無い点に注目してください。また、一回の擬似乱数生成で複数の擬似乱数からなる配列を作成するアルゴリズムならば、擬似乱数が必要となる毎に種を取得する必要がありません。
timeGetTime
GetTickCount
GetTickCount64(Vistaで実装された)
QueryPerformanceCounter
NtQueryPerformanceCounter(ネイティブAPI)
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
なお、Visual Studio 2008 付属のソースによれば、C言語の標準ライブラリ関数のうち、プログラム実行開始からの消費したプロセッサ時間を取得するclock関数や、現在時刻を取得するtime(_time32/_time64)関数は、内部処理でGetSystemTimeAsFileTime関数を使用しています。ランタイムライブラリや、ソフトウェア同梱DLLを経由して時刻情報を取得可能なことに留意してください。
さらに、インラインアセンブラを用いて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関数を使用して対象ファイルを削除している可能性も考慮してください。
もし、一時ファイルをOS再起動時に削除するならば、MoveFileEx関数が使用されます。この関数の第2引数がゼロ(NULL)で、第3引数が定数MOVEFILE_DELAY_UNTIL_REBOOT(0x00000004)ならば、第1引数でパスを指定されたファイルや空フォルダがOS再起動時に削除されます。この場合は、関数呼び出しを無効化することで一時ファイルの削除を回避可能です。
ASLRに影響されないEXEファイル改造を行いたい
Windows Vista以降では、EXEファイルやDLLファイルといったモジュールがプロセスメモリ上に読み込まれるアドレスをランダム化する、セキュリティ上の脆弱性を緩和する機能ASLR(Address Space Layout Randomization)が実装されています。参考:Windows Vista カーネルの内部 : 第 3 部
Microsoftは、このASLRやDEPをシステムレベルあるいはアプリケーションレベルで適用可能な脆弱性緩和ツール『EMET』(Enhanced Mitigation Experience Toolkit)を配布しています。EMETを使用することで、解析対象アプリケーションや解析ツールなどを指定して、ASLR、DEPその他のセキュリティ対策適用時の挙動を、簡単に確認することが可能です。
本来、ASLRが適用されるのは、ASLR対応のコンパイラでコンパイルされ、PEヘッダ内の「DllCharacteristics」で「IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE(以下DYNAMIC_BASE)」フラグがオンになっているモジュールに限られます。これに対し、EMETが持つASLR強制適用機能は、このフラグに左右されないでASLRを適用できるということになっています。しかし実際には、この機能はプラグインのようなDLLファイルを主な対象としており、すべての実行ファイルにASLRを強制適用させるものではありません。Windows 7+EMET2.1で試した限りでは、PEエディタで「DYNAMIC_BASE」フラグをオフにしたASLR対応EXEファイルには、EMETを用いて実行ファイル指定で個別にASLR強制適用の設定をしても、ASLRは有効になりませんでした。
つまり、既存のASLR対応EXEファイルに、イメージベースが0x00400000になるという前提でのプログラムコードを埋め込んだり、そのようなパッカーを用いたいケースや、固定アドレスを指定してメインモジュールのプロセスメモリを書き換えたいケースでは、「DYNAMIC_BASE」フラグをオフにすれば、たとえEMETが使用されている環境でも問題なく対処できるということです。加えて、PEヘッダ内の「Characteristics」にある「IMAGE_FILE_RELOCS_STRIPPED」フラグをオンにしておくことをお勧めします。このようなPEヘッダ内のフラグの変更は、『うさみみハリケーン』に同梱しているPEエディタ『UMPE』を用いれば簡単に行うことができます。
なお、特定プロセスではなく、全てのプロセスでASLRを無効化したい場合は、以下のレジストリ操作を行うことで対処できます。
「HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\MoveImages」に値0を設定。
ちなみに、メインモジュールの先頭アドレスは、GetModuleHandle関数(引数NULL)の戻り値か、PEBの先頭アドレス+8hにある「ImageBaseAddress」の値で動的に取得可能です。
プログラムがユーザーの入力を検出する仕組みを知りたい
●基本事項下記の解説を読む前に、プログラム解析の基本事項であるAPI関数、仮想キーコードおよび、プログラムがWindowメッセージを処理する仕組みを理解しておくことをお勧めします。なお、Microsoft社員による技術解説や、同社の公式解説書には、「Windowメッセージ」と「Windowsメッセージ」の両表記が混在しています。
主要仮想キーコードの一覧と、DirectInput用スキャンコードの一覧は、うさみみハリケーンのヘルプ内「基礎用語 仮想キーコード」に掲載していますので、参照されることをお勧めします。
プログラムの入力検出処理の解析は、プログラム上で固定化された入力検出対象(特定キー等)や入力時挙動の変更に有用です。また、特にゲームでの、開発者用デバッグモードや隠し要素等の出現条件探索にも役立ちます。
入力検出処理の該当箇所とおぼしき箇所が見つかったら、該当するコードをNOP命令で潰したり、条件ジャンプ命令を書き換えてから、キー・マウス入力に対する反応を確認します。これにより、対象となる処理箇所を特定していきます。
以下では逆アセンブルコードリスト上での、入力検出処理の解析について述べていますが、同処理の操作が目的ならば、APIフックやDirectInputのフックというアプローチが工数面でより有用なケースもあります。
●API関数
GetAsyncKeyState関数やGetKeyState関数により、特定キー(マウスボタンを含む)を仮想キーコードで指定して、そのキーの押し下げ状態を取得することが可能です。GetKeyboardState関数では、仮想キーコードで指定可能な256キー全ての押し下げ(上げ)状態を一度に取得します。全ての仮想キーコードに対してループ処理でGetAsyncKeyState関数を呼び出すケースもあります。
●Windowメッセージ
ウィンドウ作成時にRegisterClassEx関数で指定したウィンドウプロシージャ(Windowメッセージを処理するための、関数のようなもの)内の処理として、Windowメッセージを受け取ることでキー・マウス入力を検出します。ウィンドウプロシージャには、その引数として、メッセージの種類(定義値)および、そのメッセージに応じたパラメータが渡されます。
ウィンドウプロシージャのアドレスは、うさみみハリケーンで対象プロセスを選択後に、メニューで「ファイル」→「プロセスの各種情報を表示」でカテゴリの[ウィンドウ]を選択すると、項目[クラスプロシージャ]として表示されます。この方法でアドレスが取得できない場合は、逆アセンブルコードリスト上で、RegisterClassEx関数の呼び出し処理を観察あるいは、通常ウィンドウプロシージャの最後に配置されるDefWindowProc関数呼び出し箇所から辿るというアプローチを使用してください。
関連して、SetWindowsHookEx関数により、ウィンドウプロシージャとは別に、キー・マウス入力のWindowメッセージをフックするプロシージャを指定可能です。この場合、第1引数はキー入力のフックならばWH_KEYBOARD(定数2)かWH_KEYBOARD_LL(定数13)、マウス入力のフックならば同様にWH_MOUSE(定数7)かWH_MOUSE_LL(定数14)となります。さらに第2引数でフックプロシージャのアドレスが指定されます。なお、自分以外のアプリケーションもフック対象とするグローバルフックの場合、フックプロシージャは、各プロセスのプロセスメモリ上にロードされたフック用DLL内で実行されます。
基本的に、WM_KEYDOWN(定数0x0100)メッセージの受信時に、同メッセージに付随するパラメータ(この場合は仮想キーコード)を読み込むことで、入力されたキーを認識可能です。ただし、PRINT SCRNキーはWM_KEYUP(定数0x0101)メッセージ、F10キーや、ALTキーと他キーの同時押しはWM_SYSKEYDOWN(定数0x0104)メッセージの受信により認識可能です。
ウィンドウプロシージャ内では、他のメッセージを受信した場合と処理を切り分けるため、引数であるメッセージの値と、上記メッセージの定義値との単純比較を行うことで、上記メッセージ受信と判断し、それに応じた処理を行います。なお、コンパイラのコード最適化に伴い、必ずしも定義値そのものが逆アセンブルコードリスト上に現れるとは限らないことに注意が必要です。
WM_CHAR(定数0x0102)メッセージは、WM_KEYDOWNメッセージを加工して送信したものであり、このメッセージ受信時には、文字入力に使用されたキーの認識が可能です。たとえば、このメッセージ受信時には、「A」なら0x41、「a」なら0x61といった文字コードを取得できます。
ウィンドウ内でのマウスの入力は、WM_LBUTTONDOWN(左クリック、定数0x0201)、WM_RBUTTONDOWN(定数0x0204)および、WM_MBUTTONDOWN(定数0x0207)という、専用のWindowメッセージの受信によって入力を検出可能です。
●DirectInput/XInput
DirectXの一部機能であるDirectInputのAPIを用いることで、キー・マウス入力や、ジョイパッド等ゲーム用デバイスでの入力を検出可能です。一般的に、この入力検出手法はゲームや各種ゲーム機用エミュレータで使用されることが多いといえます。なお、現在では、キー・マウス入力の検出にDirectInputは推奨されていません。
DirectInputでのキー入力検出処理は、プログラマ向けの解説例として、以下のように独自の関数を呼び出し、その関数内で行うパターンが多く見受けられます。以下の例では、GetInput関数呼び出しの前後にある定型的なAPI関数呼び出しが、この独自の関数を見つける手がかりとなり得ます。
//C++ソースコード記述例
//Windowメッセージ関連の処理を行っている
while ( TRUE ) {
if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
if (!GetMessage(&msg, NULL, 0, 0)) break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
//ここでキー入力検出など、ゲームの基本処理を行う
GetInput();//プログラム独自の関数(関数名は例)
Sleep(1);
}
}
上記例の他にも、SetTimer関数やtimeSetEvent関数を使用する方法で、定期的に入力検出を行うことが可能です。ただし、SetTimer関数呼び出しにより送信される、WM_TIMER(定数0x0113)メッセージは処理優先度が低く、送信間隔の時間的な精度も低くなるため、ゲームの重要な処理に使用されるケースは少ないと考えられます。DirectInputによるキー、マウスおよびジョイパッドの入力検出処理には、DirectInputの内部的な関数(メソッド)であるGetDeviceState関数が使用可能です。逆アセンブルコードリスト上ではこの関数名は表示されないため、同関数名から目的の処理箇所を辿ることはできません。なお、キー入力検出時は、上記GetDeviceState関数呼び出し時の第2引数(入力情報を格納するバッファのサイズ)として通常0x100、マウスならばDirectInputのバージョンにより0x10または0x14、ジョイパッドならば取得する入力情報の種類により0x50または0x0110が指定されます。また、取得されたキー、マウスおよびジョイパッドの入力情報から、押されているキーやボタンを認識するための処理には、基本的に定数0x80が使用されます。
;GetDeviceState関数呼び出し例(キー入力検出時) 00401BF2 LEA ECX,DWORD PTR SS:[EBP-108] 00401BF8 PUSH ECX 00401BF9 PUSH 100 00401BFE MOV EDX,DWORD PTR DS:[410F04] 00401C04 MOV EAX,DWORD PTR DS:[EDX] 00401C06 MOV ECX,DWORD PTR DS:[410F04] 00401C0C PUSH ECX 00401C0D MOV EDX,DWORD PTR DS:[EAX+24] 00401C10 CALL EDX同様に、DirectInputの内部的な関数であるGetDeviceData関数を使用するケースもあります。この場合は第2引数はDirectInputのバージョンにより0x10または0x14となります。
上記逆アセンブルコードリストで、CALL先を指定するために使われている数値0x24は、GetDeviceState関数のアドレス取得に使われる定数です。同様にGetDeviceData関数のアドレス取得ならば、定数0x28が使用されます。これらの定数はDirectInputのバージョンに依存しません。
ここで注意すべきは、DirectInputでは、仮想キーコードではなく、スキャンコード(入力デバイスであるキーボードが生成する独自の値)により入力されたキーの種類を識別することです。たとえば、上矢印キー(↑)の押し下げは、仮想キーコードVK_UPの定義値(0x26)ではなく、DirectInputでの定義値DIK_UP(0xC8)により識別されます。
なお、WINMM.dllが提供するjoyGetPos関数やjoyGetPosEx関数でもジョイパッドの入力を検出可能です。これらのAPI関数は、DirectInputの古いバージョンに相当するもので軽快に動作します。ただし、新しいバージョンのDirectInputとの比較では、ボタン、スライダーや複数POVなどで、取得可能な入力情報に若干の制約があります。
XInputは(事実上)Xbox 360コントローラー用の新しいAPIですが、ジョイパッドの入力検出において、DirectInputとの互換性に問題があります。たとえば、ジョイパッドの入力検出にXInputのみを使用するゲームでは、DirectInput対応のジョイパッドが使用できません。ただし、この問題は、DirectInput対応のジョイパッドでの入力を、『うさみみハリケーン』のプラグイン「Joy2Write」でキー入力に変換したり、あるいは、有志により公開されている、DirectInput対応のジョイパッドをXInputに対応させるツールを使用することで対処可能です。また、現在では、DirectInput/XInputの両方に対応したジョイパッドも発売されています。
XInputを使用しているゲーム等が実行する、ジョイパッドでの入力を検出する処理の解析は、xinput1_3.dllがエクスポートするXInputGetState関数(序数2)の呼び出し箇所が手がかりとなります。なお、XInputの場合、ボタン押し下げの検出は、各ボタンに設定されている定数との論理積で行われる可能性が高いといえます。
//XInputでのボタンの定義値 XINPUT_GAMEPAD_DPAD_UP 0x0001 XINPUT_GAMEPAD_DPAD_DOWN 0x0002 XINPUT_GAMEPAD_DPAD_LEFT 0x0004 XINPUT_GAMEPAD_DPAD_RIGHT 0x0008 XINPUT_GAMEPAD_START 0x0010 XINPUT_GAMEPAD_BACK 0x0020 XINPUT_GAMEPAD_LEFT_THUMB 0x0040 XINPUT_GAMEPAD_RIGHT_THUMB 0x0080 XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100 XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200 XINPUT_GAMEPAD_A 0x1000 XINPUT_GAMEPAD_B 0x2000 XINPUT_GAMEPAD_X 0x4000 XINPUT_GAMEPAD_Y 0x8000
●Raw Input
Raw Input APIにより、キーボード、マウス、ジョイパッドおよび他の入力デバイスから、入力情報をそのまま取得することができます。このAPIの特徴として、入力を行ったデバイスを特定できるという点が挙げられます。つまり、Raw Input APIを使用しているならば、たとえば複数のキーボードや複数のマウスがPCに接続されていても、キーボードやマウスのうちの一つを特定して入力を検出可能です。
基本的な仕組みとしては、まずRegisterRawInputDevices関数で入力情報を取得したいデバイスのタイプを登録します。これにより、登録したデバイスでの入力時には、同関数の第1引数(RAWINPUTDEVICE構造体内)で指定したウィンドウに、WM_INPUT(定数0x00FF)メッセージが送信されます。このメッセージ受信に合わせて、GetRawInputData関数あるいはGetRawInputBuffer関数を呼び出すことで入力情報を読み取ります。RegisterRawInputDevices関数では、RAWINPUTDEVICE構造体の配列を指定することで、複数のデバイスのタイプを一度に登録可能です。
//RAWINPUTDEVICE構造体の定義
typedef struct tagRAWINPUTDEVICE {
USHORT usUsagePage;
USHORT usUsage;
DWORD dwFlags;
HWND hwndTarget; //WM_INPUTメッセージ送信先
} RAWINPUTDEVICE, *PRAWINPUTDEVICE, *LPRAWINPUTDEVICE;
RegisterRawInputDevices関数によるデバイスの登録は、RAWINPUTDEVICE構造体のメンバusUsageに以下の定義値を指定することで行います。以下はメンバusUsagePageがHID_USAGE_PAGE_GENERIC(定数1)の場合です。//主要デバイスのタイプ
ポインタ:1
マウス:2
ジョイスティック:4
ゲームパッド:5
キーボード:6
キーパッド:7
WM_INPUTメッセージの受信時にはそのパラメータとして、入力情報が格納されたRAWINPUT構造体のハンドルを受け取ります。このハンドルを指定してGetRawInputData関数を呼び出し、RAWINPUT構造体の実データを取得します。
//RAWINPUT構造体の定義
typedef struct tagRAWINPUT {
RAWINPUTHEADER header;
union { //共用体:下記の構造体のいずれかが配置される
RAWMOUSE mouse;
RAWKEYBOARD keyboard;
RAWHID hid;
} data;
} RAWINPUT, *PRAWINPUT; *LPRAWINPUT;
//上記RAWINPUTHEADER構造体の定義
typedef struct tagRAWINPUTHEADER {
DWORD dwType; //デバイスのタイプ(マウス(定数0)、キーボード(1)、その他(2))
DWORD dwSize;
HANDLE hDevice; //デバイスのハンドル(デバイスの特定に使用可)
WPARAM wParam;
} RAWINPUTHEADER, *PRAWINPUTHEADER;
//上記RAWKEYBOARD構造体の定義
typedef struct tagRAWKEYBOARD {
USHORT MakeCode;
USHORT Flags;
USHORT Reserved;
USHORT VKey; //入力されたキーの仮想キーコード、RAWINPUT構造体先頭から+16h
UINT Message;
ULONG ExtraInformation;
} RAWKEYBOARD, *PRAWKEYBOARD, *LPRAWKEYBOARD;
マウスボタンの入力を検出する場合は、以下の定義値との比較が行われます。
RI_MOUSE_LEFT_BUTTON_DOWN(0x0001)
RI_MOUSE_LEFT_BUTTON_UP(0x0002)
RI_MOUSE_RIGHT_BUTTON_DOWN(0x0004)
RI_MOUSE_RIGHT_BUTTON_UP(0x0008)
RI_MOUSE_MIDDLE_BUTTON_DOWN(0x0010)
RI_MOUSE_MIDDLE_BUTTON_UP(0x0020)
ジョイパッドの入力を検出する場合は、OS付属のHID.DLLが提供するHidP_GetUsages関数なども必要となり、その実装は工数がかさみます。そのため、DirectInputを用いず、あえてRaw Inputでジョイパッドの入力を検出する可能性は低いと考えられます。余談ですが、Raw Input APIを用いると、キーロガーを簡単に製作可能です。ただし、たとえキーロガーのウィンドウを不可視にしても、通常はプロセスの列挙により簡単に発見されます。
●補足
上記では、API関数やWindowメッセージを用いた基本的な入力検出処理について述べています。実際のゲームやエミュレータにおいては、入力検出処理を汎用のライブラリに任せるケースもあります。その例としては、入力検出処理等の実行に、クロスプラットフォームの「SDL」(Simple DirectMedia Layer) を使用するケースが挙げられます。
各種USBデバイスといった、HID(Human Input Devices)のプログラムでの操作には、上記HID.DLLが提供するAPI関数が使用可能です。ただし、キーボードやマウスといった一般的なデバイスは、上記のように入力を検出する仕組みが用意されています。そのため、このAPI関数はたいてい、特殊なデバイスの操作に用いられます。
上記は入力を検出する方法について述べています。これとは逆に、プログラムの処理として、キー、マウスおよびその他のデバイスでの入力を実現するには、基本的にSendInput関数を使用します。