就着Linux系统键盘监控这个话题,再写点什么。
前面已经写了三篇,可以一起汇总着看,算是一个简单的一题多解吧:
- 标准inline hook的方法:
https://blog.csdn.net/dog250/article/details/106425811 - 手工push/ret jmp inline hook的方法:
https://blog.csdn.net/dog250/article/details/106481123 - 手工构造push %rbp效果的方法:
https://blog.csdn.net/dog250/article/details/106493618
以上所有的这些,都不是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差不多也就是这么回事,封装更好而已。
浙江温州皮鞋湿,下雨进水不会胖。
来源:oschina
链接:https://my.oschina.net/u/4403186/blog/4299883