本稿は情報セキュリティ系論文紹介 Advent Calendar 2015の11日目である.

TL;DR:

 テイント解析(taint analysis)やプログラムスライシング(program slicing),記号的実行(symbolic execution)やファジング(fuzzing)といったバイナリ解析技術を総動員して,マルウェアが通信するC&CサーバのフィンガープリントをC&Cサーバと通信せずとも生成する手法–AUTOPROBE[1]を紹介する.

背景

 マルウェアによるサイバー犯罪はC&Cサーバやコンフィグファイルの配布元,二次検体の配布元など様々な悪性サーバ(malicious server)からなるインフラによって支えられている.攻撃者はこれらの悪性サーバを頻繁に閉鎖・移転させて対策から逃れようとする.とりわけ攻撃者の命令を送信してマルウェアの動作を決定するC&Cサーバはその後の攻撃の起点となるため早期発見が望ましい.したがって不審なサーバがC&Cサーバかどうか判定するフィンガープリントが必要となる.
 しかしながら従来のフィンガープリント生成手法にはC&Cサーバとの長期的な通信を前提としている[2],サーバとして動作するマルウェアを前提としている[3]といった問題点があった.

問題定義

 あるマルウェアのファミリFに属する検体Pを入力として受け取り,Fが用いるC&Cサーバのフィンガープリントφを出力することがAUTOPROBEの目的である.C&Cサーバ側のコードは参照できず,マルウェアの検体はシンボル情報やソースコードを含んでいなくともよい.またマルウェアは複数のリクエストをC&Cサーバに送信するものとする.

提案手法

 あるサーバにマルウェアと同様のリクエストを送って,マルウェアがコマンドとして解釈できるレスポンスが返ってきたら,そのサーバは疑いようもなくC&Cサーバである–AUTOPROBEの鍵となる発想は至極単純だ.
 AUTOPROBEは検体の命令・API・システムコールの実行を–おそらくQEMUによって–トレースし,リクエストを生成する処理とレスポンスをハンドルする処理をそれぞれマルウェアから抽出する.つづいて前者によって不審なサーバに対するリクエストを生成し,後者によってレスポンスをハンドルする.レスポンスハンドリングの可否をもってフィンガープリントとするから,その処理の抽出さえできれば不審なサーバがC&Cサーバかどうか判定するまで実際のC&Cサーバと通信する必要はない–でもどうやって?

リクエスト生成

 マルウェアの多くは時間や擬似乱数,OS情報などの環境依存の値によって動作を変更する.たとえばWin32/LoadMoney.AFはレジストリキーの有無によって送信するリクエストを変更する.

 不審なサーバがC&Cサーバかどうか判定するためには攻撃者の期待するリクエストをより多く送信しより多くレスポンスを得たい.そこでAUTOPROBEは以下のアルゴリズムにしたがって複数の実行トレースを得る.これは条件分岐の度に直前のシステムコールを参照する,ある種の深さ優先探索として実装される.

 さらにAUTOPROBEはトレースをスライスし,システムコールの結果に依存する値を環境依存の値として決定論的な値・定数と区別する.ここで環境依存の値を書き換えれば攻撃者の期待するリクエストを送信できるようになる.また環境依存の値に影響するシステムコールの戻り値に時間,IPアドレス,擬似乱数,OS情報など200種類のラベルを設定しているとのことだ.

レスポンスハンドリング

 生成したリクエストをC&Cサーバに送信してレスポンスを得たら,AUTOPROBEはレスポンスの各バイトを記号値として扱い,実行トレースから検体の分岐制約を充足する解およびスライスθ1を得る.ここで探索の終了条件はclosesocketexitprocessに到達した場合,データを受信してから50個の条件分岐にわたってそのデータを参照するものが存在しない場合である.つづいてレスポンスとして正しく解釈できない値を検体に与え,実行トレースから分岐制約を充足する解およびスライスθ2を得る.ここでηは以下の式によって得られる実行経路の類似度である.

 bnとfnはそれぞれ各スライスに含まれるユニークなコードブロックとシステムコールの数を示す.AUTOPROBEはηが閾値10を下回ればレスポンスを正しく解釈していない実行経路とみなしてスライスを破棄し,上回れば別々のハンドラであるとしてそれぞれの分岐制約をフィンガープリントとして保持する.たとえばこんなふうに.

 C&Cサーバからレスポンスを得られなければ,AUTOPROBEは記号的実行とファジング,フラグレジスタの書き換えによる強制実行(forced execution)を併用してレスポンスに依存する実行経路を探索する.

 このファジングでは検体にランダム値を与えるが,検体が用いるアルゴリズムがHTTPなど既知のものであればエラーメッセージのハンドラを起点に探索する.
 AUTOPROBEはこのように探索したレスポンスのハンドラをフィンガープリントとするが,期待されるレスポンスが得られずさきほどのアルゴリズムによって探索した場合は以下のようにC&Cサーバの尤もらしさを算出する.

評価

 論文ではふたつのデータセットを用いてAUTOPROBEの性能を評価している.ひとつはSality, ZeroAccess, Ramnit, Bamital, Taidoorを含む37ファミリ10亜種計370検体.もうひとつはネットワーク通信の特徴量に基づいてフィンガープリントを生成する既存研究–CYBERPROBE[2]で用いられた19ファミリ. “which have been kindly provided to us by the authors of CYBERPROBE”って書いてるけどAUTOPROBEと同じメンバーじゃねえか.
 検体の実行時間は5分.

リクエスト生成

 まずはリクエストの生成から.可能であればC&Cサーバとの接続をともなうがAlexaトップ10,000サイトは除外している.ここでAUTOPROBEはC&Cサーバに接続せずとも検体のバイナリを分析してCYBERPROBEと同様の結果を得ている.

 AUTOPROBEはふたつのデータセットに含まれる56ファミリのトレースから105のリクエストを生成した.生成にかかった時間は平均13.2分.リクエストはすべてHTTPであったとのこと.

レスポンスハンドリング

 生成したリクエストのうち76件がC&Cサーバからのレスポンスを引き出した.さらにAUTOPROBEはHTTP 200レスポンスコードを含む同数のランダムなレスポンスを生成し,検体に与えた.これはレスポンスハンドリングの可否によって検体の異なる挙動を確認したいためだ.結果として76件のテストケース中71件(93%)で検体は異なる挙動を示した–期待される受信データを得たマルウェアは一般に10以上のシステムコールと50以上のコードブロックを実行する.残りの5件はC&Cサーバではなかった.つまりAUTOPROBEはマルウェアの通信先がC&Cサーバかどうか正しく判定できている.

ケーススタディ

 たとえばBamitalのリクエストはファイル名・GetVersionExによって得たOS情報・DGA (domain generation algorithm) によって生成したホスト名を含んでいた.AUTOPROBEはこれらの値が依存するシステムコールを得ている.

 C&Cサーバと接続せずともHTTP/1.1 200 OKを与えればBamitalのハンドラは分析できるとのこと.
 また標的型攻撃に用いられるTaidoorのリクエストはGetAdaptersInfoによって得たMACアドレスに依存する値を含んでいた.これによってTaidoorのC&Cサーバは感染端末のみで動作するレスポンスを送信していたようだ.
 ドライブバイダウンロード検体であるSalityのC&Cサーバはspm/s_tasks.php, logos_s.gif, 231013_d.exeというファイルをレスポンスに含んでいた.AUTOPROBEはこれらのファイルの存在するサーバをSalityのC&Cサーバとみなす.などなど.

限定的な探索

 Malware Domain Listに含まれる9,500アドレスの近傍/24サブネット・2.6Mアドレスのスキャン結果.

 VirusTotal, Malware Domain List, そしてURLQueryに発見されていないC&Cサーバを特定できている.

インターネット全体の探索

 ここではBGPからインターネット全体をスキャンし,7,100万ものHTTPサーバを発見している.

 このうちマルウェア3ファミリのC&Cサーバをどれだけ発見できるかCYBERPROBEと比較した結果.

 CYBERPROBEが40件のC&Cサーバを特定しているのにたいしてAUTOPROBEは54件のC&Cサーバを特定している.
 高度化するマルウェアが通信内容を難読化・秘匿する傾向にあることを鑑みると,CYBERPROBEのようにネットワーク通信の特徴量を用いる手法よりもAUTOPROBEのようにバイナリ解析を応用した手法こそ吟味されるべきだろう.

感想

 AUTOPROBEは折しも私が昨年度のインターンシップでほとんど同じようなテーマに取り組んでいたとき発表された.テイント解析のソースを受信データではなくシステムコールの結果に設定することで,リクエストのセマンティクスを復元しようとするAUTOPROBEの発想は野心的であり,さまざまなバイナリ解析技術を結集して未知のC&Cサーバを発見する手際は鮮やかというほかない.
 私は来年書くことになる卒業論文のテーマとして,インターネット接続のない閉環境におけるマルウェアの分析に本手法を応用できないか検討している.Ryzhykら[4]はデバイスドライバと環境(デバイスおよびOS)との相互作用を有限オートマトンを用いて表現し,デバイスドライバを半自動的に合成する手法を提案している.問題領域は異なるがこうした手法も検討したい.

用語解説

  • テイント解析
    • 任意のデータに設定したタグをルールにしたがって伝搬させることで,データ間の依存関係を分析する手法
  • プログラムスライシング
    • 任意のデータに依存する処理部分をプログラムから抽出する手法
  • 記号的実行
    • プログラムの分岐条件を充足論理問題とし,制約を充足する解を得る手法
  • ファジング
    • 開発者が意図しない入力を自動生成してプログラムに与えることで脆弱性を顕在化させる手法

参考文献

はじめに

2015.08.11~15にわたって開催されたセキュリティ・キャンプ全国大会 2015に解析トラックの講師として参加した.講義では「仮想化技術を用いてマルウェア解析」と題して,QEMUをベースに開発が行われているDECAFという解析プラットフォームを用いて演習を行った.

講義資料

講義内容

演習では実際のマルウェアに用いられている解析妨害機能を備えたサンプルプログラムを扱った.素のDECAFには解析妨害機能への対策が施されていない.そこで,受講者にはDECAFのプラグインを拡張し,対策手法を実装して頂いた.
演習で用いたプログラムはGitHub上で公開している.

  • 解析妨害機能を備えたサンプルプログラム
  • DECAFプラグインのひな形

ひな形にある通り,IsDebuggerPresent()をフックするDECAFプラグインは以下のように書ける.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static DECAF_Handle isdebuggerpresent_handle = DECAF_NULL_HANDLE;
typedef struct {
uint32_t call_stack[1]; //paramters and return address
DECAF_Handle hook_handle;
} IsDebuggerPresent_hook_context_t;
/*
* BOOL IsDebuggerPresent(VOID);
*/
static void IsDebuggerPresent_ret(void *param)
{
IsDebuggerPresent_hook_context_t *ctx = (IsDebuggerPresent_hook_context_t *)param;
hookapi_remove_hook(ctx->hook_handle);
DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]);
free(ctx);
}
static void IsDebuggerPresent_call(void *opaque)
{
DECAF_printf("IsDebuggerPresent ");
IsDebuggerPresent_hook_context_t *ctx = (IsDebuggerPresent_hook_context_t*)malloc(sizeof(IsDebuggerPresent_hook_context_t));
if(!ctx) return;
DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 4, ctx->call_stack);
ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0], IsDebuggerPresent_ret, ctx, sizeof(*ctx));
}
static void geteip_loadmainmodule_callback(VMI_Callback_Params* params)
{
if(strcmp(params->cp.name,targetname) == 0)
{
DECAF_printf("Process %s you spcecified starts \n", params->cp.name);
target_cr3 = params->cp.cr3;
isdebuggerpresent_handle = hookapi_hook_function_byname("kernel32.dll", "IsDebuggerPresent", 1, target_cr3, IsDebuggerPresent_call, NULL, 0);
}
}

現在のプロセスがデバッガのコンテキストで実行されていない場合,IsDebuggerPresent()は0を返す.ここで,IsDebuggerPresent()の戻り値を0にするには,モジュール(この場合はkernel32.dll)から戻る段階でeaxを書き換えてやればよい.

1
2
3
4
5
6
7
8
static void IsDebuggerPresent_ret(void *param)
{
IsDebuggerPresent_hook_context_t *ctx = (IsDebuggerPresent_hook_context_t *)param;
hookapi_remove_hook(ctx->hook_handle);
cpu_single_env->regs[R_EAX] = 0; // 追加
DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]);
free(ctx);
}

このようにDECAFのプラグインを書くことで,ゲストOSに解析用のエージェントを挿入することなくAPIの戻り値を書き換えることができる.
APIの引数を書き換えたい場合はモジュールに入る段階でコンテキスト構造体のcall_stack[]を書き換えてやればよい.

おわりに

受講者にサンドボックス開発の楽しさと難しさを実感してもらえたなら,講師として冥利に尽きる.
サンプルプログラムには4種類の解析妨害機能を実装しており,APIフックで対処できるのはうち前半2つだけとなっている.限られた演習時間の制約上,3つ目以降の解析妨害機能を回避できた受講者はいなかった.解析トラックリーダーの岩村さんから,受講者の2割がギリギリ解けないような問題を作るようにと仰せつかっていたが,やや意地悪な問題設定だったと思う.
なお,今回は拙作のサンプルを用いたが,より多くの解析妨害機能を備えたOSSにpafishがある.pafishはBackdoor.Win32.Agent.dkbp(MD5: de1af0e97e94859d372be7fcf3a5daa5)など一部のマルウェアに流用されている.

Dynamic Binary Instrumentation

Dynamic Binary Instrumentation(DBI)とは,実行中のプログラムにコードを挿入する技術である.
その目的は,プログラムの性能評価であったり,アーキテクチャのシミュレーションであったり,プログラムのデバッグであったり,プログラムのsheparding(ポリシを用いたセキュリティ制限)であったり,プログラムの最適化であったり,テイント解析などのデータフロー解析であったり,リバースエンジニアリングであったり,マルウェア解析であったり,……と多岐にわたっている.
デバッガとDBIを区別することは難しく,ニュアンスの問題になってしまうが,DBIはより「挿入するコードがプログラマブルであること」を重視している.また,instrumentation(名詞)/instrument(動詞)という語は,コードの挿入を通してプログラムの情報を取得したり,プログラムを制御したりといったニュアンスを含んでいる.
DBIのメリットとして,言語に依存しないこと,古いソフトウェアに対しても適用できること,再コンパイルの必要がないこと,動的に生成されるコードを扱うことができること,実行中のプロセスにアタッチできることが挙げられる.

Pin

Intelによって開発されているPinは,DBIフレームワークの中でも最も成功している一つであり,そのAPIの数は450種類を越える.
Pinにおけるinstrumentationの手法は,コードの実行時にinstrumentするjust-in-time Instrumentation(JITI)と,イメージ(実行ファイルや共有ライブラリ,構造体)のロード時にinstrumentするahead-of-time-insturumentation(AOTI)の二種類に大別される.
これらのinstrumentにあたって,Pinは解析対象のプログラムにpinvm.dllを挿入する.

JITI

JIITは以下の三種類を単位として行うことができる.

Instruction Level

第一に,最も低い粒度として,命令単位のinstrumentationがサポートされている.
これは,INS_AddInstrumentFunctionのコールバックとして実現される.関連するAPIは,Pin 2.1.3の時点で142種類存在する.

Basic Block Level(BBL)

第二に,Basic Block(BB)単位のinstrumentationがサポートされている.
ここでのBBとは,一つの入口と一つの出口を持ち,内部に分岐を含まない命令列のことであり,コンパイラ最適化におけるそれと同義である.Pinでは,BBL_InsHeadBBL_InsTailBBL_NextBBL_Prevといった関数を用いてBBの有向グラフを操作することができる.一方でBBL_AddInstrumentationFunctionのコールバックは存在しない.関連するAPIは,Pin 2.1.3の時点で14種類存在する.

Trace Level

第三に,Trace単位のinstrumentationがサポートされている.
Traceとは,Pin独自の単位であり,分岐命令を入り口とし,無条件分岐(jmp, call, retなど)を出口とする命令列のことである.つまり,Traceは複数のBBを含みうる.これは,TRACE_AddInstrumentFunctionのコールバックとして実現される.関連するAPIは,Pin 2.1.3の時点で14種類存在する.
Pinでは,objdumpなどと同様に線型分析法によってバイナリを逆アセンブルし,Traceを取得する.ここで,JITIに用いるVMをトラップすることなく別のTraceに分岐を行うためのtrace-linking optimization[PDF]が行われる.そのため,Trace単位のinstrumentationが命令単位のinstrumentationよりも高速になる場合がある.

AOTI

AOTIは以下の二種類を単位として行うことができる.

IMG instrumentation

第一に,IMG単位のinstrumentationがサポートされている.
IMGは,特定の実行ファイルないし共有ライブラリ,構造体に相当する単位である.これは,IMG_AddInstrumentFunction APIによって実現される.また,IMGのセクション(SEC)についてもinstrumentを行うことができる.関連するAPIは,IMGについて27種類,SECについて16種類が存在する.

RTN instrumentation

第二に,RTN単位のinstrumentationがサポートされている.
RTNとは関数(Routine)に相当する単位である.これは,RTN_AddInstrumentFunction APIによって実現される.関連するAPIは,Pin 2.1.3の時点で39種類存在する.

Transparency

このように様々な機能を備えるPinだが,マルウェア解析においてはどれほど有用なのだろうか.
Pinのinstrumentationは,素朴なDLLインジェクションによって成り立っており,Anti-Debuggingについては考えられていない.そのためPinは,親プロセス,引数,ロードされるpinvm.dll, エクスポート関数であるCharmVersionC, ZwAllocateVirtualMemoryの実行権限,KiUserApcDispatcher, KiUserCallbackDispatcher, KiUserExceptionDispatcher, LdrInitializeThunkのインラインフック,命令のパターン,セクション名,FSTENV命令,FSAVE命令,FXSAVE命令,……といった様々な要素から検出される危険性を孕んでいる.
これらは,eXaitを用いてテストできる.なお,筆者の環境ではint2e/SYSENTERについてfalse positiveが発生した.
Pinを検出するマルウェアの存在は寡聞にして知らないが,Pinをマルウェア解析に用いるのであれば,現状の構造は不適切である.
Anti-Debuggingのイタチごっこから脱するためには,エージェントをゲストOSに挿入するin-VMではなく,エージェントをゲストOSに挿入しないout-of-VMな解析環境でなければならない.

QEMU

さらにPinにはカーネルへのinstrumentationが不可能であるという欠点が存在する.
翻ってQEMUは,フルシステムエミュレーションを行うため,アーキテクチャに依存することなくカーネルへのinstrumentationを行うことができる.しかしながら,QEMUのソースコードは難解であり,PinのようにユーザーフレンドリーなAPIが提供されていない.QEMUはC言語によるメタプログラミングとオブジェクト指向の良質なサンプルであるとも言えるが,できることなら避けて通りたい道である.

TEMU

QEMUにテイント解析機能を搭載し,これまで多くの研究で用いられてきたTEMUは,QEMUのソースコードを理解するための労力を削減することに成功した(と,後述するPEMUの論文にすら書かれている)が,APIの充実度はPinに劣っている.また,カーネルモジュールをゲストOSに挿入する必要があり,マルウェアから検出される可能性があること,いまや時代遅れのQEMU 0.9.1をベースとしていることから,現代的なマルウェア解析環境の候補からは外れつつある.
後継のDECAFはQEMU 1.0.0をベースとしており,カーネルモジュールを必要としないものの,APIの充実度はTEMUと変わらない.

PEMU

こうした流れを汲んで開発されたのが,VEE’15にて新たに発表されたPEMUである.嬉しい事にオープンソースとして提供されている.
Pinはin-VMであり,マルウェアの解析やカーネルへのinstrumentationに適さない.QEMUやTEMUはout-of-VMだが,Pinほど充実したAPIを用いることができない.そこでPEMUは,両者の利点と欠点を踏まえた上で,以下の四点を目的として開発された.

  1. Rich APIs
    • Pinの既存APIをそのまま流用できること
  2. Cross-OS
    • WindowsとLinuxをサポート
  3. Strong Isolation
    • ゲストOSのRing 3やRing 0ではなく,out-of-VM(Ring -1)から監視(VM Introspection)を行うこと
  4. VM Introspection
    • ゲストOSのプロセスやカーネルのセマンティクス情報を取得するAPIを提供すること

これらを達成するため,PEMUはQEMUの動的バイナリ変換器Tiny Code Generator(TCG)に手を加えてPin APIのサポートを追加した.

Instrumentation Engine

さて,PEMUにおけるinstrumentationはどのようになっているのだろうか.
問題となるのは,QEMUとPinとの差異である.QEMUは命令とBB単位でしかinstrumentできず,PinにおけるTrace単位をサポートしていない.また,PEMUのベースとなっているQEMU 1.5.3では,BBの数が640までと制限されている.
そこで,PEMUではBBとTraceを対応付けるTRACE Constructorと,PinのAPIを通じてinstrumentするCode Injectorが導入された.

TRACE Constructor

TRACE Constructorは,複数のBBを組み合わせてTraceの単位に抽象化するものである.
まずXED2ライブラリを用いて,ゲストの実行前にQEMUでBBの先頭から条件分岐命令まで逆アセンブルを行う(/target-i386/PEMU/DISAS.c).スワップされたか,ロードされていない命令については,ページフォルトを通じて取得する.さらに,コードを挿入する命令の位置をglobal hooking point hash-table(HPHT)にキャッシュする(/target-i386/PEMU/hashTable.c).
ここにおけるアルゴリズムは以下のようになっている.

まず,Trace単位の開始アドレスであるPCの度にXED2を呼び出し,PCをTPCという保存領域に保存する.次に,全命令を逆アセンブルし,命令単位のinstrumentationが必要な場合はそのアドレスをHPHTに保存する.さらに,無条件分岐の度にBBとTraceを対応付け,BB単位のinstrumentationが必要な場合はそのアドレスをHPHTに保存する.そして,新しいBBを割り当て,次のTrace単位が始まるアドレスを探す.Trace単位のinstrumentationが必要な場合はそのアドレスをHPHTに保存する.そして,さらなるTraceを逆アセンブルしていくといった仕組みになる.

Code Injector

TraceとBBの対応が取れた後は,QEMUのTCGが提供するtcg_gen_helperを用いてHPHTを参照し,instrumentを行えばよい(/target-i386/PEMU/pemu_hook_helper.c).
IMGやSEC単位のinstrumentはmallocなどの引数を確認する(セマンティクス情報を復元する)ことで実現している.
全体像は以下のようになる.

Introspection Engine

PEMUでは,Page Global Directory(PGD)とCR3レジスタの対応を用いてプロセスの識別を行う(target-i386/PEMU/pemu_helper.h).ここでは,プロセス作成時に発生するCR3レジスタへのMOVを検出して利用する.終了したプロセスのCR3は再利用されるため,プロセスの終了を検出する必要があるが,これはexit syscallを監視することで実現できる.スレッドは,kernel stack pointerのマスクから識別する.
ゲストとのセマンティックギャップの解決にあたっては,一時的にゲストを停止してシステムコールを挿入する手法を用いる.PEMU_というprefixでsyscallに相当するAPIが提供され,getpid, gettimeofdayなどゲストOSの情報を取得するためのAPI 28種類と,open, fstat, lseekなどゲストOSを操作するためのAPI 15種を用いることができる.これらは都度レジスタコンテキストの退避と復元を伴って実行される.

評価

論文での性能評価では,32-bit Ubuntu 12.04(Linux kernel 3.0.0-31-generic-pae)をホストとし,Intel Core i7, メモリ8GBのマシンが用いられた.速度の評価には32-bit Ubuntu 11.04(Linux kernel 2.6.38-8-generic)がゲストとして用いられた.
その結果として,PEMUではQEMUよりも4.33倍,Pinよりも83.61倍のオーバーヘッドが生じることが明らかになっている.

また,eXaitを用いた検出テストのため,Windows XP 32bitがゲストとして用いられた.論文ではeXaitの17手法全てがPinを検出した一方で,いずれの手法もPEMUを検出することができなかった.

関連研究

PEMUがQEMUにPin APIのサポートを追加したのに対して,PinOSはXenにPin APIのサポートを追加した.だがPinOSのinstrumentationは貧弱であり,セマンティクス情報を取得するAPIが提供されていない.また,解析ルーチンがカーネルや解析対象からアクセスできるため,isolationとして不十分である.
また,PinOS以外にカーネルへのinstrumentationをサポートしているDRKは,LKMとして実装されているため,in-VMな手法に留まっている.

おわりに

こうして,Pin APIを用いてout-of-VMにマルウェアを解析するための道標が示された.
今後のPEMUを用いた研究に期待したい,と他人事のように言っている場合ではなく,そろそろ論文を書かなければ.

補足

PEMUは新しいといえどQEMU 1.5.3をベースとしており,やがてはQEMU 0.9.1をベースとしているTEMUのように技術的負債となるだろう.
そこで,PEMUをforkしQEMU 1.5.3からQEMU 2.2.1にアップデートを試みた.が,QEMU 2.2.1に至るまでcpu_single_env変数が廃止されたことと,謎のコンパイルエラーが発生することから頓挫中である.そのうち何とかする.

参考文献

はじめに

V2E: Combining Hardware Virtualization and Software Emulation for Transparent and Extensible Malware Analysis[PDF]“という論文を通して,Record and Replayを用いた解析環境の設計を学ぶ.

著者

この論文のラストオーサーであるYinはSyracuse Universityの助教である.彼はテイント解析の第一人者として知られ,現在はDECAFの開発を主導している.またBitBlaze ProjectのDawn Songや,マルウェア解析の大御所であるChristopher Kruegelと過去に共著を出している.
ファーストオーサーのYanは提案手法をAndroidに適用し,2013年に博士論文を著している.Android向けの拡張にあたっては,DECAFのサブプロジェクトであるDroidScopeを用いているようだ.

Record and Replay

あるいはLogging and Replay, Lockstep, 順序再演法,最小情報トレースなどと呼ばれるこれらは,仮想マシンモニタ上のイベントを記録し,再生するための技術である.乱暴に言うとhistoryからdockerfileやvagrantfileを作成し,deployするようなものだ.
一般にステートマシンにおける命令の出力は,内部状態から一意に与えられる.そこで,ある環境の初期状態と入力のみを記録(Record)し,同じ環境を別の環境の上に再生(Raplay)するといった試みがなされてきた.実マシンにおいては,時刻や割り込みなどの非決定性とその記録の困難性からRecord and Replayは不可能であるとされる.だが,仮想マシンモニタの世界ではこれらの問題をある程度無視できる.実際,VMware Workstationなどにこの機能は実装されている.
本論文では,マルウェア解析に求められるサンドボックスの特性を加味したRecord and Replayについて論じられている.かつて大居さん(@a4lg)が研究されていた内容に近い.

背景

マルウェアを解析するためのサンドボックスは検出不可能(transparent)であり,なおかつ拡張可能であるべきだ.
いかに仮想マシンモニタの存在を隠蔽しようと,Timing Attackなどが成り立つ以上,transparentなサンドボックスを実現することは不可能である.しかし,Intel VTなどハードウェア仮想化技術を用いることで,ほぼtransparentな環境を作り出すことは可能である.例えば,Etherは命令・システムコール単位の解析機能を備えているが,Xenをベースに高いtransparent性を実現している.
だが,ハードウェア仮想化技術に依存した解析環境には拡張性の点で問題がある.特に,instrumentationによる詳細な解析が不可能であるという点は,大きな痛手である.
他方,QEMUなどのソフトウェアエミュレーションは動的バイナリ変換を用いることでDBI(dynamic binary instrumentation)と解析効率の向上を実現している.しかし,transparentなサンドボックスには至れない.
QEMUの動的バイナリ変換はバイナリをコードブロック単位で変換し,softmmuからTLBを参照してアドレス変換を行うというものだ.
そしてその性質から,以下のような自己書き換えコードによって検出されうる.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
__asm{
xor eax, eax
mov edi, target
mov esi, copy_insn
mov ecx, 3
target:
rep movsw // f3 66 a5
jmp qemu_found // eb XX
nop // 90
non_qemu:
...
qemu_found:
...
copy_insn:
_emit 0xeb // eb
_emit 0x01 // 01
_emit 0x90 // 90
_emit 0xeb
_emit 0x01
_emit 0x90
}

実マシンでは,ecx*word=6バイトが1回で書き換わるが,QEMUの動的バイナリ変換では2バイトずつ3回で書き換わる.そのため,1回目の書き換えで0xeb 0x01 0xa5すなわちjmp +01になってしまい,qemu_foundにジャンプしてしまう.
その他には,ページ境界を越えてブロックの変換が行われた場合にページフォルトが発生してしまうことも考えられるし,ブロック境界でのみ割り込みが行われる,CPUサイクルの消費が著しいといった実マシンとの相違点もある.
さらに問題なのが,フラグの遅延評価だ.例えば,cmpjzの組み合わせなどの条件分岐は,EFLAGSを更新する.だが,QEMUにおいてはcmpが実行される段階でEFLAGSの計算は行われない.実際に計算されるのはjzの実行時,それも分岐を決定するためのZFのみが計算される.この設計はエミュレーションの高速化に寄与しているが,もちろん検出に用いることが可能だ.
当然ながら命令のエミュレーション自体にも限界がある.SIMDさえ厳しいのだ,システム管理モードやIntel TXTなんてものは考えたくないだろう.
このように,QEMUによるエミュレーションが検出される余地は枚挙に暇がない.
なお,解析環境検出をテーマとした最近の研究では,第2回システム系論文輪読会で紹介したBareCloudや忠鉢さん(@yuzuhara)のTENTACLEなどがある.

研究目的

ハードウェア仮想化技術を用いる解析環境とソフトウェアエミュレーションを用いる解析環境にはそれぞれ問題がある.そこで,ハードウェア仮想化技術を用いる解析環境でRecordを行い,ソフトウェアエミュレーションを用いる解析環境でReplayを行うことで,検出不可能性と拡張可能性という二つの目的について達成したのが,今回紹介するV2Eである.

形式的定義

本論文におけるRecord and Replayの設計はどのようなものか.
Recorderにおける遷移関数fについて,毎時iにおけるプログラムの状態をSiとし,入力をIiとする.すなわちfSi= f(Si-1, Ii-1)と表される.
次にReplayerにおける遷移関数f'についてプログラムの初期状態をS0とし,全ての入力をIとしたとき,f = f'と言えないだろうか.
これは二つのチューリング機械の同値性が解決可能であるかという問題に相当する.だがEQTM= {(M1, M2)|M1とM2はTMであり,L(M1) = L(M2)}は判定不可能とされ(『計算理論の基礎』における定理5.4),実装上でもハードウェアの割り込みなどの要因からf != f'となってしまう.
そこでV2Eは,プログラムの初期状態と全ての入力を保存することに加え,Sj= S'jとなるようなjについて状態の変化を保存することにした.これはj= Sj- Sj-1と表される.
ここで,新たな遷移関数f'rS'i= f'r(S'i-1, Ii-1, ⊿i)として定義する.これは,i!= nullのときS'i-1+ ⊿iと同値であり,それ以外についてf'(Si-1, Ii-1)と同値をとる.
なお,S0, I, およびf'r, S'i= Sii ∈ [0, n]について恒真である.
これを実装に起こすと,特定の命令やイベントが正しくエミュレート可能な場合は単にそれらをエミュレートし,そうでなければ状態の変化を記録し,Replayにあたって変更を適用するというアプローチになる.

理論と実際

プログラムの大部分を占めるmov, push, popなどのデータ転送命令,call, ret, jz, jmpなどの制御転送命令,add, shlなどの整数演算命令におけるエミュレーションは失敗しないものと見做せる.これらはそのままRecorderでエミュレートされる.
一方で,割り込み,MMIO, Port IO, DMA, TSCについては,V2Eは既存研究を踏襲し,監視領域においてのみこれらをRecordするようになっている.
では,例外,モデル固有レジスタ,cpuidはどうすべきだろうか.既存手法は,これらのエミュレーションは困難であるという理由から,そもそも入力として扱わない戦略を採っていたようだ.V2Eではこれらを, すなわちエミュレートが困難な状態の変化としてRecordすることで,Replayの正確性を高めている.
次に問題となるのが浮動小数点演算とSIMDの扱いだ.MMXやSSEを正確にエミュレートするのは難しい.だが,として命令の結果を記録する設計は大幅なパフォーマンスの低下を招く.そこでV2EはReplayにあたってこれらの命令をパススルーする.もちろん,ReplayerはSIMDをサポートしているマシンで実行されることが前提にある.

Transparent Recorder

RecorderはKVMを用いて実装されている.
V2EはRecord対象の領域とそれ以外のシステムを分割するため,TDP(two dimensional paging)を用いている.何のことかと思ったら,Intel EPTやAMD NPTの総称らしい.

要するに,TDPとは仮想マシンと物理マシン間のページテーブルのことだ.通常のページングでは,メモリアクセスに応じてMMUによってページテーブルが参照され,仮想アドレスが物理アドレスへと変換される.TDPでは,ゲストマシンからの仮想メモリ空間へのアクセスに応じてCR3にセットされたページテーブルが参照され,ゲスト物理アドレスがホスト物理アドレスへと変換される.
この仕組を用いたV2Eは,監視対象用のTDPテーブルと,それ以外用のTDPテーブルを別々に作成する.マルウェアに属するページはCR3の監視に基づき監視対象用のTDPテーブルに書き込まれる.マルウェアとそれ以外の部分のインタラクションはTDPページフォルトやVMExitによって媒介される.共有されるデータは読み取り専用として双方に与えられる.なお,TDPページフォルトに応じてCPUの状態が入力Iiとして保存される.

Precise Replayer

ReplayerはTEMUを用いて実装されている.バイナリ変換器に中間表現がなく,単純に古いQEMU 0.9.1をベースとしているのが玉に瑕だが,TEMUは動的テイント解析に求められる機能をほぼ網羅している.
TEMUはプラグインを共有ライブラリとしてロードし,コールバック関数からテイント解析の機能を呼び出すプラットフォームとなっている.V2EのReplayerは既存のプラグインであるtracecap, unpackerを用いる.だがRecordされたログには監視対象の情報のみが記述されているため,
return ((*TEMU_cpu_hflags & HF_CPL_MASK) != 3)といった,現在実行しているコードが解析対象のものかどうか判定するコードは除去されているようだ.TEMUのプラグインについては,こうした僅かな変更しか施されていない.
一方でその下で動くQEMUには結構手が加えられている.フラグの遅延評価は廃止され,ページフォルト以外の例外は除去されており,SIMDについては独自のヘルパー関数が追加されたようだ.QEMUのdyngenに手を加えるのはなかなか骨の折れる作業だと思う.
さて,Replayを行うためには,ReplayerはRecorderと同様のページングの仕組みをエミュレーションによって再現しなければならない.そこで,V2Eは物理ページコンテナという仕組みを用いている.これは,物理ページがログからロードされていることを示すものである.通常,物理ページコンテナは監視対象用のTDPテーブルを複製する.Replayされたプログラムが物理ページコンテナに存在しないページにアクセスした場合,Replayerは適切なタイミングでログからCPUの状態を復元し,ロードするようになっている.
V2Eにおけるテイント解析はおそらく,マルウェアのメモリ領域を正確に把握するためのものではない.それは,Recorderの段階でTDPテーブルの分割というアプローチによって実現されるべきものだからだ.TEMUのプラグインを使いたかったのだろうが,論文中からはあまりテイント解析を導入することのメリットが読み取れなかった.ここでのテイント解析は解析環境検出の対策に先立つものとして設定されているのだろうか.

評価

解析環境検出については問題なし.cpuid, rdtsc, cmpxch8b, icbp, rep stosb, fnstcwといった命令や,一般保護例外などについてテストされている.なお,rdtscについてはVMRESUME前にホストのTSCを参照することで対応している.
in-the-wildのマルウェアについても実験が行われており,アンパッカーとして期待できるパフォーマンスを見せている.
Recordにおける速度だが,コンテキストスイッチが頻繁に発生するカーネルモードルートキットで17倍,Internet Explorerで5倍と高速だ.KVMのシングルステップモードには3000倍のオーバーヘッドがあると言うのに.
全体的にpositiveな結果で,かなり良い.

おわりに

マルウェアによる解析環境検出に対して,復数の環境を組み合わせたRecord and Replayを用いる研究について紹介した.何より,監視対象とそれ以外で別個にTDPテーブルを作るというアプローチが素晴らしい.自分も研究でテイント解析を扱っているが,この流れを包摂していきたい.
ただ,これはサンドボックス全般に言えることなのだが,感染ホストにおけるユーザーのブラウザ操作が条件分岐に影響するMITBマルウェアについて,どう対処すべきなのだろうか.正常系のユーザーのブラウザ操作をデータセットとしてRecorderに与えたいところだが,果たしてどうなるのか.
このエントリはシステム系論文紹介 Advent Calendar 2014の9日目として書かれた.

参考文献

はじめに

ここでは,BitBlazeのうち,静的解析に特化したコンポーネントであるVineを動かしてみる.

BitBlaze

BitBlazeはDawn Songらによるバイナリ解析プラットフォームで,2008年にBitBlaze: A New Approach to Computer Security via Binary Analysis [PDF]が発表されて以来,数多くの研究に用いられてきた.BitBlazeは,動的解析コンポーネントのTEMU,静的解析コンポーネントのVine,動的シンボリック実行コンポーネントのRudderから構成される.このうち,TEMUとVineのソースコードが公開されている.

TEMU: The BitBlaze Dynamic Analysis Component

TEMUはQEMUをベースとしたエミュレータで,テイント解析(taint analysis)の機能を備えている.テイント解析とは,タグを設定したデータの伝搬を追跡することで,データ同士の依存関係を解析する技術である.TEMUはtracecapというプラグインを用いて,ゲストOS上で動作するアプリケーションのトレースログを取得することができる.

Vine: The BitBlaze Static Analysis Component

Vineは,逆アセンブリやTEMUのトレースファイルから,中間表現VineILや最弱事前条件,STP formulaなどを出力する.公開されているVineには,TEMU/tracecapのトレースファイルとしてfive.traceが同梱されている.これを例にVineの機能を見てみよう.

Vineのインストール

Vine installation and user manualの通り.OCamlで記述されているため,関連のパッケージを導入する必要がある.また,32bit環境での動作を前提としている.

1
2
3
4
sudo apt-get install g++ ocaml ocaml-findlib libgdome2-ocaml-dev camlidl \
libextlib-ocaml-dev ocaml-native-compilers \
libocamlgraph-ocaml-dev binutils-dev texlive \
texlive-latex-extra transfig hevea

trace_reader

TEMU/tracecapが出力するトレースファイルはhuman-readableではなく,閲覧はVineのtrace_readerを介して行う必要がある.five.traceでは,T1がタグの識別子となっており,T0はデータに設定されたタグが存在しないことを意味する.なお,ここではキーボードからの入力にタグが設定されている.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
../trace_utils/trace_reader -trace five.trace | grep T1 | head -n 20
42075911: movzbl (%eax),%eax R@eax[0x40014000][4] T0 M@0x40014000[0x00000035][1] T1 {1 (1001, 0) ()()()}
42077e0c: cmp $0xffffffff,%eax I@0x00000000[0xffffffff][1] T0 R@eax[0x00000035][4] T1 {1 (1001, 0) ()()()}
42077e14: movzbl (%edx),%eax R@eax[0x00000035][4] T1 {1 (1001, 0) ()()()} M@0x40014000[0x00000035][1] T1 {1 (1001, 0) ()()()}
4205abc5: mov %eax,-0xac(%ebp) R@eax[0x00000035][4] T1 {1 (1001, 0) ()()()} M@0xbffff69c[0x00000000][4] T0
4205abce: mov -0xa8(%ebp),%eax R@eax[0x00000035][4] T1 {1 (1001, 0) ()()()} M@0xbffff6a0[0x00000000][4] T0
4205abd5: cmpl $0xffffffff,-0xac(%ebp) I@0x00000000[0xffffffff][1] T0 M@0xbffff69c[0x00000035][4] T1 {1 (1001, 0) ()()()}
4205abf5: mov -0xac(%ebp),%edx R@edx[0x40014001][4] T0 M@0xbffff69c[0x00000035][4] T1 {1 (1001, 0) ()()()}
4205ac0b: cmpl $0xffffffff,-0xac(%ebp) I@0x00000000[0xffffffff][1] T0 M@0xbffff69c[0x00000035][4] T1 {1 (1001, 0) ()()()}
4205ac14: movzbl -0xac(%ebp),%eax R@eax[0x42130b80][4] T0 M@0xbffff69c[0x00000035][1] T1 {1 (1001, 0) ()()()}
4205ac24: push %eax R@eax[0x00000035][4] T1 {1 (1001, 0) ()()()} M@0xbffff604[0x4213030c][4] T0
4205ac25: mov 0x8(%ebp),%eax R@eax[0x00000035][4] T1 {1 (1001, 0) ()()()} M@0xbffff750[0x4212d980][4] T0
4207793a: mov 0xc(%ebp),%edx R@edx[0x00000035][4] T1 {1 (1001, 0) ()()()} M@0xbffff604[0x00000035][4] T1 {1 (1001, 0) ()()()}
42077945: cmp %dl,-0x1(%eax) R@dl[0x00000035][1] T1 {1 (1001, 0) ()()()} M@0x40014000[0x00000035][1] T1 {1 (1001, 0) ()()()}
42077974: movzbl %dl,%eax R@dl[0x00000035][1] T1 {1 (1001, 0) ()()()} R@eax[0x40014000][4] T0
42077960: cmp $0xffffffff,%eax I@0x00000000[0xffffffff][1] T0 R@eax[0x00000035][4] T1 {1 (1001, 0) ()()()}
4205ac39: movzbl -0x9d(%ebp),%eax R@eax[0x00000035][4] T1 {1 (1001, 0) ()()()} M@0xbffff6ab[0x00000064][1] T0
4205c550: mov $0xa,%edx I@0x00000000[0x0000000a][4] T0 R@edx[0x00000035][4] T1 {1 (1001, 0) ()()()}
4205c566: cmpl $0xffffffff,-0xac(%ebp) I@0x00000000[0xffffffff][1] T0 M@0xbffff69c[0x00000035][4] T1 {1 (1001, 0) ()()()}
4205dcd1: movzbl (%eax),%edi R@edi[0x00000000][4] T0 M@0x40014000[0x00000035][1] T1 {1 (1001, 0) ()()()}
4205dcd8: mov %edi,-0xac(%ebp) R@edi[0x00000035][4] T1 {1 (1001, 0) ()()()} M@0xbffff69c[0x00000035][4] T1 {1 (1001, 0) ()()()}

VineIL

Vineが生成するVineILは静的単一代入形式の中間表現であり,CFGの情報が損なわれることはない.中間表現の生成をinstruction liftingという.なお,VineILはValgrindの中間表現を扱うライブラリVEXをもとに生成される.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
~/vine/examples$ ../trace_utils/appreplay -trace five.trace -ir-out five.ir
~/vine/examples$ cat five.ir | awk 'NR==1000,NR==1020'
R_CC_OP_16:reg32_t = 0xd:reg32_t;
T_32t9_1114:reg32_t = cast(T_8t3_1108:reg8_t)U:reg32_t;
R_CC_DEP1_17:reg32_t = T_32t9_1114:reg32_t;
R_CC_DEP2_18:reg32_t = 0:reg32_t;
R_CC_NDEP_19:reg32_t = 0:reg32_t;
/*eflags thunk: logic*/
R_CF_10:reg1_t = false;
T_7_1115:reg8_t = cast(T_32t9_1114:reg32_t)L:reg8_t;
R_PF_11:reg1_t =
!cast(
((T_7_1115:reg8_t >> 7:reg32_t ^ T_7_1115:reg8_t >> 6:reg32_t)
^ (T_7_1115:reg8_t >> 5:reg32_t ^ T_7_1115:reg8_t >> 4:reg32_t))
^
((T_7_1115:reg8_t >> 3:reg32_t ^ T_7_1115:reg8_t >> 2:reg32_t)
^ (T_7_1115:reg8_t >> 1:reg32_t ^ T_7_1115:reg8_t))
)L:reg1_t;
R_AF_12:reg1_t = false;
R_ZF_13:reg1_t = T_32t9_1114:reg32_t == 0:reg32_t;
R_SF_14:reg1_t = 1:reg32_t == (1:reg32_t & T_32t9_1114:reg32_t >> 7:reg32_t);
R_OF_15:reg1_t = false;

出力している行の番号は適当.

最弱事前条件

最弱事前条件(weakest precondition)はDijkstraによる述語変換意味論の基礎を成す概念である.Sを条件,式をRとした際,最弱事前条件WP(S, R)は,Rを実行する前にS’が成り立っていればSの実行後にSが成り立つ最も弱い条件S’を表す.単純な例だと,以下のように表現される.

1
2
3
WP(S, x=e) = S[e/x]
WP(new == org, new = new+taint)
= new+taint == org

Vineにおける最弱事前条件の出力は,以下のようになる.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
~/vine/examples$ ../trace_utils/appreplay -trace five.trace -wp-out five.wp
~/vine/examples$ grep let five.wp | head -n 20
let post_1681:reg1_t = true in
let R_EAX_1682:reg32_t = 0x40014000:reg32_t in
let idx_1683:reg32_t = 0x40014000:reg32_t in
let val_1684:reg8_t = INPUT_1001_0000_61:reg8_t in
let temp_1685:reg8_t = val_1684:reg8_t & 0xff:reg8_t in
let temp_1686:reg8_t = temp_1685:reg8_t >> 0:reg8_t in
let towrite_1688:reg8_t = cast(temp_1686:reg8_t)L:reg8_t in
let mem_arr_1687:reg8_t[4294967296] =
let mem_arr_57[0x40014000:reg32_t]:reg8_t = towrite_1688:reg8_t in
mem_arr_57:reg8_t[4294967296]
in
let R_EAX_1689:reg32_t = 0x40014000:reg32_t in
let R_GDT_1690:reg32_t = 0xc02dbd80:reg32_t in
let R_LDT_1691:reg32_t = 0xc02dcc58:reg32_t in
let R_DFLAG_1692:reg32_t = 1:reg32_t in
let T_32t0_1693:reg32_t = R_EAX_1689:reg32_t in
let T_8t2_1694:reg8_t = mem_arr_1687[0x40014000:reg32_t]:reg8_t in
let T_32t1_1695:reg32_t = cast(T_8t2_1694:reg8_t)U:reg32_t in
let R_EAX_1696:reg32_t = T_32t1_1695:reg32_t in
let temp_1697:reg32_t = R_EAX_1696:reg32_t & 0xffff00ff:reg32_t in
let temp_1698:reg32_t = cast(0:reg8_t)U:reg32_t in
let temp_1699:reg32_t = temp_1698:reg32_t << 8:reg8_t in

このような手法でソースコードの存在しないバイナリから最弱事前条件を抽出し,仕様書を復元する試みがあるらしい.ゾッとする.

STP formula

Vineは,TEMUのトレースファイルからSTP formulaを出力する.STP formulaとはSMTソルバであるSTP用のフォーマットである.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
~/vine/examples$ ../trace_utils/appreplay -trace five.trace -stp-out five.stp
~/vine/examples$ head -n 20 five.stp
% free variables:
mem_arr_57 : ARRAY BITVECTOR(64) OF BITVECTOR(8);
INPUT_1001_0000_61 : BITVECTOR(8);
% end free variables.
ASSERT( 0bin1 =
(LET post_1681 =
0bin1
IN
(LET R_EAX_1682 =
0hex40014000
IN
(LET idx_1683 =
0hex40014000
IN
(LET val_1684 =
INPUT_1001_0000_61
IN
(LET temp_1685 =

SMTソルバは充足可能性問題を解く.では,ここで与えられる命題とは何か.それは,任意の初期値がトレースファイルの結果と一致するというものだ.STPは命題が充足可能か解こうとし,充足不能であった場合は反例を出力する.

1
2
3
~/vine/examples$ cat >>five.stp
QUERY(FALSE);
COUNTEREXAMPLE;

ここで出力される反例は,実行経路に影響を与えた入力値となるようだ.five.traceでは,0x35(ASCIIで5)という入力値が分岐に影響を与えている.

1
2
3
~/vine/examples$ ../stp/stp five.stp
Invalid.
ASSERT( INPUT_1001_0_61 = 0hex35 );

これはトレースファイルに対する静的なシンボリック実行だと言える.シンボリック実行とは,記号によって表現したプログラムの変数を操作することで,実行経路に影響する制約を抽出する静的解析の手法である.シンボリック実行は到達定義の解析などモデルベーステストの領域で発展してきたが,マルウェアの挙動解析に役立てられないだろうか.

BAP

BAPはDavid BrumleyらによるVineの再実装である.彼らはCMUに所属しており,PPPのメンバーでもある.彼らは脆弱性解析の自動化をmotivationとしてBitBlazeを扱ってきた.BAPはVineをより発展させたプロジェクトであり,逆アセンブリやTEMUのトレースファイルから中間表現BILを生成するほか,CFGのみならずCDG(control dependence graphs)やDDG(data dependence graphs)の出力をサポートしているようだ.

おわりに

TEMUのトレースファイルについて,ざっくりVineによる静的解析を行った.とりあえず動かしてみただけなので,コードを噛み砕く必要がある.気になるのはやはり中間表現のフォーマットだ.一度,QEMU,Valgrind,DynamoRIO,LLVM,Vine,BAPなどの中間表現について,対応を整理したほうが良いかもしれない.