昨晚安德森先生大半夜开始闹,彻底睡不着了,外婆把安德森先生抱走后,我就写了一篇文章:
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回来啊!
唉,睡了。
浙江温州皮鞋湿,下雨进水不会胖。