为Linux内核函数插入二进制指令并且校准偏移的手艺

前面写了两篇关于杂耍把戏的手艺方面的文章:
https://blog.csdn.net/dog250/article/details/105129254
https://blog.csdn.net/dog250/article/details/105093969
并不是很过瘾,因为当我将一个函数完整搬移到另一处的时候,我有意避开了采用相对地址寻址指令偏移校准 核心问题 ,还美其名曰为了简单。

失去了指令校准,二进制hook技术便失去了灵魂!

那么,本文我们将直面相对地址寻址的指令偏移校准问题。

首先,我们来看前文的那个hook_read函数:

static ssize_t hook_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
	int n = 0;
	char kb[16];
	if (*ppos != 0) {
		return 0;
	}

	n = sprintf(kb, "%d", 1234);
	memcpy(ubuf, kb, n);
	*ppos += n;
	return n;
}

我们看它的dis反汇编:

crash> dis hook_read
0xffffffffa0374000 <hook_read>: nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffa0374005 <hook_read+5>:       push   %rbp
0xffffffffa0374006 <hook_read+6>:       mov    %rsp,%rbp
0xffffffffa0374009 <hook_read+9>:       push   %r13
0xffffffffa037400b <hook_read+11>:      push   %r12
0xffffffffa037400d <hook_read+13>:      push   %rbx
0xffffffffa037400e <hook_read+14>:      mov    %rcx,%rbx
0xffffffffa0374011 <hook_read+17>:      sub    $0x18,%rsp
0xffffffffa0374015 <hook_read+21>:      mov    %gs:0x28,%rax
0xffffffffa037401e <hook_read+30>:      mov    %rax,-0x20(%rbp)
0xffffffffa0374022 <hook_read+34>:      xor    %eax,%eax
0xffffffffa0374024 <hook_read+36>:      cmpq   $0x0,(%rcx)
0xffffffffa0374028 <hook_read+40>:      jne    0xffffffffa037405a <hook_read+90>
0xffffffffa037402a <hook_read+42>:      lea    -0x30(%rbp),%rdi
0xffffffffa037402e <hook_read+46>:      mov    %rsi,%r13
0xffffffffa0374031 <hook_read+49>:      mov    $0x4d2,%edx
0xffffffffa0374036 <hook_read+54>:      mov    $0xffffffffa0375024,%rsi
0xffffffffa037403d <hook_read+61>:      callq  0xffffffff812fd960 <sprintf>
0xffffffffa0374042 <hook_read+66>:      lea    -0x30(%rbp),%rsi
0xffffffffa0374046 <hook_read+70>:      movslq %eax,%r12
0xffffffffa0374049 <hook_read+73>:      mov    %r13,%rdi
0xffffffffa037404c <hook_read+76>:      mov    %r12,%rdx
0xffffffffa037404f <hook_read+79>:      callq  0xffffffff812ff530 <__memcpy>
0xffffffffa0374054 <hook_read+84>:      add    %r12,(%rbx)
0xffffffffa0374057 <hook_read+87>:      mov    %r12,%rax
0xffffffffa037405a <hook_read+90>:      mov    -0x20(%rbp),%rdx
0xffffffffa037405e <hook_read+94>:      xor    %gs:0x28,%rdx
0xffffffffa0374067 <hook_read+103>:     jne    0xffffffffa0374074 <hook_read+116>
0xffffffffa0374069 <hook_read+105>:     add    $0x18,%rsp
0xffffffffa037406d <hook_read+109>:     pop    %rbx
0xffffffffa037406e <hook_read+110>:     pop    %r12
0xffffffffa0374070 <hook_read+112>:     pop    %r13
0xffffffffa0374072 <hook_read+114>:     pop    %rbp
0xffffffffa0374073 <hook_read+115>:     retq
0xffffffffa0374074 <hook_read+116>:     callq  0xffffffff81074510 <__stack_chk_fail>

OK,现在让我们通过前文所描述的技术将它拷贝到别的地方:

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");
}
...
char *addr = 0xffffffffa0374000;
p = test_sub1;

_text_poke_smp(p, addr, 130);

然后我们看看test_sub1的反汇编成了什么样子:

crash> dis ffffffffa037f000 80
0xffffffffa037f000 <test_sub1>: nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffa037f005 <test_sub1+5>:       push   %rbp
0xffffffffa037f006 <test_sub1+6>:       mov    %rsp,%rbp
0xffffffffa037f009 <test_sub1+9>:       push   %r13
0xffffffffa037f00b <test_sub1+11>:      push   %r12
0xffffffffa037f00d <test_sub1+13>:      push   %rbx
0xffffffffa037f00e <test_sub1+14>:      mov    %rcx,%rbx
0xffffffffa037f011 <test_sub1+17>:      sub    $0x18,%rsp
0xffffffffa037f015 <test_sub1+21>:      mov    %gs:0x28,%rax
0xffffffffa037f01e <test_sub1+30>:      mov    %rax,-0x20(%rbp)
0xffffffffa037f022 <test_sub1+34>:      xor    %eax,%eax
0xffffffffa037f024 <test_sub1+36>:      cmpq   $0x0,(%rcx)
0xffffffffa037f024 <test_sub1+36>:      cmpq   $0x0,(%rcx)
0xffffffffa037f028 <test_sub1+40>:      jne    0xffffffffa037f05a <test_sub1+90>
0xffffffffa037f02a <test_sub1+42>:      lea    -0x30(%rbp),%rdi
0xffffffffa037f02e <test_sub1+46>:      mov    %rsi,%r13
0xffffffffa037f031 <test_sub1+49>:      mov    $0x4d2,%edx
0xffffffffa037f036 <test_sub1+54>:      mov    $0xffffffffa0375024,%rsi
0xffffffffa037f03d <test_sub1+61>:      callq  0xffffffff81313960 <__dynamic_netdev_dbg+48>
0xffffffffa037f042 <test_sub1+66>:      lea    -0x30(%rbp),%rsi
0xffffffffa037f046 <test_sub1+70>:      movslq %eax,%r12
0xffffffffa037f049 <test_sub1+73>:      mov    %r13,%rdi
0xffffffffa037f04c <test_sub1+76>:      mov    %r12,%rdx
0xffffffffa037f04f <test_sub1+79>:      callq  0xffffffff8130a530 <assoc_array_gc+1168>
0xffffffffa037f054 <test_sub1+84>:      add    %r12,(%rbx)
0xffffffffa037f057 <test_sub1+87>:      mov    %r12,%rax
0xffffffffa037f05a <test_sub1+90>:      mov    -0x20(%rbp),%rdx
0xffffffffa037f05e <test_sub1+94>:      xor    %gs:0x28,%rdx
0xffffffffa037f067 <test_sub1+103>:     jne    0xffffffffa037f074 <test_sub1+116>

全部乱套,像什么__dynamic_netdev_dbg,assoc_array_gc之类的调用纯属无稽之谈!如果你去运行它,系统将会跑飞!

这正是因为我们没有对其进行相对地址校准所导致的。

我们要做的很简单,只需要扫描相对地址寻址的指令即可,类似的比如 “0xe8 call”,“0xe9 jmp” 等等。

我们以hook_read的下面的指令为例:

0xffffffffa037403d <hook_read+61>:      callq  0xffffffff812fd960 <sprintf>

我们试着去拆解它:

crash> rd -8 0xffffffffa037403d
ffffffffa037403d:  e8                                                .
crash> rd -8 0xffffffffa037403e
ffffffffa037403e:  1e                                                .
crash> rd -8 0xffffffffa037403f
ffffffffa037403f:  99                                                .
crash> rd -8 0xffffffffa0374040
ffffffffa0374040:  f8                                                .
crash> rd -8 0xffffffffa0374041
ffffffffa0374041:  e0                                                .
crash> rd -8 0xffffffffa0374042
ffffffffa0374042:  48                                                H
crash> rd -32 0xffffffffa037403e
ffffffffa037403e:  e0f8991e

OK,我们知道0xe0f8991e是个相对偏移,我们只需要在新的函数中,用它减去额外的函数偏移,就是原始的偏移了,做法很简单,如下:

	unsigned int off;
	unsigned int v;
	...
	p = test_sub1;
	addr = _hook_read;
	pos = (unsigned int)((long)p - (long)addr);
	v = *((unsigned int *)&addr[62]);
	v -= off;

	_text_poke_smp(&p[62], &v, 4);

照着这个路子,我们把整个函数的类似指令全部校准后,再次dis,就会看到正确的结果了:

crash> dis ffffffffa0365000 80
0xffffffffa0365000 <test_sub1>: nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffa0365005 <test_sub1+5>:       push   %rbp
0xffffffffa0365006 <test_sub1+6>:       mov    %rsp,%rbp
0xffffffffa0365009 <test_sub1+9>:       push   %r13
0xffffffffa036500b <test_sub1+11>:      push   %r12
0xffffffffa036500d <test_sub1+13>:      push   %rbx
0xffffffffa036500e <test_sub1+14>:      mov    %rcx,%rbx
0xffffffffa0365011 <test_sub1+17>:      sub    $0x18,%rsp
0xffffffffa0365015 <test_sub1+21>:      mov    %gs:0x28,%rax
0xffffffffa036501e <test_sub1+30>:      mov    %rax,-0x20(%rbp)
0xffffffffa0365022 <test_sub1+34>:      xor    %eax,%eax
0xffffffffa0365024 <test_sub1+36>:      cmpq   $0x0,(%rcx)
0xffffffffa0365028 <test_sub1+40>:      jne    0xffffffffa036505a <test_sub1+90>
0xffffffffa036502a <test_sub1+42>:      lea    -0x30(%rbp),%rdi
0xffffffffa036502e <test_sub1+46>:      mov    %rsi,%r13
0xffffffffa0365031 <test_sub1+49>:      mov    $0x4d2,%edx
0xffffffffa0365036 <test_sub1+54>:      mov    $0xffffffffa0375024,%rsi
0xffffffffa036503d <test_sub1+61>:      callq  0xffffffff812fd960 <sprintf>
0xffffffffa0365042 <test_sub1+66>:      lea    -0x30(%rbp),%rsi
0xffffffffa0365046 <test_sub1+70>:      movslq %eax,%r12
0xffffffffa0365049 <test_sub1+73>:      mov    %r13,%rdi
0xffffffffa036504c <test_sub1+76>:      mov    %r12,%rdx
0xffffffffa036504f <test_sub1+79>:      callq  0xffffffff812ff530 <__memcpy>
0xffffffffa0365054 <test_sub1+84>:      add    %r12,(%rbx)
0xffffffffa0365057 <test_sub1+87>:      mov    %r12,%rax
0xffffffffa036505a <test_sub1+90>:      mov    -0x20(%rbp),%rdx
0xffffffffa036505e <test_sub1+94>:      xor    %gs:0x28,%rdx
0xffffffffa0365067 <test_sub1+103>:     jne    0xffffffffa0365074 <test_sub1+116>
0xffffffffa0365069 <test_sub1+105>:     add    $0x18,%rsp
0xffffffffa036506d <test_sub1+109>:     pop    %rbx
0xffffffffa036506e <test_sub1+110>:     pop    %r12
0xffffffffa0365070 <test_sub1+112>:     pop    %r13
0xffffffffa0365072 <test_sub1+114>:     pop    %rbp
// 以下即我们需要插入的7个字节的指令
0xffffffffa0365073 <test_sub1+115>:     incl   0xffffffff81977890
0xffffffffa036507a <test_sub1+122>:     retq

至于更复杂的情况,无非也就是体力活儿而已,二进制hook的基本路子都是一样的:

  • 扫描所有的指令序列。
  • 校准针对函数外部的相对寻址的指令,使其操作数减去函数搬移的偏移值。

当然,这只是工人们的杂耍而已,经理肯定会dis的。


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

发布了1580 篇原创文章 · 获赞 5111 · 访问量 1113万+

猜你喜欢

转载自blog.csdn.net/dog250/article/details/105135219
今日推荐