OllyDbg Q&A
- 以下の解説は、デバッガを用いたPCゲーム解析などプログラム解析の、初心者の方を対象として執筆しました。
- 以下の解説は、特に記載のない限り32ビットアプリケーションを対象としています。
- 以下の回答はVer1.10使用時をベースにしています。
- 以下ではメニュー等の項目名について、対応する日本語化パッチ適用後の項目名を黄色で併記しています。日本語化パッチは当サイトメインページで配布しています。
- 以下に挙げたキー操作は、標準メニューや右クリックによるポップアップメニューから同様の操作を行えるものもあります。
- 以下の「メインウィンドウ」とは、『OllyDbg』でアプリケーションを起動させると最初に表示される「CPUウィンドウ」のことです。特に指定のない場合、以下の「左上ウィンドウ」等はこの「CPUウィンドウ」内の分割されたウィンドウ(ペイン)を指します。
- デバッグ対象実行ファイルまたはプロセスのことを、一般に「デバッギー」・「デバッギ」(Debuggee)と呼びます。
- 当ページと併せて、プログラム解析全般の解説である「プログラム解析一般Q&A」も参照されることをお勧めします。
- 以下の回答は匿名希望のリバースエンジニア有志と、うさぴょん、Lucaで行っています。
| 予備知識 | 操作関連 | ゲーム解析関連 | その他 |
質問一覧
予備知識
- 『OllyDbg』などのWindows Vista以降での動作について教えてください。
- 逆アセンブルコードリストについて教えて下さい。
- ブレークポイントの種類とその違いについて教えて下さい。
- デバッグ特権の必要性が分かりません
- デバッグ中にシステムDLL内でアクセス違反が生じるのはOSの問題ですか?
- デバッギーからデタッチすることはできませんか?
- デバッギーのエントリーポイントがコードセクション内ならウイルスには感染していませんか?
- ウイルス対策としてのデバッグ検出について教えて下さい
- デバッギーのウイルス感染やバグによるレジストリ破壊等に備えて、完全に独立した環境で解析を行いたい。
- 解析時にデバッギーが行ったファイル・レジストリ操作の結果だけを簡単に確認したい。
- デバッガ検出手法を教えてください。
- INT命令を用いたデバッガ検出が上手くいきません(環境:WindowsXP)。
- 『OllyDbg』のプロセスを検出して解析を妨害するタイプのウイルスを解析したい
- OS上でデバッガが動作したことを検出する方法を教えてください
- アタッチしてブレークポイントを設定すると、ブレークポイントが機能しない上に強制終了するソフトウェアがあるのですが。
- デバッガで起動/アタッチすると、デバッギーが終了していないのにデバッガがデバッギー終了と認識します。
- 『OllyDbg』のようなデバッガが表示する「最後のエラー」情報はデバッギーのどこに格納されているのですか?
- 解析対象プロセスのPEB(Process Environment Block)の情報を取得したい
- Windows XP Service Pack 2で実装されるデータ実行防止機能で注意すべき点があるそうですが…
- 『OllyDbg』のセキュリティホールについて教えてください
- 『OllyDbg』の改造版とはどのようなものですか?
- MFCのランタイムDLLがエクスポートする関数の詳細を知りたい
- 海外のソフトウェアを日本語化してみたい
- APIフックに用いられるプロセス注入用DLLを検出したい
- 逆アセンブルが正常に行えず、しかもエントリーポイントがNOP命令というソフトウェアがあるのですが。
- デバッガなどのプログラム解析関連ツールを自作してみたい
- プロセスメモリ上のモジュールエリアを実行ファイルとしてダンプ(ファイルに保存)しても、正常に動作しないのは何故ですか?
- 解析超初心者がデバッガで解析対象を起動する前に行うべきことは?
操作関連
- 『OllyDbg』の操作方法等を解説した参考書はありますか?
- 『OllyDbg』から解析対象を簡単に起動する方法を知りたい。
- 操作上で特に覚えておくと役立つポイントはありますか?
- 『OllyDbg』プラグインの入手先は?
- API関数呼び出しへのブレークポイント設定の仕方を知りたい。
- 条件付きブレークポイントを設定してみたい。
- ウィンドウメッセージにブレークポイントを設定してみたい。
- API関数呼び出し箇所ではない任意の複数コードに一括してブレークポイントを設定したい。
- リアルタイムアセンブラで注意すべきことは?
- メモリ内容の内容変動箇所を調べるには?
- プログラムが参照している文字列の一覧表示機能を使いたい。
- 特定文字列の参照コード一覧表示機能を使いたい。
- 特定のコードにワンタッチで戻る方法が知りたい。
- コール命令からサブルーチン及び、ジャンプ命令からジャンプ先にワンタッチで移動する方法はありませんか?
- F7キーでサブルーチンにステップインした後に、簡単にサブルーチンを抜ける方法はありませんか?
- システムDLL等、デバッギー実行ファイル以外のコードを表示した場合の対処法は?
- 逆アセンブルコードウィンドウ(左上ウィンドウ)でコード変更後の実行ファイルの保存が上手くいきません。
- 「Just-in-time debugging」(ジャスト・イン・タイム デバッグ)とは何ですか?
- 『OllyDbg』でのソースレベルデバッグが上手くいきません
ゲーム解析関連
△予備知識
『OllyDbg』などのWindows Vista以降での動作について教えてください。
当サイトQuestions/Suggestionsの「プログラム解析一般Q&A」を参照してください。「64ビット版Windows上での使用」や「セキュリティソフトによる干渉」などといった複合的な要因により、『OllyDbg』のような解析ツールでの解析時に不具合が生じることもあります。そのため、このような不具合発生を回避する、解析専用の環境を構築されることをお勧めします(参照)。
逆アセンブルコードリストについて教えて下さい。
デバッガ「OllyDbg」で表示されたり、逆アセンブラ「PeRdr」で出力する、プログラムのコードを逆アセンブルと呼ばれる操作により人間がプログラムの処理の流れを視認できるようにしたものを、「逆アセンブルコードリスト」と呼びます。 OllyDbgが表示しているのは、対象アプリケーションの起動に伴いプロセスメモリ上に読み込まれた、対象アプリケーションのEXEファイルに含まれるプログラムのコードを、プロセスメモリから読み込んで逆アセンブルしたものです。一方、「PeRdr」は実行ファイルを実行されないままファイルからプログラムのコードを読み込んで逆アセンブルしています。 デバッガを用いたPCゲームの解析においては、逆アセンブルコードリストの読解は必須となります。そのため、逆アセンブルコードリストの構成要素である「レジスタ」、「ニーモニック」、「(アセンブリ言語の)命令」、「API関数」等を理解する必要があります。 デバッガを用いたPCゲーム解析を始める前に、ヘキサエディタやプロセスメモリエディタといった基本ツールの操作を理解しておくと、デバッガを用いたPCゲーム解析がよりスムーズに理解できるようになります。そのため、これからPCゲーム解析を始めようという方には、いきなりデバッガから入るのではなく、あらかじめプロセスメモリエディタの操作に慣れておくことを強くお勧めします。 以下では、32ビットアプリケーションの逆アセンブルコードリストの読解を始めるための端緒とすべく、必要最低限の基本事項を簡潔に解説します。あくまでプログラム解析という視点での解説であり、CPUアーキテクチャ全般の解説やプログラミングを目的とする解説ではありません。なお、64ビットアプリケーションの解析についてはこの質問と回答を参照して下さい。 以下はPCゲームの開発で一般的に使用されているC/C++言語で開発されたゲームの逆アセンブル例です。今後増えると予想されるC#言語で開発された.NET対応アプリケーションでは以下のような逆アセンブルは行えず、専用の逆コンパイラ等を使用して解析することになります。また、C言語で開発されたゲームでも、プログラムのコードに圧縮や暗号化を施すことで逆アセンブルを困難にしているケースもあります。 これら基本事項の詳細については、当サイトから以下のリンクにて資料や参考となる書籍を紹介していますので、入手・参照されることを強くお勧めします。特に、アセンブリ言語の命令の一覧とその詳細な解説を含む「IA-32 インテル・アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル」および、API関数の一覧とその詳細な解説を含むマイクロソフトの「MSDN Library」といったネット上の資料や「Windows SDK」は、逆アセンブルコードリストの読解を含むプログラム解析に大いに役立ちます。また、ネット上での検索も積極的に活用することをお勧めします。 逆アセンブルコードリスト読解等プログラム解析に関する各種Q&A 逆アセンブルコードリスト読解等プログラム解析に役立つ各種資料 プログラム解析に役立つ参考書籍 汎用プロセスメモリエディタ兼デバッガ『うさみみハリケーン』のヘルプ内「基礎用語解説」 逆アセンブルコードリストの基本的な読解ができるようになったら、次はデバッガが持つ「ブレークポイント機能」、「ステップ実行機能」、「レジスタ・プロセスメモリ編集機能」、「参照API関数と同呼び出し箇所の一覧表示機能」および「参照文字列一覧表示機能」と、デバッグ時の逆アセンブルコードリストとの関わりを理解されることをお勧めします。これは各種チュートリアルや実際の解析を通しての試行錯誤が、理解する上での近道になると考えられます。 ■逆アセンブルコードリスト出力例 ●OllyDbgでCPUウィンドウに表示 0040158F > 6A 10 PUSH 10 00401591 . E8 86010000 CALL <JMP.&USER32.GetAsyncKeyState> 00401596 . 80FC 80 CMP AH,80 00401599 75 55 JNZ SHORT UsaTest2.004015F0 0040159B B8 01000000 MOV EAX,1 004015A0 8305 34124000 64 ADD DWORD PTR DS:[401234],64 004015A7 813D 34124000 E8030000 CMP DWORD PTR DS:[401234],3E8 004015B1 7E 1D JLE SHORT UsaTest2.004015D0 004015B3 C705 34124000 E8030000 MOV DWORD PTR DS:[401234],3E8 ●PeRdrでファイルに出力 0040158F 6A10 push 10h * Reference to USER32.GetAsyncKeyState | 00401591 E886010000 call 0040171Ch 00401596 80FC80 cmp ah,80h 00401599 7407 jz 004015A2h 0040159B B800000000 mov eax,00000000h 004015A0 8B00 mov eax,[eax] ■逆アセンブルコードの構成要素 ●アドレス 上記の例では「40158F」といった16進数の値。これは、実行されるコードのプロセスメモリ上での番地を指し、32ビットアプリケーションでは4バイトの値となる。EXEファイル等の実行ファイルがプログラムとして実行される際には、まず実行ファイルがシステムにより用意された当該プロセスのためのプロセスメモリ上にロードされ、その実行ファイルに含まれるコードがプロセスメモリ上で実行される。基本的に、EXEファイルがロードされるプロセスメモリ上のアドレスは0x400000が起点となる(注:Windows Vista以降では、EXEモジュールをランダムなアドレスにロードする機能ASLRが実装されています。ASLRの解説と、プログラム解析におけるASLRへの対処については、この質問と回答を参照してください)。 また、実行ファイルに付属する、Kernel32.DLL等のシステムファイルではないDLLファイルは基本的にアドレス0x10000000にロードされるが、複数のDLLが使用される場合は別のアドレスにもロードされる。この配置換えをリロケーションと呼ぶ。実行ファイルのおおまかな構造としては、PEヘッダと呼ばれる実行ファイルの基本設定を格納するデータブロックが先頭にあり、その後にセクションと呼ばれる、コードやデータ等の格納内容に応じて区切られたデータブロックが並んでいる。基本的に、実行されるコードが含まれるコードセクションが最初のセクションとなる。 ●バイナリデータ 上記の例では「6A 10」といった16進数で表示されるデータ。これは、プロセスメモリ上にある実行されるコードの実体で、CPUは0か1で指定された2進数のデータ(バイナリデータ)として読み込む。CPUはこのバイナリデータをコードとして直接解釈して対応する処理を実行するため、このコード対応バイナリデータはマシン語(機械語)と呼ばれる。プログラムの処理とは、このマシン語で指定された処理をCPUが1つ1つ順番に実行していくことを意味する。なお、バイナリデータを16進数で表示しているのは人間にとっての視認性や可読性向上のため。 ●ニーモニック 上記の例では「PUSH 10」といった、ニーモニックと呼ばれる英数字文字列。これはマシン語の内容を人間が理解できるよう分かりやすく翻訳したものでアセンブリ言語と呼ばれる。本来アセンブリ言語はマシン語と1対1で対応する処理をニーモニックの形式で記述して実行ファイルを作成(アセンブル)する開発言語であるため、マシン語からアセンブリ言語に翻訳する処理を「逆アセンブル」という。なお、アセンブリ言語で記述されたコードをアセンブルするソフトウェアをアセンブラと呼ぶ。 ●オペコード ニーモニックの最初にある「PUSH」、「MOV」、「CMP」および「J**」といった英字文字列はCPUが行う処理の種類を指すもので「(アセンブリ言語の)命令」と呼ぶ。ニーモニックの構成要素としてはオペコードと呼ばれる。 ●オペランド ニーモニックの中で、演算等、命令の処理対象となる数値やレジスタ(後述)をオペランドと呼ぶ。例として、ニーモニック「MOV EAX,1」においてはEAXレジスタが第1オペランド、数値1が第2オペランドと呼ばれる。オペランドが大括弧[ ]で指定されている場合は、[ ]の中のアドレスに格納された値を意味する。「DWORD PTR [402010]」や単に「[402010]」ならば、アドレス0x402010に格納されたDWORDの値となる。対象となる値のサイズがDWORD以外ならば、「BYTE PTR」や「WORD PTR」で指定する。 ●レジスタ オペランドとして使用される、CPUの内部にある一時記憶用メモリ。一般的な命令に使用される汎用レジスタや、浮動小数点数を用いた演算に使われるものおよび、マシン語レベルでのアプリケーションのデバッグ専用のもの等がある。32ビットCPUにおける汎用レジスタは、EAX、EBX、ECX、EDX、ESI、EDIがあり、他には特殊な用途に用いられるESP、EBP、EIP、EFLAGSがある。32ビットCPUにおける汎用レジスタのサイズは4バイト(32ビット)となるが、汎用レジスタE*Xは、4バイトのうちの2バイトや1バイトを処理対象として使用することもできる(EAXレジスタの下位2バイト=AX、AXの上位1バイト=AH、下位1バイト=AL)。レジスタ名先頭の「E」は16ビットCPUのレジスタとの対比で32ビットへ拡張(Extend)されたことを意味する。 E*Xレジスタは特に演算やポインタ(アドレスを指定するためにレジスタや特定アドレスに格納された値を使うやり方)およびカウンタ等を主用途とする。E*Xレジスタにはそれぞれ本来の用途(EAX:各種演算、EBX:ポインタ、ECX:カウンタ、EDX:データ一時記憶およびEAXとの連携による乗除算)が定められているがその用途に制限される訳ではない。ESIとEDIレジスタは汎用的な使用も可能だが、本来はデータ転送命令等でデータの転送元アドレス(SI:SourceIndex)や転送先アドレス(DI:DestinationIndex)が格納されるもの。ESP(SP:StackPointer)は現在のスタック(後述)のアドレスが格納される。EBP(BP:BasePointer)は基本的にスタック上のデータに対するポインタとして使用される。EIP(IP:InstructionPointer)は、次に実行される命令のアドレスが格納され、命令が実行されるたびに内容が更新される。 ●フラグ 演算命令や比較命令の実行後、演算結果がゼロか否かや、比較結果での大きいか否かなどを意味するフラグがビット単位の1か0でEFLAGSレジスタに格納される。EFLAGSレジスタにはゼロか否かなど特定条件に対応するビットがあらかじめ指定してあり、条件に合致したらフラグをセット(該当ビットに1を設定)、条件に合致しないならばフラグをクリア(該当ビットに0を設定)する。このフラグの状況を元にジャンプ命令を使ってプログラムの処理を条件分岐させることが可能になる。EFLAGSレジスタ内のこのようなフラグをステータス・フラグと呼ぶ。 ●数値 逆アセンブルコードリスト上では、アドレスやオペランドとして使用される数値は16進数で表示される。なお、一般的に16進数であることを明示する場合は数値の後に「h」を付加するか、数値の前に「0x」を付加することで行う。 ●スタック プログラムが一時的にデータを格納するメモリ領域。スタック内で新たにデータを格納するアドレスは常にESPレジスタに格納されており、プログラム側でアドレスを直接指定する必要がない。スタックにデータを格納する際はPUSH命令を用い、スタック内では、アドレス0x10FFF8にデータを格納したら次はアドレス0x10FFF4というように、アドレスの高い方から低い方へ向かって、まるで積み木を積むようにデータが格納されるため、これをスタックに積むという。スタックからデータを取り出す際にはPOP命令が用いられ、ESPレジスタのスタックポインタを用いるスタックのデータ格納方式により、後に積まれたデータが先に取り出されることになる。PUSH/POP命令実行時には自動的にESPレジスタの格納値が修正される。 また、汎用レジスタの内容をスタックに格納/取り出しするPUSHAD/POPAD命令や、EFLAGSレジスタの内容をスタックに格納/取り出しするPUSHFD/POPFD命令もある。 ●API関数 上記の例では「USER32.GetAsyncKeyState」。API関数は、アプリケーションがOSの機能を容易に使用することを可能にする、API(Application Programming Interface)の1要素。この例では、WindowsOSのシステムDLLであるUser32.dllが提供する、特定のキーが押されているか否かを判断する機能を持つGetAsyncKeyState関数を呼び出している。API関数の呼び出しにあたり、関数の処理を実行するために必要なパラメータ(引数:ひきすう)を指定する場合は、PUSH命令で数値そのものあるいはデータが格納されたアドレス等をスタックに積むことで指定する。上記の例では10hがShiftキーを意味する。 また、API関数の多くは、関数の処理が実行されると、関数の処理結果や、関数が成功したか否か等を意味する値(戻り値)がEAXレジスタに格納される。なお、NT系OSが内部的に用いる基本的に非公開のAPI関数を「ネイティブAPI」と呼ぶ。ネイティブAPIはユーザー側(ユーザーモード)とシステム側(カーネルモード)の間で処理の橋渡しを行う役目を持つ。 API関数はそれぞれが特定の機能を持つため、例えばCD/DVDチェックの解析においては逆アセンブルコードリスト上で同チェックに頻用されるAPI関数の呼び出しを探す等、PCゲーム解析において解析の手がかりとなることが少なくない。どのようなケースにどのようなAPI関数が使われるかは、当サイトのチュートリアルやQ&Aなどで解説しているので、参照されることをお勧めする。 ●<参考> プログラム解析において頻出する基本的な命令
|
ブレークポイントの種類とその違いについて教えて下さい。
デバッガがブレークポイントでのブレークを検出するためには、デバッギーに 「例外」 と呼ばれる通常のプログラムの処理では起こらない(筈の)特殊な状況を起こさせる必要があります。デバッギーに例外が生じた時点でデバッギーの制御はデバッガに移るため、デバッガはこの時点でデバッギーのスレッドにおける汎用レジスタやステータス制御レジスタの値等を取得・変更可能です。分かりやすく言えば、ブレークポイントの違いはデバッガが検出する 「例外」 の種類の違いです。また、この違いに応じてデバッギーへの異なる解析アプローチを行うことが可能になります。1.コード実行 ブレークポイント
これは、メモリ上に展開・実行されるコード中の任意の箇所のニーモニック先頭アドレス(1バイト)を、「INT3」(オペコードはCC)命令に書き換えるものです。この割り込み命令は実行するとデバッグ例外ハンドラを呼び出す特殊なもので、この呼び出しをデバッガ側で例外として検出します。デバッガはブレーク時に「INT3」で書き換えた箇所を一時的にオリジナルの命令に書き直して、オリジナルの命令を実行させます。
2.メモリアクセス ブレークポイント
これは、デバッギーの任意のメモリブロックのアクセス属性を読み(書き)不可に変更することで、デバッギーがそのメモリブロック内のアドレスに読み(書き)操作を行った際にアクセス違反を生じさせ、このアクセス違反をデバッガ側で例外として検出します。このブレークポイントはメモリブロックのアクセス属性を本来ありえない属性に変更するため、デバッギーをクラッシュさせることがあります。このブレークポイント設定によるアクセス違反と、デバッギーのバグ等によるアクセス違反は似て非なるものです。後者の場合は、基本的にデバッガはEIP該当ニーモニックを無効化あるいはEIPの値を次のニーモニック先頭のアドレスに書き換えて例外を処理しないと、例外から抜け出すことができません。ただし、アクセス違反によっては、その例外を無視して処理を続行可能なケースもあります。
3.ハードウェア ブレークポイント
「Intel 80386」以降のCPUに実装されているデバッグレジスタを使用して、コード実行及びメモリアクセス(読み書き)でブレークさせるものです。上記1及び2と異なり、デバッギーのコードやメモリブロックのアクセス属性は変更しません。ハードウェアブレークポイントはデバッギーのプロセスが持つスレッド毎に設定を行いますが、Windows95では自動的に全スレッドに一括設定されます(『OllyDbg』はデフォルトで全スレッドに一括設定)。デバッグレジスタ8つ(DR0〜DR7)のうち、ハードウェアブレークポイントの設定には最高5つ(DR0/1/2/3+DR7)を使用し、4箇所までブレークポイントを設定可能です。ブレークポイントの設定単位は1,2,4バイトですが、解析者が意図しないブレークの可能性を考えると、通常は1バイトでの設定が適切といえます。
上記3種のブレークポイントについては、いずれもデバッギー側で設定されたことを検出可能です。また、ブレークポイントを設定されていなくとも、デバッギーは自分がデバッグされていることを容易に検出できます。つまり、ソフトウェア製作者はリバースエンジニアリング対策として、デバッガから起動・アタッチあるいはブレークポイントを設定された時点で実行される、何らかのリバースエンジニア向けの処理をソフトウェアに仕込むことも可能です。
デバッグ特権の必要性が分かりません
デバッグ特権がない状態でも、HBP/INT3ブレークポイントを設定してブレークさせるといった、デバッガとしての処理は一応可能です。そのため、実際の解析ツールの使用上で目立つ点としては、プロセスの列挙に関する点といえます。デバッグ特権がなければ、一部のシステムプロセスなどは認識できません。しかし、解析ツールとして特に重要な点は、デバッグ特権(SeDebugPrivilege)があれば、DACL(Discretionary Access Control List)を操作してアクセス権を読み書き不可に設定したプロセスであっても、問題なくそのプロセスメモリの読み書きを行うことができることです。ちなみに、解析対象アプリケーション側では、自分自身のプロセスのDACLを、GetKernelObjectSecurity関数やGetSecurityDescriptorDacl関数を使って取得し、そのACE(Access Control Entry)を書き換えて、PROCESS_VM_READのアクセス権を取り除くことが可能です。変更後のDACLの設定には、SetSecurityDescriptorDacl関数やSetKernelObjectSecurity関数を使用します。同様に、起動直後に自身を再起動させたり、ローダーを経由することで、そのプロセス生成時(CreateProcess関数呼び出し時)に制限を施したDACLを適用するという解析対策も可能です。この場合の新しいDACLを設定する処理には、BuildExplicitAccessWithName関数やSetSecurityDescriptorDacl関数などが使用されます。加えて、デバッグ特権があれば、上記の手法でPROCESS_TERMINATEのアクセス権を取り除いたプロセスでも、強制終了させることが可能です。
つまり、解析対象のプロセスは、DACLからPROCESS_VM_READなどのアクセス権を除去した状態で起動し、さらに検出したデバッガやプロセスメモリエディタのデバッグ特権を動的に奪えば、自分のプロセスのプロセスメモリをこれらの解析ツールから一切アクセス不可にしたり、プロセス強制終了不可にすることが可能になります。ほとんどの解析ツールは、自分のデバッグ特権が動的に奪われるという事態は想定していません。
なお、『うさみみハリケーン』では、特定プロセスのデバッグ特権といった各種特権を動的に有効化・無効化することができます。この機能は、メニューの[ファイル]→[プロセスの各種情報を表示]→[特権]で対象特権を右クリックから実行します。また、『うさみみハリケーン』起動時に表示されるプロセスリスト上で、右クリックによるポップアップメニューで[プロセスの各種情報を表示]からこの機能を呼び出すこともできます。
関連して、解析ツールのデバッグ特権は、解析ツールを管理者権限で起動しないと、特権が無効にされることに注意してください。解析ツールでのプロセスの列挙やオープンに不具合が生じたら、まずは解析ツールを管理者権限で起動し直すことをお勧めします。
デバッグ中にシステムDLL内でアクセス違反が生じるのはOSの問題ですか?
OSの不具合である可能性もありますが、大抵の場合はデバッギー側でのAPI関数呼び出し時の引数に原因があります。引数の指定内容(特に構造体のメンバ変数)に注意して下さい。ただし、プログラマがソフトウェア動作上で問題が無いと判断した上で、引数が不適切になるケースに対処していないこともあるため、デバッグ時のみ表面化するようなアクセス違反をソフトウェアのバグとは断定できません。デバッギーからデタッチすることはできませんか?
WindowsOSでは、基本的にデバッガがいったんデバッギーにアタッチすると、デタッチしてデバッギーのデバッグをOSに引き継ぐことはできません。そのため、デバッグ中にデバッガを終了させると、デバッギーは例外等への対処が不可能になるため必ず終了します。これを利用すれば、Windows2000/XPでのサービスプログラム等、特殊な権限を持ち通常は強制終了不可のプロセスでも、強制終了が可能なケースもあります(アタッチしてデバッガごと終了)。同様に、強制終了させたいプロセスを指定してDebugActiveProcess関数でアタッチ後に、DebugBreakProcess関数を呼び出すという方法もあります(デバッガ側では例外を処理しない)。一般的なプロセスはブレークを処理する例外ハンドラを持たないため、この関数呼び出しでも動作を停止させることが可能です。ただし、サービスプログラム等の強制終了はシステムに悪影響を与える可能性があるため、十分な注意が必要です。
なお、Windows XPで実装されたDebugSetProcessKillOnExit関数を用いれば、デバッガ終了時にデバッギーを終了させないことが可能です。ただし、このAPI関数はデバッガのスレッドとデバッギーのプロセスを切り離すものであり、デバッガのスレッドのうち、CreateProcess関数あるいはDebugActiveProcess関数でデバッギーと接続したスレッドのみがデタッチ処理対象です。つまり、デバッガがマルチスレッドで動作し、デバッガの機能とは関係ないスレッドがデバッギーのプロセスを操作していたならば、DebugSetProcessKillOnExit関数呼び出しでも完全なデタッチはできません。一方、NtRemoveProcessDebug関数などのネイティブAPIを使用すれば、ほぼ確実なデタッチが可能になります。
『OllyDbg』には、DebugSetProcessKillOnExit関数によりデタッチ機能を実装させるプラグインの、『OllyDbg De-Attach Helper』があります。
関連して、デバッガにデバッグを停止させる、Windows XPで実装されたDebugActiveProcessStopというAPI関数もあります。なお、デバッギー側でネイティブAPIのZwSetInformationThread関数を用いてデバッガからデタッチを行う方法は、デタッチ後の例外処理の面で安全性に問題があります。
デバッギーのエントリーポイントがコードセクション内ならウイルスには感染していませんか?
ウイルスによってはコードセクションの空き領域にコードを埋め込むこともあり、この場合エントリーポイントはコードセクション内になります。ソフトウェアの入手先の信頼度等に関わらず、解析前にウイルスチェックは必ず行ってください。ウイルス対策としてのデバッグ検出について教えて下さい
実行ファイルを感染対象とするウイルスとしては、実行ファイルを書き換えてコードを埋め込むタイプがよく知られていますが、一部のウイルスには実行されているプロセスの制御を奪い、そのプロセスメモリに書き込んだウイルスのコードを実行させて動的に感染・破壊活動を行うものもあります。このプロセスの制御を奪う手法は複数ありますが、その内の一つに、デバッガとしてターゲットプロセスにアタッチする手法があります。そのため、ソフトウェア開発者によっては、自作ソフトの動的ウイルス感染に対処するために、デバッグされていることを検出する機能を自作ソフトに実装するケースがあります。一般的に、フリーウェアでデバッグ検出機能が実装されている場合は、対ウイルスの自己防御目的と考えて差し支えないと思われます。
デバッギーのウイルス感染やバグによるレジストリ破壊等に備えて、完全に独立した環境で解析を行いたい。
仮想マシン構築ソフトの『VMware Workstation Pro』の使用をお勧めします。このソフトは、数ある仮想マシン構築ソフトのなかでも実機の再現性に優れています。また、『VMware Workstation Pro』ではセッションで行った全ての操作を元に戻すスナップショット機能があるため、常にOSクリーンインストール直後の状態で解析することが可能です。なお、私が仮想マシン(ゲストOS:WindowsMe/2000/XP)で試した限りでは、『OllyDbg』は問題なく動作します。ほかにも、『Oracle VM VirtualBox』を用いることにより、仮想マシンを用いた解析環境を無償で構築可能です。
基本的に、仮想マシン構築ソフトのゲストOSには、自分が所有するOSや評価用Windows OSを使用します。ただし、Microsoftが開発環境評価用に配布している、仮想マシン構築ソフト用のゲストOSイメージを使用することも可能です。
Windows Dev Center Download a virtual machine(使用期限あり)
とても古い内容のため、現在では実践用ではない参考資料となりますが、『VMware Workstation Pro』を用いた、ゲストOS上で実行されているアプリケーションをホストOS上のVMwareプロセスごと解析するアプローチについては、『「VMware」を用いたフルスクリーン型ゲームへの対処』を参照して下さい。
プログラム解析環境としては、Windowsをインストールした仮想マシンを用意し、そこに必要な各種解析ツールを導入していくのが基本といえます。この仮想マシンへの各種解析ツールの導入を自動で行い、解析環境の構築を容易にするツール「FLARE VM」もあります。導入する各種解析ツールについては、「FLARE VM」でインストールされるツール群のリストが参考になります。ただし、このリストには日本製の各種解析ツールは含まれていません。
上記はWindows OSを用いた解析環境について述べていますが、リバースエンジニアリングおよびマルウェア解析用のLinuxディストリビューションである『REMnux』などを使うというアプローチもあります。
デバッギー側から仮想マシン構築ソフト上で実行されていることを検出することは、『VMware』と『VirtualBox』のいずれも可能です。この場合、『VMware』ならばIN命令を使った『VMware』特有のポートの有無による検出か、「VMwareUser.exe」・「VMwareService.exe」等『VMware』特有のプロセスの有無や、ドライバ「vmhgfs.sys」等の有無による検出が想定されます。
また、使用されるCPUなど色々な条件を満たせば、CPUID命令を実行してHyper-Vが有効か確認することで、仮想PC上で起動されたことを検出可能になるケースもあります。
なお、『VMware』の新しいバージョンでは、仮想PCであることを検出されないための、特殊な設定を行うことが可能です。この場合はVMXファイルに以下の設定を書き込みます。その後、検出されないか確認しながら、必要に応じて以下のうち不要な設定を削除していきます。
<出典>:VMX-file parameters - Advanced parameters
monitor_control.disable_directexec = "true" monitor_control.disable_chksimd = "true" monitor_control.disable_ntreloc = "true" monitor_control.disable_selfmod = "true" monitor_control.disable_reloc = "true" monitor_control.disable_btinout = "true" monitor_control.disable_btmemspace = "true" monitor_control.disable_btpriv = "true" monitor_control.disable_btseg = "true" monitor_control.restrict_backdoor = "true" isolation.tools.getPtrLocation.disable = "true" isolation.tools.setPtrLocation.disable = "true" isolation.tools.setVersion.disable = "true" isolation.tools.getVersion.disable = "true"マルウェア等が実装している仮想PCチェック処理では、たいてい特有の文字列やバイト列を使用します。そこで、『うさみみハリケーン』に同梱している汎用ファイルアナライザ『青い空を見上げればいつもそこに白い猫』の「YARAルールでスキャン機能」では、この特有の文字列等を条件としたYARAルールで、仮想PCチェック処理の検出に対応しています。
関連して、対象アプリケーション実行時のファイル・レジストリ書き換えなどを監視し、行われるシステムへの変更内容を仮想化することで、システムを同アプリケーション実行前の状態に復元可能なタイプのソフトも使用可能です。ただし、このような「サンドボックス」などと呼ばれるソフトは、実行対象がマルウェアの場合に対抗策を講じられ、システムを元の状態に復元できなくなるケースも考えられます。解析対象アプリケーションの安全性が確認できないならば、仮想マシン構築ソフトを使用されることをお勧めします。
解析時にデバッギーが行ったファイル・レジストリ操作の結果だけを簡単に確認したい。
このようなアプローチでは、任意のタイミングでファイルやレジストリのスナップショットを作成し、異なるタイミングで作成した2つのスナップショットを比較することで、ファイルおよびレジストリの変更箇所を表示するソフトを使用します。この種のソフトとしては、『Attack Surface Analyzer』 があります。また、『SystemExplorer』が同種の機能を実装しています。レジストリへの操作を監視することに特化した『レジストリ番犬バロン』や『RegistryChangesView』、『RegFromApp』など、他にも同種のソフトがあります。なお、リアルタイムでファイル・レジストリ操作を監視したい場合は、挙動解析用のプロセスモニターを使用してください。『うさみみハリケーン』には、プロセスモニター『猫原喜雨』を同梱しています。プロセスモニターは、一般的なファイル・レジストリの読み書きだけではなく、LoadLibrary関数などによる、DLLファイルのロード処理のモニタリングにも使用可能です。他のプロセスモニターとしては、『Process Monitor』があります。
デバッガ検出手法を教えてください。
代表的なデバッガ検出手法としては、カーネルモードデバッガ検出専用のものを含めて約20種類程が知られています。そのうち、プログラマが簡単に思いつくような検出例は以下。ただし、パッケージソフト、PCゲームおよびシェアウェアのプログラマは、ユーザー視点での動作の安全性確保という点で、Microsoftが文書化していないトリッキーなデバッグ検出手法の実装には消極的です(注:好んで実装するプログラマも実在します)。実際には、プログラムコードとしての実装ではなく、デバッガ検出・対処機能を付加するパッカーの使用によるデバッガ検出というケースもあります。ちなみに、マルウェアにはデバッガ対策を施しているケースが少なくないため、そのソースコードで実践的なデバッガ検出手法を学んでいるリバースエンジニアの方も実在します。なお、現在では、詳細なデバッガ検出手法についての有用な資料がネット上で公開されています。そのため、当サイトの解説とあわせて、下記の資料を参考にされることをお勧めします。ただ、これらの資料がネット上で公開されている以上、すでにこれらの手法が陳腐化していることや、さらに発展させた手法が編み出されている可能性を考慮してください。
The“Ultimate”Anti-Debugging Reference
An Anti-Reverse Engineering Guide
デバッガ対策無効化ライブラリ「ScyllaHide」のドキュメントとソースコード
1.IsDebuggerPresent関数
このWindows98で実装されたAPI関数を使えば、ソフトウェアは自分がデバッグされているか簡単に知ることが出来ます。この関数の戻り値がゼロでなければデバッグされています。この検出手法は関数呼び出しが偽装されない限り、解析時に容易に発見可能です。 なお、WindowsXPサービスパック1で実装された、CheckRemoteDebuggerPresent関数で特定プロセスがデバッグされているか調べることも可能です。
IsDebuggerPresent関数を使用せずに、自分でプロセス関連情報(PEBのメンバ変数BeingDebugged)を取得して、デバッグされていることを検出可能ではありますが、外部アプリケーションから被デバッグ検出を簡単に無効化される等の問題があり、実装工数対効果の面で現実性に欠けます。なお、NT系OSでは、このプロセス関連情報を簡易に取得可能にする、ネイティブAPIのNtQueryInformationProcess関数やRtlGetCurrentPeb関数があります。基本的に、アプリケーションが自分自身のPEBを参照するのは、「何か特殊なことをしようとしている」と判断してよいでしょう。
2.仮想デバイスドライバ検出
これは比較的古い検出手法であり、SoftICEやTRW2000の検出などに使用されます。仮想デバイスドライバを、CreateFile関数でパス解析オフにして検出するものです。なお、一部のセキュリティソフトは、SoftICE他の検出処理を「マルウェアとして疑わしい」要素と判断しているとみられます。
仮想デバイスドライバの指定は "\\\\.\\仮想デバイスドライバ名"という形で行います。
BOOL IsDebuggerLoaded() { HANDLE hFile; hFile = CreateFile("パス解析オフの仮想デバイスドライバ名", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); return TRUE; } return FALSE; }
実行ファイル中で参照される仮想デバイスドライバ名の例は以下。
参考までにFilemon・RegmonおよびProcess Monitor(参考)の分も併記しました。
\\.\TRW
\\.\SICE
\\.\NTICE
\\.\FILEVXD
\\.\FILEMON
\\.\REGVXD
\\.\REGMON
\\.\Global\ProcmonDebugLogger
3.ウィンドウクラス名取得
OllyDbgは、そのウィンドウクラス名が「OLLYDBG」となっているため、FindWindow関数(Window名はNULL)か、EnumWindows関数とGetClassName関数併用で検出が可能です。
4.INT3サーチ
ソフトウェアがプロセスメモリのモジュールエリアにあるコードセクションを、タイマ制御等で定期的に自分でメモリサーチして、バイナリデータがINT3(0xCC)となっている箇所の数をカウントし、実行ファイル中に保存しておいたオリジナルのカウント値と比較することで、INT3によるブレークポイント設定を検出します。同様に、 INT3はコードセクションのチェックサム(単純加算・XOR・CRC-32・MD5等)生成による自己改ざん検出にもヒットします。
メモリサーチやチェックサム生成を行う以上、コードセクションへの読み込みメモリアクセスが発生する点に注目してください(注:ドライバを使って物理メモリ上からチェックする方法もある)。
5.特定メモリエリアのアクセス属性取得
VirtualQuery関数を用いて自己プロセス内の特定メモリエリアのアクセス属性を取得し、本来ありえない属性になっている場合はメモリアクセスのブレークポイントが設定されている(あるいは何らかのプロセスメモリ改ざん)と判断します。また、自己プロセスに対してWriteProcessMemory関数を使用してアクセス属性の異変を探ることも可能です。INT3ブレークポイントやコード動的改変の検出例として、コードセクションに該当するメモリエリアのアクセス属性がPAGE_EXECUTE_WRITECOPYになっている場合は、被デバッグ等検出の可能性が高いといえます。この場合、デバッガ等による同メモリエリアへのプロセスメモリ書き込みが行われた時点で、OSのプロセスメモリ関連処理に伴いアクセス属性は自動的にPAGE_EXECUTE_READWRITEに変化し、元のアクセス属性に戻すことはできません。基本的に、このようなアクセス属性取得による被デバッグ等検出は、アクセス属性を変更しないハードウェアブレークポイント設定の検出とセットで行われる可能性が高いといえます。
6.構造化例外ハンドラを用いたデバッグ検出
この検出手法を使用する場合、逆アセンブルコードリスト上には構造化例外ハンドラの設定や解除のため「MOV EAX,DWORD PTR FS:[0]」といった「FS:[0]」を含むコードが現れます。
// C++ void CDebugTest::IsDebuggerWithSEH() { __try { //DebugBreak関数も使用可能だがあからさま過ぎる //DebugBreakProcess関数は、自プロセスの-1ではない"リアル"ハンドル指定でも上手くいかない //EXCEPTION_SINGLE_STEPも可 RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL); } __except(GetExceptionCode() == EXCEPTION_BREAKPOINT ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { MessageBox("No debugger attached.","Info",MB_OK); return; } //PEBのメンバ変数BeingDebuggedには依存しない MessageBox("Debugger attached!!","Info",MB_OK); }構造化例外ハンドラを用いた、デバッグレジスタ参照によるハードウェアブレークポイント設定の検出ならば、以下の手法が使用可能です。
void CHBPtestDlg::IsDebuggerWithSEH2() { DWORD * pAV = NULL;//格納値が0x00000000のポインタ __try { //EXCEPTION_ACCESS_VIOLATION(定義値0xC0000005)を発生させる *pAV = 42; } __except ( ExcFilter( GetExceptionInformation() ) ) { //例外フィルタExcFilterの実行によりここに処理が移る } } //例外フィルタ int CHBPtestDlg::ExcFilter(PEXCEPTION_POINTERS pep) { /* EXCEPTION_POINTERS構造体の定義 typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; //Dr0を含むCONTEXT構造体のポインタ } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS; */ //ポインタの設定例 PEXCEPTION_RECORD pEXC = pep->ExceptionRecord; PCONTEXT pCTX = pep->ContextRecord; /* 「pep->ContextRecord->Dr0」でDr0の値を取得可能 逆アセンブルコードリスト上では、Dr0なら以下のように格納値を辿ります ポインタの仕組みと操作の考察をお勧めします MOV EDX,[ESP+4] ;pep MOV EAX,[EDX+4] ;ContextRecord MOV EAX,[EAX+4] ;Dr0 */ extern bool ExitProcessFlag; /* static char Buf[128]; wsprintf(Buf, "ExceptionCode:%08X, Dr0:%08X", pEXC->ExceptionCode, pCTX->Dr0); MessageBox(Buf, "ExcFilter", MB_OK); */ //ここでデバッグレジスタを書き換える処理は容易に解析されうる //デバッグレジスタがメインモジュール内アドレスなら特殊なフラグをセットする例 //ASLR対応のためhModuleをGetModuleHandle関数で取得しておく //実際はDr0からDr3までチェック if( (pCTX->Dr0 < (DWORD)hModule+0x160000) && (pCTX->Dr0 > (DWORD)hModule) ){ //他の処理で使用するプロセス強制終了フラグなど ExitProcessFlag = true; } //例外ハンドラに処理を移させる return EXCEPTION_EXECUTE_HANDLER;//定義値1 }このようなケースへのアプローチにおいては、「FS:[0]」の操作処理や、上の例では「ExcFilter( GetExceptionInformation() )」に相当し、たいていJMP命令のすぐ後で実行される、CALL命令での例外フィルタ呼び出し処理を端緒とするのが有効と考えられます。また、CONTEXT構造体の内容についても各種資料で参照されることをお勧めします。なお、GetExceptionInformation関数はAPI関数ではなく組み込み関数であり、プログラムコードとして展開されます。
7.GetThreadContext関数を用いたハードウェアブレークポイント検出
デバッグレジスタではなく汎用レジスタならばインラインアセンブラを用いることで簡単に取得可能です。
// C++ void CDebugTest::IsDebuggerWithContext() { HANDLE hThead = GetCurrentThread(); CONTEXT ct; ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; if(GetThreadContext(hThead, &ct)){ //実際はDr0からDr3までチェック //デバッグレジスタは一部のドライバやセキュリティツール等が使用することもある //そのため0か否かでチェックしてはいけない //このアドレス範囲はあくまで目安としての例 if( (ct.Dr0 < 0x3F000000) && (ct.Dr0 >= 0x10000) ){ MessageBox("HBP(Dr0) is active!!","Info",MB_OK); //デバッグレジスタのクリア ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; ct.Dr0 = 0; SetThreadContext(hThead, &ct); } else MessageBox("HBP(Dr0) is inactive.","Info",MB_OK); } }8.ネイティブAPIのNtQueryInformationProcess関数を使用したデバッグ検出
この関数やZwQuerySystemInformation関数他のネイティブAPIでデバッグを検出可能ですが、これらの関数は将来的に互換性が失われる可能性があるため、一般的なアプリケーションへ実装される可能性は低いと考えられます。
// C++ //この手法はWin9x/Meでは不可 void CDebugTest::IsDebuggerWithDebugPort() { //CheckRemoteDebuggerPresent関数と同様の処理 HANDLE hProcess = GetCurrentProcess(); enum PROCESS_INFO_CLASS { ProcessDebugPort = 7 }; HANDLE hPort = 0; if(NT_SUCCESS(NtQueryInformationProcess (hProcess, ProcessDebugPort, &hPort, sizeof(hPort), NULL)) ){ if(hPort) //PEBのメンバ変数BeingDebuggedには依存しない MessageBox("Debugger attached!!","Info",MB_OK); else MessageBox("No debugger attached.","Info",MB_OK); } }9.特定処理の経過時間監視によるデバッグ検出
特に解析されるであろう特定の処理の実行に要した時間をGetTickCount関数などで計測し、あらかじめ指定した閾値(しきいち・いきち)を超えるとデバッグされている(ブレークやステップ実行が行われた)と判断します。対象プログラムの処理として、本来必要とは考えられない時間関連のAPI関数(参考)の呼び出しと戻り値の大小比較に注目してください。
このデバッグ検出手法は、解説や実装例を見る限り、たいていGetTickCount関数が使用されます。そのため、『うさみみハリケーン』が実装している対象プロセスの「実行速度調整」機能を用いて、GetTickCount関数ベースでの時間経過を極端に遅くすれば、プログラムコードの改変なしで対処できるケースもあると考えられます。API関数を用いたミリ秒単位の経過時間取得は、PCゲームでの処理タイミング制御時などにも行いますが、PCゲームの場合は、より精度の高いtimeGetTime関数を使用するのが基本とされています。
なお、逆アセンブルコードリスト上での、GetTickCount関数呼び出しと戻り値の単純な大小比較は解析が容易なため、Windows Vista以降では、アドレス0x7FFE0000にあるKUSER_SHARED_DATA構造体の、メンバであるTickCountなどを直接参照する可能性があります(参考)。
10.親プロセスの検査
自分のプロセスの親プロセスについて、プロセスIDから実行ファイル名やパスを取得し、さらに実行ファイル名変更に対処するためリソース内のバージョン情報(ファイルの説明、内部名、正式ファイル名など)を参照します。これはデバッガや、各種APIフックを行うソフトなどから起動された場合に有効な検出手法です。なお、起動後にデバッガからアタッチされても、親プロセスがデバッガのプロセスに変更される訳ではありません。
この場合は、まずGetCurrentProcessId関数で自分自身のプロセスIDを取得します。参考までに、以下のインラインアセンブラや、同様にNtCurrentTeb関数の戻り値+20hも、プロセスID取得に使用可能です。
DWORD m_ProcessID; _asm{ mov eax, FS:[0x18] mov eax, [eax+0x20] mov m_ProcessID, eax }次に、CreateToolhelp32Snapshot関数やProcess32First/Process32Next関数を使って、自分のプロセスにおけるPROCESSENTRY32構造体のメンバth32ParentProcessIDを取得します。そして、親プロセスのプロセスIDに対して、CreateToolhelp32Snapshot関数とModule32First関数で、親プロセスのメインモジュールのファイル名やパスを取得します。なお、親プロセスのプロセスIDは、NtQueryInformationProcess関数でも取得可能です(PROCESS_BASIC_INFORMATION構造体のメンバInheritedFromUniqueProcessId)。
さらに、取得したパスを使って、GetFileVersionInfoSize関数、GetFileVersionInfo関数やVerQueryValue関数で、バージョン情報の詳細を取得します。たとえば、『OllyDbg』のバージョン情報の「内部名」は「OllyDbg」となっており、この文字列を基に自分がデバッグされていることを検出可能です。デバッガ側でのこの検出手法の回避策としては、リソースのバージョン情報文字列を改変したり、リソースセクションを操作するパッカーを適用することが想定されます。
自分がデバッグされている場合への対処策としては、親プロセスがエクスプローラ以外のプロセスならば、親プロセスに対して、パラサイトルーチンを使って、デバッグを行うスレッドが必ず使用するWaitForDebugEvent関数のエントリをフックし、自分のプロセスIDを引数にしてDebugActiveProcessStop関数を呼び出させます。パラサイトルーチンは以下のようになります。
;フック後の関数エントリからのジャンプ先(メインモジュールのコードセクション未使用領域等) PUSHAD PUSHFD PUSH 自分のプロセスID CALL DebugActiveProcessStop関数のエントリのアドレス POPFD POPAD (JMPのニーモニックで上書きした関数エントリのコードを復元) JMP 関数エントリこれにより、もし自分のプロセスがデバッグされているならば、デバッガをデバッグ不能にさせるか、デバッガの状況によっては自分のプロセスを強制終了させることになり、いずれにしてもデバッガ対策となります。
動作確認環境:Windows Vista Home Premium (32Bit) Service Pack 2
11.INT命令によるデバッガ検出
INT3h(BCHK検出)、INT2Fh、INT41h、INT68h等を用います。デバッガ検出手法として必ずしも信頼性の高いものではありません。
INT命令を用いたデバッガ検出が上手くいきません(環境:WindowsXP)。
INT3h、INT2Fh、INT41h、INT68hを用いたデバッガ検出手法はOSのバージョンやその他の条件に依存するため、一般的なソフトウェアへの実装はお奨めしません。『OllyDbg』のプロセスを検出して解析を妨害するタイプのウイルスを解析したい
基本的なOllyDbgのプロセス検出手法としては、Windows上で実行されているプロセスの一覧を取得して、各プロセス名のチェックによりOllyDbgが起動されているかを判断します。API関数を用いてプロセス一覧を取得する方法は5種類ありますが、通常は以下の3つの方法のいずれかを使用します。・Process32First(Next)関数等のTool Help APIを使用
・EnumProcesses関数等のProcess status API(PSAPI)を使用
・OSが保持するパフォーマンスデータからレジストリの不可視キーとして取得
最も簡単なプロセス検出回避アプローチとしては、Windowsが把握するプロセス名はプロセスの実行ファイル名となるため、単純にOllyDbgの実行ファイル名を別のものに変更すれば、起動時にエラーメッセージは出るもののプロセス名も変更されOllyDbgのプロセスは検出されなくなります。
しかし、全プロセスのプロセスメモリ上のモジュールエリアで特定文字列を検索するケースなど、詳細なOllyDbgプロセス検出を行っており、かつマルウェアがプロセス検出ルーチンの無効化を阻止する強固な改ざん対策を実装している場合には、API関数呼び出しへの操作や実行ファイルのリネームとは別のアプローチを行う必要があります。
このようなケースでは、まず、そのような解析対策を無効化するための、特殊なツールまたはOllyDbgプラグインを使用します。このような対解析対策用のOllyDbgプラグインとしては、『Olly Advanced』、『StrongOD』、『PhantOm』、『TitanHide』、『ScyllaHide』および『Ollydbg Anti Anti Hardware Breakpoint』などがあります。
これらの対解析対策ツールやOllyDbgプラグインで対処できないときは、rootkitと呼ばれるツールを用いて、Windowsが保持するプロセスのリストを改変したりプロセス一覧取得用のAPI関数を操作することで、OllyDbgのプロセスをWindows上で隠蔽してからマルウェアの解析を行うというアプローチが有効と考えられます。任意のプロセスの隠蔽機能を持つrootkitは以下のものなどが挙げられます。なお、プロセスの隠蔽はセキュリティ上深刻な問題を招きかねないため、セキュリティソフトによってはこのようなrootkit自体をウイルスやトロイの一種と判断することがあります。下記rootkitは、これらがどのようなツールであるかを十分理解した上で、必ず自己の責任において入手と使用を行って下さい。
・FU rootkit/FUto rootkit (DKOM: Direct Kernel Object Manipulation)
・Redkod Rootkit (Ring3ベースAPIフック)
同様に、商用プロテクトが施されたオンラインゲーム用の、ほとんどrootkitと同じ処理を行う、北東アジア製ゲーム解析補助ツールを使用するという選択肢もあります。もともとゲーム用ツールでありリバースエンジニアリングという面では知名度が低く、かつ英語版が存在しないということもあり、比較的対策されにくいという利点があります。
なお、拙作の汎用プロセスメモリエディタ兼デバッガ『うさみみハリケーン』では、FU rootkitのようなDKOMで隠蔽されたプロセスのウィンドウ情報を元にプロセスIDを取得して、隠蔽されていないプロセス同様に解析することが可能です。また、『うさみみハリケーン』のパフォーマンスデータからプロセス一覧を取得する機能や、プロセスID総当りでプロセス一覧を取得する機能を使用すれば、Redkod Rootkitが用いるようなAPIフックで隠蔽されたプロセスでも、プロセスIDを取得・解析することができます。これらの特殊なプロセス列挙機能は、『うさみみハリケーン』起動時に表示されるプロセスリスト上で、右クリックにより表示されるポップアップメニューから呼び出します。
さらに、『うさみみハリケーン』ではWindows XP/2000用の、プロセスメモリ上のKernel32.DLL・Ntdll.DLLモジュール書き換えや、カーネルスペースでの Service Descriptor Table (SDT) 書き換えによるAPIフックの解除機能を実装しており、rootkit等が実装しているAPIフックを用いた各種解析対策に対処可能なケースもあります。
ちなみに、ウイルスの一般的な解析アプローチについては、以下のページが参考になります。
IDA Proを用いたマルウェアの解析例
Symantec、ウイルス対策の最前線「Security Response」を公開
「シマンテック、日本におけるウイルス解析ルームを公開」
星澤裕二のSecurityWatch : マルウエアを解析する(その1〜)
CCI: Windows Season2 未知のバイナリの解析
OS上でデバッガが動作したことを検出する方法を教えてください
この場合は、OS上で実行されている全てのプロセスで、APIフックを用いてデバッガに関連するAPI関数(DebugActiveProcess関数他)の呼び出しを監視します。APIフックにより、デバッガの動作に限らず、プロセスのオープンや強制終了、プロセスメモリの読み書き、プロセスやスレッドの作成他、API関数を使用する色々な処理を監視可能です。監視だけに止まらず、引数の操作等によるAPI関数呼び出しの無効化もできます。APIフックの手法については、参考になる資料がネット上で多々公開されています。
APIフックに関して、Windows2000/XP以降を動作環境とする一部のセキュリティソフトやトロイ等では、監視するプロセスの実行ファイルにパッカーが使用されているケース等に対処するために、監視するプロセスのメインモジュールのIATではなく、スタブDLL(各プロセスに読み込まれたシステムDLLのコピー)内にあるAPI関数の開始アドレス以降のバイナリデータを書き換えて、各プロセスにロードさせた監視用DLL内の処理にジャンプさせています。Windows2000/XP/Vistaおよびそれ以降においては全てのスタブDLLがアプリケーション有効アドレス範囲内に収まるため、このような書き換えが可能になります。Windows9xではKernel32.DLLなど一部のシステムDLLのモジュールアドレスはアプリケーション有効アドレス範囲外となるため、一般的なアプリケーションからそのシステムDLLに属するアドレスのバイナリデータは読み込みは可能ですが書き換えはできません。なお、このAPIフック手法において、API関数呼び出しを監視するためのコードは、監視用DLL内ではなくフック対象プロセスの仮想アドレス空間に書き込んだものを使用するという方法もあります。
もしこのAPIフックを行うアプリケーション側が逆アセンブラを実装している場合は、監視対象プロセスにおけるAPI関数の開始アドレスではなく、API関数の開始アドレスから4つめのニーモニックの先頭アドレスを書き換えて、監視用DLL内の処理にジャンプさせるといったことも可能です。この場合は監視用DLL内の処理でPUSHAD/POPAD+PUSHFD/POPFD命令を使って処理の安全性を高めると考えられます。必ずしもAPI関数の開始アドレス直後を書き換える必要はないことに注意して下さい。
ユーザーモードでのAPIフックを用いたOS上でのデバッガ動作検出は、どのような状況でも完璧に検出できる訳ではありません。基本的に、Windows9x環境でかつデバッガがパッカーで圧縮/暗号化されプロセスメモリ上でのIATの解析が必要なケースでは、このデバッガ動作検出は難しくなると考えられます。また、API関数の開始アドレス以降のバイナリデータを書き換える検出手法では、デバッガ側がデバッガ関連各API関数の開始アドレス以降のバイナリデータ16バイト程度を、『あらかじめAPIフックされていないクリーンな状況で取得しておいた』オリジナルのバイナリデータを用いて高頻度で書き戻すことにより、APIフックによるデバッガ動作検出を無効化させることも可能です。
ちなみに、一部のセキュリティソフトやトロイ等は、ユーザーモードでのAPIフックではなく、カーネルモードでドライバからSDT(Service Descriptor Table)を同ドライバ内の関数に繋がるよう書き換えています。この場合は、『Win2K/XP SDT Restore』を用いてSDTを書き戻すことで無効化が可能です。なお、x64用の64ビット版Windowsでは、その仕様上このようなSDT(およびIDT、GDT)の書き換えは行えません。
●<参考>セキュリティソフトのAPIフック対象リスト例(一部を抜粋)
[kernel32.dll]
CreateProcessInternalW
DebugActiveProcess
GetProcAddress
LoadLibraryExW
MapViewOfFile
MapViewOfFileEx
MoveFileW
OpenProcess
ReadProcessMemory
VirtualProtect
VirtualProtectEx
WriteProcessMemory
[user32.dll]
GetWindowThreadProcessId
PostMessageA
PostMessageW
SendInput
SendMessageA
SendMessageW
SetCursorPos
SetWindowsHookExA
SetWindowsHookExW
keybd_event
mouse_event
[ntdll.dll]
NtOpenProcess
NtQuerySystemInformation
NtSuspendThread
NtTerminateThread
RtlGetNativeSystemInformation
ZwOpenProcess
ZwQuerySystemInformation
ZwReadVirtualMemory
ZwSuspendThread
ZwTerminateThread
ZwWriteVirtualMemory
アタッチしてブレークポイントを設定すると、ブレークポイントが機能しない上に強制終了するソフトウェアがあるのですが。
実物を見ていないのであくまで推測ですが、構造化例外ハンドラ (SEH: Structured Exception Handler) を用いたデバッガ対策である可能性があります。逆アセンブルコードリスト上で、構造化例外ハンドラに特有のセグメントレジスタへのアクセスを追いかけてみてください。 なお、解析者が逆アセンブルコードリスト上で「FS:[0]」を検索することを想定して、意図的に「FS:[初期化した汎用レジスタ]」としているケースもありますので注意が必要です。デバッガで起動/アタッチすると、デバッギーが終了していないのにデバッガがデバッギー終了と認識します。
まれなケースですが、デバッガのデバッグアプローチの方法とデバッギーのデバッガ対策の組み合わせにより、この様な状況が生じることがあります。デバッガでのアプローチ手法を変えてみてください。『OllyDbg』のようなデバッガが表示する「最後のエラー」情報はデバッギーのどこに格納されているのですか?
NT系OSでは「Thread Environment Block (TEB)」、9x系OSでは「Thread Information Block (TIB)」と呼ばれる、スレッドのローカル格納領域です。このデータブロックの先頭アドレスはセグメントレジスタを用いて、「FS:[18]」で取得することができます。また、「MOV EAX,FS:[18]」の処理を行う、ネイティブAPIのNtCurrentTeb関数を使用する方法もあります。なお、『うさみみハリケーン』には、解析対象プロセスのPEBおよびTEB用の構造体表示機能を実装しています( メニューの[デバッグ]→[PEB/TEB表示] )。『OllyDbg』が表示する「最後のエラー」のエラーコードは「FS:[34]」(Win2k/XP/Vista/7)または「FS:[60]」(Win9x/Me)の値です。実際に、このエラーコードを取得する、Windows 7でのGetLastError関数の内部処理は以下のようになっています。
MOV EAX,FS:[18] MOV EAX,[EAX+34] RETNただし、このエラーコードの値がGetLastError関数の戻り値と一致しないケースもあります。特にWindowsの一部のバージョンでのみGetLastError関数でエラーコードを取得可能なAPI関数に注意して下さい。なお、『うさみみハリケーン』には、エラーコードを日本語あるいは英語の説明文に変換する機能を実装しています(メニュー[その他]→[エラーコードを説明文に変換])。また、『スペシャルねこまんま57号』にもエラーコードを日本語の説明文に変換する機能を実装しています(メニューの[システム]→[稼動アプリケーション制御]のダイアログ下部)。
ちなみに、デバッギーのメインスレッドで「FS:[04]」と「FS:[08]」の値からスタックエリアを特定することもできます。
解析対象プロセスのPEB(Process Environment Block)の情報を取得したい
システムが使用する、特定プロセスに関する情報を格納する構造体PEB(Process Environment Block)については、WindowsXP SP2で仕様が変更されたため、NT系OSにおいて必ずしも従来のように各プロセスの特定アドレス(0x7FFDF000)以降にあるとは限りません。PEBのアドレスは、ネイティブAPIであるNtQueryInformationProcess関数を使用することで取得可能ですが、Windows SDK等の資料ではこの関数の仕様が将来的に変更される可能性を示唆しており、プログラム解析ツールへの実装は注意が必要です。また、解析対象プログラム側では、Microsoftが文書化していないネイティブAPIである、RtlGetCurrentPeb関数を使用することで自分のPEBのアドレスを取得できます。なお、『うさみみハリケーン』には、解析対象プロセスのPEBおよびTEB用の構造体表示機能を実装しています( メニューの[デバッグ]→[PEB/TEB表示] )。
他のアプローチとしては、VirtualAllocEx関数、WriteProcessMemory関数およびCreateRemoteThread関数を用いて解析対象プロセスのスレッドとして以下の様なアセンブルコードを実行させることで、PEBのアドレスが格納されるFS:[30]の値を出力させ、ReadProcessMemory関数でその値を取得することも可能です。
●参考<PEBのアドレス取得例>
動作確認環境:Windows XP 32bit SP2, Windows 7 32bit SP1 (Windows9x/Meでは動作しません)
注意:
・アドレス403000は、解析対象プロセスのプロセスメモリ内に確保したメモリエリアのアドレス例
・以下の「00403000-648B0530000000」といった文字列は、『うさみみハリケーン』や『スペシャルねこまんま57号』で使用可能なプロセスメモリ書き換え用の改造コード(書き換え処理自動化用のスクリプト)です。
;TEB(Thread Environment Block)のアドレス+30hのDWORDの値 ;MOV EAX,FS:[30] 00403000-648B0530000000 ;MOV [403020],EAX 00403007-890520304000 ;RETN 0040300D-C3 ;TEBのアドレスFS:[18]を使用する例 ;Windows SDKのwinternl.h内サンプルはこの方法を使用 ;RtlGetCurrentPeb関数の内部処理も同様 ;MOV EAX,FS:[18] 00403000-648B0518000000 ;MOV EAX,[EAX+30] 00403007-8B8030000000 ;MOV [403050],EAX 0040300D-890550304000 ;RETN 00403013-C3●参考<IsDebuggerPresent関数無効化>
PEBのアドレス取得に伴い、IsDebuggerPresent関数がTEB経由で参照する、PEB構造体に含まれるプロセスの被デバッグ情報を指すメンバ変数BeingDebuggedの書き替え(1→0)により、同関数によるデバッグ検出の無効化が可能になります。
;MOV EAX,FS:[30] 00403000-648B0530000000 ;MOV BYTE PTR [EAX+2],0 00403007-C6800200000000 ;RETN 0040300E-C3●参考<OSバージョン偽装>
PEBのアドレス取得に伴い、解析対象アプリケーションがGetVersionEx関数で取得するWindowsのバージョンを偽装することも可能です。
;MOV EAX,FS:[18] 00403000-648B0518000000 ;MOV EAX,[EAX+30] 00403007-8B8030000000 ;Windows XP ;MOV DWORD PTR [EAX+0A4],5 ;OSMajorVersion 0040300D-C780A400000005000000 ;MOV DWORD PTR [EAX+0A8],1 ;OSMinorVersion 00403017-C780A800000001000000 ;MOV DWORD PTR [EAX+0B0],2 ;OSPlatformId->定数VER_PLATFORM_WIN32_NT 00403021-C780B000000002000000 ;RETN 0040302B-C3Windows 8.1以降では、GetVersion(Ex)関数によるWindowsのバージョン取得時に、Windows 8のバージョン情報を返す仕様になっています(参照)。この仕様は実行ファイルのマニフェストへの、Windows 8.1/10対応項目の記述により回避可能ですが、同マニフェストによる対処時は、GetVersion(Ex)関数以外の処理にも影響が及びます。そのため、Windows 8.1以降の正確なバージョン情報の取得のみが目的のケースでは、NtQueryInformationProcess関数でアドレスを取得した、自プロセスのPEBからバージョン情報を取得するというアプローチが選択肢の一つとなります。PEBでは先頭アドレスから+A4h(32Bit)、あるいは+118h(64Bit)以降に、メジャーバージョン、マイナーバージョンおよびビルドナンバーといったバージョン情報が格納されています(下記参照)。なお、ntdll.dllがエクスポートするRtlGetVersion関数でもPEBを参照しており、正確なバージョン取得が可能です。
●参考<PEB構造体>
注意:以下は公式の情報ではない上、今後のWindowsのアップデートでメンバが変更される可能性もあります。あくまで参考程度として下さい。
typedef struct _PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; //IsDebuggerPresent BOOLEAN BitField; HANDLE Mutant; PVOID ImageBaseAddress; PPEB_LDR_DATA LoaderData; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PVOID FastPebLock; PVOID AtlThunkSListPtr; PVOID IFEOKey; ULONG CrossProcessFlags; PPVOID KernelCallbackTable; ULONG SystemReserved[1]; ULONG AtlThunkSListPtr32; PVOID ApiSetMap; //+38h ULONG TlsExpansionCounter; PVOID TlsBitmap; ULONG TlsBitmapBits[0x2]; PVOID ReadOnlySharedMemoryBase; PVOID HotpatchInformation; PPVOID ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; ULONG NumberOfProcessors; ULONG NtGlobalFlag; BYTE Spare2[0x4]; LARGE_INTEGER CriticalSectionTimeout; ULONG HeapSegmentReserve; ULONG HeapSegmentCommit; ULONG HeapDeCommitTotalFreeThreshold; ULONG HeapDeCommitFreeBlockThreshold; ULONG NumberOfHeaps; ULONG MaximumNumberOfHeaps; PPVOID *ProcessHeaps; PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; PVOID GdiDCAttributeList; PVOID LoaderLock; ULONG OSMajorVersion; //+A4h ULONG OSMinorVersion; //+A8h ULONG OSBuildNumber; //Word*2: OSBuildNumber and OSCSDVersion ULONG OSPlatformId; //+B0h ULONG ImageSubSystem; ULONG ImageSubSystemMajorVersion; ULONG ImageSubSystemMinorVersion; ULONG GdiHandleBuffer[0x22]; ULONG PostProcessInitRoutine; ULONG TlsExpansionBitmap; BYTE TlsExpansionBitmapBits[0x80]; ULONG SessionId; //中略 UNICODE_STRING CSDVersion; //+1F0h USHORT Length; //Word USHORT MaximumLength; //Word PWSTR Buffer; //+1F4h //以下略 } PEB, *PPEB;関連して、解析ツールがGetThreadContext関数を用いて解析対象プロセスに属するスレッドのセグメントレジスタを取得済みである場合は、GetThreadSelectorEntry関数でTEBのアドレスを取得して、そこからプロセスとスレッドの各種情報を参照することもできます。また、WindowsXPのSP1以前やWindows2000であれば、メインスレッドのTEBのアドレスに+1000hすることでPEBのアドレスになります。
LDT_ENTRY ldt = { 0 }; GetThreadSelectorEntry(hThread, ct.SegFs, &ldt); DWORD dwTebPtr = (ldt.HighWord.Bytes.BaseHi << 24) | (ldt.HighWord.Bytes.BaseMid << 16) | (ldt.BaseLow) + 0x18; DWORD dwReadBytes = 0, dwTEBAddr = 0; ReadProcessMemory(hProcess, (LPBYTE)dwTebPtr, &dwTEBAddr, sizeof dwTEBAddr, &dwReadBytes);
Windows XP Service Pack 2で実装されるデータ実行防止機能で注意すべき点があるそうですが…
このデータ実行防止(DEP:Data Execution Prevention)機能により、コードが実行されるメモリエリアは、必ずアクセス属性として実行属性が必要になります。例えば、パラサイトルーチンを埋め込むメモリエリアとして、コードセクション以外のセクションの空き領域を使用する場合、そのセクションを含むメモリエリアにはアクセス属性として実行属性が必須となります。また、パッカーによりパックされたアプリケーションが、起動時に実行する解凍/復号化ルーチンを格納したセクションも同様です。十分な検証は行っていませんが、 このようなケースでは、PEヘッダで指定された当該セクションのCharacteristicsとして実行属性IMAGE_SCN_MEM_EXECUTE(0x20000000)を論理和で追加設定することで対処可能と考えられます。『うさみみハリケーン』付属のPEエディタ『UMPE』を使用することで、セクションの詳細情報の表示や属性変更が可能です。また、『UMPE』あるいは、『スペシャルねこまんま57号』のPEファイル再構築機能( メニューの[ファイル]→[PEファイルを指定して再構築] )を使えば、実行及び読み書き属性を持つ任意の名前とサイズのセクションを、実行ファイルに追加することができます。モジュールエリア上の実行属性を持つセクション以外のメモリエリアでは、コードを実行するためにはVirtualAlloc関数によるメモリ割り当て時にPAGE_EXECUTE等の実行属性を付加する必要があります。VirtualAllocEx関数による他プロセス上でのメモリ割り当てや、すでに割り当てられたメモリエリアのアクセス属性変更についても同様です。なお、既定プロセスヒープやスタックエリア上でのコード実行は、原則不可となります。ただし、HeapCreate関数で第1引数に定数HEAP_CREATE_ENABLE_EXECUTE(0x00040000)を論理和で設定することにより、コードを実行可能なヒープの作成が可能です。
データ実行防止機能が動作していれば、実行属性の無いメモリエリアでコードが実行された場合、例外としては従来のメモリアクセス違反同様にSTATUS_ACCESS_VIOLATION (0xC0000005)が発生します。しかし、従来のメモリアクセス違反と異なり、例外ハンドラのサーチ無しにプロセスが終了されることに注意が必要です。つまり、デバッガを用いてもこのデータ実行防止機能により発生した例外を動的に無効化することはできません。
なお、このデータ実行防止機能にはアプリケーションを指定して無効化するオプションが実装されています。設定方法については、「プログラム解析一般Q&A」を参照してください。
『OllyDbg』のセキュリティホールについて教えてください
OllyDbgの1.10及び過去のいくつかのバージョンでは、デバッギーがOutputDebugString関数の第一引数に「%s」や「%08X」等の書式指定文字列を含んだ文字列を指定した場合に、その出力文字列をそのまま表示せず、wsprintf関数を実行するように、スタックの内容を元にその書式指定文字列を変換して表示するというバグがあります。このため、悪意のあるデバッギーが、書式指定文字列を大量に含む文字列をOutputDebugString関数で出力した場合、OllyDbgはクラッシュすることがあります。
応急的な対処策としては、デバッギーのOutputDebugString関数呼び出しを無効化することが挙げられます。あるいは、同関数の呼び出しにブレークポイントを設定しておいて、第一引数の内容を確認するようにしておくことも有効な対処策と考えられます。
OllyDbg実行ファイル書き換えによる対処の方法は、下記質問を参照して下さい。ただし、完璧な対処ではありません。
なお、『うさみみハリケーン』のデバッガには、上記のセキュリティホールはありませんので、『うさみみハリケーン』であらかじめデバッグ文字列の取得を試してみることも予防策として有効です。また、『うさみみハリケーン』には、デバッガとして対象プロセスにアタッチせずに、デバッグ出力文字列を取得するモニター機能(メニューの[デバッグ]→[デバッグ文字列出力を監視] )も実装しています。
『OllyDbg』の改造版とはどのようなものですか?
OllyDbg1.10は複数の『改造版』がネット上で配布されています。これは大抵ネット上で有志が公開したパッチ情報を組み込んだり、ネット上で公開されているOllyDbgプラグインなどを同梱したものです。また、リソースを改変しているケースもありますが、OllyDbgの機能や利便性にはさほど影響のないものが殆どです。さらに、アンチデバッグ対策等、改造版がウリにしている追加機能も、実際はそのような機能を持つOllyDbgプラグインを同梱したり、実行ファイル名を変えただけということが多いようです。既知のプラグインに偽装した、悪意のあるプラグインが同梱される可能性を考慮すれば、ネット上の出所不明な再配布『OllyDbg』の入手はお勧めできません。パッチについては概ね以下のものが組み込まれています。以下のパッチコードによる『OLLYDBG.EXE』の書き換えは、『スペシャルねこまんま57号』の「簡易バイナリファイル書き換え」機能(メニューの[ファイル]→[簡易バイナリファイル書き換え] )で簡単に行うことが可能です。「シンボルサーバー使用可能化」については、下記質問を参照して下さい。
*OllyDbg1.10パッチ例 FILENAME OLLYDBG.EXE *シンボルサーバー使用可能化 00090709: 10 37 0009070A: 12 02 0009070B: 00 03 0009070C: 00 80 000907EC: 74 EB *OutputDebugString関数関連バグ修正 *注意:強引な手法のため一部文字列が崩れます *2バイト文字は考慮されていません 0003094C: 83 E9 0003094D: C4 F3 0003094E: 10 E2 0003094F: 3B 07 00030950: C3 00 000AEC44: 00 51 000AEC45: 00 50 000AEC46: 00 57 000AEC47: 00 8B 000AEC48: 00 7C 000AEC49: 00 24 000AEC4A: 00 0C 000AEC4B: 00 8B 000AEC4C: 00 4C 000AEC4D: 00 24 000AEC4E: 00 14 000AEC4F: 00 B8 000AEC50: 00 25 000AEC54: 00 F2 000AEC55: 00 AE 000AEC56: 00 83 000AEC57: 00 F9 000AEC59: 00 74 000AEC5A: 00 06 000AEC5B: 00 C6 000AEC5C: 00 47 000AEC5D: 00 FF 000AEC5E: 00 20 000AEC5F: 00 EB 000AEC60: 00 F3 000AEC61: 00 5F 000AEC62: 00 58 000AEC63: 00 59 000AEC64: 00 83 000AEC65: 00 C4 000AEC66: 00 10 000AEC67: 00 3B 000AEC68: 00 C3 000AEC69: 00 E9 000AEC6A: 00 E3 000AEC6B: 00 1C 000AEC6C: 00 F8 000AEC6D: 00 FF *ウィンドウクラス名変更 *実行ファイル名変更との併用が効果的 000B6018: 4F 4E 000B6019: 4C 6F 000B601A: 4C 74 000B601B: 59 65 000B601C: 44 70 000B601D: 42 61 000B601E: 47 64なお、シンボルサーバー使用可能化など、環境によっては上記パッチによりOllyDbgの一部の機能が使用不能になることもあります。パッチコード実行の際にはバックアップの作成をお勧めします。
MFCのランタイムDLLがエクスポートする関数の詳細を知りたい
「mfc90u.dll」など「MFCxx.DLL(xはMFCバージョン番号等) 」というファイル名の、MFC(Microsoft Foundation Class)の共有DLLがエクスポートする関数は、そのDLL内では名前が設定されていないため、逆アセンブルコードリスト上ではたいてい関数名が序数(関数に設定された番号)で表示されることになります。この関数にも、一般的なAPI関数のような、実際の機能を表す関数名が設定されています。Visual Studio(有償版)に付属するDEFファイルをテキストエディタで開けば、関数の序数を基にして本来の関数名を確認することができます。このDEFファイルは、Visual Studioインストール先フォルダの以下の場所にあります。
(例)"C:\Software\Visual Studio 9.0\VC\atlmfc\src\mfc\intel\mfc90.def"
なお、Visual Studioの有償版は、Microsoftがネット上で配布している評価版で試用可能です。有償のProfessional版には条件付き無償化版である「Visual Studio Community」もあります。
また、MFCのエクスポート関数名の解析については、以下のソフトの同梱資料が参考になります。
i.j Shell Property Sheets Export/Import 32
i.j Shell Property Sheets Export/Import 32 改造版
海外のソフトウェアを日本語化してみたい
以下の解説は、デバッガを用いたプログラム解析の応用としての日本語化について述べています。プログラム解析とはリンクしない一般的な日本語化では、基本的にバイナリエディタとリソースエディタでの書き換えのみで日本語化を行います。ソフトウェアの日本語化にあたっては、バイナリエディタ、リソースエディタ、さらにプロセスメモリエディタやデバッガも使用することになるため、これら各種解析ツールの使い方を学ぶという点で良い経験になります。加えて、日本語化という形で結実したものが他の方に役立つ点も、有益なことといえるでしょう。ただ、日本語化する対象のソフトウェアは、日本語のものでは同種または同機能を持つソフトウェアが存在しないものにしましょう。私の日本語化経験を含めた、過去の日本語化例を見る限り、日本語のものでおおむね事足りるにもかかわらず、需要を無視して同種の海外のソフトウェアを日本語化しても、さして見向きもされず虚しい結果に終わります。また、対象ソフトウェアがシェアウェアならば、トラブル回避のため、すでに日本の企業が翻訳権を取得し、日本語化したものを販売していないか、事前に十分に調査してください。
実際の日本語化は、基本的に以下の手順で行います。過去の実例を見る限り、日本語化における各種の問題を、一人で全てを解決しようとするより、色々な協力者からアイディアを頂いて解決していくことで、日本語化の完成度が高まることが少なくありません。自分で努力し、かつ「善意による人の意見には耳を傾ける」という姿勢が、日本語化の質を高めるといえます。一方で、(悪意によるものを含む)人の意見に左右され過ぎないよう自分を律することも必要です。
以下のAPI関数の引数など詳細については、「MSDN Library」といったネット上の資料やWindows SDKなどを参照してください。また、使用するツールについては、当Webサイトのツール欄を参照してください。
対象ソフトウェアがパッカーを使用している場合は、作者の意図を尊重して、あえてアンパックをしてまで日本語化せず、まずは作者に多言語対応用DLLの導入など、アンパックなしで日本語化できるよう要望を出すことが望ましいといえます。 なお、使用されているパッカーが可逆性のあるUPXの場合は、アンパックや翻訳パッチも「作者の想定範囲内」と考える向きもあります。次善策としては、後述のプロセスメモリ書き換え型日本語化パッチの作成が挙げられます。
1.バイナリエディタで実行ファイル内データセクションの文字列を書き換え
実行ファイル中のオリジナル言語文字列を、元の文字列サイズ内に収まるよう注意しながら、日本語文字列に書き換えます。事前にANSI・Unicodeといった文字コードの違いや、その文字列終端を意味する00h・0000hを理解する必要があります。ユーザー視点での分かりやすさを基準に、正確な日本語訳と若干の意訳を織り交ぜて使うと、ユーザーに違和感を感じさせない「センスの良い」日本語化になります。
使用されるコンパイラによっては、1つの文字列が共用されるケースが生じることに注意してください。たとえば「Open File」という文字列があり、プログラムの1箇所からはこの文字列そのものを、もう1箇所からはこの文字列中の「File」という文字列を参照するといったケースです。そのため、日本語化の進捗状況に応じて、デバッガあるいはプロセスメモリエディタの逆アセンブラを使い、逆アセンブルコードリスト上での参照文字列一覧を確認されることをお勧めします。
この段階での作業工数削減のため、可能ならば、バイナリエディタでの文字列書き換えを補助するツールの自作をお勧めします。
2.リソースエディタでリソースのフォント名や文字列を書き換え
日本語化に併せて、ダイアログのサイズ、ボタンのサイズや配置、コンボボックスのリスト縦サイズなどで、ユーザーインターフェイスを改良し「使いやすくする」ことにも配慮しましょう。対象ソフトウェアのバージョンアップ時の、リソース書き換え工数削減には、『XN Resource Editor 日本語版』付属の『ResUpdate』が使用可能です。私の場合は、リソース書き換え内容を『eXeScope』用のスクリプト形式で保存しておき、以降のバージョンアップ時のリソース書き換え処理を自動化させています。
参考までに、Microsoft Visual C++ 2008日本語版でコンパイルした、アプリケーションのダイアログ設定フォントは、初期設定では「MS UI Gothic」の9ポイントになっています。
なお、ボタンなどのリソースをリソースセクションに格納せず、プログラムの処理としてボタンなどのコントロールを動的に作成するケースもあります。プログラミングでは、主にダイアログ上で表示されるボタンやエディットボックス、リストボックスなどを「コントロール」といいます。この場合は、CreateWindow関数の第1引数や、CreateWindowEx関数の第2引数で、コントロールの作成を設定する以下の文字列(例)が参照されます。これらのAPI関数呼び出し時の引数で、コントロールの文字列やサイズも設定されます。
BUTTON
COMBOBOX
EDIT
LISTBOX
他
3.プログラム上のフォント作成処理を書き換え
対象アプリケーションが動的にフォントを作成しているならば、デバッガあるいはプロセスメモリエディタの逆アセンブラを使ってその処理を書き換え、アドレス→オフセット変換結果を使って、その変更内容を実行ファイルに適用して日本語文字列対応にします。CreateFont関数やCreateFontIndirect関数呼び出し時の引数が書き換え対象となります。CreateFont関数ならば、第9引数のfdwCharSetを定数SHIFTJIS_CHARSET(0x80)に、第14引数の参照文字列を「MS UI Gothic」など使用状況に応じた適当な日本語フォント名に変更します。CreateFontIndirect関数ならば、第1引数で参照されるLOGFONT構造体の内容に対し、同様の書き換えを行います。
4.プログラム上の動的文字列生成・変更箇所を書き換え
アプリケーションによっては、プログラムの処理で必要となる固定文字列を、プロセスメモリ上に動的に作成・変更して使用することがあります。解析やプログラムコード書き換えに要する作業工数を勘案すれば、このような文字列は「できれば」日本語化するという考えで良いと思われます。
5.プログラム上の文字列変換処理などの修正
散見されるケースとしては、プログラム解析ツールなどのUnicode文字列(バイナリデータ)検索機能で、入力文字列をANSI文字列として認識し、それを内部的にUnicode文字列へ変換している場合に、変換後の文字列のサイズ(バイト数)を、単純に元の文字列のサイズの2倍に固定するケースがあります。例として日本語で「諸行無常」はANSIでもUnicodeでも文字列のサイズは8バイトですが、上記のケースでは文字列サイズを2倍にした16バイトのバイナリデータとして検索することになります(当然検索に失敗する)。これは、特に欧米の1バイト文字を使用している人たちにとって、ANSI文字列をUnicode文字列に変換すれば必ず文字列のサイズは2倍になるという、彼らの「常識」が原因で生じる問題(≠バグ)です。この場合はMultiByteToWideChar関数呼び出し時の処理を観察し、同関数の戻り値(変換後の正しい文字数)を使って、検索対象バイト数を単純に元の文字列サイズの2倍にしている処理を書き換えることになります。ただし、プログラムコードの注入といった、やや煩雑な書き換え処理が必要となるため、可能ならば作者に対応を促すという選択もありえます。この場合は作者側では動作確認ができないため、対応を求めた自分が責任を持って動作確認を行うことになります。
6.パッチの作成
以上の日本語化結果を適用した実行ファイルと、オリジナルの実行ファイルを元に、パッチ作成ツールを使って日本語化パッチを作成します。同梱する説明文書も作成し、日本語化済実行ファイルの動作確認や、パッチのウイルスチェックを行ったうえで配布します。
(参考)プロセスメモリ書き換えによる日本語化
もし、日本語化対象のソフトウェアが解析・改造対策を施している場合や、実行ファイルのデジタル署名を保持させたい場合は、そのプロセスメモリを書き換えるタイプの日本語化パッチを製作するという手段もあります。ただし、すでに表示済みのコントロールの文字列などは、単純なプロセスメモリ書き換えでは日本語化できないといった制約があります。そのため、SetWindowText関数呼び出しや、WM_SETTEXT メッセージ送信で、プログラムの処理としての動的な文字列書き換えが必要となるケースもあります。あるいは、対象実行ファイル起動時にエントリーポイントで停止させ(参照)、その状態でプロセスメモリ上のリソースデータを書き換えます。
また、この種の日本語化には、ローダー兼プロセスメモリパッチャーを製作する、プログラミングの知識と開発環境が必要となります。プロセスメモリへのパッチ当てだけならば、『スペシャルねこまんま57号』の 「起動時改造コード自動実行」機能( 詳細はヘルプを参照してください )を使用すれば、『スペシャルねこまんま57号』を起動するだけで、用意しておいた日本語化用の改造コード群を簡単に自動実行できます。
APIフックに用いられるプロセス注入用DLLを検出したい
自分のプロセスに注入された、ユーザーモードでのAPIフック用DLLモジュールの検出は、基本的に特定モジュール名を元に行います。 一般的に、APIフックに用いられるDLLはファイル名が固定になっているケースが多いため、たいていはモジュールのファイル名を元に、使用されるであろう特定ツールからのAPIフックを検出可能です。 用途として、ゲームならば「Speed Hack Tool」の類、シェアウェアならば「Run As Date」系、他にはユーザーモードで動作する各種モニタリングツールの検出が挙げられます。この場合は、GetModuleHandle関数を使用して、その戻り値で特定DLLモジュールの有無を判断します。参考までに、特定DLLモジュールの有無のチェックには、Tool Help APIのModule32Next関数や、Process status API(PSAPI)のEnumProcessModules関数・GetModuleFileNameEx関数も使用可能です。ネイティブAPIのNtQueryVirtualMemory関数を使用し、「MemorySectionName」として自分のプロセスにロードされたDLLモジュール(のパス)を列挙することもできます。API関数を使用せずに、PEBからプロセスメモリ上にあるモジュールのリストを辿ることも可能です(参考)。
また、CreateRemoteThread関数などを用いた、外部ツールからのDLL注入(ロード)を検出するために、ntdll.dllのエクスポート関数であるLdrLoadDll関数を自分自身でフックする方法もあります。さらに、セキュリティソフトが原因でLdrLoadDll関数へのフックが失敗することもあるため、必要に応じてLoadLibraryEx関数を自前でフックします。ただし、この手法では、APIフック対象プロセスを、起動時にエントリーポイントでいったん停止させてから、DLL注入とAPIフックを行うツールには対処できません。『うさみみハリケーン』では実行ファイルを指定して起動する際に、エントリーポイントで実行を一時停止させることができます(メニューの[ファイル]→[ファイルを指定して実行])。
近年では、APIフックを行うツールが、モジュール一覧からAPIフックを検出されること等に対処するため、APIフック対象プロセスのプロセスメモリ上にある、ロードされたモジュールのリストを書き換えて、APIフックに用いるDLLモジュールを隠蔽する手法が広まりつつあります。この隠蔽手法では、対象プロセスのPEBからPEB_LDR_DATA構造体をたどり、同構造体の3つのLIST_ENTRYでFlink・Blinkを修正して、APIフック用DLLモジュールをリンクから除外します。ただし、これはあくまでモジュールのリストの改変であり、プロセスメモリ上のモジュール自体が隠蔽される訳ではありません。つまり、この隠蔽手法が使用されても、VirtualQuery関数を使用して64KB境界上にあるタイプがMEM_IMAGEのページでPEヘッダ取得後、リソースセクションのアドレスとサイズを取得してから特定文字列検索などといった、プロセスメモリの精査により、APIフック用DLLモジュールを検出できる余地はあります。
なお、APIフックにあたって、DLLモジュールの注入が必須となる訳ではありません。実行属性を持つプロセスメモリ上の任意のメモリエリアにプログラムコードを書き込み、IATやAPI関数のエントリからそのプログラムコードにジャンプさせることで、注入DLLを用いるAPIフックと同様の処理を行わせることが可能です。APIフック時の取得データをアウトプットする場合は、API関数呼び出し時の引数あるいは呼び出し後の戻り値を、wsprintf関数などで文字列データに変換し、OutputDebugString関数で出力します。API関数の呼び出しは関数エントリのアドレスを直接CALLします。このようなAPIフックについては、『うさみみハリケーン』の「パラサイトルーチン作成」機能(メニューの[デバッグ]→[パラサイトルーチン作成] )を参照してください。また、この出力されたデバッグ文字列は、『うさみみハリケーン』の全プロセス用デバッグ文字列モニター機能( メニューの[デバッグ]→[デバッグ文字列出力を監視] )を使えば、対象プロセスへのアタッチなしに取得可能です。
逆アセンブルが正常に行えず、しかもエントリーポイントがNOP命令というソフトウェアがあるのですが。
単純な偽装済UPXでした(実物で確認)。エントリーポイントを1バイト前にずらしてNOP命令から開始するのは、『UMPE』や『PEiD』等パッカーの種類を特定する特殊なソフトウェアにより、エントリーポイントを基点とするバイナリパターンを元に、UPX圧縮と判定されるのを避けるためです。UPXで自作のソフトウェアの実行ファイルを圧縮し、デバッガで起動してエントリーポイントから展開ルーチンを抜けて、本来のエントリーポイントに至るまでの処理を考察されることをお奨めします。 また、オリジナルとUPX圧縮後の実行ファイルでPEヘッダの内容を比較してみて下さい。
デバッガなどのプログラム解析関連ツールを自作してみたい
デバッガを含むプログラム解析関連ツールを自作して無償で公開するのは、プログラム解析の振興だけにとどまらず、自己のプログラミングや解析のスキルアップという面からみても素晴らしいことです。しかし、『OllyDbg』のような優秀なデバッガがある以上、重複する機能を持つデバッガの自作よりは、『OllyDbg』に無い機能を実装した『OllyDbg』プラグインやプログラム解析関連ツールなどを自作する方が、そのプラグインやツールのユーザーにとって、より解析における利便性を向上させたりアプローチの可能性を広げるという点で有益と考えられます。 『うさみみハリケーン』のデバッガが、スレッド毎のブレークポイント設定機能など『OllyDbg』に無い機能を重視して実装しているのは上記の考えによるものです。『OllyDbg』プラグインの作成については、優れたプラグイン製作者であるDokoDon氏が『クラッキングバイブル』 でその手法を解説されていますので、参照されることをお勧めします。
プログラム解析関連ツールの自作については、Windows SDKやネット上の検索に加え、参考図書、プログラミング系サイトの資料も役立ちます。プログラム解析関連ツールの自作で参考になるサイトは、当サイトのメインページでリンクしていますが、それ以外では以下などが挙げられます。また、ロシア語や中国語のサイトに有用な解説がみつかることもありますので、Googleなどで検索対象を日本語・英語のサイトに限定しないことをお勧めします。
CodeGuru
http://www.codeguru.com/
The Code Project
http://www.codeproject.com/
Google ソースコード検索
http://code.google.com/
なお、プラグインやツールの製作にあたっては、まずそれらが必要となる局面や需要を十分に検討した上でプログラミングを進めて下さい。最初にコンセプトを明確にしておかないと、実装する機能などで迷いが生じ、プログラミングが迷走することになりかねません。また、ソフトウェア製作一般にいえることですが、『ユーザーに目を向けて』開発とサポートを行って下さい。ユーザーが喜ぶものを、ユーザーの役に立つものを作るという「もの作りの基本」を忘れると、ユーザーから目を向けられなくなります。
プロセスメモリ上のモジュールエリアを実行ファイルとしてダンプ(ファイルに保存)しても、正常に動作しないのは何故ですか?
プロセスメモリ上にロードされた実行ファイルと、元のディスクイメージ上の実行ファイルは構造が異なるためです。当サイトのチュートリアル「オフセット−アドレス変換の原理とCDチェック解除への応用」で述べているように、PEヘッダ内のセクション情報にはメモリイメージ上での設定とディスクイメージ上での設定両方が含まれており、単純にプロセスメモリ上からダンプしたものでは、セクション情報に齟齬が生じるため修正が必要です。また、UPX等による実行ファイルの圧縮やその他の各種パッカーによる実行ファイルの暗号化等で実行ファイルに操作が加えられている場合は、他にも修正しなければならない箇所があります。なお、プログラムとしては自分がオリジナルかダンプされたものか判別するのは容易であり、ダンプした実行ファイルを起動すると警告を表示するソフトウェアも実在します。例:UPXで圧縮された実行ファイルをプロセスメモリ上からダンプした場合は、以下の操作で正常に動作する実行ファイルが得られます。このような解凍あるいは復号化操作は、リバースエンジニアの間ではどちらも「アンパック」と呼ばれています。
1.エントリーポイントを本来のもの(OEP:オリジナルエントリーポイント)に修正
2.セクション情報(Raw Offset等)の修正
3.IAT(インポートアドレステーブル)の再構築
4.リソースディレクトリの再構成(必須ではない)
5.「Base of Code」の修正(動作上必須ではない)
『うさみみハリケーン』および同梱PEダンパー兼PEエディタ「UMPE」では、モジュールのダンプ時に「pe_unmapper」を使って自動的にセクション情報の修正等を行います。これによりダンプしたモジュールをPEファイルとしてそのまま実行可能にしたり、解析を容易にすることができます。ただしこの修正にIATの再構築は含まれません。
なお、他人が製作したソフトウェアの実行ファイルのアンパックは、公益を目的とする脆弱性の解析や、マルウェアの解析および、日本語化目的等の特殊なケース以外は、著作権法や不正競争防止法に抵触するといわれていますので注意して下さい。
解析超初心者がデバッガで解析対象を起動する前に行うべきことは?
以下は複数のリバースエンジニアの方々から頂いた意見をまとめたものです。質問で「起動前」となっているので、解析対象の実際の動作を見ないとアプローチを想定できないケースは考慮していません。以下のような解析の方向性を探るための、本格的な解析の前段階にあたるアプローチは、「表層解析」と呼ばれることがあります。1.ウイルスチェック(ウイルス対策ソフトやVirusTotalなどを使用)
2.ネット上での検索結果や、ヘルプ等の添付文書に目を通して、解析に役立ちそうな情報はないか確認
3-1.解析対象を『うさみみハリケーン』に同梱しているPEエディタ『UMPE』で開いて、セクション名、インポートDLL、使用API関数、PEiDの出力結果、エントリーポイント等のPEファイル情報取得により、解析対象の特徴的な挙動(ふるまい)および、コンパイラやパッカー使用の有無などを推測
3-2.解析対象とその付属ファイルに対して、『うさみみハリケーン』に同梱している汎用ファイルアナライザ『青い空を見上げればいつもそこに白い猫』の「YARAルールでスキャン機能」や「文字コード別文字列抽出機能」などを使って、参照文字列、使用される各種アルゴリズム、各種解析対策、その他挙動に関する情報といった付随情報を取得
4.PCゲームの場合はリソースエディタでリソースも確認してデバッグモード等の有無を判断(PCゲームに限らずリソースにEXE・DLLファイルを格納するケースもある)
5.以上と既存のチュートリアル等を元にどのようなアプローチを行うか想定
6.想定したアプローチにおいて、API関数等の必要な知識が不足している場合はあらかじめ資料で確認しておく
デバッガで解析対象を起動後に場当たり的なアプローチを行うのではなく、あらかじめ解析対象について考察することが解析スキルを向上させる上で重要です。また、既存のチュートリアルで解説されている手法を漫然となぞるのではなく、解析中も常にアプローチのポイントとなる点を自分なりに考えることにより、必要最小限の手数で効果的な解析を行うための解析センスを磨くことが可能です。
△操作関連
『OllyDbg』の操作方法等を解説した参考書はありますか?
プログラム解析の解説書で、OllyDbgについての解説があるものとしては以下が挙げられます。いずれもOllyDbgを使いこなす上で必要となるアセンブリ言語やAPI関数および基本的なアプローチ方法等、プログラム解析の基本事項も解説しており、有用な解説書と言えます。・デバッガによるx86プログラム解析入門 【x64対応版】
・解析魔法少女美咲ちゃん マジカル・オープン!
・クラッカー・プログラム大全
・クラッキングバイブル
『OllyDbg』から解析対象を簡単に起動する方法を知りたい。
メニューから「Options」 (オプション) → 「Add to Explorer」 (エクスプローラメニューに追加) を使用してください。また、バイナリエディタやリソースエディタ他の、各種解析ツールなどについても、エクスプローラ上での実行ファイル右クリックで表示されるポップアップメニューに同ツールを登録する、上記同様のレジストリ操作を行うことをお奨めします。このレジストリ操作は、当Webサイトのツール欄で配布している、『PeRdr』用の「エクスプローラへの登録用レジストリファイル(日本語説明つき)」が参考になります。
操作上で特に覚えておくと役立つポイントはありますか?
『OllyDbg』では、マウスで各ウィンドウ上の数値や範囲等を選択すると、そこから右クリックでポップアップメニューを呼び出し、選択内容に応じた色々な機能を使うことができます。操作に慣れないうちは色々な箇所で右クリックして、どのような機能があるか確認しておくことをお奨めします。少し分かり辛いのですが、レジスタウィンドウ(右上ウィンドウ)では汎用レジスタだけでなくEFLAGSレジスタやゼロフラグ等の各ステータスフラグも変更可能です。また、ウィンドウによってはドラッグまたは「Shift」キーを押しながら範囲を選択することで複数列等範囲選択が可能で、レジスタウィンドウ(右上ウィンドウ)ではドラッグで表示内容の移動を行うこともできます。
<参考>主なショートカットキー(特に左上ウィンドウ用に使用するもの)
Enter:選択されているコマンドを表示リストに追加(選択されているのがJUMP命令やCALL命令の場合は対象アドレス該当箇所を表示)
-/+:表示リスト中の前/次のアドレス該当箇所を表示
F2:ブレークポイント設定/解除
F7:詳細ステップ実行(ステップイン:サブルーチンに入る)
F8:ステップ実行(ステップオーバー:サブルーチンは実行して乗り越える)
F9:デバッギー実行
F12:デバッギー実行一時停止
Ctrl+F9:リターンまで実行
Alt+F9:ユーザーコードまで実行(システムDLL内から抜ける際等に使用)
Space:アセンブル
Ctrl+E:バイナリデータの編集
Ctrl+G:アドレスを指定して移動(表示位置変更)
ESC:詳細自動ステップ実行の停止
『OllyDbg』プラグインの入手先は?
以下のページを参照して下さい。最初は「OllyDump」、「OllyScript」などの基本的なプラグインを導入して使用法を理解し、その後必要に応じて少しずつ追加していくと良いでしょう。OpenRCE Hosted Downloads: OllyDbg Plugins
API関数呼び出しへのブレークポイント設定の仕方を知りたい。
特定のコードにブレークポイントを設定する場合は、メインウィンドウの左上ウィンドウ上で逆アセンブルコードリストから目的のコードを選択してF2キーですが、API関数へのブレークポイントの場合は以下の手法を用います。まず、メインウィンドウの左上ウィンドウ上で右クリック→「Search for」(検索)→「Name (label) in current module」(ラベル名(現在のモジュール内))で、デバッギーが使用する関数一覧を表示させます(「Ctrl」キー+「N」キーでも可)。ここで、関数一覧表示ウィンドウの各カラムをクリックあるいは右クリック→「Sort by」(整列)メニューを使うと目的の関数を探し易くなります。また、標準の整列順序を標準メニューの「Options」(オプション)→「Debugging options」(解析詳細設定)→「Addresses」(アドレス)タブで設定できます。
次に、目的の関数を選択し右クリックから「Set breakpoint on every reference」(全ての参照にブレークポイントをセット)で、目的の関数を呼び出すコードに一括してブレークポイント設定を行います。ただし、この方法ではインポートセクションへのジャンプ命令(例:「JMP DWORD PTR DS:[<USER32.DialogBoxParamA>]」)も参照箇所に含まれますので、必要に応じてこのジャンプ命令へのブレークポイントを解除して下さい。
個々のAPI関数呼び出し箇所にブレークポイントを設定(解除)する場合は、目的の関数を選択して右クリック→「Find references to import」(インポート関数の一覧表示)で関数呼び出し箇所のコード一覧を表示させ、各コードをマウスや上下カーソルキーで選択してF2キーでブレークポイント設定(解除)を行います。
条件付きブレークポイントを設定してみたい。
レジスタや参照アドレスの値等が条件の場合は、メインウィンドウの左上ウィンドウ上で、目的のコードを選択してから「Shift」キー+F2キーでブレークする条件を設定します。条件式のフォーマットはヘルプを参照して下さい。例としては下記。
EAX==64
DWORD PTR [432100]<=(EAX+270F)
また、「Shift」キー+F4キーでログ付き条件ブレークポイントを設定できます。ここでの式の例としては上記条件式の左辺(レジスタや特定アドレスの格納値)等です。これにより、条件判定時の式の値がログウィンドウに記録されます。ログウィンドウは「Alt」キー+「L」キーで呼び出せます。
ウィンドウメッセージにブレークポイントを設定してみたい。
基本的なウィンドウメッセージへのブレークポイント設定は、標準メニューの「View」(表示)→「Windows」(ウィンドウ)でウィンドウのリストを表示してから、目的のウィンドウを選択して右クリック→「Message breakpoint on WinProc」(メッセージブレークポイントをセット->WinProc)で詳細設定ダイアログを呼び出してブレークポイントを設定します。NT系OSでは、標準メニューの「Options」(オプション)→「Debugging options」(解析詳細設定)→「Security」(セキュリティ)タブの「Allow code injection to get address of WinProc」(WinProcのアドレス取得の為コードの注入を許可する)で正確なウィンドウプロシージャを認識することが可能です。ただし、この手法は安全性と確実性に問題がありますので注意して下さい。このオプションを使用した場合は、デバッギーをF12キー等で停止させた状態でのみウィンドウプロシージャにメッセージブレークポイントを設定して下さい。
上記の手法で上手くメッセージブレークポイントを設定できない場合は手動で行います。この場合は、ウィンドウメッセージを認識するウィンドウプロシージャの、先頭コードか呼び出し元コードに対し条件付きブレークポイントを設定します。同プロシージャのアドレスはRegisterClass関数やダイアログ生成関数の引数で確認可能です。また、このアドレスは、ウィンドウプロシージャで処理しないウィンドウメッセージをシステム側で処理させるための、DefWindowProc関数の呼び出し箇所から探し出すことも可能です。DefWindowProc関数は通常ウィンドウプロシージャの最後に近い部分で呼び出されます。なお、ダイアログベースのアプリケーションでは通常プログラムの実行処理としてDefWindowProc関数を呼び出すことはありません。
WM_LBUTTONDBLCLKの場合の条件式は下記。
MSG==0203
目的のプロシージャ内でウィンドウメッセージの値が格納されるアドレスとメッセージ名を指定することもできます。
[EBP+C]==WM_LBUTTONDBLCLK
なお、目的のウィンドウに送られるウィンドウメッセージの監視を行いたい場合は、メッセージループのTranslateMessage関数呼び出し箇所にログ付き条件ブレークポイント(式:MSG)を設定してブレーク結果をログウィンドウに表示させることで、メッセージモニターの様に処理させることが可能です。ただし、ダイアログベースのソフトウェアは、動作上コード中にメッセージループが必須ではありません。
<参考>C言語でのメッセージループ例。ウィンドウメッセージやこれらのAPI関数とMSG構造体について、「MSDN Library」といったネット上の資料やWindows SDKに目を通すことをお奨めします。また、当サイトで公開している『改造初心者向け練習用プログラム』のソースコードも参考になると思われます。
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
API関数呼び出し箇所ではない任意の複数コードに一括してブレークポイントを設定したい。
予めその様なブレークポイントを設定したいコードにユーザー定義ラベルを設定しておいて下さい。それからメインウィンドウの左上ウィンドウ上で右クリック→「Search for」(検索)→「User-defined label」(ユーザ定義ラベル)でラベル一覧ウィンドウを表示し、そのウィンドウ上で右クリック→「Set breakpoint on every command」(全てのコマンドにブレークポイントをセット)です。これにより一括ブレークポイント設定が可能になります。リアルタイムアセンブラで注意すべきことは?
PCゲームでのパラサイトルーチン埋め込み等、リアルタイムアセンブラによりコードを動的に変更/追加する場合は、汎用レジスタの値が変更/追加箇所の前後で保持されないと不具合を招くケースがあります(特にESI及びEDIレジスタ)。この場合はPUSHAD命令とPOPAD命令を変更/追加箇所の先頭と終端に用いれば最小限のコードで対処可能です。また、EFLAGSレジスタの内容を保持したい場合はPUSHFD命令とPOPFD命令を用います。また、Windows XP Service Pack 2で実装されるデータ実行防止機能により、コードが実行されるメモリエリアは、必ずアクセス属性として実行属性が必要になります(参照)。
リアルタイムアセンブラでAPI関数呼び出しをコーディングする場合は、「call kernel32.ExitProcess」というように関数名を入力します。関数名は大文字/小文字も正確に入力して下さい。
コードの動的変更中に『OllyDbg』が誤った逆アセンブルコードを表示した場合は、「Ctrl」キー+「A」キーでコードを再解析させると対処できます。
メモリ内容の内容変動箇所を調べるには?
まず標準メニューから「View」(表示)→「Memory」(メモリ)でメモリーマップを表示します。次に内容変動を調べたいエリアを選択・右クリックから「Dump」(Dump)で同エリアのメモリ内容をダンプし、右クリックで表示されるポップアップメニューから「Backup」(BKUP)→「Create backup」(BKUP 作成)を選択します。これによりそのエリアのメモリ内容に変動があれば、その変動箇所がハイライト表示になります。変動箇所の検索は同様にポップアップメニューから「Search for」(検索)→「Modified data」(修正済データ)で検索可能です。
なお、上の機能では値増減等の変動内容詳細を区別できず、また、絞り込み検索機能も実装されていません。そのため、PCゲーム解析時等のプロセスメモリ上の変動検索には、『うさみみハリケーン』や『スペシャルねこまんま57号』を使用されることをお奨めします。
プログラムが参照している文字列の一覧表示機能を使いたい。
メインウィンドウの左上ウィンドウ上で右クリックし、ポップアップメニューから「Search for」(検索)→「All referenced text strings」(全ての参照文字列)を選択して下さい。事前に参照文字列の検索を日本語文字列対応可能にして下さい(当サイト配布の日本語化パッチ添付文書を参照願います)。特定文字列の参照コード一覧表示機能を使いたい。
デフォルトでデータセクションの内容が表示されるメインウィンドウの左下ウィンドウで、目的の文字列を探し出し(ポップアップメニューから検索可能)、その文字列の先頭アドレスに当たるバイナリデータを選択・右クリックします。これにより表示されたポップアップメニューから「Find references」(参照を検索)(「Ctrl」キー+「R」キーでも可)を選択して下さい。特定のコードにワンタッチで戻る方法が知りたい。
目的のコードを右クリックから「New origin here」(移動元にセット)を設定しておくと、移動後に右クリックから「Go to」(移動)→「Origin」(移動元)で元のコードへワンタッチで戻ってくることができます。なお、ここでいう移動はコードを実行させている訳ではなく、あくまで逆アセンブルコードリスト上での表示箇所を変更しているだけです。また、目的のコードを「Enter」キーで表示リストに登録しておいて「+」や「-」キー等で移動する方法や、Bookmarkプラグインを利用するという選択肢もあります。コール命令からサブルーチン及び、ジャンプ命令からジャンプ先にワンタッチで移動する方法はありませんか?
コールまたはジャンプ命令のコードを選択して、「Enter」キーです。F7キーでサブルーチンにステップインした後に、簡単にサブルーチンを抜ける方法はありませんか?
「Ctrl」キー+F9キーでリターンまで実行させるか、予めCALL命令によるプロシージャ呼び出し部の次のコードにブレークポイントを設定しておきます。システムDLL等、デバッギー実行ファイル以外のコードを表示した場合の対処法は?
上記の手法で表示位置を変更あるいは、「Alt」キー+F9キーでユーザーコード(デバッギーのコード)まで実行させます。ユーザーコードまで実行させる場合は、いったんデバッギーに制御を戻して何らかの処理を行わせることが必要なケースもあります。また、左上ウィンドウ上で右クリックから「View」(表示)で任意のモジュールにワンタッチで表示を変更することも可能です。 他には、メインメニューの「View」(表示)→「Executable modules」(実行モジュール)で実行モジュール一覧を表示させ、デバッギーのモジュールを選択・右クリックで表示されるポップアップメニューから「View code in CPU」(CPUコード表示)でも対処できます。逆アセンブルコードウィンドウ(左上ウィンドウ)でコード変更後の実行ファイルの保存が上手くいきません。
右クリックで「Copy to executable 」(実行ファイルへコピー)からコード変更箇所のうち実行ファイルに反映させる箇所を全てか一部か特定して、更にダンプウィンドウ上で右クリックして「Save file」(ファイル保存)で保存します。なお、この保存先ファイルは必ずオリジナルとは別名のものにして、オリジナルの実行ファイルには一切変更を加えないで下さい。「Just-in-time debugging」(ジャスト・イン・タイム デバッグ)とは何ですか?
簡単に言えば、アプリケーションが何らかの問題によりクラッシュした場合に、デバッガでそのアプリケーションにアタッチして問題箇所の発見・解決を促す機能です。例えば『VC』(マイクロソフト社のVisual C++)等コンパイラをインストールすると、そのコンパイラの付属デバッガが「Just-in-time debugging」用デバッガとしてレジストリに登録されることもあります。プログラミングをされる方はもちろん、プログラミングをされない方も『OllyDbg』を「Just-in-time debugging」用デバッガとして登録しておくことをお奨めします。アプリケーションの不具合を作者に知らせる際に役立つことがあります。
『OllyDbg』でのソースレベルデバッグが上手くいきません
ソースレベルデバッグ動作確認環境:Visual C++ 6.0
Visual C++ 2008
OllyDbgでソースレベルデバッグを行う場合は、事前にコンパイラでリンク時の設定を変更して、「デバッグ情報の生成」でpdb形式のデバッグ情報ファイルを出力させるようにしてからコンパイルしておきます。これでデバッギーを起動し、「View」(表示)→「Source files」(ソースファイル)で表示されるソースファイルリスト上で目的のソースファイルを選択・ダブルクリックすると、そのソースファイル内のソースコードが別ウィンドウで表示されます。表示されたソースコードの行番号の左に「>」がある行では、行選択→右クリックで表示されるポップアップメニューから各種ブレークポイントを設定可能です。さらに、同ポップアップメニューから「View in Disassembler」(逆アセンブラ画面表示)で、ソースコード上の選択行に対応する逆アセンブルコードをCPUウィンドウ内の左上ウィンドウの逆アセンブルコードリスト上で選択・ハイライト表示にします。逆に、逆アセンブルコードリスト上でブレークポイントを設定すると、表示されたソースコードの該当位置の行番号がハイライト表示になります。ソースレベルデバッグ中は、ブレークポイントでのブレーク時のログにソースコードの情報(関数名と関数開始アドレスからのオフセット)が併せて表示されます。
なお、このソースレベルデバッグは、コンパイラにVCを用いる場合では、OllyDbgがPDBファイルのデバッグ情報を元にデバッグシンボルを利用するために、コンパイルをデバッグビルドとして行う必要があります。
OllyDbgはソースレベルデバッグをデバッグヘルプ系API関数によって実現していますが、このAPI関数はバージョン差異を含む全てのコンパイラが生成するデバッグシンボルに対応している訳ではないため、ソースレベルデバッグができないケースもあります。その場合はOllyDbg同梱のDBGHELP.DLLを、使用しているコンパイラが出力するデバッグシンボルに対応するバージョンのものを入手して置き換えることで対処可能と考えられます(DBGHELP.DLLは再配布可能コンポーネント)。ケースによってはMicrosoftが無償配布している『Debugging Tools for Windows』から同DLLを抽出する必要があります。なお、DBGHELP.DLLはMicrosoft製以外のコンパイラで出力されたデバッグシンボルにも対応しているとは限りませんので注意して下さい。
また、ユーザーモードで動作するアプリケーションのデバッグにおいては、大抵システムデバッグシンボルは不要ですが、OllyDbgでシステムデバッグシンボルを利用したい場合は、以下の操作を行ってシンボルサーバーを使えるようにしておいて下さい。ただし、以下の操作結果に伴うOllyDbgの動作は保証致しかねます。
1. 『Debugging Tools for Windows』をインストールし、『dbghelp.dll』、『symsrv.dll』および『srcsrv.dll』をOllyDbgのフォルダにコピーします。
2. 実行ファイル「OLLYDBG.EXE」に以下の書き換えを行います。このパッチコードによる書き換えは、『スペシャルねこまんま57号』でメニューの「ファイル」から「簡易バイナリファイル書き換え」で簡単に行うことが可能です。
*OllyDbg1.10用 FILENAME OLLYDBG.EXE 00090709: 10 37 0009070A: 12 02 0009070B: 00 03 0009070C: 00 80 000907EC: 74 EB3. 環境変数「_NT_SYMBOL_PATH」でシステムデバッグシンボルのフォルダが設定されているか確認します。
(例)_NT_SYMBOL_PATH=symsrv*symsrv.dll*c:\symbols*http://msdl.microsoft.com /download/symbols
△ゲーム解析関連
『OllyDbg』などを使ったPCゲーム解析のチュートリアルはありませんか?
もし入手可能ならば、ゲーム誌『ExGAME』Vol6にBlueAshさんが詳細なチュートリアルを寄稿されていますのでご覧下さい。API関数へのブレークポイント、ステップ実行、全セクションからの文字列検索や注意事項他、基本的な操作方法だけでなく、アプローチに対する考え方が詳しく解説されており、必見だと思います。ちなみに、このチュートリアルが日本人向けに公開された最初の『OllyDbg』を用いた解析チュートリアルです。また、当サイトや解説書『デバッガによるx86プログラム解析入門 【x64対応版】』および、当サイトリンク先の「DANAの部屋」にもチュートリアルがありますので、ご覧になることをお奨めします。
近年では、文章としての解析チュートリアルではなく、動画による解析チュートリアルを製作してYouTubeなどにアップロードするケースも増えています。特にメジャーなゲームならば、必要に応じて解析関連動画がないか検索してみることをお勧めします。
チュートリアルに動画を使えば、アプローチにおける操作手順解説などをより分かりやすくすることが可能であり、特に超初心者向けの解説に有用です。チュートリアルを製作して他の方に解説し、情報の共有を行いたいと考えている方は、動画という選択肢も視野に入れておくと良いでしょう。
<参考>動画チュートリアル例:
PCゲーム改造講座第1回「チートを探ろう!」編
『OllyDbg』でのメモリ書き換え操作を記録・再現するには?
『うさみみハリケーン』や『スペシャルねこまんま57号』のデバッガに付属するAPIトレース機能を使用して下さい。この機能で『OllyDbg』のWriteProcessMemory関数呼び出しをトレースすることにより、『OllyDbg』が行った全てのプロセスメモリ書き換え操作を、『うさみみハリケーン』や『スペシャルねこまんま57号』用の改造コードとして出力することができます。なお、このAPIトレース機能は、『OllyDbg』以外のWriteProcessMemory関数を用いるソフトウェアにも使用可能です。海外のゲーム専門リバースエンジニアの『OllyDbg』に対する評価はどうでしょうか?
プログラム解析ツールとしてある程度の評価は得ています。しかし、海外で人気があるFPSゲーム等は、『OllyDbg』が対応できないDirectX排他モードで動作するものも多いため、アダルトゲームや同人ゲームなどの、『OllyDbg』で解析可能なゲームが潤沢である日本ほどは評価されていないように見受けられます。なお、現在ではDirectX初期化処理のフック等により強制ウィンドウモード化を実現するソフトウェアも公開されているため、『OllyDbg』はメインの解析用デバッガとして十分使用可能です。また、 DirectX排他モードのゲームでも、エントリーポイントからウィンドウ作成の間でコードの実行を停止させた状態ならば、『OllyDbg』単体でも各種解析が可能です。
△その他
自作ツールがマルウェアに流用されたら罪に問われますか?
現在の日本の法制度上では、正当な目的で作成したデバッガ等の自作ツールが、マルウェアあるいは、マルウェアの補助ツールとして配布されても、罪に問われることはありません。参考:いわゆるコンピュータ・ウイルスに関する罪について(法務省の見解)
実際にマルウェアに流用されたケースとしては、Sysinternalsの『PsTools』が挙げられます。この例を見る限り、マルウェアに流用されることがあっても、日本人に向けた同ツールの公開中止には至っておらず、また、そのことが何らかの問題を生じているようには見えません。そもそも、ツールの仕様にもよりますが、悪意を持って流用されることを完璧に防ぐというのは無理があります。
法的問題に関連して、日本での現状を見る限り、自作の「汎用」プログラム解析・改造ツールが、詐欺、不正アクセス、著作権侵害、威力業務妨害ほか各種業務妨害、電磁的記録不正作出・供用、不正競争防止法に抵触といった事例に流用されても、不法行為への使用禁止条項や免責条項を含む使用許諾契約書を備えたツールの作者が、責任を問われることはほとんど無いといえます。ただし、社会的影響の程度によっては、責任を問われる可能性が生じます。
一方、特定の有償ソフトウェア・ゲーム「専用」のプログラム改造ツールを配布することは、訴訟コスト等からみて危険性は低いものの、常に法的リスクに晒されているといえます。特に、近年改正が進んでいる不正競争防止法に抵触するようなツールの配布は、控えたほうがよいと思われます。
なお、万一の際には、無料あるいは安価な国選弁護人ではなく、私選弁護人を雇うことを強くお勧めします。国選弁護人の中には、弁護士としての役目や責任を果たさない者もいます。私の経験から言えば、ここで弁護士に払う金を惜しむと、一生後悔するような裁判内容や判決になりかねません。