
スクリプト可能なバイナリエミュレーションフレームワークで、IDA Pro/Radare2とUnicornエンジンを統合し、x86、ARM、ARM64アーキテクチャにおける自動マルウェア解析、文字列復号化、コードパス探索を実現します。
flare-emuは、IDA ProやRadare2などのサポートされているバイナリ解析フレームワークと、Unicornのエミュレーションフレームワークを組み合わせ、ユーザーがエミュレーションタスクをスクリプト化するための使いやすく柔軟なインターフェースを提供します。このツールは、サポートするアーキテクチャ向けに柔軟で堅牢なエミュレータをセットアップするための面倒な処理をすべて引き受け、ユーザーがコード解析の問題に集中できるように設計されています。現在、flare-emuはx86、x86_64、ARM、ARM64のアーキテクチャをサポートしています。
現在、5つの異なるインターフェースと、それに関連する多数のヘルパー関数およびユーティリティ関数を提供しています。
emulateRange – このAPIは、ユーザー指定のコンテキスト内で命令の範囲または関数をエミュレートするために使用します。個々の命令に対するフックと、"call"命令が出現したときのフックについて、ユーザー定義フックのオプションを提供します。ユーザーは、エミュレータが関数呼び出しをスキップするか、またはその中に入るかを選択できます。このインターフェースにより、ユーザーは特定のレジスタやスタック引数に値を簡単に指定できます。バイト文字列が指定された場合、それはエミュレータのメモリに書き込まれ、ポインタがレジスタまたはスタック変数に書き込まれます。エミュレーション後、ユーザーはflare-emuのユーティリティ関数を使用して、エミュレートされたメモリやレジスタからデータを読み取るか、返されたUnicornエミュレーションオブジェクトを直接調査に使用できます。emulateRangeの小さなラッパー関数であるemulateSelectionを使用すると、IDA Proで現在強調表示されている命令の範囲をエミュレートできます。
iterate - このAPIは、特定の分岐に沿って強制的にエミュレーションを行い、指定されたターゲットに到達するために使用します。ユーザーはターゲットアドレスのリスト、または関数のアドレス(その関数へのクロスリファレンスのリストがターゲットとして使用される)と、ターゲットに到達したときのコールバックを指定できます。エミュレーション中に別の分岐が取られる原因となった条件に関係なく、ターゲットに到達します。emulateRange APIと同様に、個々の命令に対するフックと、"call"命令が出現したときのフックについて、ユーザー定義フックのオプションが提供されます。iterate APIの使用例としては、argtrackerツールと同様のことを実現することが挙げられます。
iterateAllPaths - このAPIはiterateと非常によく似ていますが、ターゲットアドレスを指定する代わりに、すべてのパスを見つけてエミュレートしようとするターゲット関数を指定する点が異なります。これは、関数のすべての基本ブロックに到達するコード解析を行う場合に便利です。
emulateBytes – このAPIは、外部のシェルコードのブロブを単純にエミュレートする方法を提供します。指定されたバイトはIDBに追加されず、そのままエミュレートされます。これはエミュレーション環境の準備に役立ちます。例えば、flare-emu自体がこのAPIを使用して、Unicornでは公開されていないARM64 CPUのモデル固有レジスタ(MSR)を操作し、ベクトル浮動小数点(VFP)命令とレジスタアクセスを有効にします。Unicornエミュレーションオブジェクトが返され、ユーザーがさらに調査できます。
emulateFrom - このAPIは、難読化されたバイナリやシェルコードでよく見られるように、関数の境界が明確に定義されていない場合に便利です。開始アドレスを指定すると、エミュレートするものがなくなるか、フックのいずれかでエミュレーションを停止するまでエミュレートします。IDA Proでは、strictパラメータをFalseに設定して呼び出すことで動的なコード発見を有効にできます。flare-emuは、エミュレーション中に遭遇した命令をIDA Proに作成させます。
flare-emuをIDA Proにインストールするには、flare_emu.py、flare_emu_ida.py、flare_emu_hooks.pyをIDA Proのpythonディレクトリにコピーし、IDAPythonスクリプトでモジュールとしてインポートするだけです。
flare-emuをRizinにインストールするには、flare_emu.py、flare_emu_rizin.py、flare_emu_hooks.pyがPythonのモジュール検索パスに存在することを確認してください。Rizinをflare-emuのバイナリ解析コンポーネントとして使用する場合、rzpipeが必要です。
flare-emuをRadare2にインストールするには、flare_emu.py、flare_emu_radare.py、flare_emu_hooks.pyがPythonのモジュール検索パスに存在することを確認してください。Radare2をflare-emuのバイナリ解析コンポーネントとして使用する場合、r2pipeが必要です。
いずれの場合でも、flare-emuはUnicornとそのPythonバインディングに依存しています。
重要なお知らせ
flare-emuは新しいIDA Pro 7x APIを使用して記述されており、以前のバージョンのIDA Proとの下位互換性はありません。
flare-emuは多くのコード解析問題を解決するために使用できますが、一般的な用途の1つは、マルウェアバイナリ内の文字列を復号化することです。FLOSSは、文字列復号化関数を特定し、それへのすべてのクロスリファレンスで渡された文字列をエミュレーションによって復号化することで、これを自動的に実行できる優れたツールです。しかし、FLOSSが常にこれらの関数を識別し、汎用的なアプローチで適切にエミュレートできるとは限りません。時にはもう少し作業が必要になる場合があり、flare-emuに慣れれば、多くの時間を節約できます。暗号化された文字列を扱う際にマルウェア解析者が遭遇する一般的なシナリオを見てみましょう。
x86_64バイナリ内のすべての文字列を復号化する関数を特定したとします。この関数はいたるところで呼び出され、多くの異なる文字列を復号化します。IDA Proで、この関数にdecryptStringと名前を付けます。以下は、すべての文字列を復号化し、各関数呼び出しの場所に復号化された文字列をコメントとして追加し、復号化された各文字列とそのアドレスをログに記録するためのflare-emuスクリプトです。```
from future import print_function
import flare_emu
def decrypt(argv): myEH = flare_emu.EmuHelper() myEH.emulateRange(myEH.analysisHelper.getNameAddr("decryptString"), registers = {"arg1":argv[0], "arg2":argv[1], "arg3":argv[2], "arg4":argv[3]}) return myEH.getEmuString(argv[0])
def iterateCallback(eh, address, argv, userData): s = decrypt(argv) print("%s: %s" % (eh.hexString(address), s)) eh.analysisHelper.setComment(address, s, False)
if name == 'main':
eh = flare_emu.EmuHelper()
eh.iterate(eh.analysisHelper.getNameAddr("decryptString"), iterateCallback)
`__main__`では、まず`flare-emu`の`EmuHelper`クラスのインスタンスを作成します。このクラスを使って`flare-emu`のすべての操作を行います。次に、`iterate` APIを使用して、`decryptString`関数のアドレスと、`EmuHelper`がエミュレートする各クロスリファレンスごとに呼び出すコールバック関数の名前を指定します。
`iterateCallback`関数は、`EmuHelper`のインスタンス(ここでは`eh`)、クロスリファレンスのアドレス、この特定の呼び出しに渡された引数、そしてここでは`userData`という特別な辞書を受け取ります。この簡単な例では`userData`は使用しませんが、エミュレータの永続的なコンテキストとして、独自のカスタムデータを保存できるものと考えてください。ただし注意が必要です。なぜなら、`flare-emu`自体もこの辞書を使用して、タスクを実行するために必要な重要な情報を保存しているからです。そのようなデータの1つは`EmuHelper`インスタンスそのもので、`"EmuHelper"`キーに保存されています。興味があれば、ソースコードを検索してこの辞書について詳しく調べてください。このコールバック関数は単に`decrypt`関数を呼び出し、復号された文字列を出力し、その`decryptString`への呼び出しのアドレスにコメントを作成します。
`decrypt`は`EmuHelper`の2番目のインスタンスを作成し、それを使用して`decryptString`関数自体をエミュレートし、文字列を復号します。この`decryptString`関数のプロトタイプは次のとおりです。`char * decryptString(char *text, int textLength, char *key, int keyLength)`。これは文字列をその場で復号するだけです。`decrypt`関数は、`iterateCallback`関数が受け取った引数をそのまま`EmuHelper`の`emulateRange` API呼び出しに渡します。これは`x86_64`バイナリであるため、呼び出し規約では引数をスタックではなくレジスタで渡します。`flare-emu`はIDA Proが判断したアーキテクチャとファイル形式に基づいて、どのレジスタがどの引数を表すかを自動的に判断するため、少なくともある程度アーキテクチャにとらわれないコードを書くことができます。これが32ビット`x86`の場合は、代わりに`stack`引数を使用して引数を渡すことになります。例:`myEH.emulateRange(myEH.analysisHelper.getNameAddr("decryptString"), stack = [0, argv[0], argv[1], argv[2], argv[3]])`。`x86`では最初のスタック値が戻りアドレスになるため、ここではプレースホルダとして`0`を使用しています。エミュレーションが完了すると、`getEmuString` APIを呼び出して、関数に渡された最初の引数が指すメモリ位置に格納されているNULL終端文字列を取得します。
### flare-emu と idalib
* IDA Proをインストール
* Hex-Raysユーザーガイドに従ってidalibをインストール
* (仮想環境をアクティブにする)
* pip install /path/to/IDA/installation/idalib/python
* python /path/to/IDA/installation/idalib/python/py-activate-idalib.py [-d /path/to/active/IDA/installation]
* idaproをインポートしてスクリプトを作成
* 例については tests/test_flare_emu_idalib.py を参照
### Rizinを使った簡単な文字列復号シナリオ
上記と同じ例を使用する場合、Rizinで作業する際に大きく変わることはありません。違いの1つは、`flare-emu`は現在、Rizinで作業する場合、コマンドラインスクリプトまたはPythonシェル内で実行するように設計されていることです。Pythonシェルはアドホックな問題解決に最適ですが、コマンドラインスクリプトはバッチ処理に最適です。上記のスクリプトのRizin版は次のようになります(rizin内で実行するためにサンプルパスを省略することもできます):```
from __future__ import print_function
import sys
import flare_emu
def decrypt(argv, eh):
myEH = flare_emu.EmuHelper(samplePath=sys.argv[1], emuHelper=eh, isRizin=True)
myEH.emulateRange(
myEH.analysisHelper.getNameAddr("decryptString"),
registers={
"arg1": argv[0],
"arg2": argv[1],
"arg3": argv[2],
"arg4": argv[3],
},
)
return myEH.getEmuString(argv[0])
def iterateCallback(eh, address, argv, userData):
s = decrypt(argv, eh)
print("%s: %s" % (eh.hexString(address), s))
eh.analysisHelper.setComment(address, s, False)
if __name__ == "__main__":
eh = flare_emu.EmuHelper(samplePath=sys.argv[1], isRizin=True)
rz = eh.analysisHelper.r
eh.analysisHelper.setName(0x100000D60, "decryptString")
eh.iterate(eh.analysisHelper.getNameAddr("decryptString"), iterateCallback)
上記と同じ例を使用する場合、IDA ProではなくRadare2で作業してもほとんど変わりません。違いの一つは、flare-emuが現在、Radare2で作業する際にコマンドラインスクリプトまたはPythonシェル内で実行するように設計されていることです。Pythonシェルはアドホックな問題解決に最適ですが、コマンドラインスクリプトはバッチ処理に最適です。上記のスクリプトのRadare2版は次のようになります:```
from future import print_function
import flare_emu