いまさらながら,情報セキュリティ系論文紹介 Advent Calendar 2016あるいはBitVisor Advent Calendar 2016に投稿されるはずだった文章を供養する.

はじめに

 本稿では,シン,すなわち薄いハイパーバイザ (thin hypervisor) の動向を紹介する.
 薄いハイパーバイザとは,小規模であることを志向したハイパーバイザだ——ということにしておこう.小規模というのは,一部のイベントのみトラップするということだ.メリットとしては,実装・学習コストやオーバーヘッド,TCB (trusted computing base) を削減できる点が挙げられる.
 用語の初出はBitVisorの論文[T. Shinagawa, et al. VEE’09]だが,多くはWindows向けルートキットとして開発されたBlue Pillという手法[J. Rutkowska. Black Hat USA’06]の流れを汲んでいる.そのため,ブート時からゲストを掌握するのではなく,のちほど——あえてchain of trustの構築を放棄して——カーネルドライバとしてロードされるものが一般的である.そのほか,単一のゲストのみ対象とする,セキュリティを意識しているといった傾向が見られる.
 前編となる今回は,3種類の薄いハイパーバイザを見ていく.

ksm

リポジトリ https://github.com/asamy/ksm
形態 カーネルドライバ
仮想化支援機能 Intel VT-x with EPT
サポートしている環境 Windows 7/8/8.1/10, Linux
言語 C, アセンブリ言語
ライセンス GNU GPL v2

 ksmは高速,拡張可能かつ小規模であることを旨としているハイパーバイザで,アンチウイルスソフトウェアやサンドボックスへの利用を意識して開発されている.機能は以下の通り:

  • IDT Shadowing
  • EPT violation #VE (Broadwell以降)
  • EPTP switching VMFUNC (Haswell以降,もしサポートされていなければVMCALLで代替)
  • Builtin Userspace physical memory sandboxer (ビルドオプション)
  • Builtin Introspection engine (ビルドオプション)
  • APIC virtualization (実験的機能)
  • VMX Nesting (実験的機能)

 主なソースコードは以下の通り:

main_nt.c, main_linux.c カーネルドライバのエントリポイント,ioctlディスパッチ
ksm.c ハイパーバイザ全体の初期化
vmx.S, vmx.asm IDTやEPT violationのトラップ,ゲストのレジスタ退避
vcpu.c VMCSの読み書き
exit.c VMExitのハンドラ
um/um.c ユーザーランドのエージェント
sandbox.c サンドボックス機能
introspect.c イントロスペクション機能
epage.c EPTフック機能
mm.c メモリ管理
hotplug.c CPUホットプラグに関する処理

 カーネルドライバをロードし,ユーザーランドのエージェントからioctlを発行,サンドボックス機能またはイントロスペクション機能,EPT機能を呼び出すという流れになっている.詳細はDocumentation/SPEC.rstを参照のこと.

SimpleVisor

リポジトリ https://github.com/ionescu007/SimpleVisor
形態 カーネルドライバ
仮想化支援機能 Intel VT-x with EPT, AMD-V
サポートしている環境 Windows 8/8.1/10, UEFI
言語 C, アセンブリ言語
ライセンス 2 clause BSD

 SimpleVisorは,名前の通り極力シンプルであることをめざしたハイパーバイザだ.全体で1700行程度.開発者はWindows Internalsの著者のひとりで,README.mdにはこう書かれている:

Have you always been curious on how to build a hypervisor? Has Intel’s documentation (the many hundreds of pages) gotten you down? Have the samples you’ve found online just made things more confusing, or required weeks of reading through dozens of thousands of lines and code? If so, SimpleVisor might be the project for you.

 そういうわけで,SimpleVisorはIntel SDM (とくに,ハイパーバイザまわりはVol. 3C) の解読にうんざりした人向けだ.学習にはもってこい.CPUID, INVD, VMX, XSETBVのみトラップするようになっており,ネストには対応していない.しかし,XenやBochsでさえ追いついていない最新の仮想化支援機能にいちはやく追随しようとしている.
 主なソースコードは以下の通り:

nt/shvos.c カーネルドライバのエントリポイント
shv.c VMExit/VMEntryのコールバック関数の登録
shvvp.c コールバック関数の実体,仮想CPUの初期化
shvvmx.c VMCSの初期化
shvvmxhv.c VMExitのハンドラ
shvutil.c GDTの変換

 シンプル.

HyperPlatform

リポジトリ https://github.com/tandasat/HyperPlatform
形態 カーネルドライバ
仮想化支援機能 Intel VT-x with EPT
サポートしている環境 Windows 7/8.1/10
言語 C++, アセンブリ言語
ライセンス MIT

 HyperPlatformは,カーネルランドで動くコード,すなわちWindows向けルートキットやWindowsカーネル自体の解析を目的として開発されているハイパーバイザ.物理メモリと仮想メモリへのアクセス,関数呼び出し,命令単位のコード実行を監視できるようになっている[S. Tanda. REcon’16]
 主なソースコードは以下の通り:

driver.cpp カーネルドライバのエントリポイント,各種コールバック関数の登録
log.cpp ログ出力
global_object.cpp グローバル変数の初期化
performance.cpp パフォーマンス計測
util.cpp PTE_BASEの取得,メモリアドレス変換,VMCALLのラッパなど
power_callback.cpp 電源状態のコールバック関数
hotplug_callback.cpp CPUホットプラグのコールバック関数
vm.cpp 仮想CPUの初期化,VMCSの読み書き
ept.cpp EPTの構成
vmm.cpp 命令のトラップ,VMExitのハンドラ
Arch/x64/x64.asm, Arch/x86/x86.asm VMX命令呼び出しにともなう命令列
kernel_stl.cpp ntoskrnl経由でカーネルドライバからSTLを利用する

 エントリポイントから手続き的に書き下されていて,わかりやすい.ハイパーバイザとしての機能もさることながら,STLを強引に呼び出すハックがかっこいい.
 この拡張例には以下がある:

MemoryMon カーネルランドへのコード挿入を検知する
EopMon マルウェアによる特権昇格攻撃を検知する
DdiMon EPTを用いたAPIフック
GuardMon PatchGuardの挙動解析

 いずれもカーネルドライバのエントリポイントで追加機能を初期化するしくみ.
 やや温め納豆は遠くなりにけり.

おわりに

 いずれもSandy Bridge以降のいまどきの環境であれば動作する.
 卒業研究ではQEMUをベースとしたマルウェア解析環境を開発していたのだけど,やはり速度面に難があるし,このあたりの技術を再検討しないとなー.
 なお,今回取り上げなかったハイパーバイザには以下のようなものがある:

MoRE ルートキット文脈のハイパーバイザ.やや古い
HyperBone HyperPlatformと似た機能をもつ.やや古い
Noah 未踏のアレ.OS X上でLinuxバイナリを動かす.MacBook持ってないので試せないのだわ
Bareflank type 1, 2, ドライバいずれの形態のVMMもサポートしたライブラリ.しかもC++で書ける

 なかでもBareflankがヤバいので後編ではこれを読んでいきます.

はじめに

いわゆるVM床抜きをXenで試す.

Xenのインストール

いつかインストールしたXen 4.4.1を用いた.

  • 依存パッケージ

    1
    # sudo apt-get install wget git bcc bin86 gawk bridge-utils iproute libcurl3 libcurl4-openssl-dev bzip2 module-init-tools pciutils-dev build-essential make gcc libc6-dev libc6-dev-i386 linux-libc-dev zlib1g-dev python python-dev python-twisted python-gevent libncurses5-dev patch libvncserver-dev libssl-dev libsdl-dev iasl libbz2-dev e2fslibs-dev git-core uuid-dev ocaml libx11-dev bison flex ocaml-findlib xz-utils gettext libyajl-dev libpixman-1-dev libaio-dev libfdt-dev cabextract libglib2.0-dev autoconf automake libtool check libjansson-dev libfuse-dev
  • Xenのビルド

    1
    2
    3
    4
    5
    6
    7
    # wget http://bits.xensource.com/oss-xen/release/4.4.1/xen-4.4.1.tar.gz
    # tar xzvf xen-4.4.1.tar.gz
    # cd ./xen-4.4.1
    # export C_INCLUDE_PATH=/usr/include/x86_64-linux-gnu
    # ./configure --enable-systemd --enable-githttp
    # make -j4 dist-xen
    # make -j4 dist-tools
  • DomUの設定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # sudo su
    # make -j4 install-xen
    # make -j4 install-tools
    # echo "GRUB_CMDLINE_XEN_DEFAULT=\"dom0_mem=4096M,max:4096M dom0_max_vcpus=4 dom0_vcpus_pin=true hap_1gb=false hap_2mb=false\"" >> /etc/default/grub
    # echo "/usr/local/lib" > /etc/ld.so.conf.d/xen.conf
    # ldconfig
    # update-grub
    # echo "none /proc/xen xenfs defaults,nofail 0 0" >> /etc/fstab
    # echo "xen-evtchn" >> /etc/modules
    # echo "xen-privcmd" >> /etc/modules
    # update-rc.d xencommons defaults 19 18
    # update-rc.d xendomains defaults 21 20
    # update-rc.d xen-watchdog defaults 22 23
    # reboot
  • 動作確認

    1
    2
    # sudo xen-detect
    Running in PV context on Xen v4.4.

ハイパーコールの追加

予約済みの__HYPERVISOR_xc_reserved_opに代わって39番目のハイパーコールを定義する.

  • xen-4.4.1/xen/include/public/xen.h

    1
    2
    3
    4
    5
    6
    7
    8
    @@ -100,6 +100,7 @@
    #define __HYPERVISOR_domctl 36
    #define __HYPERVISOR_kexec_op 37
    #define __HYPERVISOR_tmem_op 38
    +#define __HYPERVISOR_rdtsc_hypercall 39
    #define __HYPERVISOR_xc_reserved_op 39 /* reserved for XenClient */
    /* Architecture-specific hypercall definitions. */
  • xen-4.4.1/xen/arch/x86/x86_64/entry.S

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @@ -757,6 +757,7 @@
    .quad do_domctl
    .quad do_kexec_op
    .quad do_tmem_op
    + .quad do_rdtsc_hypercall
    .rept __HYPERVISOR_arch_0-((.-hypercall_table)/8)
    .quad do_ni_hypercall
    .endr
    @@ -805,6 +806,7 @@
    .byte 1 /* do_domctl */
    .byte 2 /* do_kexec */
    .byte 1 /* do_tmem_op */
    + .byte 1 /* do_rdtsc_hypercall */
    .rept __HYPERVISOR_arch_0-(.-hypercall_args_table)
    .byte 0 /* do_ni_hypercall */
    .endr
  • xen-4.4.1/xen/include/asm-x86/hypercall.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @@ -110,4 +110,8 @@
    arch_compat_vcpu_op(
    int cmd, struct vcpu *v, XEN_GUEST_HANDLE_PARAM(void) arg);
    +extern int
    +do_rdtsc_hypercall(
    + char* str);
    +
    #endif /* __ASM_X86_HYPERCALL_H__ */
  • xen-4.4.1/xen/arch/x86/traps.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @@ -3762,6 +3762,14 @@
    __domain_crash_synchronous();
    }
    +int do_rdtsc_hypercall(char* str)
    +{
    + unsigned long long tsc;
    + __asm__ volatile("rdtsc" : "=A" (tsc));
    + printk("str: %s, tsc: %llu\n", str, tsc);
    + return 0;
    +}
    +
    /*
    * Local variables:
    * mode: C

このハイパーコールは引数として受け取った文字列をTSCの値とともにコンソールログに出力する.有効化するには再度make -j4 dist-xenして再起動するとよい.

ハイパーコールの呼び出し

ハイパーコールの呼び出しはlibxcやlibxlによって抽象化されているが,ここではそれらが内部で参照している/proc/xen/privcmdに対してioctlを発行してみる.

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
38
39
40
41
42
// rdtsc_hypercall.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <fcntl.h>
#include <string.h>
#include <xenctrl.h>
#include <xen/sys/privcmd.h>
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("input the param");
exit(1);
}
char *message = malloc(sizeof(char)*(strlen(argv[1])+1));
strcpy(message, argv[1]);
privcmd_hypercall_t my_hypercall = {
39, // __HYPERVISOR_rdtsc_hypercall
{(__u64)message, 0, 0, 0, 0}
};
int fd = open("/proc/xen/privcmd", O_RDWR);
if(fd < 0)
{
printf("cannot open /proc/xen/privcmd");
exit(1);
}
if(!ioctl(fd, IOCTL_PRIVCMD_HYPERCALL, &my_hypercall))
{
printf("cannot call do_rdtsc_hypercall");
exit(1);
}
return 0;
}

このプログラムを実行するとハイパーコールが呼び出されたことが分かる.

1
2
3
4
# gcc -o rdtsc_hypercall rdtsc_hypercall.c
# sudo ./rdtsc_hypercall test
# sudo xl dmesg | less
(XEN) str: test, tsc: 2835883086

おわりに

Xenに追加したハイパーコールを呼び出すことができた.
さしあたってはMirage 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変数が廃止されたことと,謎のコンパイルエラーが発生することから頓挫中である.そのうち何とかする.

参考文献

耐解析機能の分類

Gabriel N. BarbosaRodrigo R. Brancoは,マルウェアの耐解析機能をAnti-Debugging, Anti-Disassembly, Obfuscation, Anti-VMの四種類に分類した.彼らはIntelのセキュリティ研究者であり,マルウェアに備わった耐解析機能の統計を過去二回に渡って発信してきた.
気になるのは,サンドボックスの実装にあたって問題となるAnti-VMだ.8,103,167もの検体を用いた調査によると,その内訳は以下のようになっている.

これらはいずれもVMware社製品を検出するための手法である.残念ながら他の仮想マシンモニタについての情報は掲載されていない

仮想マシンモニタの分類

以前にも少しばかり述べた,サンドボックスの透明性(transparency)に関する話を蒸し返そう.
サンドボックスにOut-of-the-boxなVMIを採用すれば,少なくともAnti-Debuggingについては無視できる.
では,サンドボックスにXenを用いれば,QEMUより検出されにくくなるだろうか.あるいは,その逆はどうだろうか.
多くの研究者が好き勝手なことを言っているが,仮想マシンモニタの実現手法をもってサンドボックスの透明性を語るのは些か性急だろう.なぜなら,サンドボックスの透明性はその設計ではなくマルウェアの実装によって左右されるからだ.Out-of-the-box VMIは設計の話だが,Anti-VMは実装の話だ(と思う).
ところで私は,仮想マシンモニタの実現手法を「プロセッサ拡張によるもの」と「バイナリ変換によるもの」に分類していたが,別の分類方法を見つけた.

命令セットアーキテクチャを基準に分類することで,より分かりやすくなっている.今後はこの図に準拠したい.
話を戻そう.
何が言いたいのかというと,in-the-wildなマルウェアに備わったAnti-VMの内訳が分からないことには,サンドボックスの透明性について云々することはできないということだ.

検体

誰もやっていないようなので,調べてみることにした.
やや恣意的(誤用?)な選択だが,今回はVirusShare.comからCitadelのバリアント479検体や,MandiantのAPT1レポートにて報告された中国軍総参謀部第三部第二局こと61398部隊と思しきグループ製の874検体を含む総計33,260検体を用いた.

Anti-VMの実態

YARAを用いてAnti-VMの実態を調べた.
ルールには公式のantidebug.yarを用いた.なお,このルールにはAnti-VMのみならずAnti-Debuggerについても記載されている.

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
# awk '{ print $1}' yara.log | sort | uniq -c | sort -r
20340 DebuggerPattern__RDTSC
18966 DebuggerPattern__CPUID
9292 DebuggerTiming__Ticks
7371 DebuggerPattern__SEH_Inits
6955 DebuggerException__UnhandledFilter
5715 DebuggerTiming__PerformanceCounter
4377 DebuggerPattern__SEH_Saves
2949 DebuggerCheck__API
2631 DebuggerCheck__QueryInfo
2182 SEH__vba
1315 DebuggerOutput__String
887 ThreadControl__Context
604 vmdetect
303 DebuggerException__SetConsoleCtrl
186 SEH__vectored
83 DebuggerHiding__Thread
80 DebuggerHiding__Active
52 DebuggerException__ConsoleCtrl
10 Check_Dlls
5 DebuggerCheck__RemoteAPI
2 DebuggerCheck__GlobalFlags
1 ▒▒DebuggerPattern__RDTSC
1 Debuggeattern__SEH_Inits
1 Check_VBox_VideoDrivers
1 Check_VBox_Description

マルウェアの開発者がよりgenericな手法を好むことは,RDTSC命令を用いる検体が61.15%, CPUID命令を用いる検体が57.02%含まれていることからも明らかだ.たった2バイトを基準に検出しているため,false positiveだらけなのだろうとはいえ.
一方でvmdetectとして計上された検体は全体の1.18%に過ぎない.これはVMware, Virtual PC, Xen, Virtual Boxに関する文字列が含まれる検体を抽出するルールであり,コードは以下のようになっている.

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
rule vmdetect
{
meta:
author = "nex"
description = "Possibly employs anti-virtualization techniques"
strings:
// Binary tricks
$vmware = {56 4D 58 68}
$virtualpc = {0F 3F 07 0B}
$ssexy = {66 0F 70 ?? ?? 66 0F DB ?? ?? ?? ?? ?? 66 0F DB ?? ?? ?? ?? ?? 66 0F EF}
$vmcheckdll = {45 C7 00 01}
$redpill = {0F 01 0D 00 00 00 00 C3}
// Random strings
$vmware1 = "VMXh"
$vmware2 = "Ven_VMware_" nocase
$vmware3 = "Prod_VMware_Virtual_" nocase
$vmware4 = "hgfs.sys" nocase
$vmware5 = "mhgfs.sys" nocase
$vmware6 = "prleth.sys" nocase
$vmware7 = "prlfs.sys" nocase
$vmware8 = "prlmouse.sys" nocase
$vmware9 = "prlvideo.sys" nocase
$vmware10 = "prl_pv32.sys" nocase
$vmware11 = "vpc-s3.sys" nocase
$vmware12 = "vmsrvc.sys" nocase
$vmware13 = "vmx86.sys" nocase
$vmware14 = "vmnet.sys" nocase
$vmware15 = "vmicheartbeat" nocase
$vmware16 = "vmicvss" nocase
$vmware17 = "vmicshutdown" nocase
$vmware18 = "vmicexchange" nocase
$vmware19 = "vmdebug" nocase
$vmware20 = "vmmouse" nocase
$vmware21 = "vmtools" nocase
$vmware22 = "VMMEMCTL" nocase
$vmware23 = "vmx86" nocase
$vmware24 = "vmware" nocase
$virtualpc1 = "vpcbus" nocase
$virtualpc2 = "vpc-s3" nocase
$virtualpc3 = "vpcuhub" nocase
$virtualpc4 = "msvmmouf" nocase
$xen1 = "xenevtchn" nocase
$xen2 = "xennet" nocase
$xen3 = "xennet6" nocase
$xen4 = "xensvc" nocase
$xen5 = "xenvdb" nocase
$xen6 = "XenVMM" nocase
$virtualbox1 = "VBoxHook.dll" nocase
$virtualbox2 = "VBoxService" nocase
$virtualbox3 = "VBoxTray" nocase
$virtualbox4 = "VBoxMouse" nocase
$virtualbox5 = "VBoxGuest" nocase
$virtualbox6 = "VBoxSF" nocase
$virtualbox7 = "VBoxGuestAdditions" nocase
$virtualbox8 = "VBOX HARDDISK" nocase
// MAC addresses
$vmware_mac_1a = "00-05-69"
$vmware_mac_1b = "00:05:69"
$vmware_mac_1c = "000569"
$vmware_mac_2a = "00-50-56"
$vmware_mac_2b = "00:50:56"
$vmware_mac_2c = "005056"
$vmware_mac_3a = "00-0C-29" nocase
$vmware_mac_3b = "00:0C:29" nocase
$vmware_mac_3c = "000C29" nocase
$vmware_mac_4a = "00-1C-14" nocase
$vmware_mac_4b = "00:1C:14" nocase
$vmware_mac_4c = "001C14" nocase
$virtualbox_mac_1a = "08-00-27"
$virtualbox_mac_1b = "08:00:27"
$virtualbox_mac_1c = "080027"
condition:
any of them
}

見ての通りQEMUに関する記述が存在しない.
そこで,vmdetectに以下の様なコードを追加し,再度スキャンしてみた.

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
rule vmdetect
{
~ 略 ~
strings:
$QEMU1 = "Bochs" nocase
$QEMU3 = "QEMU_AUDIO_DRV" nocase
$QEMU4 = "QEMU VVFAT" nocase
$QEMU5 = "QEMU ADB Mouse" nocase
$QEMU6 = "QEMU ADS7846-driven Touchscreen" nocase
$QEMU7 = "QEMU HARDDISK" nocase
$QEMU8 = "QEMU CD-ROM" nocase
$QEMU9 = "QEMU MICRODRIVE" nocase
$QEMU10 = "QEMU-MEMORY" nocase
$QEMU11 = "QEMU_BIOS" nocase
$QEMU12 = "QEMU PS/2 Mouse" nocase
$QEMU13 = "QEMU Sun Mouse" nocase
$QEMU14 = "QEMU TSC2102-driven Touchscreen" nocase
$QEMU15 = "QEMU USB Mouse" nocase
$QEMU16 = "QEMU USB Tablet" nocase
$QEMU17 = "QEMU_VERSION" nocase
$QEMU18 = "QEMU USB Keyboard" nocase
$QEMU19 = "QEMU USB Hub" nocase
$QEMU20 = "QEMU USB MSD" nocase
$QEMU21 = "QEMU PenPartner tablet" nocase
$QEMU22 = "QEMUware SVGA" nocase
condition:
any of them
}

結果は608検体.QEMUを検出するのは4検体だけという結果になった.
自分がマルウェアを開発する立場だったらこうした文字列をそのまま埋め込むことはしないし,このルールは表層的なものでしかなく,例えばQEMUの動的バイナリ変換のスケジューリングに着目したAnti-VMを検出することはできない.
とはいえ,おかげで現状を大雑把に把握することはできた.

パッカーの実態

ついでに,PEiDを用いてパッカーの利用状況を調べた.
UserDBにはBob / Team PEiD signaturesを用いた.

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# awk '{{for(i=3;i<NF;i++)printf("%s ",$i) }print($NF)}' peid.log | sort | uniq -c | sort -r
21159 Nothing found *
3065 Nothing found [Overlay] *
1412 Microsoft Visual Basic 5.0 / 6.0
916 Microsoft Visual C++ 8.0 DLL Method2
747 Microsoft Visual C++ 6.0
685 Microsoft Visual Basic 5.0 - 6.0
405 Borland Delphi 6.0 - 7.0 [Overlay]
351 UPX 0.89.6 - 1.02 / 1.05 - 2.90 -> Markus & Laszlo
333 Microsoft Visual C++ 7.0 [Overlay]
320 UPX 2.93 - 3.00 [LZMA] -> Markus Oberhumer, Laszlo Molnar & John Reiser [Overlay] *
239 Borland Delphi 6.0 - 7.0
201 Microsoft Visual C++ 6.0 [Overlay]
185 UPX 0.89.6 - 1.02 / 1.05 - 2.90 -> Markus & Laszlo [Overlay]
167 Nullsoft PiMP Stub [Nullsoft PiMP SFX] *
99 Microsoft Visual C++ 6.0 DLL
83 Microsoft Visual C++ 7.0 [Debug] [Overlay]
82 Nothing found [Debug] *
79 UPX 0.89.6 - 1.02 / 1.05 - 2.90 (Delphi) stub -> Markus & Laszlo [Overlay]
63 Microsoft Visual C++ 7.0
62 Microsoft Visual C# / Basic .NET
61 Microsoft Visual Basic 5.0 / 6.0 [Overlay]
42 Themida 1.8.x.x - 1.9.x.x -> Oreans Technologies
42 ASPack 2.12 -> Alexey Solodovnikov [Overlay]
38 ASProtect 1.2x - 1.3x [Registered] -> Alexey Solodovnikov
32 ASPack 2.12 -> Alexey Solodovnikov
31 PE Win32 DLL (0 EntryPoint)
31 Nothing found [RAR SFX] *
31 Microsoft Visual C++ 6.0 DLL [Overlay]
28 Borland C++
26 PECompact 2.x -> Jeremy Collake [Overlay]
24 Morphine 1.2 - 1.3 -> rootkit
23 MinGW GCC 3.x [Overlay] *
22 UPX 0.89.6 - 1.02 / 1.05 - 2.90 (Delphi) stub -> Markus & Laszlo
22 Microsoft Visual C# / Basic .NET [Overlay]
19 Nothing found [ZIP SFX] *
18 PECompact 2.x -> Jeremy Collake
18 Microsoft Visual C++ 7.0 [Debug]
15 Borland C++ [Overlay]
14 Borland Delphi 4.0 - 5.0
14 ASProtect 1.33 - 2.1 Registered -> Alexey Solodovnikov *
13 UPX 2.93 - 3.00 [LZMA] -> Markus Oberhumer, Laszlo Molnar & John Reiser *
13 Microsoft Visual C++ 6.0 [Debug]
13 Microsoft Visual C++ 5.0
12 UPX 0.80 - 1.24 DLL -> Markus & Laszlo
12 BobSoft Mini Delphi -> BoB / BobSoft [Overlay] *
11 Themida 1.8.x.x -> Oreans Technologies *
11 Microsoft Visual C++ Private Version 1
11 Borland Delphi 6.0
11 Borland Delphi 2.0 [Overlay]
10 BobSoft Mini Delphi -> BoB / BobSoft *
9 NsPack 2.9 -> North Star *
9 Microsoft Visual C++ 5.0 [Debug]
8 Microsoft Visual C++ 7.1 DLL
8 Microsoft Visual C++ 7.0 Method2
7 Xtreme-Protector v1.05 *
7 Microsoft Visual C++ 8.0 DLL Method2 [Overlay]
6 Xtreme-Protector v1.05 [Overlay] *
6 Microsoft Visual C++ 8.0 [Debug] [Debug]
6 ASProtect 1.2x - 1.3x [Registered] -> Alexey Solodovnikov [Overlay]
5 nSPack 3.7 -> North Star/Liu Xing Ping
5 Wise Installer Stub [Overlay] *
5 WinZip 32-bit SFX 8.x module
5 Thinstall 2.4x - 2.5x -> Jitit Software [Overlay] *
5 PureBasic 4.x -> Neil Hodgson *
5 PE Win32 DLL (0 EntryPoint) [Overlay]
5 Microsoft Visual C++ 7.1 DLL [Overlay]
4 tElock 0.98b1 -> tE!
4 Themida 1.8.x.x - 1.9.x.x -> Oreans Technologies [Overlay]
4 PureBasic 4.x -> Neil Hodgson [Overlay] *
4 PC-Guard 5.0 -> Blagoje Ceklic [Overlay]
4 MoleBox v2.0 *
4 Microsoft Visual C++ 8.0 [Debug]
4 Microsoft Visual C++ 5.0 [Overlay]
4 Borland Delphi 4.0 - 5.0 [Overlay]
4 Borland C++ 1999 [Overlay]
4 Borland C++ 1999
4 Armadillo 3.78 - 4.xx -> Silicon Realms Toolworks
4 Armadillo 1.xx - 2.xx -> Silicon Realms Toolworks
4 ASProtect 2.1x SKE -> Alexey Solodovnikov
3 nSPack 3.7 -> North Star/Liu Xing Ping [Overlay]
3 Upack 0.39 beta -> Dwing
3 UPX 0.80 - 1.24 DLL -> Markus & Laszlo [Overlay]
3 Themida 1.2.0.1 (compressed) -> Oreans Technologies [Overlay]
3 Themida 1.2.0.1 (compressed) -> Oreans Technologies
3 NsPack 3.4 -> North Star *
3 NakedPacker 1.0 - by BigBoote *
3 MinGW GCC 3.x [ZIP SFX] *
3 Microsoft Visual C++ [Overlay]
3 Microsoft Visual C++ 6.0 SPx Method 1
3 Microsoft Visual Basic 5.0 - 6.0 [Overlay]
3 Inno Setup Module Heuristic Mode [Overlay]
3 ASProtect 2.1x SKE -> Alexey Solodovnikov [Overlay]
2 tElock 0.90 -> tE! [Overlay]
2 Upack 0.28 - 0.399 (relocated image base) - Delphi, .NET, DLL -> Dwing
2 Upack 0.24 - 0.29 beta -> Dwing
2 UltraProtect 1.x -> RISCO Software Inc. [Overlay]
2 UltraProtect 1.x -> RISCO Software Inc.
2 UPX 0.89.6 - 1.02 / 1.05 - 2.90 -> Markus & Laszlo [RAR SFX]
2 SVKP 1.3x -> Pavol Cerven
2 PureBasic DLL -> Neil Hodgson *
2 PEtite 2.x [Level 0] -> Ian Luck
2 PE-Armor V0.7X -> hying *
2 PC-Guard 5.0 -> Blagoje Ceklic
2 Nullsoft Install System 2.1x [Nullsoft PiMP SFX]
2 NsPack 1.4 -> Liuxingping *
2 NTKrnl Security Suite -> NTKrnl Team
2 Morphine 1.4 - 2.7 -> Holy_Father & Ratter/29A
2 MinGW GCC 3.x *
2 Microsoft Visual C++ Private Version 1 [Overlay]
2 Microsoft Visual C++ DLL Method 1
2 Microsoft Visual C++ 7.0 Method2 [Debug] [Overlay]
2 Microsoft Visual C++ 7.0 DLL Method 3
2 Microsoft Visual C++
2 Microsoft Visual Basic 5.0 / 6.0 [Debug]
2 MASM32 / TASM32
2 EXECryptor 2.2.4 -> Strongbit/SoftComplete Development (h1) *
2 EXECryptor 1.x.x -> SoftComplete Developement
2 Borland Delphi DLL
2 Borland Delphi 6.0 [Overlay]
2 Borland Delphi 3.0
2 Borland C++ DLL Method 2 [Overlay]
1 yoda's Protector 1.3 -> Ashkbiz Danehkar
1 yoda's Protector 1.03.3 -> Ashkbiz Danehkar
1 yoda's Protector 1.03.2 -> Ashkbiz Danehkar
1 tElock v0.90 [Overlay] *
1 tElock v0.90 *
1 tElock 1.0 (private) -> tE!
1 tElock 0.61 -> tE!
1 nSpack V2.3 -> LiuXingPing *
1 eXPressor 1.3.0 -> CGSoftLabs
1 Winkript 1.0 -> Mr. Crimson/WKT
1 WinRAR 32-bit SFX Module [RAR SFX] *
1 WWPack32 1.x -> Piotr Warezak
1 Upack 0.28 - 0.399 (relocated image base) - Delphi, .NET, DLL -> Dwing [Overlay]
1 Upack 0.24 - 0.29 beta -> Dwing [Overlay]
1 UPX 1.03 - 1.04 -> Markus & Laszlo [Overlay]
1 UPX 1.03 - 1.04 -> Markus & Laszlo
1 UPX 0.89.6 - 1.02 / 1.05 - 2.90 -> Markus & Laszlo [Nullsoft PiMP SFX]
1 Themida 1.8.x.x -> Oreans Technologies [Overlay] *
1 Themida -> Oreans Technologies 2004 *
1 SafeDisc 2.05.030 -> Macrovision [Overlay]
1 RLPack V1.11 -> ap0x [Overlay] *
1 RLPack 1.20 Basic Edition [aPLib] -> Ap0x *
1 RCryptor v1.6d --> Vaska *
1 Pelles C 3.00, 4.00, 4.50 EXE (X86 CRT-LIB) [Overlay]
1 PKLITE32 v1.1 *
1 PEtite 2.x [Level 1/9] -> Ian Luck [Overlay]
1 PEtite 2.x [Level 1/9] -> Ian Luck
1 PEncrypt 4.0 Gamma / 4.0 Phi -> junkcode
1 PESpin 0.3x - 1.xx -> cyberbob
1 PECompact 2.xx --> BitSum Technologies [Overlay] *
1 PECompact 2.xx --> BitSum Technologies *
1 PECompact 1.40 - 1.45 -> Jeremy Collake [Overlay]
1 PEBundle 2.0x - 2.4x-> Jeremy Collake
1 PEBundle 0.2 - 3.x -> Jeremy Collake
1 PE Pack 1.0 -> ANAKiN [Overlay]
1 PE Pack 1.0 -> ANAKiN
1 PE Crypt 1.02 -> random, killa & acpizer
1 Obsidium 1.3.0.4 -> Obsidium Software [Overlay]
1 Nullsoft PiMP stub [Nullsoft PiMP SFX]
1 Nullsoft Install System 2.x [Nullsoft PiMP SFX]
1 NsPack 2.9 -> North Star [Overlay] *
1 Nothing found [CAB SFX] *
1 NSPack 3.x -> Liu Xing Ping *
1 Morphine 1.4 - 2.7 -> Holy_Father & Ratter/29A [Overlay]
1 MoleBox v2.0 [Overlay] *
1 Microsoft Visual C++ 8.0 [Debug] [Overlay]
1 Microsoft Visual C++ 7.0 Method2 [Overlay]
1 Microsoft Visual C++ 7.0 DLL Method 3 [Overlay]
1 Microsoft Visual C++ 6.0 [ZIP SFX]
1 Microsoft Visual C++ 6.0 SPx Method 1 [Overlay]
1 Microsoft Visual C++ 6.0 DLL [Debug]
1 Microsoft Visual C++ 4.x [Overlay]
1 Microsoft Visual C++ 4.x [Debug]
1 Microsoft Visual Basic 6.0 DLL [Overlay]
1 Microsoft CAB SFX module
1 LCC Win32 1.x -> Jacob Navia
1 KByS V0.28 -> shoooo [Overlay] *
1 KByS V0.22 -> shoooo *
1 InstallShield AFW [Overlay]
1 Install Stub 32-bit -> InstallShield [Overlay]
1 Inno Installer 4.0.5 [Inno SFX]
1 FSG 2.0 -> bart/xt
1 EZIP 1.0 -> Jonathan Clark [Overlay]
1 EXECryptor 1.x.x -> SoftComplete Developement [Overlay]
1 CD-Cops II -> Link Data Security [Overlay]
1 Borland Delphi DLL [Overlay]
1 Borland Delphi 5.0 KOL/MCK [Overlay]
1 Borland Delphi 5.0 KOL [Overlay]
1 Borland Delphi 3.0 [Overlay]
1 Borland Delphi 2.0 [Inno SFX]
1 Borland Delphi 2.0
1 Borland Component
1 Armadillo v1.71 [Overlay] *
1 Armadillo 3.78 - 4.xx -> Silicon Realms Toolworks [Overlay]
1 Armadillo 2.01 -> Silicon Realms Toolworks [Overlay]
1 ASProtect SKE 2.1x (exe) -> Alexey Solodovnikov [Overlay] *
1 ASProtect 2.0x Registered -> Alexey Solodovnikov [Overlay]
1 ASProtect 2.0x Registered -> Alexey Solodovnikov
1 ASProtect 1.33 - 2.1 Registered -> Alexey Solodovnikov [Overlay] *
1 ASProtect 1.2 / 1.2c-> Alexey Solodovnikov
1 ASPack 2.12b -> Alexey Solodovnikov
1 ASPack 1.08.03 -> Alexey Solodovnikov [Overlay]
1 ASPack 1.06b / 1.061b -> Alexey Solodovnikov [Overlay]
1 AHTeam EP Protector 0.3 (fake Microsoft Visual C++ 7.0) -> FEUERRADER *
1 ACProtect V2.0 -> risco *
1 ACProtect 2.00 - RISCO Software Inc. [Overlay]
1 ACProtect 1.40 - 1.41 - RISCO Software Inc.
1 .BJFNT 1.1b -> :MARQUiS:
1 * PseudoSigner 0.2 [Microsoft Visual Basic 5.0 - 6.0] --> Anorganix [Overlay] *
1 * PseudoSigner 0.2 [Microsoft Visual Basic 5.0 - 6.0] --> Anorganix *

ほとんど検出できていないのが残念.ひときわ面倒なThemidaは11検体だった.

サンドボックスの実態

サンドボックスの透明性が研究されている一方で,実際のマルウェアはあまり凝ったAnti-VMを備えていなかった.
一方で,現実のサンドボックスはどうだろうか.いくら論文で優れた手法が編み出されようが,役立てられていなければ意味がない.
そこで,一般に公開されているサンドボックスに対してシステム情報を取得するプログラムを挿入し,仮想マシンモニタの種類を判別できるかどうか試してみたのだが,実態はあまりにもお粗末だった.
詳細は言うまい.
だが,systeminfoコマンドとNtQuerySystemInformationだけで露見する代物ばかりだった.
マルウェアの解析ではなく検出を目的としたサービス,言ってしまえばVirusTotalをサンドボックスのフロントエンドとしたことが,この一因として挙げられる.
凝ったAnti-VMを備えたマルウェアがあまり多くないことも考えると,現状のサンドボックスで十分だということなのだろうか.

感想

研究をしているうちに袋小路に突入してしまい,サンドボックスの透明性やAnti Anti-VMあくまでマルウェア解析の一側面に過ぎないことを私は忘れかけていた.
本質は別のところにある.

TEQUILABOOMBOOM

公開されているサンドボックスの中にTEQUILABOOMBOOMというホスト名のものがあった.
Trojan.Win32.Inject.uljvBackdoor.Win32.Agent.dkbpなど一部のマルウェアは,このホスト名に基づいてサンドボックスを検出することが知られている.調べてみると,Question about keylogged PC - Printable Version[FUD][FREE] Agent Tesla [Keylogger] [ClipboardLogger] [On-Screen Keyboard Logger] - Page 16など,2013年の時点で情報が攻撃者の間で共有されている.

So I managed to crypt a keylogger and have my victim download it through dropbox (I think). I haven’t received any logs yet but it shows that I’m connected to a computer from france which is janettedoe@TEQUILABOOMBOOM. I’m not sure how this is possible, or if it’s some sort of error. Does anybody know what this is? My keylogger hasn’t been sent anywhere other than to my targets email.

どうやらこれはGoogleが動かしているCuckoo Sandboxが用いるホスト名らしく,攻撃者によるサンドボックスの実態調査が進んでいることを伺わせる.
サンドボックスの透明性について拘りすぎる必要はないにせよ,その情報が攻撃者の手に渡ることを前提としたサービスについては検討の余地がある.

参考文献

はじめに

LibVMIはXenならびにKVMを対象としたVMIライブラリである.ここでは,その基盤と応用についてまとめる.

libxc

そもそもXenにおけるVMIの研究ではかねてより,各ドメインの制御機能ないしハイパーコールを提供するlibxcというライブラリが用いられてきた.libxcという名称はthe Xen Control libraryの略称であり,Xenによって公式に提供されている(tools/libxc/).なお,libxcとして提供されている関数はxc_というprefixを備えている.
このうち,VMIと密接に関連があるのはxenctrl.hxenguest.hである.なかでもxenctrl.hはlibxenctrlとも呼ばれ,PFNの参照やハイパーコールの追加などの機能が提供されている.
LibVMIのソースコードにgrepを掛けると,libxcに由来する構造体と関数が多く用いられていることが分かる.

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
38
39
40
41
42
% grep -rhoe "xc_[a-zA-z]*_[a-zA-z]*" . | grep -v "]" | sort | uniq
xc_domain_debug_control
xc_domain_decrease_reservation_exact
xc_domain_getinfo
xc_domain_getinfolist
xc_domain_hvm_getcontext
xc_domain_hvm_getcontext_partial
xc_domain_hvm_setcontext
xc_domain_maximum_gpfn
xc_domain_pause
xc_domain_populate_physmap_exact
xc_domain_set_access_required
xc_domain_unpause
xc_domaininfo_t
xc_dominfo_t
xc_evtchn_bind_interdomain
xc_evtchn_close
xc_evtchn_fd
xc_evtchn_notify
xc_evtchn_open
xc_evtchn_pending
xc_evtchn_t
xc_evtchn_unbind
xc_evtchn_unmask
xc_get_hvm_param
xc_hvm_get_mem_access
xc_hvm_inject_trap
xc_hvm_set_mem_access
xc_interface_close
xc_interface_open
xc_map_foreign_batch
xc_map_foreign_range
xc_mem_access_disable
xc_mem_access_enable
xc_mem_access_resume
xc_mem_event_disable
xc_mem_event_enable
xc_memory_op
xc_set_hvm_param
xc_set_mem_access
xc_vcpu_getcontext
xc_vcpu_setcontext

とりわけVMIにおいて重要なのはxc_map_foreign_range()という関数である.

1
2
3
void *xc_map_foreign_ranges(xc_interface *xch, uint32_t dom,
size_t size, int prot, size_t chunksize,
privcmd_mmap_entry_t entries[], int nentries);

Xenの各ドメインにはドメインや仮想CPUの構造体(xen/include/xen/sched.h)のほか,マシンメモリ(物理メモリ)と擬似物理メモリが割り当てられる.ドメインにおけるページングは,このマシンフレーム番号(MFN)と疑似物理フレーム番号(PFN)とを対応付けるP2Mテーブルによって実現されている.つまり,ドメインの仮想アドレスは擬似物理メモリフレーム番号に対応し,その疑似物理フレーム番号はさらにマシンフレーム番号に対応する.なお,Intel EPTが有効な環境においてはP2MはEPTによって代替される(ept-p2m.c).
xc_map_foreign_range()はこうした仕組みに基づき,ページテーブルを辿ってDom0のメモリ空間にDomUのマシンフレームを割り当てる.
VMIに求められるデータ構造の解釈はXenにおいて,この関数があることから成り立つものである.また,これによるフォールトインジェクションに関してもかつて研究が行われていたようだ.

libxenstore

Xenではxenstore-lsやxenstore-readなどのコマンドからxenstoredというデーモンを経由して各ドメインの情報を読み出すことができる.この仕組みをXenStoreといい,関連してlibxenstoreというライブラリが提供されている.(tools/xenstore/).libxsと呼ばれることもある.
XenStoreは名の通りKVSとしてドメインのUUIDやドメイン名,メモリのコミット状況などを格納している.
LibVMIにおいても,xenstordへのアクセスのため用いられている(xen_private.h).

libxenlight

libxenlightはlibxcとlibxenstoreのラッパとして公式に開発されているライブラリである(tools/libxl/).
LibVMIでは用いられていないものの,Xen Projectの本流ではこちらが推進されているように見受けられる.

XenAccess

XenAccessはlibxcのラッパであり,LibVMIの前身にあたる.
目的としてセマンティックギャップの解決が掲げられており,任意のPIDに対応するEPROCESS構造体を取得することができる.
EPROCESS構造体はWindowsの各プロセスに割り当てられる構造体であり,Windowsのプロセスリストはこの構造体のActiveProcessLinksメンバからなる双方向リンクリストによって成り立っている.タスクマネージャが参照するのはこの構造体である.
XenAccessはこの構造体を手掛かりにカーネルのベースアドレスを取得する.
まず,メモリ空間をPEヘッダの先頭すなわちMZという文字列で検索する.そして,発見したアドレスについてシステムプロセス固有のPsInitialSystemProcessのオフセットを加算し,EPROCESS構造体の先頭メンバである_EPROCESS.Pcb.headerにあたるシグネチャと比較する.一致した場合,発見したアドレスがベースアドレスということになる(windows_memory.c, windows_core.c, windows_process.c).
これによって,プロセスリストと任意のプロセスの情報を取得することができるようになった.

LibVMI

LibVMIはXenAccessの後継であり,かつてvmitoolsとも呼ばれていた.
先述したようなカーネル空間の解析機能をLibVMIはフォレンジックツールに委譲した.フォレンジックツールを導入するメリットとして,より詳細な構造体の解析を低い実装コストで実現できるという点が挙げられる.
さて,LibVMIはVolatilityRekallという二種類のフォレンジックツールに対応している.RekallはGoogleによるVolatilityの拡張であり,かつてはLibVMIのサポート外だったものの,commit a4f065a31b986562dc39c9a10d9dff080792f3f4にて導入された.
では,両者の差異はどこにあるのか.

Kernel Debugger Block

XenAccessが行っていたようなEPROCESS構造体のスキャンについて考えてみよう.
VolatilityとRekallはいずれも,まずリンクリストの先頭であるPsActiveProcessHeadを特定しようとする.
x86において,このPsActiveProcessHeadは,KPCR(Kernel Processor Control Region)構造体のKdVersionBlockメンバが示すKDDEBUGGER_DATA32構造体から取得することができる.
このKPCR構造体だが,x86ではFSレジスタ,x86_64ではGSレジスタが先頭アドレスを保持しており,Windows XPでは0xFFDFF000というマジックナンバーを用いて検索することができる.
では,KdVersionBlockはどうか.なんと,Windows 7の64-bit版からこのメンバは空になってしまっている.ゆえに,x86_64ではKDDEBUGGER_DATA64構造体をKdVersionBlockに頼らず検索する必要が生ずる.
ここで,VolatilityはKDDEBUGGER_DATA64構造体のヘッダに存在するKDBGシグネチャをスキャンするのに対し(kdbgscan.py),Rekallはカーネルのバージョンに対応したプロファイル情報を用いて構造体にアクセスする.

DRAKVUF

DRAKVUFはTamas K Lengyelによって開発されたサンドボックスであり,論文のオーサーにはXenAccessとLibVMIの開発者であるBryan D. Payneも名を連ねている.
LibVMIにRekallのサポートを追加したのがほかでもないTamas K Lengyelその人である.
Volatilityは前述の通りメモリからKDBG構造体を検索する.だが,ゲストOSのメモリはマルウェアによって改竄されてしまうおそれがあることに加え,Windows 8の64-bit版ではKDBG構造体が難読化されているという問題点がある.
一方で,Rekallのプロファイルはゲストから不可視であり,改竄の影響を受けないため,この問題を解決することができる.
DRAKVUFはマルウェア解析を目的としてLibVMIを拡張しており,当然プログラムの実行をトラップする機能を備えている.
特筆すべきは,Intel EPTを用いたメモリアクセスの監視である(vmi.c).

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
// Now we can set the EPT permissions
if (!container->guard) {
container->guard = g_malloc0(sizeof(vmi_event_t));
SETUP_MEM_EVENT(container->guard, container->pa, VMI_MEMEVENT_PAGE,
VMI_MEMACCESS_RW, trap_guard);
if (VMI_FAILURE == vmi_register_event(vmi, container->guard)) {
printf("*** FAILED TO REGISTER MEMORY GUARD @ PAGE %lu ***\n",
pa >> 12);
free(container->guard);
free(container);
continue;
}
container->guard->data = g_hash_table_new(g_int64_hash,
g_int64_equal);
printf("\t\tNew memory event trap set on page %lu\n", pa >> 12);
} else {
printf("\t\tMemory event trap already set on page %lu\n", pa >> 12);
}
struct memevent *test = g_hash_table_lookup(container->guard->data,
&container->pa);
if (!test) {
g_hash_table_insert(container->guard->data, &container->pa,
container);
} else if (test->sID == SYMBOLWRAP) {
printf("Address is already guarded\n");
} else {
printf("Address is trapped by another feature! ERROR/TODO!\n");
}

このアイデア自体はKVMをベースとして開発されたCXPInspectorを下敷きにしている.
かつては任意のアドレスについてNX bitを設定し,ページフォルトハンドラをブレークポイントとする手法が一般的であった.これがカーネル空間から可視であるのに対し,EPTは不可視であるためよりカーネルルートキットの解析に適している.

おわりに

LibVMIの登場と洗練によってもはや,セマンティックギャップは過去の問題になりつつある.XenにおけるVMIにおいて,これを用いない手はないだろう.

参考文献

VMI

仮想マシンモニタからゲストのリソースを監視・制御する技術をVMI(Virtual Machine Introspection)という.
その発祥は2003年に遡る.当時はハニーポットの研究が盛んな時期であり,VMIは仮想マシンにおける侵入検知を実現する手法として提案された.これはマルウェア解析の自動化に応用されており,仮想マシンモニタによるサンドボックスを構築し,VMIによってマルウェアの挙動を監視するというアプローチが一般的となっている.
さて,VMIにおける問題点にセマンティックギャップというものがある.原義は高級言語とハードウェアとの乖離であるが,OSと同じ粒度で情報を取得することのできるゲスト内部(in-the-box)とハードウェアの粒度で情報を取得することしかできないゲスト外部(out-of-the-box)の乖離という意味でも援用される.

Transparent Sandbox

セマンティックギャップを解決する方法として,ゲスト内部にエージェントを挿入し,仮想マシンモニタに情報を送信するというアプローチが考えられる.
だがマルウェア解析において,ゲスト内部にエージェントを挿入することは,自らの姿を曝け出していることにほかならない.ファイルやサービス名,フックによって変化するAPIの挙動といったものは,マルウェアがエージェントと同じ権限で動作する限り避けられない.そもそも仮想マシンモニタは特権を有していたところでRDTSCICEBPなどの命令,あるいはハードウェア名やIDTといった要素から検出される可能性を孕んでいる.そのため,ゲスト内部にエージェントを挿入することは,仮想マシンモニタがマルウェアから検出される可能性を高めることに繋がる.フォレンジックにおける完全性(integrity)のためにも,ゲスト内部にエージェントを挿入すべきではないと私は考える.
この議論に関して,マルウェアから検出されない仮想マシンモニタをtransparent sandbox(透明なサンドボックス?)といい,仮想マシンモニタの検出を試みるマルウェアをevasive malwareという.
ではどのような仮想マシンモニタがより「透明」に近いのか.

仮想マシンモニタの歴史

ここでPopekとGoldbergの仮想化要件に立ち返ると,ベアメタルな仮想マシンモニタは等価性(equivalence),資源管理(resource control),効率性(efficiency)の三原則を実現しており,なおかつユーザセンシティブ命令が特権命令でなければならない.センシティブ命令かつ特権命令でなければ,例外によって仮想マシンモニタに通知することができないためである.
だが,以前のx86アーキテクチャはこの要件を満たしていなかった.そこで,ニ種類の解決方法が編み出された.
一つ目はバイナリ変換(binary translation)による完全仮想化である.これはソフトウェアで命令をエミュレートする手法で,QEMUやVMwareが該当する.QEMUは全ての命令を中間コードに変換するのに対し,VMwareはPopekとGoldbergの仮想化要件を満たさない命令をライブラリ呼び出しに変換する.
二つ目は準仮想化である.これはPopekとGoldbergの仮想化要件を満たさない命令に差し掛かると仮想マシンモニタに通知されるようゲストOSのカーネルを修正する手法で,Xenが該当する.
ではバイナリ変換による完全仮想化とカーネル書き換えによる準仮想化のどちらがより「透明」に近いのか.
その前にIntel VT-xについて考えなければならない.Intel VT-xはCPUのモード切り替えによってセンシティブ命令かつ特権命令でない命令のトラップを実現する拡張機能である.すなわちPopekとGoldbergの仮想化要件を満たすハードウェア側からのアプローチである.これにより準仮想化カーネルの必要はなくなった.エージェントの挿入が「透明」性に関わるのと同様,カーネルの書き換えは仮想マシンモニタを検出する手掛かりとなる.
そのためマルウェア解析においては,バイナリ変換による完全仮想化か,Intel VT-xによる完全仮想化が前提となる.なおKVMはIntel Vt-xを前提としているが,部分的にQEMUを用いている.
ではどちらの手法がより「透明」に近いのか.

バイナリ変換とIntel VT-x

Christopher KruegelはWineのようなシステムコール単位のエミュレーションあるいはフックは情報量として不十分であり,またIntel VT-xによる完全仮想化はRedPillのようなアプローチで検出可能であることから,バイナリ変換による完全仮想化すなわちQEMUを支持している.
システムコール単位の監視はIsDebuggerPresentのようなカーネルモードに遷移せず構造体を参照するだけの処理を見逃してしまう.システムコールのフックを用いた研究や製品は多数存在するが,それらは不完全である.この点で私は氏と意見を一にするが,バイナリ変換がより「透明」に近いかどうかは検討の余地があるだろう.寧ろ氏は実行パスの拡張がより容易に行えるがゆえにバイナリ変換を支持しているのだろうということは読み取れるが,氏がチーフサイエンティストを務めるLastlineの製品がQEMUをベースとしていることもあり,些かポジショントークのようにも感じられる.
このような論調でバイナリ変換とIntel VT-xを二項対立の図式に落とし込んでいる論文が数多く見られる.しかし,どちらがより「透明」に近いのかという議論はナンセンスであろう.
ゲスト内部にエージェントを挿入することで,果たして本当にマルウェアから検出されやすくなるのかと言うと,それは飽くまで可能性でしかない.だがゲスト内部にエージェントを挿入しないという選択で,その問題は検討項目から外すことができる.一方で,バイナリ変換であるという理由でIntel VT-xを用いた仮想マシンモニタよりも検出されにくいといったことはない.逆もまた然りである.
そしてそもそも,ネットワーク経由で仮想マシンモニタを検出するという手法がある以上,完全に「透明」なサンドボックスは実現不可能であるとされる.
ゆえにそれぞれのアプローチについて「透明」性の他にもメリット・デメリットを検討し,なおかつ両者を接合するようなシステムこそが望ましい.

参考文献

はじめに

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日目として書かれた.

参考文献