Immunity DebuggerでWin32 APIのフックをやってみる
「Immunity Debuggerを使ってみる」でPyCommandデビューしたため、もっと実用的なそれっぽいことをやってみる。
環境
・Windows 10 Pro 64bit版
・Immunity Debugger v1.85
・Python 2.7.9
デバッグ対象
・C:\Windows\SysWOW64\notepad.exe
Win32 APIのフック
Immunity DebuggerではFastLogHookというオブジェクトが提供されており、簡単にフックをセットアップできる。フックをセットする一連のコードは次のようになる。
imm = immlib.Debugger() fast = immlib.FastLogHook(imm) fast.logFunction(address,num_arguments) #フックポイントを定義 fast.logRegister(register) #フック時における特定のレジスタの値をログ出力 fast.logDirectMemory(address) #フック時における一定のメモリオフセットをログ出力 fast.logBaseDisplacement(register,offset) #レジスタから一定のオフセットに位置するデータをログ出力 fast.Hook()
メソッドFastLogHook()
でフックポイントを設定できる。このメソッドはフックを設定すべきアドレスと捕捉すべき引数の数を受け取る。logRegister()
,logDirectMemory()
,logBaseDisplacement()
が実際にログ出力するメソッドであり、状況によって使い分けることができる。
フックポイントに到達してログ出力メソッドが実行されると、捕捉された情報はFastLogHookオブジェクトが作成したアロケート済みメモリ領域に保存される。そのためフックの結果を取得する必要があり、それにはgetAllLog()
というメソッドが使える。このメソッドは対象のメモリ領域の内容を解析して次の形のリストを返す。
[(hook_address,(arg1,arg2,...,argN)),...]
呼出し規約による違い
ここまでの説明ではFastLogHook()
を使用すると書いたが、Win32 APIではstdcall呼出し規約が使われているためSTDCALLFastLogHook()
というメソッドを使う必要がある。ただし、使い方は上で説明したFastLogHook()
と変わらない。
実際にPyCommandを作成してみる
ここではRtlAllocateHeap関数をフックポイントに設定し、3つのパラメータを捕捉しログ出力する。
RtlAllocateHeapのプロトタイプは次のようになっている。
PVOID RtlAllocateHeap( _In_ PVOID HeapHandle, _In_opt_ ULONG Flags, _In_ SIZE_T Size );
実際にスクリプトを書くと次のようになる。
import immlib def main(args): imm = immlib.Debugger() fast = imm.getKnowledge("my_hook") if fast: #すでにフックが設定されていたら結果をログ出力 hook_list = fast.getAllLog() for a in hook_list: imm.log("RtlAllocateHeap(0x%08x,0x%08x,0x%08x)" % (a[1][0],a[1][1],a[1][2])) return "Logged : %d hook hits." % len(hook_list) imm.pause() #デバッガを停止 addr_rtlallocate = imm.getAddress("ntdll.RtlAllocateHeap") #ntdll.dll!RtlAllocateHeapのアドレスを取得 imm.log("ntdll.RtlAllocateHeap : 0x%08x" % addr_rtlallocate) #ここからフックの構築 fast = immlib.STDCALLFastLogHook(imm) fast.logFunction(addr_rtlallocate,3) fast.Hook() #フックの設定 imm.addKnowledge("my_hook",fast,force_add=1) #あとから結果を取得できるようにフックオブジェクト(fast)を保存 return "Hook set, press F9 to continue the process."
addKnowledge()
メソッドはPythonのオブジェクトをタグ(1つ目のパラメータ)をつけてナレッジデータベースに追加するメソッドで、getKnowledge()
で、追加したPythonオブジェクトをタグを指定して取り出すことができる。上のコードではフックが設定されているかどうかをこのメソッドで確かめている。
このスクリプトを次の手順で実際に使ってみる。
- Immunity Debuggerを起動し
notepad.exe
を開く - コマンドバーに
!myhook
のように感嘆符に続けてスクリプト名を入力しENTERキーを押下 - F9キーで
notepad.exe
のウィンドウが表示されるまで進める - 2と同様にスクリプトを実行
上の手順通りに進めるとログウィンドウ([View] -> [Log]
)に次のような結果が表示され、RtlAllocateHeapのパラメータを捕捉できていることが分かる。
おまけ:フック後のアセンブリコードを見てみる
RtlAllocateHeapの逆アセンブルコードは次のようになっている。
しかしフック後は先頭の5バイト(MOV EDI,EDI;PUSH EBP;MOV EBP,ESP
)がJMP命令(JMP 060F0008
)に書き換えられている。このジャンプ先がフックコードである。
CPUウィンドウで[Ctrl-G] -> 060f0008
を入力しJMP先のアドレスを見てみると、以下のフックコードが生成されているのが分かる。
上のコードでは060F0008
のPUSHAD
,060F0078
のPOPAD
によって元のレジスタの値を保存している。
上のフックコードの処理内容を簡単にまとめると次のようになる。
060F0000
から(捕捉したRtlAllocateHeapのパラメータが保存される)メモリ領域へのアドレスをEDI
に格納する060F0042
のMOV EAX,DWORD PTR SS:[ESP+C]
でPUSHAD
で保存したESP
の値をEAX
に格納する060F0049
のMOV EAX,DWORD PTR DS:[EAX+4]
で1つ目のパラメータ([ESP+4]
)をEAX
に格納する060F004F
のSTOS DWORD PTR ES:[EDI]
で1つ目のパラメータをEDI
の指す先に格納する- 2~4と同様に2つ目、3つ目のパラメータもメモリ領域に格納する
- 最後に
060F0000
に格納されたメモリ領域へのアドレスを更新する
捕捉したRtlAllocateHeapのパラメータが保存されるメモリ領域を確認してみる。
16進ダンプウィンドウで[Ctrl-G] -> 060f0000
を入力すると、赤枠部分に目的のアドレスが格納されているのが分かる。
16進ダンプウィンドウで[Ctrl-G] -> 060f6b24
を入力して少し上にスクロールすると、ピンク枠部分に捕捉したRtlAllocateHeapのパラメータが保存されているのが分かる。
060F0079
から最後までの命令は次のようになっており、フックコードへのJMP命令で書き換えられていた5バイトの命令を実行した後、元のRtlAllocateHeapにリターンしているのが分かる。
MOV EDI,EDI PUSH EBP MOV EBP,ESP PUSH 7752DA95 RETN