何がパッキングですか?#
この章では、upx パッキングプログラムのデパッキングを示します。
パッキングとは、プログラムの実行可能コードを圧縮または暗号化する手段を用いて隠すことを指し、容易に逆アセンブルされるのを防ぎます。パッキングは、プログラムに追加のセクション(STUB、スタブ)を挿入し、プログラムが実行を開始した後に、暗号化されたファイルを復号化してメモリ内の他のセクションに保存するか、元のプログラムのセクションを作成し、復号化されたコードにジャンプして実行します。
ほとんどの場合、IAT(インポートテーブル)やファイルヘッダー(HEADER)を破壊することでファイルを保護します。これらは、元のファイルをデパックされるのを防ぐために、逆デバッグコードを追加します。
die を使用してパッキングされているかどうかを確認します。
上の図から、upx 3.91 バージョンでパッキングされていることがわかります。プログラムは 32 ビット、i386 アーキテクチャです。
パッキングされたファイルの読み込み#
パッキングされたファイルを読み込む際は、入力セクションの作成を無効にする
を選択し、手動読み込み
を選択します。
OK をクリックすると、次のウィンドウが表示されるので、確認をクリックします。
パッキング後のプログラムのエントリ
元のプログラムのエントリ
パッキング後のプログラムのエントリは、アドレスが 0x409BE0 であり、元のファイルのアドレスは 0x401000 です。
ファイルとメモリの使用量#
これら 2 つのファイルのセクションを比較すると、パッキングされたファイルのファイルヘッダーの下に upx0 セクションがあり、使用されるメモリは元のファイルの他のセクションよりも大きいです。
元のファイル
パッキングファイル
パッキングファイルの upx0 ブロックは0x409000
で終了し、元のファイルのヘッダー以下のセクションは0x401000
から0x408200
までです。プログラムが実行されると、ハードディスク上では 1k しか占有しないかもしれませんが、メモリ上では 20k 以上を占有することがあります。
上の図のように、元のファイルの CODE セクションの開始アドレスは0x401000
で、セクションファイルのサイズ(Section size in file)は 0x600 バイトであり、メモリサイズ(Virtual size)は 0x1000 バイトを占有しています。
パッキングファイルに移ると、上の図のように、upx0 セクションの起点は0x401000
で、upx0 セクションのハードディスク上のサイズは 0 であり、メモリ使用量は 0x8000 バイトです。プログラムはここで元のプログラムコードを保存するために十分なスペースを占有し、その後ジャンプして実行します。
パッキングファイルの 0x401000 でのジャンプ
0x401000 の前の dword_はデータ型が DWORD であることを示し、"?" はメモリ位置を占有しているが、何も保存されていないことを示し、dup は 0xc00 個の dword、すなわち 0x3000 バイトを示します。0x404000 も同様に 0x1400 バイトを占有しています。
したがって、合計で 0x8000 バイトが元のコードの内容を保存するために使用されています。
下の図のように、0x401000 で x キーを押すと、ここに 2 つの参照があることがわかります(後でこの部分を見てみます)。
実行可能コードの参照
upx1 セクションファイルの使用量は 0xe00 で、メモリ使用量は 0x1000 です。
upx1 セクションのファイルおよびメモリ使用量
プログラムは元のコードを隠すためにいくつかの簡単な暗号化を使用している可能性があり、このセクションの起点 0x409000 にはいくつかの参照があります。
0x409000 の参照
1 つの参照は(下方、down)実行可能部分から来ており、そこにジャンプします。
プログラムエントリ
スタブと OEP#
上の図のプログラムエントリの後のスタブで、ESI レジスタは 0x409000 というアドレスを渡します。下の図のように、実行可能コードは元のファイルのパッキングされたコードの下にあり、upx1 ブロックに属しています。したがって、upx1 ブロック内には元のファイルが暗号化された後に保存された内容と 0x409be0 以降のスタブコードが存在します。
追跡された実行可能コード
下の図では、プログラムが 0x409000 から内容を読み取り、何らかの演算を経て0x401000
(EDI=ESI-0x8000
)に保存することがわかります。プログラムは ESI が指す内容をソースとして読み取り、操作を実行した後、EDI が指す内容に保存し、元のコードを復元します。
upx0 セクションに戻ると、upx0 セクションには 1 つの参照があります。
0x401000 の参照
下の図には、0x401000 に無条件にジャンプする 1 つの参照があります。これは上の図の 0x401000 の参照です。
jmp near
は、後のアドレスに直接ジャンプする命令です。したがって、ここでスタブを実行し、元のコードを生成した後、プログラムは 0x401000(OEP、オリジナルエントリポイント)にジャンプします。これは元のプログラムのエントリ(プログラムが最初に実行される場所)であり、対応するスタブエントリ(スタブエントリポイント)は 0x409be0 です。
その後、元のプログラムエントリは単にORIGINAL ENTRY POINT
またはOEP
と呼ばれます。パッキングされたプログラムの場合、具体的な位置を知ることはできませんが、このプログラムには OEP があり、OEP は 0x401000 です。
OEP を探す#
ほとんどの場合、元のプログラムを取得することはできないため、パッキングプログラムの OEP アドレスを直接取得することはできません。次に、OEP を探す方法を紹介します。
スタブが復号化操作を完了し、元のコードを生成すると、プログラムを実行するためにジャンプします。一般的に、元のコードが書き込まれたブロックで実行される最初の命令が OEP です。
OEP に入る前にブレークポイントを設定し、ここに到達する前に元のプログラムが生成されているかどうかを確認します。
ジャンプポイントでブレークポイントを設定
ローカルウィンドウデバッガーを選択してデバッグを開始し、ブレークポイントに到達するまで実行します。
ブレークポイントに到達
プログラムがブレークポイントに到達したら、F8 を押してステップ実行します。
警告メッセージが表示され、upx0 セクションが元々データとして解析されていたため、クリックしてコードとして解析します。
OEP に到達
現在、プログラムは元のコードを復号化し、実行にジャンプしています。ここでのコードは元のプログラムの 0x401000 のコードに非常に似ています。しかし、関数として定義されていないため(loc_401000)、グラフィカルビューに切り替えることはできません。ただし、これも自動的に実行できます。
IDA インターフェースの左下隅には隠れたメニューがあり、右クリックして「プログラムを再解析」を選択します。これで 0x401000 に戻ると、sub_401000 が表示され、これは関数であることを示します。スペースを押すとグラフィカルビューに切り替えることができます。
再解析後のコード
sub_401000
実行ブレークポイントを使用して OEP を探す#
OEP を探す別の方法は、最初のセクションの最初の実行命令を見つけることです。この方法は時々成功します。
デバッガーでパッキングされたプログラムを起動し、エントリポイントで一時停止します。
スタブエントリポイント
セクションの起点、すなわち 0x401000 に移動します。
F2 を押してブレークポイントを設定し、実行時に中断するように設定します。読み取りまたは書き込み時ではなく、そうでないと、復号化中にパスワードを読み取ったり、元のコードを書き込むときに中断されます。具体的な位置がわからないため、著者はセクション全体にブレークポイントを設定しました(0x8000 バイト)。
設定が完了すると、すべての命令が赤くなります。
セクション全体にブレークポイントを設定
次に、メニューバー-デバッガー-ブレークポイント-ブレークポイントリスト
を開き、他のブレークポイントを無効にします。
その後、プログラムを実行します。
ブレークポイントをトリガー
この方法が成功した場合、OEP は 0x401000 であることがわかります。このブレークポイントを無効にします。
OEP
再解析すると、0x401000 が関数として認識されます。
これまでに、OEP を探す 2 つの方法を紹介しました。復号化コードのメモリスナップショットを作成します。次に行うべきことは、DUMP(ダンプ)および IAT を再構築して、パッキングされていない実行可能なプログラムを取得することです。