パックされたプログラムを手動でアンパックする際のメモ。
Windowsファイルが対象。
最終的な目的はOriginal Entry Point (OEP) を見つけること
パックされたプログラムにはオリジナルのプログラムをアンパックするためのコードが内包されており、プログラムがパックされた状態だと、大抵の場合、エントリーポイントはこの自己解凍のコード部分を指している。
自己解凍 (アンパック)コードの大まかな流れは以下の通り。
- オリジナルのプログラムをメモリに展開する。
- オリジナル・プログラムのインポート関数を解決する。
- オリジナル・プログラムのエントリーポイントへ処理を飛ばす。
アンパック後のオリジナル・プログラムのエントリーポイントのことをOriginal Entry Point (OEP) と呼ぶ。
展開されたコードが、push ebp
、mov ebp,esp
(関数のプロローグ)で始まっているかどうかが、OEPか否かの判断の目安になる。
OEPを特定できれば、後はOEPを起点にしてメモリに展開されたコードをダンプして、解析するだけである。
※ x32dbgでプログラムをダンプする方法はこちらの記事のプロセスをダンプした後にインポート・テーブルを修正するの項を参照
tail jumpについて
アンパックが完了すると、プログラムのOEPへ処理を飛ばさなければならない。アンパック後にOEPへ処理を飛ばす命令部分のことをtail jumpと呼ぶ。
tail jumpの特徴は極端に離れたアドレスへジャンプすることである。(通常のジャンプ命令はせいぜい数十から数百バイト以内のアドレスへジャンプする。)
tail jumpと言うものの、必ずしもjmp
が使われるとは限らず、push
とret
を組み合わせてOEPへ飛ばすといったパターンも存在する。
OEPを特定する
手動でOEPを特定するには、以下のような方法がある。
逆アセンブラからtail jumpを目視で探す
パックの形式によっては、ファイルをIDAなどの逆アセンブラで開いてアセンブリを眺めるだけで、容易にtail jumpを見つけることができる。
tail jumpを見つけたら、ファイルをデバッガで開いて、tail jump部分にブレークポイントをセットする。tail jumpにブレークポイントが到達したら、1回だけステップ実行する。するとジャンプ先のアドレスにオリジナルのプログラムが展開されているはずである。
GetVersion、GetCommandlineA、GetModuleHandleAにブレークポイントをセットする
GetVersion、GetCommandlineA、GetModuleHandleAはプログラムの序盤で呼び出される確率の高い関数である。これらの関数にブレークポイントをセットして、ブレークポイントが起動したら、これらの関数のcall
命令から処理を遡って、push ebp
、mov ebp,esp
(関数のプロローグ)を探す。
※ x32dbgでAPI関数にブレークポイントをセットするには、こちらの記事のコマンドからブレークポイントをセットするの項を参照
LoadLibraryA (LoadLibraryW)、GetProcAddressにブレークポイントをセットする
アンパック・コードがオリジナルのプログラムをメモリに展開すると、次にオリジナル・プログラムが使用するインポート関数を解決する。大抵の場合はLoadLibraryA (LoadaLibraryW)で利用するDLLを読み込み、 GetProcAddressで利用する関数を解決する。全てのインポート関数が解決されたら、最後にOEPへ処理を飛ばす。
上記を踏まえて。。
LoadLibraryA (LoadLibraryW)にブレークポイントをセットしてプログラムを実行する。ブレークポイントが起動したら、都度、読み込まれたDLLの名前を控えて、再びプログラムを実行する。
全ての DLLが解決されたら、LoadLibraryA (LoadLibraryW) のブレークポイントは起動せず、プログラムが最後まで実行される (はず)。
プログラムを再度デバッガにロードして、LoadLibraryA (LoadLibraryW)にブレークポイントをセットし、プログラムが一番最後に読み込むDLLがLoadLibraryA (LoadLibraryW) によってロードされたら、GetProcAddressにブレークポイントをセットする。ブレークポイントが起動したら、都度、解決された関数名を控えて、再びプログラムを実行する。
全ての関数名が解決されたら、GetProcAddressのブレークポイントは起動せず、プログラムが最後まで実行される (はず)。
プログラムを再度デバッガにロードして、LoadLibraryA (LoadLibraryW)にブレークポイントをセットし、プログラムが一番最後に読み込むDLLがLoadLibraryA (LoadLibraryW) によってロードされたら、GetProcAddressにブレークポイントをセットする。最後の関数名が解決されたら、後はOEPを探すだけである。デバッガ画面をスクロールするなり、ステップ実行するなりして、tail jumpやpush ebp
、mov ebp,esp
(関数のプロローグ)を探す。
スタック・アドレスにブレークポイントをセットする
パックされたプログラムが自身をアンパックする際、多くの場合、アンパック処理を行う前にpush
やpushad
でレジスタやフラグ情報などをスタックに保存し、アンパックが完了すると、pop
やpopad
でスタックに保存したデータを復元する。
上記を踏まえて。。
アンパック処理の直前に呼び出されているpush
やpushad
を探す。見つけたら、プログラムをステップ実行してpush
またはpushad
実行直後のスタック・アドレス (ESPの値)を控える。
控えたスタック・アドレスにブレークポイント (ハードウェア・ブレークポイントが望ましい)をセットして実行する。
※ x32dbgでスタック・アドレスにハードウェア・ブレークポイントをセットするには、こちらの記事のスタック・アドレスにハードウェア・ブレークポイントをセットするの項を参照
push
またはpushad
に対応するpop
またはpopad
が実行されると、スタック・アドレスにセットしたブレークポイントが起動する。pop
またはpopad
付近にOEPへジャンプするtail jump命令があるはずである。
参考
Practical Malware Analysis (by Michael Sikorski and Andrew Honig) Chapter 18 PACKERS AND UNPACKING