Loadable Kernel Module

実行中のカーネルに対してコードを導入する方法として一般的なLoadable Kernel Moduleは当然FreeBSDにも存在する.

FreeBSDではKLD(Dynamic Kernel Linker)と呼ばれる.

Environment

Vagrantでfreebsd/FreeBSD-11.4-STABLEを使用している.

vagrant@freebsd
OS: FreeBSD
Kernel: amd64 FreeBSD 11.4-STABLE
Uptime: 1h 14m
Packages: 105
Shell: csh (csh)
Disk: 192G / 495G (39%)
CPU: AMD Ryzen 7 3700X 8-Core
GPU: VirtualBox Graphics Adapter
RAM: 619MiB / 4096MiB

クソデカソースコード落としたりビルドしたりするからストレージもCPUも強めに.

Get kernel source code

FreeBSDではソースコードの入手にsvnを用いるのが一般的らしい.知らんけど.

このVMは11.4-STABLEなので

cd /usr/src
svnlite checkout https://svn.freebsd.org/base/releng/11.4 .

gitはここにある.

バージョン毎のブランチがあったりするからいい感じにアレできそう.

Hello kernel

module event handler

モジュールがカーネルにloadされる,カーネルからunloadされる度,module event handlerと呼ばれる関数が呼び出される.

全てのモジュールはmodule event handlerを実装する必要があり,それは<sys/module.h>で定義されている.

typedef int (*modeventhand_t)(module_t, int /* modeventhand_t */, void *);

module_tmoduleへのポインタ.

また,modeventtype_t<sys/module.h>で以下のように定義されている

typedef enum modeventtype {
    const char  *name;       /* module name */
    modeventhand_t  evhand;     /* event handler */
    void        *priv;      /* extra data */
} moduledata_t;

イベントハンドラの実装はこんな感じ

static int
load(struct module *module, int cmd, void *arg)
{
    int error = 0;

    switch (cmd) {
    case MOD_LOAD:
        uprintf("Hello, world!\n");
        break;

    case MOD_UNLOAD:
        uprintf("Good-bye, cruel world!\n");
        break;

    default:
        error = EOPNOTSUPP;
        break;
    }

    return error;
}

自明だけど,ロードされるとHello, world!,アンロードされるとGood-bye, cruel world!と出力される.

DECLARE_MODULE

KLDがkldloadによってロードされると,カーネルにリンクし,登録されるが, これは<sys/module.h>で定義されているDECLARE_MODULEで簡単に実行できる.

#define MODULE_KERNEL_MAXVER    (roundup(__FreeBSD_version, 100000) - 1)

#define DECLARE_MODULE_WITH_MAXVER(name, data, sub, order, maxver)  \
    MODULE_DEPEND(name, kernel, __FreeBSD_version,      \
        __FreeBSD_version, maxver);     \
    MODULE_METADATA(_md_##name, MDT_MODULE, &data, #name);      \
    SYSINIT(name##module, sub, order, module_register_init, &data); \
    struct __hack

#define DECLARE_MODULE(name, data, sub, order)          \
    DECLARE_MODULE_WITH_MAXVER(name, data, sub, order, MODULE_KERNEL_MAXVER)

各引数(name, data, sub, order)

name

モジュール名

data

モジュール名とイベントハンドラを含むmoduledata

typedef struct moduledata {
    const char      *name;
    modeventhand_t  evhand;
    void            *priv;
} moduledata_t;

sub

モジュールの種類

<sys/kernel.h>sysinit_sub_idのどれかを指定する.

とりあえずSI_SUB_DRIVERSにしておく.

order

KLDによる初期化の順序

<sys/kernel.h>sysinit_elem-orderのどれかを指定する.

とりあえずSI_ORDER_MIDDLEにしておくと,途中で初期化される.

somewhere in the middle

“Hello, world!”

#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernle.h>
#include <sys/systm.h>

static int
load(struct module *module, int cmd, void *arg)
{
    int error = 0;

    switch (cmd) {
    case MOD_LOAD:
        uprintf("Hello, world!\n");
        break;
    case MOD_UNLOAD:
        uprintf("Good-bye, cruel world!\n");
        break;
    default:
        error = EOPNOTSUPP;
        break;
    }

    return (error);
}

static moduledata_t hello_mod = {
    "hello",
    load,
    NULL
};

DECLARE_MODULE(hello, hello_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);

Makefileは

KMOD= hello
SRCS= hello.c
SYSDIR= /usr/src/11/sys

.include <bsd.kmod.mk>

makeで普通にビルドできる.

$ make
machine -> /usr/src/11/sys/amd64/include
x86 -> /usr/src/11/sys/x86/include
Warning: Object directory not changed from original /home/vagrant/src/hello_kernel
cc -O2 -pipe  -fno-strict-aliasing -Werror -D_KERNEL -DKLD_MODULE -nostdinc   -I. -I/usr/src/11/sys -fno-common  -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer   -MD  -MF.depend.hello.o -MThello.o -mcmodel=kernel -mno-red-zone -mno-mmx -mno-sse -msoft-float  -fno-asynchronous-unwind-tables -ffreestanding -fwrapv -fstack-protector -Wall -Wredundant-decls -Wnested-externs -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Winline -Wcast-qual -Wundef -Wno-pointer-sign -D__printf__=__freebsd_kprintf__ -Wmissing-include-dirs -fdiagnostics-show-option -Wno-unknown-pragmas -Wno-error-tautological-compare -Wno-error-empty-body -Wno-error-parentheses-equality -Wno-error-unused-function -Wno-error-pointer-sign -Wno-error-shift-negative-value -Wno-address-of-packed-member  -mno-aes -mno-avx  -std=iso9899:1999 -c hello.c -o hello.o
ld -m elf_x86_64_fbsd -d -warn-common -r -d -o hello.ko hello.o
:> export_syms
awk -f /usr/src/11/sys/conf/kmod_syms.awk hello.ko  export_syms | xargs -J% objcopy % hello.ko
objcopy --strip-debug hello.ko

$ l
export_syms  hello.c  hello.ko  hello.o  machine  Makefile  x86

load, unloadもできる

$ sudo kldload ./hello.ko
Hello, world!
$ sudo kldunload hello.ko
Good-bye, cruel world

参考文献

Designing BSD Rootkits

非常に良書だが,情報が古いので,自分でやりながら現在の環境に合わせてメモをしている.