読者です 読者をやめる 読者になる 読者になる

Log.i53

Themidaのアンパックを目指すブログ改め使い物になるえんじにゃを目指すブログ

マニュアルアンパック:ASPack

Unpack ODbgScript

導入

 ASPackはUPXよりかは幾分セキュリティに焦点を当てており、プログラムのアンパックを難しくするためのいくつかのテクニックを採用しています。ASPackは自己置き換えコード(self-modifiing code)と呼ばれるブレークポイントの設置や一般的な分析を難しくする技術を使用しています。
 ASPackは著名であるため多くの自動アンパックツールが利用可能となっています。これらの自動アンパックツールは常に有効であるとは限らないものの手っ取り早くアンパックしたい場合には最初に試す価値があります。
 自動アンパックツールによりASPackでパックされたファイルを正常にアンパックすることもできるでしょうが、分析者であればマニュアルでファイルをアンパックできるに越したことはありません。ASPackでパックされたファイルをデバッガでロードすると、UPXでパックされたファイルと同様に、アンパッキングスタブのコードの最初にPUSHAD命令があるのを確認することができるでしょう。UPXで利用したESPトリックはASPackにおいても有効です。PUSHAD命令をステップ実行した後でスタックアドレス上にハードウェアブレークポイントを設置します。対応するPOPAD命令が呼び出されるとブレークポイントがトリガーされ、そのPOPAD命令のすぐ後ろにOEPに到達するための(PUSH・RET命令を利用した)テールジャンプを確認することができるでしょう。

ASPack(試用版)を利用して実行ファイルをパックする

 ASPackは1ヶ月間無償で利用できる試用版が提供されています。www.aspack.com

 試用版をインストールしてパックサンプルを生成しましょう。今回はnotepad.exeをパックしたものをサンプルとして利用します。
f:id:i53:20150630160948p:plain

 また、旧バージョンのサンプルになりますが、tuts4youでいくつかのUnpackmeサンプルが公開されています。

 PEiDではASPackによりパックされたバイナリであるかどうか簡単に確認できます:
 f:id:i53:20150630163257p:plain
# ただしバージョンは常に正確なものが表示されるとは限りません

OEPの探索

 OllyDumpプラグインが追加されたOllyDbgでASPackでパックされた実行ファイルをロードしましょう。例によってロード対象がパックされている可能性があることが検出されダイアログが表示されますが、「いいえ」を選択しておきます。

 ロードが完了すると最初にPUSHAD命令が現れるのでこれをステップ実行(F8)しましょう:
f:id:i53:20150630164418p:plain

 PUSHAD命令によりレジスタの内容がスタックに格納されている(右下のスタックビューを参照)のが分かります。対応するPOPAD命令が実行されるとスタックに格納されている値を元のレジスタに復帰させるため、このスタックアドレスへのアクセスが発生することになります。ESPトリックでは、POPAD命令がたいていOEPの直前で実行されるという前提知識を利用しています。
 
 では、現在のスタックアドレス(ESPレジスタが指すアドレス)上にハードウェアブレークポイントを設置しましょう。これはコマンドラインプラグインを利用して"hr esp"を実行するか、または左下のHEXエディタでスタックアドレスに移動して【Breakpoint】➤【Hardware, on access】➤【Dword】でハードウェアブレークポイントを設置できます:
f:id:i53:20150630170001p:plain

 ハードウェアブレークポイントが設置されたかどうかは【Debug】➤【Hardware breakpoints】で確認することができます:
f:id:i53:20150630170320p:plain

 ハードウェアブレークポイントを設置したらF9で実行を再開しましょう。すると対応するPOPAD命令の直後でブレークします:
f:id:i53:20150630172509p:plain

 赤枠で囲われている部分がテールジャンプになりますがUPXとは違いJMP命令ではありません。PUSHとRET命令を組み合わせることによって無条件ジャンプを構築することができます。まずPUSH命令によってDWORD値がスタックに積まれます。その後で実行されるRET命令はスタック(ESPレジスタが指すアドレス)に格納されているアドレス(DWORD値)を取り出してそのアドレスにジャンプする命令です。従って赤枠で囲われた部分の命令はJMP 0100739D命令と等価なテールジャンプとなります。このサンプルではOEPは0x0100739Dであることが分かりました。

アンパックされたバイナリのメモリダンプ

 RET命令に到達したらステップ実行してOEPにジャンプします。OEPに到達したら【Plugin】➤【OllyDump】➤【Dump debugged process】でOllyDumpを起動しましょう。赤枠の部分にOEPのオフセットを入力します。OEPまでステップ実行させた後でOllyDumpを起動した場合には自動的にオフセット(今回の場合100739D - 1000000 = 739D)が入力されています:
f:id:i53:20150630181934p:plain

 また、左下の【Rebuild import】がチェックしてあればダンプ時にインポートテーブルを自動で再構築してくれます。ダンプしたファイルを実行して正常に起動したならばアンパック成功となります。

ASPack OEP Finder

 上記の操作を自動化するOllyScriptの例を以下に示します:

//////////////////////////////////////////////////
//
//  FileName    :  ASPack OEP Finder
//  Author      :  i53
//  Date        :  2015-06-30
//  Comment     :  Use ESP Trick to get the OEP
//
//////////////////////////////////////////////////
INIT:
  bc      // SBPをすべてクリアしておく
  bphwc   // HBPをすべてクリアしておく

FIND_PUSHAD:
  gci eip, COMMAND         // GCIコマンドで現在の命令のオペコード文字列を取得
  cmp $RESULT, "PUSHAD"    // PUSHAD命令に一致するまで
  sto                      // ステップオーバーを繰り返す
  jne FIND_PUSHAD 
  bphws esp, "r"           // PUSHAD命令実行後のESPにHBP(read)を設置
  eob FIND_JMP_OEP         // ブレーク時にFIND_JMP_OEPにジャンプ
  run                      // 実行(F9)

FIND_JMP_OEP:
  // log esp
  bphwc esp-20             // 先ほど設置したHBPをクリア
  find eip, #68????????C3# // 直近のPUSH・RET命令シーケンスを検索
  cmp $RESULT, 0
  je NOT_FOUND
  gopi $RESULT, 1, DATA    // GOPIコマンドでOEPを取得
  bphws $RESULT, "x"       // OEPにHBP(execute)を設置
  eob OEP_FOUND            // ブレーク時にOEP_FOUNDにジャンプ
  run                      // 実行(F9) 

OEP_FOUND:
  bphwc $RESULT            // 先ほど設置したHBPをクリア
  an eip                   // 分析機能(Ctrl+A)実行 
  cmt eip, "This is the OEP!"
  msg "OEP is found :)"
  ret                      // スクリプト終了

NOT_FOUND:
  msg "PUSH RET sequence is not found :("
  ret

実行すると自動的にOEPに遷移します:
f:id:i53:20150630184509p:plain

OllyScriptの各種コマンドについては以下のリファレンス翻訳記事を参照して下さい。i53.hatenablog.jp

ハッピーアンパッキング:)