int3 and SingleStep jointly implement keyboard monitoring in Linux system (kprobe principle)

Keywords: Linux

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

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.

Posted by Tyen on Thu, 04 Jun 2020 09:08:22 -0700