int3和SingleStep联合实现Linux系统键盘监控(kprobe原理)

做~自己de王妃 提交于 2020-07-27 12:59:34

就着Linux系统键盘监控这个话题,再写点什么。

前面已经写了三篇,可以一起汇总着看,算是一个简单的一题多解吧:

以上所有的这些,都不是Linux内核标准的做法,如果把这些看作一些奇技淫巧的话,Linux内核标准的做法则是另一种把戏:

  • int3结合debug寄存器单步执行。

是的,这就是Linux内核kprobe的方法。一个完整的kprobe闭环如下图所示:
在这里插入图片描述【绝大多数文章都没有说清楚kprobe的原理,只是单纯的源码分析】

按照这个逻辑, inline hook可以hook 任意位置,任意指令长度的代码了。

对于键盘监控,按照上述原理,下面是代码:

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

		// 按照x86_64传参规则,从rdi中取出参数1,从rsi中取参数2
		tty = (struct tty_struct *)regs->di;
		c = regs->si;
		printk("debug %c  %s\n", c, tty->name);

		// 保留原始IP寄存器
		old = regs->ip;
		// 新的IP设置为被int3替换的指令
		regs->ip = (unsigned long)inst;
		// 设置调试寄存器标志,开启单步
		regs->flags |= X86_EFLAGS_TF;
		regs->flags &= ~X86_EFLAGS_IF;

		ret = NOTIFY_STOP;
		break;
	}
	case DIE_DEBUG:
	{
		// 关闭单步模式
		regs->flags &= ~X86_EFLAGS_TF;
		// 恢复原始指令
		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");

下面是效果:

[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

其实,上述代码中,我所不规范的是,printk应该封装在一个独立的handler中,所以,case DIE_INT3应该是:

handler(...)
{
	SAVE_ALL_REGS;
	do_some_thing;
	RESTORE_ALL_REGS;
}

...
case DIE_INT3:
{
	// 保留原始IP寄存器
	old = regs->ip;
	// 新的IP设置为handler
	regs->ip = (unsigned long)handler;
	// 暂不开启单步
	break;

嗯,kprobe差不多也就是这么回事,封装更好而已。


浙江温州皮鞋湿,下雨进水不会胖。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!