Linux kernel binary hook craft - a simple demo

Sometimes, we want to modify some behaviors Linux kernel, or more generally, we need some statistics, for example, we want real-time statistics of the current system of half the number of connections TCP.

But the problem is, there is no ready-made tools may be used, nor can recompile the kernel, then how to do it?

kpatch mechanism is doing that.

You might think I am going to introduce kpatch start of the principles and usage. But not, I'm not good at writing document describes the use of a tool, I am better at craftsman approach. The following pages, started to show a kpatch not use technology to achieve equivalent functionality approach.

I start with a demo introduces the principle, the next article, I will present an example of a formal can be used. If you have some questions on the details, it is recommended try to find the answer in two days before I wrote the article, if there are problems to be solved, directly email me or wechat I can.

My previous article linked below:
https://blog.csdn.net/dog250/article/details/105135219
https://blog.csdn.net/dog250/article/details/105129254
https://blog.csdn.net / dog250 / article / details / 105093969

Well, let us direct on demo module code:

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

static ssize_t sample_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\n", 1234);
	memcpy(ubuf, kb, n);
	*ppos += n;
	return n;
}

static struct file_operations sample_ops = {
	.owner = THIS_MODULE,
	.read = sample_read,
};

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

	return 0;
}

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

module_init(sample_init);
module_exit(sample_exit);
MODULE_LICENSE("GPL");

The module creates / proc / test file in the file system procfs, my goal is that every time the file is read when a counter counter another kernel module this value is incremented by one.

Because the module has been written, we can not to modify it, this is the point craft show, and we from the outside in another module in operation to increase the counter is incremented.

In order to live hook sample_read this function, we look at its disassembly:

crash> dis sample_read
0xffffffffa00e1000 <sample_read>:       nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffa00e1005 <sample_read+5>:     push   %rbp
0xffffffffa00e1006 <sample_read+6>:     mov    %rsp,%rbp
0xffffffffa00e1009 <sample_read+9>:     push   %r13
0xffffffffa00e100b <sample_read+11>:    push   %r12
0xffffffffa00e100d <sample_read+13>:    push   %rbx
0xffffffffa00e100e <sample_read+14>:    mov    %rcx,%rbx
0xffffffffa00e1011 <sample_read+17>:    sub    $0x18,%rsp
0xffffffffa00e1015 <sample_read+21>:    mov    %gs:0x28,%rax
0xffffffffa00e101e <sample_read+30>:    mov    %rax,-0x20(%rbp)
0xffffffffa00e1022 <sample_read+34>:    xor    %eax,%eax
// 下面是if (*ppos != 0) 判断语句
0xffffffffa00e1024 <sample_read+36>:    cmpq   $0x0,(%rcx)
0xffffffffa00e1028 <sample_read+40>:    jne    0xffffffffa00e105a <sample_read+90>
// 下面的两行就是HOOK点!!!
0xffffffffa00e102a <sample_read+42>:    lea    -0x30(%rbp),%rdi
0xffffffffa00e102e <sample_read+46>:    mov    %rsi,%r13
0xffffffffa00e1031 <sample_read+49>:    mov    $0x4d2,%edx
0xffffffffa00e1036 <sample_read+54>:    mov    $0xffffffffa00e2024,%rsi
0xffffffffa00e103d <sample_read+61>:    callq  0xffffffff812fd960 <sprintf>

We only need to lea and two rows behind mov replaced with a jmp / call to address their own definition of it, while the lea / mov moved from inside-defined logic to execute after the execution is complete, the jmp / ret back.

In the present embodiment, in order to avoid jmp jmp back again past, the use of Call, since ret jmp back automatically.

Very simple logic, directly on the bar code:

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

char *stub;
char *addr = NULL;

// laddr 参数保存sample_read的地址,通过/proc/kallsyms查询后传入
static unsigned long laddr = 0xffffffffa0267000;
module_param(laddr, ulong, 0644);

// 独立的计数器,每次读取/proc/test时,该计数器加1
static unsigned int counter = 0;
module_param(counter, int, 0444);

void test_stub1(void) __attribute__ ((aligned (1024)));
void test_stub2(void) __attribute__ ((aligned (1024)));
void test_stub1(void)
{
	printk("yes\n");
}
void test_stub2(void)
{
	printk("yes yes\n");
}

#define POKE_OFFSET		42 // sample_read的42偏移处需要被poke成jmp
#define POKE_LENGTH		7  // 被poke的必须是完整的指令行,如果有空余的,用nop填充

// text_poke和text_mutex通过/proc/kallsyms查询地址
static void *(*_text_poke_smp)(void *addr, const void *opcode, size_t len);
static struct mutex *_text_mutex;

static int __init hotfix_init(void)
{
	unsigned char e8_call[POKE_LENGTH];
	unsigned char incl[8];
	s32 offset;
	u32 low32 = (unsigned int)(((unsigned long)&counter) & 0xffffffff);

	addr = (void *)laddr;

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

	stub = (void *)test_stub1;


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

	// 插入的指令中需要save/restore寄存器,但这里简单,略过
	incl[0] = 0xff;
	incl[1] = 0x04;
	incl[2] = 0x25;
	(*(u32 *)(&incl[3])) = low32; // 写入需要递增的counter变量地址
	incl[7] = 0xc3; // retq 指令

	// 执行poke:1. 首先拷贝原始函数中的指令; 2. 其次写入新增的计数器递增指令 
	_text_poke_smp(&stub[0], &addr[POKE_OFFSET], POKE_LENGTH);
	_text_poke_smp(&stub[POKE_LENGTH], &incl, 8);

	// call比jmp方便,可以自动帮忙return,不然还要自己jmp回来,但是代价是push/pop
	e8_call[0] = 0xe8;
	(*(s32 *)(&e8_call[1])) = offset - POKE_OFFSET;
	e8_call[5] = 0x90; e8_call[6] = 0x90; // nop 占位符
	get_online_cpus();
	mutex_lock(_text_mutex);
	// 执行call指令替换
	_text_poke_smp(&addr[POKE_OFFSET], e8_call, POKE_LENGTH);
	mutex_unlock(_text_mutex);
	put_online_cpus();

	return 0;
}

static void __exit hotfix_exit(void)
{
	get_online_cpus();
	mutex_lock(_text_mutex);
	_text_poke_smp(&addr[POKE_OFFSET], &stub[0], POKE_LENGTH);
	mutex_unlock(_text_mutex);
	put_online_cpus();
}

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

We look at the effect of:

[root@localhost ~]# cat /proc/test
1234
[root@localhost ~]# cat /sys/module/nowa/parameters/counter
1
[root@localhost ~]# cat /proc/test
1234
[root@localhost ~]# cat /sys/module/nowa/parameters/counter
2
[root@localhost ~]# cat /proc/test
1234
[root@localhost ~]# cat /sys/module/nowa/parameters/counter
3

The effect has been achieved. We look at the original sample_read and test_stub1 into what looks like:

crash> dis sample_read
...
0xffffffffa00e1024 <sample_read+36>:    cmpq   $0x0,(%rcx)
0xffffffffa00e1028 <sample_read+40>:    jne    0xffffffffa00e105a <sample_read+90>
// 注意下面的7个字节的指令,已经被替换了
0xffffffffa00e102a <sample_read+42>:    callq  0xffffffffa0264000 <test_stub1>
0xffffffffa00e102f <sample_read+47>:    nop
0xffffffffa00e1030 <sample_read+48>:    nop
0xffffffffa00e1031 <sample_read+49>:    mov    $0x4d2,%edx
...
crash> dis test_stub1
// 下面的7字节指令拷贝自原始函数
0xffffffffa0264000 <test_stub1>:        lea    -0x30(%rbp),%rdi
0xffffffffa0264004 <test_stub1+4>:      mov    %rsi,%r13
// 计数器递增指令
0xffffffffa0264007 <test_stub1+7>:      incl   0xffffffffa0266278
0xffffffffa026400e <test_stub1+14>:     retq
...

Wenzhou shoes wet, rain water will not be fat.

Released 1580 original articles · won praise 5111 · Views 11,130,000 +

Guess you like

Origin blog.csdn.net/dog250/article/details/105205966