今回は、子プロセスにPEイメージをインジェクションするタイプのパッカーでパックされた実行ファイルを
マニュアルアンパックする方法を紹介します。

【参考URL】
The Art of Unpacking
https://www.blackhat.com/presentations/bh-usa-07/Yason/Whitepaper/bh-usa-07-yason-WP.pdf

【対象】
URL
https://tuts4you.com/download.php?view.980

Filename : UnPackMe_NtPacker1.0.exe
MD5 : ec1f237cb0ef4ad100fd70692e345c74
SHA1 : d30aedb3ed6c5b058e1b6b1db93f705cc879aacd

【使用ツール】
・ollydbg 1.10
・ollydbgプラグイン「PhantOm」
理由は分かりませんが、PhantOmの「hide from PEB」を使わないと、うまくアンパックできなかったため使用しています。
・IDA Pro Free
構造体を見やすいため、ollydbgと併用して使っています。
・ImportREC 1.7f

【パッカー概要】
詳細は「The Art of Unpacking」を読んでください。
重要な部分のみ説明します。

1 「CreateProcess」APIを使い、サスペンド状態で子プロセスを作成します。


2 「GetThreadContext」APIを使い、子プロセスのコンテキスト(※)を取得します。
※ スレッドでのレジスタの値

「コンテキスト」参考URL
http://www.interq.or.jp/chubu/r6/masm32/tute/tute029_Jp.html

コンテキストは「Context構造体」として取得されます。
「Context.Ebx」にはPEBのアドレスが格納されています。
また、「Context.Eax」にはエントリポイントのアドレスが格納されています。

今回の対象プログラムでは、
Context.Eax(エントリポイント) 0x1000817C
がGetThreadContextの結果として格納されました。

「Context構造体」参考URL
http://msdn.microsoft.com/en-us/library/windows/desktop/ms679284%28v=vs.85%29.aspx

3 「VirtualAllocEx」APIを使い、子プロセスにメモリを確保します。

今回はアドレス「0x400000」からサイズ「0x6B0000」だけ確保されました。
このサイズは、後に「WriteProcessMemory」で書き込まれるPEイメージのサイズです。

4 「WriteProcessMemory」APIを使い、子プロセスにPEイメージを書き込みます。

「MZ」から始まるPEイメージ(サイズ:0x6B0000バイト)を、子プロセスのアドレス「0x400000」に書き込みます。

5 「WriteProcessMemory」APIを使い、子プロセスのPEBに記録された「ImageBase」の値を書き換えます。


「Context.Ebx」には、子プロセスのPEBのアドレスが格納されているので、
子プロセスのPEB.ImageBaseAddress(PEBのアドレス+0x8)に「0x00400000」を書き込みます。
(つまり、子プロセスのイメージベースを0x10000000から0x400000に更新しています。)

「PEB構造体」参考URL
http://www.atmarkit.co.jp/ait/articles/1111/18/news146_2.html

6 「SetThreadContext」APIを使い、子プロセスのエントリポイントを変更します。

「Context.Eax」に0x004271B0を格納し、「SetThreadContext」APIを使って子プロセスのコンテキストを更新します。
(つまり、子プロセスのエントリポイントを0x4271B0に更新しています。)



7 「ResumeThread」APIを使い、子プロセスをレジュームします。

子プロセスは、パックされる前の本来の動作を行います。
今回は、次のメッセージボックスが表示されました。


以上がパッカーが行う動作の概要です。
サスペンド状態のプロセスにはデバッガでアタッチできないため、アンパックには一工夫が必要となります。

【マニュアルアンパック方法】

1 最終的に「ResumeThread」APIで子プロセスがレジュームされる際のエントリポイントを調べます。

「パッカー概要」で説明しましたが、「SetThreadContext」APIで設定される直前に、「Context.Eax」に格納されている値がエントリポイントのアドレスです。
今回の対象プログラムでは、0x4271B0でした。
よって、エントリポイントのRVAは、イメージベースの値(0x400000)を引いた0x271B0です。

2 「WriteProcessMemory」APIにブレークポイントを設定し、「F9」を押下して実行します。

3 子プロセスにPEイメージが書き込まれる前に、PEイメージを修正します。
(PEイメージのエントリポイントにおける命令を、アンパックに都合の良いように書き換えます。)


今回の場合では、子プロセスに書き込まれるPEイメージは、親プロセスのアドレス「0x143608」にありました。(アドレスは実行の度に変わりますので注意してください。)


アドレス「0x143608」に、「MZ」から始まるデータを確認できます。
「アドレス」列の「00143608」の部分をダブルクリックすると、表示が0x143608からのオフセット表示に切り替わるので、見やすくなります。


次にエントリポイントの命令を確認するため、0x271B0分だけずらした部分を確認します。


エントリポイントにおける命令の機械語は「55 8B」になっています。
これは子プロセスがレジュームされた際に最初に実行される命令です。
この2バイトの数値を覚えておき、値を「EB FE」に書き換えます。


「EB FE」は「自身へのジャンプ命令」を表します。
つまり、子プロセスのエントリポイントをこの命令に書き換えることで、「ResumeThread」APIで子プロセスがレジュームされた際、この命令で無限ループに入ることになります。

4 親プロセスを最後まで実行します。

「ResumeThread」APIを実行し、子プロセスをレジュームします。
「3」でエントリポイントにおける命令を無限ループ命令に書き換えたため、子プロセスはエントリポイントから動作が先に進まない状態になります。
親プロセスにアタッチしているollydbgを終了させます。

5 ollydbgを改めて起動し、無限ループしている子プロセスにアタッチします。
(子プロセスは無限ループして「実行状態」になっているため、アタッチすることが可能です。)


「DbgBreakPoint」APIで停止するので、「Alt」+「F9」を押下し、ユーザ関数まで実行します。
アドレス「0x4271B0」で停止します。
ここで、アドレス「0x4271B0」における命令が、「自身へのジャンプ命令」になっていることを確認できます。


6 エントリポイントの命令を、変更前に戻します。
ollydbgのダンプ画面(左下画面)でアドレス「0x4271B0」の値2バイト分を「EB FE」から「55 8B」に書き換えます。


以上の作業で、アンパックされたプロセスをデバッガで解析できる状態にすることができました。

以下では、アンパックされたPEファイルを得るために、ダンプやIATの再構築などの作業を行っていきます。
「The Art Of Unpacking」には載っていなかった作業のため、自己流で行っており、この方法では上手くいかない場合もあるかと思いますが、ご了承ください。

7 「ImportREC」を起動し、子プロセスを指定します。
次の画面が表示されます。


ここで問題が起こりました。
ImportRECはファイルからPEのイメージベースやエントリポイントを取得するので、対象プログラムファイルに記録されたイメージベースとエントリポイントが取得されてしまいました。

イメージベース:0x10000000
エントリポイント:0x1DC0

この問題を解決するため、ImportRECの「オプション」を選択し、「Active Process Infomation」の「Use PE Header From Disk」のチェックを外し、ImportRECの対象として子プロセスを再選択しました。


設定を変更することで、次のとおり、アンパックされた状態でのイメージベースとエントリポイントが取得されました。

イメージベース:0x400000
エントリポイント:0x271B0


8 子プロセスのメモリをダンプします。

ImportRECの「Imported Functions Found」で右クリックし、「Advanced Commands」-「Select Code Section(s)」を選択し、「Full Dump」を選択します。
作成するダンプファイルの名前を聞かれるので適当に決めます。

9 IATの再構築などを行います。
ImportRECの「IAT AutoSearch」を選択し、「Get Imports」を選択します。
「Imported Functions Found」に「valid:NO」が無いことを確認し、「Fix Dump」を選択します。
IATの再構築などを行う対象ファイルを聞かれるので、「8」で作成したダンプファイルを選択します。

IAT等が修正されたファイル(つまり、アンパックされたファイル)のファイル名を聞かれるので適当に決めます。
マニュアルアンパックは以上で完了です。

次のとおり、実行可能な状態でアンパックできていることが分かります。


それでは。