「たのしいバイナリの歩き方」勉強メモ#4
処理を自在に実行させるプログラミングのテクニック
2014/09/15 : CTF
「たのしいバイナリの歩き方」の勉強メモ第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 が 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
« 戻る