wivern ロゴ

サイバーセキュリティ研究所

サイバーセキュリティを中心に、軍事、防犯、サーバーの管理と監視、その他、最新技術を研究しています。


「たのしいバイナリの歩き方」勉強メモ#4

処理を自在に実行させるプログラミングのテクニック

「たのしいバイナリの歩き方」#4

たのしいバイナリの歩き方」の勉強メモ第4弾です。
実行環境は、Windows7(64bit版)です。


4.1 デバッガを自作して動作を理解する

CreateProcess 関数で、デバッグ対象のプロセス(デバッギ)を立ち上げる。
その際に、DEBUG_PROCESS か DEBUG_ONLY_THIS_PROCESS フラグを指定すると、立ち上がったプロセス(デバッギ)の例外などをデバッガ側で補足できる。

 DEBUG_PROCESS:デバッギが生成した子プロセス、孫プロセスもデバッグ対象にする。
 DEBUG_ONLY_THIS_PROCESS:最初に CreateProcess したプロセスのみがデバッグ対象となる。

 [動作イメージ]
 ①CREATE_SUSPENDED は、プロセスをサスペンド状態で起動するので、このフラグが指定されていると、CreateProcess 関数呼び出し後の時点で、全スレッドは止まっている。
 ②実行はされないが、実行ファイルはメモリ上に展開されているので、実行前にデバッギのデータを書き換えたい場合は、このタイミングで行う。
 ③ResumeThread を呼ぶと、デバッギのスレッドが動き出す。
 ④デバッグイベントを、WaitForDebugEvent 関数で受け取る。
 ⑤処理がデバッガにあるときは、デバッギは停止している。
 ⑥実行再開は、ContinueDebugEvent 関数を呼び出す。→④へ

デバッグイベント 意 味
EXCEPTION_DEBUG_EVENT 例外が発生した
CREATE_THREAD_DEBUG_EVENT スレッドが生成された
CREATE_PROCESS_DEBUG_EVENT プロセスが生成された
EXIT_THREAD_DEBUG_EVENT スレッドが終了した
EXIT_PROCESS_DEBUG_EVENT プロセスが終了した
LOAD_DLL_DEBUG_EVENT DLL がロードされた
UNLOAD_DLL_DEBUG_EVENT DLL がアンロードされた
OUTPUT_DEBUG_STRING_EVENT OutputDebugString 関数が呼び出された
RIP_EVENT システムデバッグエラーが発生した
例外が発生したら、発生した場所、そのときのレジスタの値を表示するようにデバッガを改良する。
例外を発生させた命令を表示するために逆アセンブラを実装する。

udis86:オープンソースの逆アセンブラで GitHub で公開されている。
https://github.com/vmt/udis86

筆者が fork して VisualStudio2012 でビルドしたもの(Windows版バイナリ)
https://github.com/kenjiaiko/udis86

Windows では、ユーザーに権限がなければ OpenProcess は失敗するが、プロセスハンドルさえ得られれば、そのプロセスのメモリ空間には自由に読み書きできる。

[他プロセスへアクセスするための関数]
OpenProcess
http://msdn.microsoft.com/ja-jp/library/cc429278.aspx

ReadProcessMemory
http://msdn.microsoft.com/ja-jp/library/cc429006.aspx
例外が発生した命令を取り出すために使用

WriteProcessMemory
http://msdn.microsoft.com/ja-jp/library/cc429067.aspx

OpenThread
http://msdn.microsoft.com/ja-jp/library/cc429286.aspx
スレッドを開く

GetThreadContext
http://msdn.microsoft.com/ja-jp/library/cc428970.aspx
コンテキストを取得

SetThreadContext
http://msdn.microsoft.com/ja-jp/library/cc428991.aspx
コンテキストをセット
レジスタ変更を加える処理を追加する場合に必要になる。

4.2 他プロセス内で任意のコードを実行させる ~コードインジェクション
「コードインジェクション」:他プロセス内で任意のコードを実行させる手法の総称。
DLL を使う場合は「DLL インジェクション」と呼ばれる。

Three Ways to Inject Your Code into Another Process( Robert Kuster, 20 Aug 2003 )
「(邦題)他プロセスへコードを注入させる3つの方法」
http://www.codeproject.com/Articles/4610/Three-Ways-to-Inject-Your-Code-into-Another-Process

[OS のシステムメッセージをフック(横取り)するための3つの API]
SetWindowsHookEx
http://msdn.microsoft.com/ja-jp/library/cc430103.aspx

CallNextHookEx
http://msdn.microsoft.com/ja-jp/library/cc429591.aspx

UnhookWindowsHookEx
http://msdn.microsoft.com/ja-jp/library/cc430120.aspx

他プロセスにあるウィンドウプロシージャに渡されるメッセージをフックするためには、DLL が「他プロセスから」ロードされる必要がある。

SetWindowsHookEx は、呼び出した時点から他プロセスへ DLL がマッピングされるが、レジストリの AppInit_DLLs に DLL のパスを登録しておけば、OS 起動直後から、任意の DLL を他プロセスへロードできる。

[確認したレジストリ( Windows7 64bit 版 )]
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
 種類:REG_SZ
 データ:(未設定)
 読み込む DLL のリストをスペースまたはコンマで区切って指定する。
 DLL のフルパス名には、短い名前を指定する。

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\LoadAppInit_DLLs
 種類:REG_DWORD
 データ:0x00000000 (0x0:AppInit_DLLs が無効、0x1:AppInit_DLLs は有効)
 説明:システム全体で AppInit_DLLs を有効または無効にする。

RequireSignedAppInit_DLLs
 種類:REG_DWORD
 データ:0x00000000
 (0x0:すべての DLL を読み込む、0x1 : コード署名された DLL のみを読み込む)
 説明:コード署名された DLL のみ読み込む。

Windows7 64bit 版では、「RequireSignedAppInit_DLLs」は確認できませんでした。
「Windows7 および Windows Server 2008 R2 における AppInit_DLLs」
http://msdn.microsoft.com/ja-jp/library/dd744762(v=vs.85).aspx

64bit 環境では、32bit プログラムに関する設定は Wow6432Node にリダイレクトされる。

HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows\LoadAppInit_DLLs

AppInit_DLLs に登録した DLL は、user32.dll からロードされるので、user32.dll を使わない(ロードしない)プロセスにはマッピングされない。

CreateRemoteThread:他プロセス内でスレッドを生成する API。
 CreateRemoteThread を使って、LoadLibrary をスレッドとして実行することで、他プロセスに強制的に DLL をロードさせる。
  LoadLibrary に渡す引数は、プロセス内のものを使わなければならないので、引数となる文字列だけは、対象となるプロセスにあらたに書き込む。
 DLL ではなく、関数(コード)そのものをプロセス内へコピーすれば、その関数を CreateRemoteThread で実行できる。

Windows は権限さえあれば、他プロセスへ自由にアクセスできるので、基本的に他プロセスへ自由にコードを注入でき、デバッガでなくとも他のプロセスになりすますことは容易。

4.3 処理を任意のものに置きかえる ~API フック
フック:プログラムに独自の処理を追加すること
API フック:API に独自の処理を追加すること
 ・対象となる関数の先頭数バイトを書きかえるタイプ
 ・IAT( Import Address Table) を書きかえるタイプ
  「Advanced Windows 改訂第4版(2001/5/1)」に詳細が書かれている。
  http://amazon.co.jp/dp/4756138055

「第22章 DLL の注入と API フック」の「22.9 API フック:例」への言及と思われます。
API フック用ライブラリ「Detours 3.0」
http://research.microsoft.com/en-us/projects/detours/
 DLL がエクスポートしている関数がわかれば、実行時にその関数呼び出しをフックできる。

Detours Professional 3.0:¥1,234,286 (税込)
http://www.microsoftstore.com/store/msjp/ja_JP/pdp/Microsoft-Research-Detours-v3-Professional/productID.280902400
価格設定は正しいのか???

Detours Express 3.0(インストーラ):無償
http://research.microsoft.com/en-us/downloads/d36340fb-4d3c-4ddd-bf5b-1db25d03713d/default.aspx
Detours が API フックを行うシンプルな仕組みの詳細
「Detours: Binary Interception of Win32 Functions」 http://research.microsoft.com/pubs/68568/huntusenixnt99.pdf

フックは、関数の先頭数バイトを jmp 命令に置きかえ、一時的に別の関数へ飛ばすことで実現している。
Detours はソースコードが公開されている。

インストーラ(DetoursExpress30.msi)を実行後、
C:\Program Files (x86)\Microsoft Research\Detours Express 3.0\src
にソースコードが格納されていました。
API フックは、基本的にユーザーランドにおいては DLL が持つエクスポート関数に対して行うものだが、非公開の API をフックしたり、カーネルランド(Ring0)で動作するドライバに対して行う手法も存在する。

コラム:「ハッキングらしい」技術の象徴が DLL インジェクションと API フック?
いまでは、アンチウィルスソフトも含め、多くのセキュリティ製品が API フックを使っている。
カーネルで呼ばれる API をフックすることもできる。
Microsoft のセキュリティツール EMET も、手法は異なるが、他プロセスへ DLL をロードさせている。


記述に際しては、細心の注意をしたつもりですが、間違いやご指摘がありましたら、こちらからお知らせいただけると幸いです。


→「たのしいバイナリの歩き方」勉強メモ#5
←「たのしいバイナリの歩き方」勉強メモ#3


« 戻る