手工處理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]:

浙江溫州皮鞋溼,下雨進水不會胖!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章