LoginSignup
1
0

More than 5 years have passed since last update.

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

Posted at

この記事は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すごく扱いやすい。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0