puahad と popad#
加壳プログラム:unpackme.aspack.2.2
、インターネットで検索可能。
ida を開き、プログラムを手動でロードし、入力セクションの作成をオフにします。
加壳プログラムのエントリ
上の図で、最初の命令はpushad
で、pushad
はすべての汎用レジスタの値をスタックにプッシュします。
pushad
は以下の順序で全レジスタの値をスタックに保存します。
pushad の値の順序
popad
はpushad
の逆の操作で、スタックから値を取り出し、以下の順序でレジスタに保存します。
ほとんどのシンプルなシェルでは、pushad
命令を使用してレジスタの初期状態を保存し、その後OEP
にジャンプしてメモリ内の元のコードを実行する前にPOPAD
を使用してレジスタの初期状態を復元します。
この規則に従って、pushad-popad法
を使用してOEP
を簡単に見つけることができます。
idapython を使用したデバッグ#
pushad-popad法
とは何でしょうか?
デバッガを使用してプログラムを実行します。デバッガを実行する方法は 2 つあります。1 つ目はメニューバー-デバッガ-デバッガの選択、local windows debuggerを選択
する方法、2 つ目は python を使用してデバッガを実行する方法です。今回は 2 つ目の方法でデバッガを実行します。
1、import idc
で idc モジュールをインポートし、idc.load
と入力して tab キーで補完し、idc.load_debugger
を見つけ、パラメータはidc.load_debugger("win32",0) //1はリモートデバッグ
、戻り値が True であれば実行成功を示します。
load_debugger をロードし、パラメータを設定
この時点でlocal windows debugger
がロードされました。
pusha の後にブレークポイントを設定します。
pusha の後にブレークポイントを設定
pushad-popad法
は、pushad の後の命令の前に、スタック上に保存されたレジスタの値の位置を見つけ、その位置にブレークポイントを設定します。プログラムが元のコードを復号した後、OEP にジャンプする前に popad を使用してレジスタの初期値を復元し、そのブレークポイントをトリガーして実行を一時停止させ、OEP の位置を特定します。
F2 キーを押して pusha の次の命令にブレークポイントを設定すると、pusha の実行後にデバッグが一時停止します。(pusha は pushad に似ています)
python を使用してブレークポイントを設定する場合、次のような文を使用できます:
idaapi.add_bpt(0x46b002,0,idc.BPT_SOFT)
idaapi.add_bpt(0x46b002,0,0)
- 最初のパラメータはブレークポイントのアドレス
- 2 番目のパラメータはブレークポイントの長さ
- 3 番目のパラメータはブレークポイントのタイプ(ソフトウェアブレークポイント BPT_SOFT または 0)
デバッガを選択し、最初のブレークポイントを設定した後、デバッガを起動してプログラムを実行し、そのブレークポイントで一時停止させる必要があります。F9 キーを押すか、python 文を使用してプログラムを実行します。
idc.StartDebugger("","","");
高バージョン(ida 7.7)では、上記のコマンドがエラーを返すため、次のコマンドを使用する必要があります。
idc.start_process("","","")
上記の文を実行すると、デバッガは以前設定したブレークポイント、つまり0x46b002
に到達します。
ブレークポイント0x46b002
に到達
下の図では、スタックビューの値は pushad が保存したレジスタの値であり、その後 popad 命令を通じて読み取られます。したがって、最初の行にブレークポイントを設定できます。
このデバッグでは、ブレークポイントの位置は 0x19ff54 で、esp の実行スタックの位置です。
ESP の右側の小さな矢印をクリックすると、対応するウィンドウのそのアドレス(ここではアセンブリウィンドウ)にジャンプします。
F2 を押してここにブレークポイントを設定し、実行時ではなく、読み取りと書き込み時にトリガーされるように変更します。
ウィンドウが表示されない場合は、メニューのデバッガー - ブレークポイント - ブレークポイントリストを開き、右クリックして設定を編集を選択します。
python を使用してブレークポイントを設定する場合、次のようにインストールできます:
idaapi.add_bpt(0x19ff54,1,3)
ブレークポイントリストは以下の通りです。
ブレークポイントリスト
上記のコードでは、最初のパラメータはブレークポイントのアドレス、2 番目のパラメータはブレークポイントのサイズ、3 番目のパラメータはタイプを示し、3 は read-write access(読み取りと書き込み)を表します。この文を実行することは、手動でブレークポイントを設定するのと同じです。
BPT_EXEC = 0,
BPT_WRITE = 1,
BPT_RDWR = 3,
BPT_SOFT = 4,
ブレークポイントタイプのパラメータ
ブレークポイントリストで、最初に設定したブレークポイントを右クリックし、DISABLE を選択して無効にするか、次の python 文を使用します。
idaapi.enable_bpt(0x46b002,0)
- 最初のパラメータはブレークポイントのアドレス
- 2 番目のパラメータが 1 の場合はブレークポイントを有効にし、0 の場合は無効にします
0x46b002 が緑色にマークされている場合は無効を示し、スタック上のブレークポイントは赤色で有効を示します。
F9 キーを押してデバッグを続行するか、次の python 文を入力します。
idaapi.continue_process()
下の図では、デバッグが popad 命令によってレジスタの初期値を取得した後に中断され、プログラムは OEP、つまり 0x4271b0 にジャンプします。なぜなら、push ret は jmp に似ているからです。
popa 命令
OEP までステップ実行
を続けます。
OEP エントリを見つけたので、実行可能ファイルを再分析し、関数を識別できます。
プログラムを再分析
idapython を使用したダンプ#
OEP を見つけた後、次のステップはダンプを行うことです。ファイルのベースアドレスと実行可能ファイルの最後のセクションの最高アドレスが必要です。
上の図から、ベースアドレスは 0x40000、最高アドレスは 0x46e000 であることがわかります。
ダンプスクリプトは以下の通りです:
import idaapi
import idc
import struct
start_ea = 0x400000
end_ea = 0x46e000
step = 4 # 各アドレスのデータは4バイトを占める
file_path = "dump.bin"
with open(file_path, "wb") as f:
for ea in range(start_ea, end_ea, step):
# 指定されたアドレスの4バイトデータを読み取り、リトルエンディアンに変換
bin_data = struct.pack("<L", idaapi.get_32bit(ea))
f.write(bin_data)
ファイル - スクリプトファイルから上記の py スクリプトをロードし、実行後、現在のディレクトリに dump.bin ファイルが生成され、その拡張子を exe に変更します。
peeditor を使用して、セクションビューを開き、すべてのセクションを右クリックして dumpfixer を選択します。
この時点で、アイコンが修正されました。
アイコンを修正した後、Scylla 0.98 を使用してプロセスをデバッグ対象の加壳ファイルにアタッチします。現在 OEP エントリに到達しています。
このプロセスをロードします。
OEP を 004271B0 として入力し、IAT Autosearch と Get Imports をクリックします。
無効な API が 1 つ見つかると、修正が自動で失敗し、手動で修正する必要があります。
0x460818 の API
上の図で、0x460818 は最初の有効セクションの API 関数で、その上には未認識の API アドレスが多数あります。
最初の未認識の 0x46080c の内容を確認し、D キーを押してデータタイプを変更し、バイトを再構成します。以下の図のように:
ここにある内容は有効なアドレスを指しておらず、CTRL+X を押しても参照はありません。
本当の API 関数には、どこで呼び出されているかを示す参照情報があるはずです。
したがって、これらは API 関数ではないため、削除できます。
clear キーをクリックし、IAT Autosearch をクリックして、ポップアップウィンドウで「いいえ」を選択します。
現在の IAT の開始位置は 0x460810 で、Get Import をクリックするとすべての API を見つけることができます。
Fix Dump をクリックしてファイルをダンプし、以前にエクスポートしたファイルを選択します。
最後に、脱壳プログラムが正常に実行されます。
これで、プログラムの脱壳は終了です。