Snoozy

1.Sleep-inducing; tedious.

IDAPythonによる解析の自動化をやってみる(動的解析編)

IDAPythonによる解析の自動化をやってみる(動的解析編)

前回の記事では,IDAPythonを使ってマルウェア解析工程の一部の自動化をやってみた.要約すると,IDAPythonを使って検体内に埋め込まれた暗号化されたAPI名を復号し,グローバルな変数をその値でリネームするというものだった.これにより動的にインポートされた関数ポインタがバイナリ内のどの場所でcallされたとしても,それがなんであるか判別できるようになった.また関数プロトタイプが適用されることで引数情報がIDAに反映され解析効率が上がるなどの効果もあった.一方で,即座に復号スクリプトを用意できないような複雑な,あるいは自明でない暗号方式を採用している検体に出くわした場合はどうすればよいだろうか.解析者は逆アセンブル結果やデコンパイル結果を見て復号スクリプトを書くことになるが,個人的にこれは非常に苦しい作業であるように思う.そこで動的解析の出番である.IDA Proはデバッガー機能も備えているのでこの問題にも対応できる.

ここでは前回のバイナリを例に,APIの動的なインポートをIDAのデバッガーとそれをIDAPythonスクリプトで制御することで解決してみる.静的解析では埒が明かないようなバイナリに対して強力なオプションになるだろう.

シナリオ

前回同様に次のような関数を解析対象に想定する.

f:id:snoozekvn:20211213145322p:plain

GetProcAddressで取得されるAPI名を取りたい.実行時にどのようにデータが受け渡しされるのかデバッガで処理を追ってみる. 以下はGetProcAddressで取得した値がデータ領域dword_8F3384に保存される直前にブレークポイントを設定し,レジスタとメモリの内容を表示した画像.

f:id:snoozekvn:20211213160601p:plain

GetProcAddressの戻り値としてEAXにアドレスが入っている.その横にIDAがシンボルをもとに[モジュール名]_[API名]という形でそのアドレスがどこを指しているのかを表示してくれている.また右下にはDecryptString関数で復号された文字列がメモリに載っている.このように目的の値が取れる個所はいくつかあるが,今回は前者のアドレスに対してIDAが表示してくれるシンボルを取ることにする.

IDAのデバッガーをIDAPythonで操作する

まず,次のようにしてGetProcAddressがcallされた次のアドレスを全て列挙し,ブレークポイントを設定する.

addr = get_name_ea_simple('GetProcAddress')
for x in XrefsTo(addr):
    if x.iscode:
        bp_addr = next_head(x.frm)
        add_bpt(bp_addr, BPT_DEFAULT)

単純にGetProcAddressを参照するすべてのアドレスを取ると不要なデータ領域のアドレスなども取れるため,iscodeでコード領域のアドレスだけを取っている.Breakpointsのリストを見ると確かにブレークポイントが設定されている.

f:id:snoozekvn:20211213145333p:plain

次にブレークポイントにconditionを設定する.conditionとは各ブレークポイントに設定できる条件のことで,ブレークポイント発火時に呼び出されるコールバック関数のようなものだ.この関数の戻り値がTrueなら通常通りブレークし,そうでない場合はブレークしない.条件付きブレークポイントは文字通りブレークポイントの条件分岐のための機能だが,condition内でレジスタやメモリ操作をすることでフックのような処理ができる.conditionの中でレジスタの値を取る処理とリネームする処理を加えれば今回の目的は達成できるだろう.

スクリプト

import ida_dbg
idaapi.enable_extlang_python(True) #1

def get_api_name_and_rename():
    ida_dbg.refresh_debugger_memory() #3
    api_name = get_name(get_reg_value('eax')).split('_')[-1] #4
    ea = get_reg_value('eip')
    print(">>> Conditional breakpoint called  on {} {}".format(hex(ea), api_name))

    try: #5
        set_cmt(ea, api_name, 0)
        set_name(get_operand_value(ea, 0), api_name)
    except:
        # If the return value of the code is True, the breakpoint will trigger.
        return True

    return False

addr = get_name_ea_simple('GetProcAddress')
for x in XrefsTo(addr):
    if x.iscode:
        bp_addr = next_head(x.frm)
        add_bpt(bp_addr, BPT_DEFAULT)
        enable_bpt(bp_addr, True)
        set_bpt_cond(bp_addr, "get_api_name_and_rename()") #2
        
print("[+] Conditional Break point installation completed!")
  1. enable_extlang_pythonをTrueにすることでconditionに登録する式をPythonで記述できるようにする
  2. set_bpt_condで条件付きブレークポイントを設定する.ブレークポイントのアドレスとconditionを設定する
  3. refresh_debugger_memoryでデバッガのバッファをリフレッシュする.これをしないと値が取れないなどおかしなことになる
  4. eaxのアドレスが指す場所の名前を取っている
  5. リネーム処理をtry文内で行う.例外が発生した場合は戻り値Trueを返してユーザーがデバッグしやすいようにブレークさせる

これを実行すると以下のような出力が得られる.GetProcAddressの値が得られており,コメントとリネーム処理もうまくいっているようだ.

f:id:snoozekvn:20211213145339p:plain

GetProcAddressがいくつもある以下のようなバイナリに対しても応用できる.

f:id:snoozekvn:20211213145326p:plain

スクリプトをこのバイナリ内で実行すると以下のような結果が得られうまく実行できているようだ.

f:id:snoozekvn:20211213145336p:plain

以上.

参考

https://adelmas.com/blog/ida_api_hooking.php

おわりに

このエントリは,IPFactory Advent Calendar 2021 13日目のエントリです.詳細はこちら.前日の記事は@y0d3nによる「スクショのためのchrome拡張機能を自作した」でした.明日の枠は@DuGlaserです.楽しみですね.