パックされたプログラムを手動でアンパックする際のメモ

パックされたプログラムを手動でアンパックする際のメモ。

Windowsファイルが対象。

最終的な目的はOriginal Entry Point (OEP) を見つけること

パックされたプログラムにはオリジナルのプログラムをアンパックするためのコードが内包されており、プログラムがパックされた状態だと、大抵の場合、エントリーポイントはこの自己解凍のコード部分を指している。

自己解凍 (アンパック)コードの大まかな流れは以下の通り。

  1. オリジナルのプログラムをメモリに展開する。
  2. オリジナル・プログラムのインポート関数を解決する。
  3. オリジナル・プログラムのエントリーポイントへ処理を飛ばす。

アンパック後のオリジナル・プログラムのエントリーポイントのことをOriginal Entry Point (OEP) と呼ぶ。

展開されたコードが、push ebpmov ebp,esp (関数のプロローグ)で始まっているかどうかが、OEPか否かの判断の目安になる。

OEPを特定できれば、後はOEPを起点にしてメモリに展開されたコードをダンプして、解析するだけである。

※ x32dbgでプログラムをダンプする方法はこちらの記事プロセスをダンプした後にインポート・テーブルを修正するの項を参照

tail jumpについて

アンパックが完了すると、プログラムのOEPへ処理を飛ばさなければならない。アンパック後にOEPへ処理を飛ばす命令部分のことをtail jumpと呼ぶ。

tail jumpの特徴は極端に離れたアドレスへジャンプすることである。(通常のジャンプ命令はせいぜい数十から数百バイト以内のアドレスへジャンプする。)

tail jumpと言うものの、必ずしもjmpが使われるとは限らず、pushretを組み合わせてOEPへ飛ばすといったパターンも存在する。

OEPを特定する

手動でOEPを特定するには、以下のような方法がある。

逆アセンブラからtail jumpを目視で探す

パックの形式によっては、ファイルをIDAなどの逆アセンブラで開いてアセンブリを眺めるだけで、容易にtail jumpを見つけることができる。

tail jumpを見つけたら、ファイルをデバッガで開いて、tail jump部分にブレークポイントをセットする。tail jumpにブレークポイントが到達したら、1回だけステップ実行する。するとジャンプ先のアドレスにオリジナルのプログラムが展開されているはずである。

GetVersion、GetCommandlineA、GetModuleHandleAにブレークポイントをセットする

GetVersion、GetCommandlineA、GetModuleHandleAはプログラムの序盤で呼び出される確率の高い関数である。これらの関数にブレークポイントをセットして、ブレークポイントが起動したら、これらの関数のcall命令から処理を遡って、push ebpmov 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 ebpmov ebp,esp (関数のプロローグ)を探す。

スタック・アドレスにブレークポイントをセットする

パックされたプログラムが自身をアンパックする際、多くの場合、アンパック処理を行う前にpushpushadでレジスタやフラグ情報などをスタックに保存し、アンパックが完了すると、poppopadでスタックに保存したデータを復元する。

上記を踏まえて。。

アンパック処理の直前に呼び出されているpushpushadを探す。見つけたら、プログラムをステップ実行して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

https://mymanfile.com/?p=3695

Leave a Reply

Your email address will not be published.