手工处理int3实时监控Linux系统键盘输入

上周写了一篇Linux系统监控键盘输入的文字:
https://blog.csdn.net/dog250/article/details/106425811
事后想了下,能不能不用标准的5字节32位相对地址跳转实现inline hook,换一种方法实现呢?

嗯,换int3实现!

stap就是int3实现的,但是那毕竟是现成的工具,我需要一种手艺人的方法。

我们知道,n_tty_receive_char函数实属有符号的inline,它并非FTRACE NOP开头的标准函数,所以本来就很难用5字节替换来inline hook,事实上,int3是另一种标准的做法。

现在,让我们开始。

首先想到的是使用jprobe,这简直是手到擒来(tcp_probe也是用的这种机制):

// ttyprobe.c
#include <linux/kprobes.h>
#include <linux/module.h>
#include <linux/tty.h>

static void jn_tty_receive_char(struct tty_struct *tty, char c)
{
    printk("jprobe [0x%02x]:%c  from %s\n", c, c, tty->name);
    jprobe_return();
}

static struct jprobe tty_jprobe = {
    .kp = {
        .symbol_name = "n_tty_receive_char",
    },
    .entry   = jn_tty_receive_char,
};

static __init int ttyprobe_init(void)
{
    int ret = 0;

    ret = register_jprobe(&tty_jprobe);
    if (ret)
        return -1;

    return 0;
}

static __exit void ttyprobe_exit(void)
{
    unregister_jprobe(&tty_jprobe);
}

module_init(ttyprobe_init);
module_exit(ttyprobe_exit);
MODULE_LICENSE("GPL");

我们看下效果:

[root@localhost probe]# insmod ./ttyprobe.ko
[root@localhost probe]# dmesg
[19299.821572] jprobe [0x64]:d  from pts0
[19299.965399] jprobe [0x6d]:m  from pts0
[19300.053259] jprobe [0x65]:e  from pts0
[19300.253390] jprobe [0x73]:s  from pts0
[19300.429460] jprobe [0x09]:     from pts0
  from pts069] jprobe [0x0d]:

OK,不错。

但是呢,使用jprobe不够手艺,我需要一种更加麻烦的手工方法,好,这就是下面的方法了:

  • 使用int3的die notify!

每当系统遇到int3指令,就会陷入内核,内核会遍历die notifier block chain来逐一通知这件事,特定的notifier block可以按照自己的意思去处理它,kprobe/jprobe就是用这种机制实现的,本文中,我也要用这种方式手工实现。

代码如下:

// int3hook.c
#include <linux/module.h>
#include <linux/kdebug.h>
#include <linux/kallsyms.h>
#include <linux/tty.h>

#define DIE_INT3    2

unsigned long orig;
// DIE_INT3的实际处理函数,RIP将会跳转到这里执行
void stub(struct tty_struct *tty, char c)
{
    /* 第一行inline汇编两个作用:
     * 1. 执行被int3替换掉的push %%rbp指令;
     * 2. 压栈原始函数需要继续执行的位置,用于ret.
     */
    asm ("push %%rbp; push %0;"
         "push %%rbp;" // 从这里开始,将可能会被破坏的寄存器压栈保护
         "push %%rsp;"
         "push %%rsi;"
         "push %%rdi;"
         "push %%r15;"
         "push %%r14;"
         "push %%r13;"
         "push %%r12;"
         "push %%rbx;"
         ::"m"(orig):);
    printk("get key [0x%02x]:%c from %s\n", c, c, tty->name);
    asm ("pop %%rbx;" // 弹出被保护的寄存器
         "pop %%r12;"
         "pop %%r13;"
         "pop %%r14;"
         "pop %%r15;"
         "pop %%rdi;"
         "pop %%rsi;"
         "pop %%rsp;"
         "pop %%rbp;" 
         "ret":::); // 此时的栈顶是orig,ret即可返回到该处继续前行
}

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:
        /* 需要跳过编译器自动生成的stub函数的prologue,比如push rbp之类:
         * <stub>:      nopl   0x0(%rax,%rax,1) [FTRACE NOP]
         * <stub+5>:    push   %rbp
         * <stub+6>:    mov    %rsp,%rbp
         * 所以说,stub直接用汇编写会更方便直接
         */
        regs->ip = (unsigned long)stub + 9;
        ret = NOTIFY_STOP; // 处理结束
        break;
    default:
        break;
    }

    return ret;
}

static struct notifier_block int3_nb = {
    .notifier_call = int3_notify,
    .priority =0x7fffffff,
};

unsigned char *p, old;
unsigned long cr0;
static int __init int3hook_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");

    // 将n_tty_receive_char的第一个字节替换成int3,即0xcc
    p = (unsigned char *)orig;
    old = *p;
    cr0 = read_cr0();
    clear_bit(16, &cr0);
    memset(p, 0xcc, 1);
    set_bit(16, &cr0);
    write_cr0(cr0);

    orig ++;
    return 0;
}

// 也可以不提供exit函数,让经理无条件监控,不允许卸载!
static void __exit int3hook_exit(void)
{
    cr0 = read_cr0();
    clear_bit(16, &cr0);
    memset(p, old, 1);
    set_bit(16, &cr0);
    write_cr0(cr0);
    unregister_die_notifier(&int3_nb);
}

module_init(int3hook_init)
module_exit(int3hook_exit)
MODULE_LICENSE("GPL");

来吧,看看效果:

[root@localhost probe]# insmod ./int3hook.ko
[root@localhost probe]# dmesg
[19851.756885] get key [0x64]:d from pts0
[19851.884746] get key [0x6d]:m from pts0
[19851.989217] get key [0x65]:e from pts0
[19852.180906] get key [0x73]:s from pts0
[19852.412744] get key [0x09]:   from pts0
 from pts0865] get key [0x0d]:

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

原文链接: https://blog.csdn.net/dog250/article/details/106481123

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    手工处理int3实时监控Linux系统键盘输入

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/405962

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午9:32
下一篇 2023年4月26日 上午9:32

相关推荐