BitVisorに「移植」する
東京農工大学 市川 遼
2017/12/05 BitVisor Summit 6
自己紹介
● 東京農工大学 工学府情報工学専攻1年
● twitter: @icchyr
● CTF: TokyoWesterns
● 研究: OS, セキュリティ
本日お話しすること
● LVisorについて
● LVisorの実装上の課題
○ BitVisorのどの部分で動かすのか
○ どうやって呼び出すのか
○ エラーが起きたらどうするのか
○ 依存ライブラリはどうするのか
○ etc.
● これらを解決する方法を検討
○ BitVisorに外部のプログラムを移植した例は多くあるが,
ソースコードが公開されているものは少ない
○ 初期実装ではひたすらアドホックに試した → 後に限界が見えてきた
■ なるべくソースコードを改変する必要がない方法を模索
○ 実装する上での具体的な手順について共有
LVisorの背景
● VMMに汎用スクリプト言語を組み込むモチベーション
○ 比較的簡単に複雑な処理を記述可能
○ eval相当の機能がある場合は動的にプログラムを追加可能
○ 通常VMMに動的に機能を追加するのは難しい
● BitVisorにVMI (Virtual Machine Introspection) があるとうれしい
○ ゲストOSから不可視
■ マルウェア解析環境として適している
○ 他のVMMに比べてパフォーマンスが優れている
■ 解析効率の向上
● BitVisorにLuaとLibVMIを組み込んだ
○ 普通のBitVisorでは処理を追加するために Cで直接VMMを書き換える必要がある
○ スクリプト言語でBitVisorを使いたい
○ LibVMI+シンボル情報でゲスト OS解析の補助
○ Luaのプログラムをルールとしてプロセスやネットワークを監視
Luaを選んだ理由
● Luaの特徴
○ (比較的) 早い
○ バイナリが小さい,依存ライブラリが少ない (最低限libc, libmのみ)
○ 記述方法が多様
■ 手続き型
■ プロトタイプベース
■ など
○ インターフェースとして有用
● BitVisorの特徴
○ 早い
○ VMMが小さい
○ 拡張のハードルが高い
■ processなどを書いてビルドし直す必要がある
● BitVisorに組み込む上でLuaは比較的相性が良い
インターフェースとしてのLua
● LuaをインターフェースとするAPIを提供
○ システムコールフック
○ 仮想メモリアクセス
○ ネットワークパケットフィルタ
● オブジェクト指向でデータにアクセス
○ フックするイベントに対して処理を登録
○ イベントハンドラにフック対象に関わるオブジェクトが渡される
● Luaはプロトタイプベースなので記述しやすい
○ メソッドに対して動的に処理を追加できる
○ 関数の動作を上書きすることが可能なため用途に合わせてフレームワーク的に扱える
LVisorの例
● 使用例
○ /tmp/ で始まるバイナリが起動 (execve) された時に全てのネットワーク通信を遮断
○ 現状プロセス単位の通信は制御できない
denyall = function(eth)
return DROP
end
execve_hook = function(args)
filename = args[0] -- execveの第一引数 (filename) へのポインタを取得
filename_s = pmem.read(filename, 0x100) -- filenameを文字列で取得
-- /tmp/で始まるバイナリの場合全てのネットワーク通信を遮断
if string.match(filename_s, “^/tmp/”) ~= nil then
net.eth.hooks.add(“*”, “*”, “denyall”, denyall)
end
end
syscall.hooks.add(“netproc”, execve_hook)
LVisorの実装方法
BitVisorに機能を追加する方法
● BitVisorで処理を走らせる手段
○ 一回のみ
■ INITFUNC
○ 定期的
■ thread
○ dbgshから任意のタイミング
■ process
○ ゲストOSから任意のタイミング
■ vmmcall
● BitVisorの実装 (vpn, storageなど)
○ BitVisorの機能が必要な部分のみ msgbufで呼び出し,それ以外は保護ドメインで処理
○ 必要な時のみしかRing 0で動作させない方針
LVisorの実装方針
● Ring 0で動かすのは得策ではない
○ 移植するプログラムが巨大であるため
● 基本的にvpnやstorage等と同じ方針をとる
○ 必要な部分だけmsgbufでやりとりする
○ Lua,LibVMIは保護ドメインで動作させる
● まずLuaのインタプリタを組み込み,次にLibVMIを移植
● 環境
○ Lua: 5.3.3
○ LibVMI: 2016年9月時点でのGitHubの版
BitVisorのビルドシステムおさらい
● makeをベースとしたビルドシステム
○ 下の階層をビルドした結果を上の階層にリンク
○ オブジェクト (.o) を作る場合は subdirs-1 に追加
○ 静的ライブラリ (.a) を作る場合は asubdirs-1 に追加
○ objs-1 にオブジェクトファイルを追加することでビルド対象を決定
○ CFLAGSを書き換えることでコンパイルオプションを調節
○ 基本的にカレントディレクトリは一番上 (bitvisor/) になることに注意
● processのビルド
○ $(name)-objs にオブジェクトファイルを追加
○ $(name)-libs に依存ライブラリ (.a) を追加
○ bins-1 にprocessとして登録する名前を追加
リンクについて
● BitVisorは基本的にstatic link
○ dynamic linkがサポートされていない
○ そもそもサポートする必要がない (再利用されるコードが極めて小さいため )
○ バイナリのローダは存在するがライブラリのローダは存在しない
● Ring 0の部分は最終的に全て同じ階層でリンクされる
○ どこかにシンボルが存在していればよい
○ processからリンクしたい場合は process-dependsに加える必要がある
ソースコードの配置場所
● 全てprocess配下に置くのが正しいか?
○ vpnやstorageはそうではない
■ dbgshから呼び出すことを想定していない
● 例: vpn
○ vpn/lib/user.c に処理を書き,process/vpn.c ではexternのようにして呼び出す
○ この構成のメリット
■ process/Makefileを汚さない
■ process/ 以外からもリンクすることができる
● musl, LibVMIは一番上の階層に置く
移植に必要な手順
● 静的ライブラリまたはオブジェクトを作成する
○ BitVisorのビルドシステムに組み込む
■ findコマンドなどである程度自動化 , コンパイルオプションに注意
○ プロジェクトのMakefileを直接叩く
■ カレントディレクトリに注意
● 必要なプログラムの依存関係に加える
○ processであれば$(name)-libs
○ それ以外であればasubdirs-1 (or subdirs-1)
● 標準的なOSの機能を使う部分は修正する必要がある
○ 標準入出力, メモリ管理は特にBitVisorに合わせる必要がある
Luaの組み込み方法
● Luaは比較的移植がしやすい
○ 依存ライブラリが少ない
○ 全てのソースコードが同じ階層に存在し,コンパイルしたオブジェクトをリンクするだけ
■ ビルドシステムへの負担が少ない
○ 静的ライブラリ (.a) を作成できればよい
● BitVisorのビルドシステムに組み込む
○ BitVisorのビルドシステムを理解する
○ process/ 以下にluaのソースコードを丸ごとコピー
○ LuaのMakefileをBitVisor向けに編集 (objs-1にLuaのプログラムを追加していくだけ )
○ process/MakefileにLuaに関わる部分を追記
● これだけでは動かない
○ libcが必要
○ BitVisorは部分的にしかlibcの関数をサポートしていない
libcの組み込み方法
● 必要なlibcを移植するのは大変 → 既存のlibcを移植する
● どのlibcを使うか?
○ eglibc
○ uClibc
○ newlib
○ musl libc
○ etc.
○ 比較的サイズが小さくビルドも簡単な musl libcを採用
● BitVisorに合わせた変更が必要
○ ファイルシステムおよび fdの概念が存在しない
○ BitVisorのprocess/libでサポートしている関数に注目
■ 標準出力 (printf)
■ 標準入力 (lineinput)
■ メモリ管理 (alloc/free)
○ 最低限これらの機能と接続しないと動作しない
libcの組み込み方法
● muslの関数を編集
○ 標準出力
■ __fwritexでputcharを使う (process/lib/lib_putchar.c)
■ muslのprintfを使えるようにするため
● BitVisorのprintfは浮動小数点系のフォーマット指定子に対応していない
○ 標準入力
■ libc側で対応せずにprocessの入力を受け取るところで対処
○ メモリ管理
■ malloc
● process/lib/lib_mm.c:alloc を使うように修正
■ free
● process/lib/lib_mm.c:free を使うように修正
■ realloc
● size == 0の時のバグを修正
■ 確保可能な領域は各 processでheap, heaplenを定義して調整
libcの組み込み方法
● ビルドシステムの修正
○ muslのMakefileをBitVisorに対応させるのは困難
○ BitVisorのmake中にmuslのmakeを走らせるようにする
■ cleanを回避する場合は ifndef clean_p の中に書く (clean時に定義される変数の一つ )
● リンク方法の検討
○ プロジェクトの.aを直接リンクする (ビルド時に生成された .aを指定)
○ BitVisorのビルドシステムで.aを作る (ビルド時に生成された .oを集める) ← こちらを採用
● 例: include makefiles/musl.mak
ifndef clean_p
$(info building musl...)
$(info $(shell cd $(musl_dir) && make -j4 && cd - >/dev/null))
objs-1 += $(musl_o)
endif
Luaの組み込み方法 (続き)
● Luaのインタプリタを用意する
○ lua.cをprocess向けに編集
○ lua_writelineマクロとlua_readlineマクロをそれぞれ編集
■ printfとlineinputを使うように修正
○ main関数を修正
■ setlimitを呼び出して大きくスタックを確保するように修正
● 標準ではスタックが足りなくてクラッシュする
■ pushcfunction~reportまで削除し,代わりに doREPLを呼び出す
● argc, argvが存在しないためpushcfunctionが使えない
■ exitprocessで終了
● 浮動小数点レジスタのサポート
○ VMM起動時にCR0を操作して有効化しておく (一時的な措置)
● debugshのprocessからLuaインタプリタが使用可能に
LibVMIの組み込み方法
● LibVMIはlibcの他にGlib, json-cに依存
○ libcと同様の方法でビルドシステムに組み込む
● LibVMIのBitVisor対応
○ XenをベースにBitVisor用のdriverを作成
■ ゲストOSのCPUの読み書き
■ ゲストOSの物理メモリの読み書き
■ ゲストOSの電源操作 (pause, resume, halt)
■ メモリサイズの取得
○ CPUレジスタ操作,メモリ操作があれば理論上は動作する
○ rekallのprofileを用意
■ 現状はファイルシステムが無いのでバイナリの dataセクションに埋め込む
■ objcopyでJSONを変換
■ サイズが非常に大きい (Linuxで約3.3M) ため改善の必要あり
LibVMIの組み込み方法
● ビルドシステムの修正
○ LibVMIのビルドシステムはKVM, Xenなどの本来対応する VMM用のライブラリに深く依存
○ BitVisor用のdriverをビルドするようにMakefileを作成
■ libvmi/およびその下のarch, driver, os以下
● 例: include makefiles/libvmi.mak
CFLAGS += $(libvmi_INCLUDE)
curdir = $(libvmi_dir)/driver
subdirs-1 += bitvisor
obj = $(subst .c,.o,$(shell find $(curdir) -maxdepth 1 -name "*.c"))
objs-1 += $(obj:$(curdir)/%=%)
近況
● Luaはほぼ完全に動作
○ muslのprint系が壊れていて変な出力が出るものの特に影響はない
● LibVMIが完全に動いていない
○ 動きはするがオフセットがずれている
● ネットワーク監視が完全に動いていない
○ netapiの使い方が把握しきれていない
● 全てのAPIに対応できていない
まとめ
● BitVisorへの移植は比較的容易な手順で実現可能
○ 一番上の階層にプロジェクト用のディレクトリを切り分ける
○ プロジェクトをそのままコピーする
○ Makefile内でプロジェクトのビルドを行う
○ 生成された.oファイルを検索してobjs-1に追加
● libc, GLibcが移植できたため大抵のプログラムは移植可能
○ OSの機能を必要とするものは移植できない
○ BitVisorを拡張するのが容易に

how to port * to BitVisor

  • 1.
  • 2.
    自己紹介 ● 東京農工大学 工学府情報工学専攻1年 ●twitter: @icchyr ● CTF: TokyoWesterns ● 研究: OS, セキュリティ
  • 3.
    本日お話しすること ● LVisorについて ● LVisorの実装上の課題 ○BitVisorのどの部分で動かすのか ○ どうやって呼び出すのか ○ エラーが起きたらどうするのか ○ 依存ライブラリはどうするのか ○ etc. ● これらを解決する方法を検討 ○ BitVisorに外部のプログラムを移植した例は多くあるが, ソースコードが公開されているものは少ない ○ 初期実装ではひたすらアドホックに試した → 後に限界が見えてきた ■ なるべくソースコードを改変する必要がない方法を模索 ○ 実装する上での具体的な手順について共有
  • 4.
    LVisorの背景 ● VMMに汎用スクリプト言語を組み込むモチベーション ○ 比較的簡単に複雑な処理を記述可能 ○eval相当の機能がある場合は動的にプログラムを追加可能 ○ 通常VMMに動的に機能を追加するのは難しい ● BitVisorにVMI (Virtual Machine Introspection) があるとうれしい ○ ゲストOSから不可視 ■ マルウェア解析環境として適している ○ 他のVMMに比べてパフォーマンスが優れている ■ 解析効率の向上 ● BitVisorにLuaとLibVMIを組み込んだ ○ 普通のBitVisorでは処理を追加するために Cで直接VMMを書き換える必要がある ○ スクリプト言語でBitVisorを使いたい ○ LibVMI+シンボル情報でゲスト OS解析の補助 ○ Luaのプログラムをルールとしてプロセスやネットワークを監視
  • 5.
    Luaを選んだ理由 ● Luaの特徴 ○ (比較的)早い ○ バイナリが小さい,依存ライブラリが少ない (最低限libc, libmのみ) ○ 記述方法が多様 ■ 手続き型 ■ プロトタイプベース ■ など ○ インターフェースとして有用 ● BitVisorの特徴 ○ 早い ○ VMMが小さい ○ 拡張のハードルが高い ■ processなどを書いてビルドし直す必要がある ● BitVisorに組み込む上でLuaは比較的相性が良い
  • 6.
    インターフェースとしてのLua ● LuaをインターフェースとするAPIを提供 ○ システムコールフック ○仮想メモリアクセス ○ ネットワークパケットフィルタ ● オブジェクト指向でデータにアクセス ○ フックするイベントに対して処理を登録 ○ イベントハンドラにフック対象に関わるオブジェクトが渡される ● Luaはプロトタイプベースなので記述しやすい ○ メソッドに対して動的に処理を追加できる ○ 関数の動作を上書きすることが可能なため用途に合わせてフレームワーク的に扱える
  • 7.
    LVisorの例 ● 使用例 ○ /tmp/で始まるバイナリが起動 (execve) された時に全てのネットワーク通信を遮断 ○ 現状プロセス単位の通信は制御できない denyall = function(eth) return DROP end execve_hook = function(args) filename = args[0] -- execveの第一引数 (filename) へのポインタを取得 filename_s = pmem.read(filename, 0x100) -- filenameを文字列で取得 -- /tmp/で始まるバイナリの場合全てのネットワーク通信を遮断 if string.match(filename_s, “^/tmp/”) ~= nil then net.eth.hooks.add(“*”, “*”, “denyall”, denyall) end end syscall.hooks.add(“netproc”, execve_hook)
  • 8.
  • 9.
    BitVisorに機能を追加する方法 ● BitVisorで処理を走らせる手段 ○ 一回のみ ■INITFUNC ○ 定期的 ■ thread ○ dbgshから任意のタイミング ■ process ○ ゲストOSから任意のタイミング ■ vmmcall ● BitVisorの実装 (vpn, storageなど) ○ BitVisorの機能が必要な部分のみ msgbufで呼び出し,それ以外は保護ドメインで処理 ○ 必要な時のみしかRing 0で動作させない方針
  • 10.
    LVisorの実装方針 ● Ring 0で動かすのは得策ではない ○移植するプログラムが巨大であるため ● 基本的にvpnやstorage等と同じ方針をとる ○ 必要な部分だけmsgbufでやりとりする ○ Lua,LibVMIは保護ドメインで動作させる ● まずLuaのインタプリタを組み込み,次にLibVMIを移植 ● 環境 ○ Lua: 5.3.3 ○ LibVMI: 2016年9月時点でのGitHubの版
  • 11.
    BitVisorのビルドシステムおさらい ● makeをベースとしたビルドシステム ○ 下の階層をビルドした結果を上の階層にリンク ○オブジェクト (.o) を作る場合は subdirs-1 に追加 ○ 静的ライブラリ (.a) を作る場合は asubdirs-1 に追加 ○ objs-1 にオブジェクトファイルを追加することでビルド対象を決定 ○ CFLAGSを書き換えることでコンパイルオプションを調節 ○ 基本的にカレントディレクトリは一番上 (bitvisor/) になることに注意 ● processのビルド ○ $(name)-objs にオブジェクトファイルを追加 ○ $(name)-libs に依存ライブラリ (.a) を追加 ○ bins-1 にprocessとして登録する名前を追加
  • 12.
    リンクについて ● BitVisorは基本的にstatic link ○dynamic linkがサポートされていない ○ そもそもサポートする必要がない (再利用されるコードが極めて小さいため ) ○ バイナリのローダは存在するがライブラリのローダは存在しない ● Ring 0の部分は最終的に全て同じ階層でリンクされる ○ どこかにシンボルが存在していればよい ○ processからリンクしたい場合は process-dependsに加える必要がある
  • 13.
    ソースコードの配置場所 ● 全てprocess配下に置くのが正しいか? ○ vpnやstorageはそうではない ■dbgshから呼び出すことを想定していない ● 例: vpn ○ vpn/lib/user.c に処理を書き,process/vpn.c ではexternのようにして呼び出す ○ この構成のメリット ■ process/Makefileを汚さない ■ process/ 以外からもリンクすることができる ● musl, LibVMIは一番上の階層に置く
  • 14.
    移植に必要な手順 ● 静的ライブラリまたはオブジェクトを作成する ○ BitVisorのビルドシステムに組み込む ■findコマンドなどである程度自動化 , コンパイルオプションに注意 ○ プロジェクトのMakefileを直接叩く ■ カレントディレクトリに注意 ● 必要なプログラムの依存関係に加える ○ processであれば$(name)-libs ○ それ以外であればasubdirs-1 (or subdirs-1) ● 標準的なOSの機能を使う部分は修正する必要がある ○ 標準入出力, メモリ管理は特にBitVisorに合わせる必要がある
  • 15.
    Luaの組み込み方法 ● Luaは比較的移植がしやすい ○ 依存ライブラリが少ない ○全てのソースコードが同じ階層に存在し,コンパイルしたオブジェクトをリンクするだけ ■ ビルドシステムへの負担が少ない ○ 静的ライブラリ (.a) を作成できればよい ● BitVisorのビルドシステムに組み込む ○ BitVisorのビルドシステムを理解する ○ process/ 以下にluaのソースコードを丸ごとコピー ○ LuaのMakefileをBitVisor向けに編集 (objs-1にLuaのプログラムを追加していくだけ ) ○ process/MakefileにLuaに関わる部分を追記 ● これだけでは動かない ○ libcが必要 ○ BitVisorは部分的にしかlibcの関数をサポートしていない
  • 16.
    libcの組み込み方法 ● 必要なlibcを移植するのは大変 →既存のlibcを移植する ● どのlibcを使うか? ○ eglibc ○ uClibc ○ newlib ○ musl libc ○ etc. ○ 比較的サイズが小さくビルドも簡単な musl libcを採用 ● BitVisorに合わせた変更が必要 ○ ファイルシステムおよび fdの概念が存在しない ○ BitVisorのprocess/libでサポートしている関数に注目 ■ 標準出力 (printf) ■ 標準入力 (lineinput) ■ メモリ管理 (alloc/free) ○ 最低限これらの機能と接続しないと動作しない
  • 17.
    libcの組み込み方法 ● muslの関数を編集 ○ 標準出力 ■__fwritexでputcharを使う (process/lib/lib_putchar.c) ■ muslのprintfを使えるようにするため ● BitVisorのprintfは浮動小数点系のフォーマット指定子に対応していない ○ 標準入力 ■ libc側で対応せずにprocessの入力を受け取るところで対処 ○ メモリ管理 ■ malloc ● process/lib/lib_mm.c:alloc を使うように修正 ■ free ● process/lib/lib_mm.c:free を使うように修正 ■ realloc ● size == 0の時のバグを修正 ■ 確保可能な領域は各 processでheap, heaplenを定義して調整
  • 18.
    libcの組み込み方法 ● ビルドシステムの修正 ○ muslのMakefileをBitVisorに対応させるのは困難 ○BitVisorのmake中にmuslのmakeを走らせるようにする ■ cleanを回避する場合は ifndef clean_p の中に書く (clean時に定義される変数の一つ ) ● リンク方法の検討 ○ プロジェクトの.aを直接リンクする (ビルド時に生成された .aを指定) ○ BitVisorのビルドシステムで.aを作る (ビルド時に生成された .oを集める) ← こちらを採用 ● 例: include makefiles/musl.mak ifndef clean_p $(info building musl...) $(info $(shell cd $(musl_dir) && make -j4 && cd - >/dev/null)) objs-1 += $(musl_o) endif
  • 19.
    Luaの組み込み方法 (続き) ● Luaのインタプリタを用意する ○lua.cをprocess向けに編集 ○ lua_writelineマクロとlua_readlineマクロをそれぞれ編集 ■ printfとlineinputを使うように修正 ○ main関数を修正 ■ setlimitを呼び出して大きくスタックを確保するように修正 ● 標準ではスタックが足りなくてクラッシュする ■ pushcfunction~reportまで削除し,代わりに doREPLを呼び出す ● argc, argvが存在しないためpushcfunctionが使えない ■ exitprocessで終了 ● 浮動小数点レジスタのサポート ○ VMM起動時にCR0を操作して有効化しておく (一時的な措置) ● debugshのprocessからLuaインタプリタが使用可能に
  • 20.
    LibVMIの組み込み方法 ● LibVMIはlibcの他にGlib, json-cに依存 ○libcと同様の方法でビルドシステムに組み込む ● LibVMIのBitVisor対応 ○ XenをベースにBitVisor用のdriverを作成 ■ ゲストOSのCPUの読み書き ■ ゲストOSの物理メモリの読み書き ■ ゲストOSの電源操作 (pause, resume, halt) ■ メモリサイズの取得 ○ CPUレジスタ操作,メモリ操作があれば理論上は動作する ○ rekallのprofileを用意 ■ 現状はファイルシステムが無いのでバイナリの dataセクションに埋め込む ■ objcopyでJSONを変換 ■ サイズが非常に大きい (Linuxで約3.3M) ため改善の必要あり
  • 21.
    LibVMIの組み込み方法 ● ビルドシステムの修正 ○ LibVMIのビルドシステムはKVM,Xenなどの本来対応する VMM用のライブラリに深く依存 ○ BitVisor用のdriverをビルドするようにMakefileを作成 ■ libvmi/およびその下のarch, driver, os以下 ● 例: include makefiles/libvmi.mak CFLAGS += $(libvmi_INCLUDE) curdir = $(libvmi_dir)/driver subdirs-1 += bitvisor obj = $(subst .c,.o,$(shell find $(curdir) -maxdepth 1 -name "*.c")) objs-1 += $(obj:$(curdir)/%=%)
  • 22.
    近況 ● Luaはほぼ完全に動作 ○ muslのprint系が壊れていて変な出力が出るものの特に影響はない ●LibVMIが完全に動いていない ○ 動きはするがオフセットがずれている ● ネットワーク監視が完全に動いていない ○ netapiの使い方が把握しきれていない ● 全てのAPIに対応できていない
  • 23.
    まとめ ● BitVisorへの移植は比較的容易な手順で実現可能 ○ 一番上の階層にプロジェクト用のディレクトリを切り分ける ○プロジェクトをそのままコピーする ○ Makefile内でプロジェクトのビルドを行う ○ 生成された.oファイルを検索してobjs-1に追加 ● libc, GLibcが移植できたため大抵のプログラムは移植可能 ○ OSの機能を必要とするものは移植できない ○ BitVisorを拡張するのが容易に