BitVisor

BitVisorにVMCALLを追加してドライバを検知してみる

More than 1 year has passed since last update.

この記事はBitVisor Advent Calendar 8日目の記事です.

BitVisorにVMCALLを追加する事で、Linux上でのデバイスドライバのロードを検知してみます。


システムコールフック

デバイスドライバはinsmodなどでカーネルに組み込まれます。この時init_moduleまたはfinit_moduleシステムコールが呼ばれドライバがロードされるわけですが、これらのシステムコールをフックしてVMCALLでBitVisorに通知するようなエージェントの役割を持つカーネルモジュールを開発してみます。

システムコールのフックはsys_call_tableのページパーミッションを書き込み可能に変更して、フック関数にすげ替える手法を取ります。

- Linuxカーネルでsystem callのhook http://kernhack.hatenablog.com/entry/2014/12/05/000346


ドライバのアドレス取得

init_moduleによって読み込まれたモジュールは内部でロード処理を行い、struct module->module_coreメンバにロードされたアドレスが格納されます。

よってinit_moduleのフック関数内で、オリジナルのinit_moduleを実行してロードさせた後、struct moduleを参照してやればロードアドレスが取得できます。実際に該当処理部分のコードを載せます。


tools/drvhook/drvhook.c

static struct module*

get_module(const char *modname)
{
struct module *mod = NULL;
if (mutex_lock_interruptible(&module_mutex) != 0) {
goto mutex_fail;
}

mod = find_module(modname);

mutex_unlock(&module_mutex);
mutex_fail:
pr_info("get_module(%s) = %p\n", modname, mod);
return mod;
}

asmlinkage long
k2e_sys_finit_module (int fd, const char __user *uargs, int flags)
{
long orig;
struct module *mod = NULL;
pr_info("call k2e_sys_finit_module\n");

orig = orig_sys_finit_module (fd, uargs, flags);

mod = get_module(target);
if (mod)
vmcall_drvhook(mod->module_core);

return orig;
}


k2e_sys_finit_moduleというのがフック関数で、find_moduleでターゲットとするドライバを検索します。orig_sys_finit_moduleでオリジナルを実行した後なので、この段階では既にロードが完了しており、find_moduleで情報が取得できるわけです。


BitVisorへの通知

では取得したドライバのロードアドレスをVMCALLでBitVisorに通知してみます。

通常BitVisorにVMCALLを追加するにはtools/common/call_vmm.cなどを使うのですが、

call_vmm.cはそのままではカーネルモジュール用にはコンパイルできないので、必要な部分だけを切り出して使用しました。よってハイパーコール番号取得のためのCALL_VMM_GET_FUNCTIONマクロなどを使わず、一部再実装してカーネルモジュールからも使えるようにしました。


tools/drvhook/drvhook.c

MODULE_DESCRIPTION("guest driver hook for k2e");

MODULE_AUTHOR("rkx1209");
MODULE_LICENSE("GPL");

/* following string SYSCALL_TABLE_ADDRESS will be replaced by set_syscall_table_address.sh */
static void **syscall_table = (void *) 0xffffffff81a001c0;
static char *target = "blahblah";
module_param(target, charp, S_IRUGO);

asmlinkage long (*orig_sys_init_module)(void __user *umod, unsigned long len, const char __user *uargs);
asmlinkage long (*orig_sys_finit_module)(int fd, const char __user *uargs, int flags);

void
call_vmm_call_function (call_vmm_function_t *function,
call_vmm_arg_t *arg, call_vmm_ret_t *ret)
{
struct call_vmm_call_function_sub_data data;
data.function = function;
data.arg = arg;
data.ret = ret;
pr_info("vmcall %d(arg=0x%lx)\n", data.function->vmmcall_number, data.arg->rbx);
switch (data.function->vmmcall_type) {
case VMMCALL_TYPE_VMCALL:
asm volatile ("vmcall"
: "=a" (data.ret->rax), "=b" (data.ret->rbx),
"=c" (data.ret->rcx), "=d" (data.ret->rdx),
"=S" (data.ret->rsi), "=D" (data.ret->rdi)
: "a" (data.function->vmmcall_number),
"b" (data.arg->rbx),
"c" (data.arg->rcx), "d" (data.arg->rdx),
"S" (data.arg->rsi), "D" (data.arg->rdi)
: "memory");
break;
case VMMCALL_TYPE_VMMCALL:
asm volatile ("vmmcall"
: "=a" (data.ret->rax), "=b" (data.ret->rbx),
"=c" (data.ret->rcx), "=d" (data.ret->rdx),
"=S" (data.ret->rsi), "=D" (data.ret->rdi)
: "a" (data.function->vmmcall_number),
"b" (data.arg->rbx),
"c" (data.arg->rcx), "d" (data.arg->rdx),
"S" (data.arg->rsi), "D" (data.arg->rdi)
: "memory");
break;
}
}

/* Get a entry number of specified vmmcall */
void
vmmcall_get_function(const char *vmmcall, call_vmm_function_t *res)
{
call_vmm_function_t gf;
call_vmm_arg_t gf_a;
call_vmm_ret_t gf_r;

gf.vmmcall_number = GET_VMMCALL_NUMBER;
gf.vmmcall_type = VMMCALL_TYPE_VMCALL;
gf_a.rbx = (intptr_t)vmmcall;
pr_info("vmmcall_string:%p, 0x%lx, %s\n", vmmcall, gf_a.rbx, vmmcall);
call_vmm_call_function(&gf, &gf_a, &gf_r);

/* RAX = vmmcall number */
res->vmmcall_number = (int)gf_r.rax;
res->vmmcall_type = VMMCALL_TYPE_VMCALL; // XXX: should get this value too.
}


vmmcall_get_functionでハイパーコールの番号を取得できます。後はdrvhookという新しいVMCALL(手元では4番になりました)を発行して、BitVisorにモジュールのアドレスを通知します。


tools/drvhook/drvhook.c

static void

vmcall_drvhook (void *mod)
{
call_vmm_function_t drvf;
call_vmm_arg_t drv_a;
call_vmm_ret_t drv_r;
char drvhook[] = "drvhook";

vmmcall_get_function(drvhook, &drvf);
pr_info("drvhook number=%d\n",drvf.vmmcall_number);

drv_a.rbx = (intptr_t)mod;
call_vmm_call_function(&drvf, &drv_a, &drv_r);
}



BitVisorにVMCALLを追加する

ゲストのエージェントモジュールは実装できたので、あとはBitVisor側にVMCALLを追加します。とりあえず引数で渡ってきたモジュールのアドレスを表示するだけです。


core/vmmcall_drvhook.c

#include "config.h"

#include "constants.h"
#include "current.h"
#include "debug.h"
#include "initfunc.h"
#include "panic.h"
#include "printf.h"
#include "process.h"
#include "thread.h"
#include "spinlock.h"
#include "vmmcall.h"

static void
drvhook(void)
{
ulong drvaddr;
ulong ret = 0;
current->vmctl.read_general_reg(GENERAL_REG_RBX, &drvaddr);
printf("k2e: driver has loaded at 0x%lx", drvaddr);
current->vmctl.write_general_reg(GENERAL_REG_RAX, (ulong)ret);
}

static void
vmmcall_drvhook_init(void)
{
vmmcall_register("drvhook", drvhook);
}
INITFUNC ("vmmcal0", vmmcall_drvhook_init);



実行

では実際に実行してみましょう。

testmodというテスト用のモジュールを書いてフックしてみます。

sudo insmod drvhook.ko target="testmod"

sudo insmod testmod.ko

dmesgで確認するとちゃんとエージェントが動いている事が分かります。


[ 47.663567] system call replaced
[ 57.012967] call k2e_sys_finit_module
[ 57.014397] testmod: module license 'unspecified' taints kernel.
[ 57.014399] Disabling lock debugging due to kernel taint
[ 57.014696] try 'sudo mknod testdev c 200 0'
[ 57.014729] get_module(testmod) = ffffffffc095b100
[ 57.014732] vmmcall_string:ffff880117e7fef8, 0xffff880117e7fef8, drvhook
[ 57.014733] vmcall 0(arg=0xffff880117e7fef8)
[ 57.014751] drvhook number=4
[ 57.014753] vmcall 4(arg=0xffffffffc0959000)

dbgshでBitVisor側のログを見てみると

k2e: driver has loaded at 0xffffffffc0959000`

ちゃんとドライバの正しいロード場所が検知できました。

簡単な処理ですが、一日ちょっとで書けてしまった。BitVisorすごく扱いやすい。