On the topic of Linux system keyboard monitoring, write something more.
I've written three articles before. I can summarize them together. It's a simple problem and solve it more often
-
Methods of standard inline hook:
https://blog.csdn.net/dog250/article/details/106425811
-
Manual push/ret jmp inline hook method:
https://blog.csdn.net/dog250/article/details/106481123
-
How to manually construct the push% RBP effect:
https://blog.csdn.net/dog250/article/details/106493618
All of the above are not Linux kernel standard practices. If these are regarded as some tricks, the Linux kernel standard is another trick:
- int3 single step with debug register.
Yes, that's how kprobe works in the Linux kernel. A complete kprobe closed-loop is shown in the figure below:[most of the articles don't explain the principle of kprobe, just simple source code analysis]
According to this logic, inline hook can hook any location, any instruction length code.
For keyboard monitoring, according to the above principle, the following is the code:
// int3debughook.c #include <linux/module.h> #include <linux/kdebug.h> #include <linux/kallsyms.h> #include <linux/tty.h> #define DIE_INT3 2 unsigned long orig, old; void inst(void) { asm ("nop;"); } int int3_notify(struct notifier_block *self, unsigned long val,void* data) { struct die_args *args = data; struct pt_regs *regs = args->regs; int ret = NOTIFY_DONE; switch(val){ case DIE_INT3: { struct tty_struct *tty; char c; // As per x86_64 parameter transfer rule, take parameter 1 from rdi and parameter 2 from rsi tty = (struct tty_struct *)regs->di; c = regs->si; printk("debug %c %s\n", c, tty->name); // Keep original IP register old = regs->ip; // New IP set to instruction replaced by int3 regs->ip = (unsigned long)inst; // Set debug register flag to enable single step regs->flags |= X86_EFLAGS_TF; regs->flags &= ~X86_EFLAGS_IF; ret = NOTIFY_STOP; break; } case DIE_DEBUG: { // Turn off single step mode regs->flags &= ~X86_EFLAGS_TF; // Restore original instruction regs->ip = old; ret = NOTIFY_STOP; break; } default: break; } return ret; } static struct notifier_block int3_nb = { .notifier_call = int3_notify, .priority =0x7fffffff, }; unsigned char *p; unsigned long cr0; static int __init int3debug_hook_init(void) { int ret; ret = register_die_notifier(&int3_nb); if (ret) { printk("register_die_notifier failed %d\n", ret); return ret; } orig = (unsigned long)kallsyms_lookup_name("n_tty_receive_char"); p = (unsigned char *)orig; cr0 = read_cr0(); clear_bit(16, &cr0); write_cr0(cr0); *(char *)inst = *p; memset(p, 0xcc, 1); set_bit(16, &cr0); write_cr0(cr0); return 0; } static void __exit int3debug_hook_exit(void) { cr0 = read_cr0(); clear_bit(16, &cr0); write_cr0(cr0); memset(p, *(char *)inst, 1); set_bit(16, &cr0); write_cr0(cr0); unregister_die_notifier(&int3_nb); } module_init(int3debug_hook_init) module_exit(int3debug_hook_exit) MODULE_LICENSE("GPL");
Here are the effects:
[root@localhost probe]# insmod ./int3debughook.ko [root@localhost probe]# dmesg [ 3187.284028] debug d pts0 [ 3187.419775] debug m pts0 [ 3187.556342] debug e pts0 [ 3188.372819] debug s pts0 [ 3188.587878] debug pts0 pts0.204373] debug
In fact, what I don't regulate in the above code is that printk should be encapsulated in a separate handler, so case DIE_INT3 should be:
handler(...) { SAVE_ALL_REGS; do_some_thing; RESTORE_ALL_REGS; } ... case DIE_INT3: { // Keep original IP register old = regs->ip; // New IP set to handler regs->ip = (unsigned long)handler; // Do not open single step temporarily break;
Well, that's almost what kprobe is about. Encapsulation is better.
Wenzhou leather shoes in Zhejiang Province are wet, and they will not be fat if it rains and floods.