何がパッキングですか?#
この章では、upx パッキングプログラムのアンパックを示します。
パッキングとは、プログラムの実行可能コードを隠すために圧縮または暗号化の手段を用いることを指し、容易に逆コンパイルされるのを防ぎます。パッキングはプログラムに追加のセクション(STUB、スタブ)を追加し、プログラムが実行を開始した後、暗号化されたファイルを復号化してメモリ内の他のセクションに保存するか、元のプログラム内のセクションを作成し、復号化されたコードにジャンプして実行します。
ほとんどの場合、IAT(インポートテーブル)やファイルヘッダー(HEADER)を破壊することでファイルを保護します。これらは、元のファイルをアンパックされるのを防ぐために、逆デバッグコードを追加します。
die を使用してパッキングされているかどうかを確認します。
上の図から、upx 3.91 バージョンでパッキングされていることがわかります。プログラムは 32 ビット、i386 アーキテクチャです。
パッキングされたファイルの読み込み#
パッキングされたファイルを読み込むときは、入力セクションの作成をチェック解除
し、手動読み込み
をチェックします。
OK をクリックすると、次のウィンドウがポップアップし、確認をクリックします。
パッキング後のプログラムのエントリ
元のプログラムのエントリ
パッキング後のプログラムのエントリはアドレス 0x409BE0 であり、元のファイルのアドレスは 0x401000 です。
ファイルとメモリの使用量#
これら 2 つのファイルのセクションを比較すると、パッキングされたファイルのファイルヘッダーの下に upx0 セクションがあり、使用されるメモリは元のファイル内の他のセクションよりも大きいです。
元のファイル
パッキングファイル
パッキングファイルの upx0 ブロックは0x409000
で終了し、元のファイルのヘッダー以下のセクションは0x401000
から0x408200
までです。プログラムが実行されると、ハードディスク上では 1k しか占有しないかもしれませんが、メモリ内では 20k 以上を占有する可能性があります。
上の図のように、元のファイルの CODE セクションの開始アドレスは0x401000
で、セクションファイルのサイズ(ファイル内のセクションサイズ)は 0x600 バイト、メモリサイズ(仮想サイズ)は 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 つの参照は(下方)実行可能部分から来ており、クリックするとその場所にジャンプします。
プログラムのエントリ
スタブと 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 を見つける方法を紹介します。
STUB が復号化操作を完了し、元のコードを生成した後、プログラムを実行するためにジャンプします。一般的に、元のコードが書き込まれるブロックで実行される最初の命令が 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 を再構築して、パッキングされていない実行可能なプログラムを取得することです。