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; }