この記事は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を参照してやればロードアドレスが取得できます。実際に該当処理部分のコードを載せます。
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などを使うのですが、
- [参考] BitVisorにVMMCALLを追加する
http://qiita.com/deep_tkkn/items/ca794e66c25df9a9bf66
call_vmm.cはそのままではカーネルモジュール用にはコンパイルできないので、必要な部分だけを切り出して使用しました。よってハイパーコール番号取得のためのCALL_VMM_GET_FUNCTIONマクロなどを使わず、一部再実装してカーネルモジュールからも使えるようにしました。
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にモジュールのアドレスを通知します。
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を追加します。とりあえず引数で渡ってきたモジュールのアドレスを表示するだけです。
#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すごく扱いやすい。