jprobe
Rev.5を表示中。最新版はこちら。
jprobeはkprobeで実装されており、その時の引数が継承して関数をフックする。以下はカーネル関数do_sys_openをフックし、新規作成でファイル名がaaaの時、ファイル名をhogehogeにする。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/fs.h>
MODULE_DESCRIPTION("jprobe test");
MODULE_AUTHOR("y.kitamura");
MODULE_LICENSE("GPL");
static long jprobe_do_sys_open(int dfd, char __user *filename,
int flags, umode_t mode)
{
if (flags & O_CREAT && !strcmp(filename, "aaa")) {
printk("create %s file to hogehoge\n", filename);
strcpy(filename, "hogehoge");
}
jprobe_return();
return 0;
}
static struct jprobe jprobe_entry = {
.entry = jprobe_do_sys_open,
.kp = {
.symbol_name = "do_sys_open",
},
};
static int __init jprobe_init(void)
{
int ret;
ret = register_jprobe(&jprobe_entry);
if (ret < 0) {
return -1;
}
return 0;
}
static void __exit jprobe_exit(void)
{
unregister_jprobe(&jprobe_entry);
}
module_init(jprobe_init)
module_exit(jprobe_exit)
実行結果[root@localhost lkm]# insmod jprobe_test.ko [root@localhost lkm]# echo "data to aaa" > aaa [root@localhost lkm]# cat hogehoge data to aaa [root@localhost lkm]# dmesg : [ 826.516923] create aaa file to hogehoge
補足
検証時は、if (flags & O_CREAT && !strcmp(filename, "aaa"))で、条件しておかないと、システム下でdo_sys_openが呼ばれており、システムが不安定になるようです。実装?
register_jprobe()は、num=1でregister_jprobes()をコールする。arch_deref_entry_point()はjp->entr(サンプルのjprobe_do_sys_open)のアドレスを取得する。それが関数のエントリであるかどうか、kallsyms_lookup_size_offset()でチェックする。サンプルではlkmで定義した関数を設定しているが、カーネル内にある関数を設定してもいいみたい。で、struct kprobe->pre_handlerにsetjmp_pre_handleを、struct kprobe->brea_handlerにlongjmp_break_handlerを設定して、register_kprobe()をコールしている。
post_handlerでなく、brea_handlerに設定しているところがポイント。post_handlerはプロブしたアドレスが実行した後に、呼び出される。brea_handlerは、ブレイクしてpre_handlerが呼び出され、プロブしたアドレスに戻る前に実行する。(と思う。)
int __kprobes register_jprobes(struct jprobe **jps, int num)
{
struct jprobe *jp;
int ret = 0, i;
if (num <= 0)
return -EINVAL;
for (i = 0; i < num; i++) {
unsigned long addr, offset;
jp = jps[i];
addr = arch_deref_entry_point(jp->entry);
if (kallsyms_lookup_size_offset(addr, NULL, &offset) &&
offset == 0) {
jp->kp.pre_handler = setjmp_pre_handler;
jp->kp.break_handler = longjmp_break_handler;
ret = register_kprobe(&jp->kp);
} else
ret = -EINVAL;
if (ret < 0) {
if (i > 0)
unregister_jprobes(jps, i);
break;
}
}
return ret;
}
setjmp_pre_handler()はCPU変数struct kprobe_ctlblk *kcbに、ブレイクした時に各レジスタおよびスタック内容を保存し、次に実行するアドレスをregs->ipに返している。
int __kprobes setjmp_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
struct jprobe *jp = container_of(p, struct jprobe, kp);
unsigned long addr;
struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
kcb->jprobe_saved_regs = *regs;
kcb->jprobe_saved_sp = stack_addr(regs);
addr = (unsigned long)(kcb->jprobe_saved_sp);
memcpy(kcb->jprobes_stack, (kprobe_opcode_t *)addr,
MIN_STACK_SIZE(addr));
regs->flags &= ~X86_EFLAGS_IF;
trace_hardirqs_off();
regs->ip = (unsigned long)(jp->entry);
return 1;
}
longjmp_break_handler()でsetjmp_pre_handler()で設定しれレジスタ/スタックダンプを復帰してるっぽい。
int __kprobes longjmp_break_handler(struct kprobe *p, struct pt_regs *regs)
{
struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
u8 *addr = (u8 *) (regs->ip - 1);
struct jprobe *jp = container_of(p, struct jprobe, kp);
if ((addr > (u8 *) jprobe_return) &&
(addr < (u8 *) jprobe_return_end)) {
if (stack_addr(regs) != kcb->jprobe_saved_sp) {
struct pt_regs *saved_regs = &kcb->jprobe_saved_regs;
printk(KERN_ERR
"current sp %p does not match saved sp %p\n",
stack_addr(regs), kcb->jprobe_saved_sp);
printk(KERN_ERR "Saved registers for jprobe %p\n", jp);
show_registers(saved_regs);
printk(KERN_ERR "Current registers\n");
show_registers(regs);
BUG();
}
*regs = kcb->jprobe_saved_regs;
memcpy((kprobe_opcode_t *)(kcb->jprobe_saved_sp),
kcb->jprobes_stack,
MIN_STACK_SIZE(kcb->jprobe_saved_sp));
preempt_enable_no_resched();
return 1;
}
return 0;
}





