飞道的博客

给一个Linux内核函数插入一段指令玩玩手艺?

279人阅读  评论(0)

昨晚安德森先生大半夜开始闹,彻底睡不着了,外婆把安德森先生抱走后,我就写了一篇文章:
https://blog.csdn.net/dog250/article/details/105093969
然而没有个实际的东西怎么能行。

虽然很难,但可以来个简单的。

我的意思是,我想选择一个简单的内核函数,向其中插入指令,这个函数简单到不需要矫正相对偏移。可是我并没有找到这样的函数供我测试。于是我准备自己写一个。下面的内核模块就是例子:

#include <linux/module.h>
#include <linux/proc_fs.h>

// 足够简单的函数!
static ssize_t hook_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
	int n = 0;
	return n;
}

static struct file_operations hook_ops = {
	.owner = THIS_MODULE,
	.read = hook_read,
};

static struct proc_dir_entry *ent;
static int __init hookstat_init(void)
{
	ent=proc_create("test",0660,NULL,&hook_ops);
	if (!ent)
		return -1;

	return 0;
}

static void __exit hookstat_exit(void)
{
	proc_remove(ent);
}

module_init(hookstat_init);
module_exit(hookstat_exit);
MODULE_LICENSE("GPL");

加载之。

接下来,我们看看这个hook_read函数:

crash> dis hook_read
0xffffffffa0374000 <hook_read>: nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffa0374005 <hook_read+5>:       push   %rbp
0xffffffffa0374006 <hook_read+6>:       xor    %eax,%eax
0xffffffffa0374008 <hook_read+8>:       mov    %rsp,%rbp
0xffffffffa037400b <hook_read+11>:      pop    %rbp
0xffffffffa037400c <hook_read+12>:      retq

我希望在retq之前插入一行指令,它的作用是 递增系统中panic_on_oops的值!

我们先通过asm内联汇编来dump中递增panic_on_oops值的指令码:

asm ("incl 0xffffffff81977890" :::);

其中的0xffffffff81977890是我在/proc/kallsyms里查到的panic_on_oops的地址,当然你也可以直接用API来查符号。

dump出来的指令码一共7个字节:

	incl[0] = 0xff;
	incl[1] = 0x04;
	incl[2] = 0x25;
	incl[3] = 0x90;
	incl[4] = 0x78;
	incl[5] = 0x97;
	incl[6] = 0x81;
	incl[7] = 0xc3;

我们需要把这7个字节插入retq前。

下面的代码完成了这一过程:

#include <linux/module.h>
#include <linux/cpu.h>

char *p;
static struct mutex *_text_mutex;

void test_sub1(void) __attribute__ ((aligned (1024))); // 提供足够大的空间容纳原始函数
void test_sub2(void) __attribute__ ((aligned (1024)));
void test_sub1(void)
{
	printk("yes\n");
}
void test_sub2(void)
{
	printk("yes yes\n");
}

static void *(*_text_poke_smp)(void *addr, const void *opcode, size_t len);
static int __init hotfix_init(void)
{
	unsigned char e9_jmp[5];
	unsigned char incl[8];
	char *addr;
	s32 offset;

	_text_poke_smp = (void *)0xffffffff8163e1f0;
	_text_mutex = (void *)0xffffffff81984920;

	p = test_sub1;
	addr = 0xffffffffa0374000; // 该地址是通过hook_read符号查到地址。

	_text_poke_smp(p, addr, 20); // 通过crash dis指令,20个字节够了

	offset = (s32)((long)p - (long)addr - 5);

	incl[0] = 0xff;
	incl[1] = 0x04;
	incl[2] = 0x25;
	incl[3] = 0x90;
	incl[4] = 0x78;
	incl[5] = 0x97;
	incl[6] = 0x81;
	incl[7] = 0xc3;
	incl[8] = 0xc3; // 原有的retq指令往后移7个字节
	_text_poke_smp(&p[12], incl, 8); // 在retq前插入incl指令

	e9_jmp[0] = 0xe9;
	(*(s32 *)(&e9_jmp[1])) = offset;
	get_online_cpus();
	mutex_lock(_text_mutex);
	_text_poke_smp(addr, e9_jmp, 5); // 替换原始函数的前面5个字节
	mutex_unlock(_text_mutex);
	put_online_cpus();

	return 0;
}

static void __exit hotfix_exit(void)
{
}

module_init(hotfix_init);
module_exit(hotfix_exit);
MODULE_LICENSE("GPL");

加载之,这个时候,我们不断cat /proc/test,然后看panic_on_oops的值:

[root@localhost ~]# cat /proc/test
[root@localhost ~]# sysctl -a|grep panic_on_oops
kernel.panic_on_oops = 9
[root@localhost ~]# cat /proc/test
[root@localhost ~]# sysctl -a|grep panic_on_oops
kernel.panic_on_oops = 10
[root@localhost ~]# cat /proc/test
[root@localhost ~]# sysctl -a|grep panic_on_oops
kernel.panic_on_oops = 11

效果就是这样。

现在说说复杂的情况,如果我们去hook一个正常的函数呢?如果说我们的hook_read函数变成下面的样子:

static ssize_t dump_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
	char kbuf[16] = {0};

	if (*ppos != 0) {
		return 0;
	}

	n = snprintf(kbuf, 16, "%d\n", 1234);
	memcpy(ubuf, kbuf, n);
	*ppos += n;

	return n;
}

我看看原始的函数的样子:

crash> dis hook_read
0xffffffffa0365000 <hook_read>: nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffa0365005 <hook_read+5>:       push   %rbp
0xffffffffa0365006 <hook_read+6>:       mov    %rsp,%rbp
0xffffffffa0365009 <hook_read+9>:       push   %r13
0xffffffffa036500b <hook_read+11>:      push   %r12
0xffffffffa036500d <hook_read+13>:      push   %rbx
0xffffffffa036500e <hook_read+14>:      mov    %rcx,%rbx
0xffffffffa0365011 <hook_read+17>:      sub    $0x18,%rsp
0xffffffffa0365015 <hook_read+21>:      mov    %gs:0x28,%rax
0xffffffffa036501e <hook_read+30>:      mov    %rax,-0x20(%rbp)
0xffffffffa0365022 <hook_read+34>:      xor    %eax,%eax
0xffffffffa0365024 <hook_read+36>:      cmpq   $0x0,(%rcx)
0xffffffffa0365028 <hook_read+40>:      jne    0xffffffffa036505f <hook_read+95>
0xffffffffa036502a <hook_read+42>:      lea    -0x30(%rbp),%rdi
0xffffffffa036502e <hook_read+46>:      mov    %rsi,%r13
0xffffffffa0365031 <hook_read+49>:      mov    $0x4d2,%ecx
0xffffffffa0365036 <hook_read+54>:      mov    $0xffffffffa0366024,%rdx
0xffffffffa036503d <hook_read+61>:      mov    $0x10,%esi
0xffffffffa0365042 <hook_read+66>:      callq  0xffffffff812fd8f0 <snprintf>
0xffffffffa0365047 <hook_read+71>:      lea    -0x30(%rbp),%rsi
0xffffffffa036504b <hook_read+75>:      movslq %eax,%r12
0xffffffffa036504e <hook_read+78>:      mov    %r13,%rdi
0xffffffffa0365051 <hook_read+81>:      mov    %r12,%rdx
0xffffffffa0365054 <hook_read+84>:      callq  0xffffffff812ff530 <__memcpy>
0xffffffffa0365059 <hook_read+89>:      add    %r12,(%rbx)
0xffffffffa036505c <hook_read+92>:      mov    %r12,%rax
0xffffffffa036505f <hook_read+95>:      mov    -0x20(%rbp),%rdx
0xffffffffa0365063 <hook_read+99>:      xor    %gs:0x28,%rdx
0xffffffffa036506c <hook_read+108>:     jne    0xffffffffa0365079 <hook_read+121>
0xffffffffa036506e <hook_read+110>:     add    $0x18,%rsp
0xffffffffa0365072 <hook_read+114>:     pop    %rbx
0xffffffffa0365073 <hook_read+115>:     pop    %r12
0xffffffffa0365075 <hook_read+117>:     pop    %r13
0xffffffffa0365077 <hook_read+119>:     pop    %rbp
0xffffffffa0365078 <hook_read+120>:     retq
0xffffffffa0365079 <hook_read+121>:     callq  0xffffffff81074510 <__stack_chk_fail>

然后我们看看它hook的样子:

crash> dis 0xffffffffa0374000 100
0xffffffffa0374000 <test_sub1>: nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffa0374005 <test_sub1+5>:       push   %rbp
0xffffffffa0374006 <test_sub1+6>:       mov    %rsp,%rbp
0xffffffffa0374009 <test_sub1+9>:       push   %r13
0xffffffffa037400b <test_sub1+11>:      push   %r12
0xffffffffa037400d <test_sub1+13>:      push   %rbx
0xffffffffa037400e <test_sub1+14>:      mov    %rcx,%rbx
0xffffffffa0374011 <test_sub1+17>:      sub    $0x18,%rsp
0xffffffffa0374015 <test_sub1+21>:      mov    %gs:0x28,%rax
0xffffffffa037401e <test_sub1+30>:      mov    %rax,-0x20(%rbp)
0xffffffffa0374022 <test_sub1+34>:      xor    %eax,%eax
0xffffffffa0374024 <test_sub1+36>:      incl   0xffffffff81977890
0xffffffffa037402b <test_sub1+43>:      retq
0xffffffffa037402c <test_sub1+44>:      jge    0xffffffffa0373ffe
0xffffffffa037402e <test_sub1+46>:      mov    %rsi,%r13
0xffffffffa0374031 <test_sub1+49>:      mov    $0x4d2,%ecx
0xffffffffa0374036 <test_sub1+54>:      mov    $0xffffffffa0366024,%rdx
0xffffffffa037403d <test_sub1+61>:      mov    $0x10,%esi
0xffffffffa0374042 <test_sub1+66>:      callq  0xffffffff8130c8f0 <zlib_inflate+2384>
0xffffffffa0374047 <test_sub1+71>:      lea    -0x30(%rbp),%rsi
0xffffffffa037404b <test_sub1+75>:      movslq %eax,%r12
0xffffffffa037404e <test_sub1+78>:      mov    %r13,%rdi
0xffffffffa0374051 <test_sub1+81>:      mov    %r12,%rdx
0xffffffffa0374054 <test_sub1+84>:      callq  0xffffffff8130e530 <lzo1x_1_do_compress+672>
0xffffffffa0374059 <test_sub1+89>:      add    %r12,(%rbx)
0xffffffffa037405c <test_sub1+92>:      mov    %r12,%rax
0xffffffffa037405f <test_sub1+95>:      mov    -0x20(%rbp),%rdx
0xffffffffa0374063 <test_sub1+99>:      xor    %gs:0x28,%rdx
0xffffffffa037406c <test_sub1+108>:     jne    0xffffffffa0374079 <test_sub1+121>
0xffffffffa037406e <test_sub1+110>:     add    $0x18,%rsp
0xffffffffa0374072 <test_sub1+114>:     pop    %rbx
0xffffffffa0374073 <test_sub1+115>:     pop    %r12
0xffffffffa0374075 <test_sub1+117>:     pop    %r13
0xffffffffa0374077 <test_sub1+119>:     pop    %rbp
0xffffffffa0374078 <test_sub1+120>:     retq
0xffffffffa0374079 <test_sub1+121>:     callq  0xffffffff81083510 <exit_ptrace+128>

跳转乱套了吧,因为没有矫正偏移啊。

不过,和单独分配一篇内存拷贝函数指令相比,直接在附近平移整个函数会比较好,一般而言尾部的retq之后,会有一些空余空间,函数平移下来之后,空下来的空间就可以插入指令。

事实上,只需要可以插入一个相对跳转即可,空间不够,可以先跳到别的地方再jmp回来啊!

唉,睡了。


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


转载:https://blog.csdn.net/dog250/article/details/105129254
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场