Linux x86架构内核Hook实现

一、内核函数

text_poke()函数用于在内核动态替换opcode,从而达到Inline Hook的效果。

/**
 * text_poke - Update instructions on a live kernel
 * @addr: address to modify
 * @opcode: source of the copy
 * @len: length to copy
 *
 * Only atomic text poke/set should be allowed when not doing early patching.
 * It means the size must be writable atomically and the address must be aligned
 * in a way that permits an atomic write. It also makes sure we fit on a single
 * page.
 *
 * Note that the caller must ensure that if the modified code is part of a
 * module, the module would not be removed during poking. This can be achieved
 * by registering a module notifier, and ordering module removal and patching
 * trough a mutex.
 */
void *text_poke(void *addr, const void *opcode, size_t len)
{
	lockdep_assert_held(&text_mutex);

	return __text_poke(addr, opcode, len);
}

//5.11.x内核这个函数没有,需要自己使用kallsyms_lookup_name()找到函数入口地址。

/* Lookup the address for this symbol. Returns 0 if not found. */
unsigned long kallsyms_lookup_name(const char *name)
{
	char namebuf[KSYM_NAME_LEN];
	unsigned long i;
	unsigned int off;

	for (i = 0, off = 0; i < kallsyms_num_syms; i++) {
		off = kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf));

		if (strcmp(namebuf, name) == 0)
			return kallsyms_sym_address(i);

		if (cleanup_symbol_name(namebuf) && strcmp(namebuf, name) == 0)
			return kallsyms_sym_address(i);
	}
	return module_kallsyms_lookup_name(name);
}

//该函数返回函数的入口地址。

编译出的vmlinux使用objdump进行返汇编操作,查看ip_rcv()反汇编之后的汇编代码。

ffffffff818f5f90 <ip_rcv>:
ffffffff818f5f90:       e8 ab b8 30 00          callq  ffffffff81c01840 <__fentry__>
ffffffff818f5f95:       55                      push   %rbp
ffffffff818f5f96:       48 89 e5                mov    %rsp,%rbp
ffffffff818f5f99:       41 55                   push   %r13
ffffffff818f5f9b:       41 54                   push   %r12
ffffffff818f5f9d:       53                      push   %rbx
ffffffff818f5f9e:       48 89 f3                mov    %rsi,%rbx
ffffffff818f5fa1:       48 83 ec 38             sub    $0x38,%rsp
ffffffff818f5fa5:       4c 8b ae 28 05 00 00    mov    0x528(%rsi),%r13
ffffffff818f5fac:       65 48 8b 04 25 28 00    mov    %gs:0x28,%rax
ffffffff818f5fb3:       00 00
ffffffff818f5fb5:       48 89 45 e0             mov    %rax,-0x20(%rbp)
ffffffff818f5fb9:       31 c0                   xor    %eax,%eax
ffffffff818f5fbb:       49 8d b5 90 01 00 00    lea    0x190(%r13),%rsi
ffffffff818f5fc2:       e8 e9 fa ff ff          callq  ffffffff818f5ab0 <ip_rcv_core.isra.0>
ffffffff818f5fc7:       48 85 c0                test   %rax,%rax
ffffffff818f5fca:       74 7d                   je     ffffffff818f6049 <ip_rcv+0xb9>
ffffffff818f5fcc:       49 89 c4                mov    %rax,%r12
ffffffff818f5fcf:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
ffffffff818f5fd4:       4c 89 e2                mov    %r12,%rdx
ffffffff818f5fd7:       31 f6                   xor    %esi,%esi
ffffffff818f5fd9:       4c 89 ef                mov    %r13,%rdi
ffffffff818f5fdc:       e8 af f7 ff ff          callq  ffffffff818f5790 <ip_rcv_finish>
ffffffff818f5fe1:       48 8b 4d e0             mov    -0x20(%rbp),%rcx
ffffffff818f5fe5:       65 48 33 0c 25 28 00    xor    %gs:0x28,%rcx
ffffffff818f5fec:       00 00
ffffffff818f5fee:       75 60                   jne    ffffffff818f6050 <ip_rcv+0xc0>
ffffffff818f5ff0:       48 83 c4 38             add    $0x38,%rsp
ffffffff818f5ff4:       5b                      pop    %rbx
ffffffff818f5ff5:       41 5c                   pop    %r12
ffffffff818f5ff7:       41 5d                   pop    %r13
ffffffff818f5ff9:       5d                      pop    %rbp
.....

从上面反汇编的结果可以看出函数调用了函数__fentry__,我们的目的是当内核调用ip_rcv()函数走进我们自定义函数,x86架构跳转指令jump opcode为e9,把这条call指令替换成无条件跳转指令,jmp offset,这样就可以进入自定义函数hook_ip_rcv函数。

二、Hook ip_rcv()

ip_rcv()函数原型

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
	   struct net_device *orig_dev)
{
    .....
}

构造跳转函数,如果hook_ip_rcv()直接跳转回orig_ip_rcv()将造成死循环,所以需要一中间跳转的桩,这个函数可以由我们自己定义。

stub_ip_rcv()

static int stub_ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
                struct net_device *orig_dev)
{

        printk("this is stub function\n");
        asm("nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop");
        return 0;
}

hook_ip_rcv()

static int hook_ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
                struct net_device *orig_dev)
{
        printk(KERN_ERR"This is hook ip_rcv function\n");
        return stub_ip_rcv(skb, dev, pt, orig_dev);
}

三、实现源码

使用print_hex_dump()函数来打印opcode,高内核版本kallsyms_lookup_name()函数没有导出,需要在代码中指定该函数的入口地址,查找方法:

root@rlk:/home/rlk/code/text_poke# cat /proc/kallsyms | grep kallsyms_lookup_name
ffffffffa4b56bd0 T module_kallsyms_lookup_name
ffffffffa4b576c0 T kallsyms_lookup_name
ffffffffa5e93594 r __ksymtab_kallsyms_lookup_name
ffffffffa5ea1446 r __kstrtab_kallsyms_lookup_name
#include <linux/printk.h>
#include <linux/cpu.h>
#include <linux/net.h>
#include <net/tcp.h>

#define HOOK_SIZE 5 

char save_opcode[HOOK_SIZE];
char jump_opcode[HOOK_SIZE];
char stub_opcode[HOOK_SIZE];

static unsigned long (*kallsyms_lookup_name_ptr)(const char *name);
static void * (*text_poke_ptr)(void *addr, const void *opcode, size_t len);

static int (*orig_ip_rcv)(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
                struct net_device *orig_dev) ;

static int stub_ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
                struct net_device *orig_dev)
{

        printk("this is stub function\n");
        asm("nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop");
        return 0;
}

static int hook_ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
                struct net_device *orig_dev)
{
        printk(KERN_ERR"This is hook ip_rcv function\n");
        return stub_ip_rcv(skb, dev, pt, orig_dev);
}

static int hook_framework_init(void)
{
    	//代码编译前需修改此函数入口地址
        kallsyms_lookup_name_ptr = (unsigned long (*)(const char *))0xffffffffa4b576c0;

        text_poke_ptr = (void * (*)(void *, const void *, size_t))
                kallsyms_lookup_name_ptr("text_poke");
        if (text_poke_ptr == NULL) {
                printk("Get text_poke function address failed\n");
                return -1;
        }

        return 0;
}

static int text_poke_init(void)
{
        int32_t hook_offset = 0;
        int32_t stub_offset = 0;

        orig_ip_rcv =int (*)(struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *))kallsyms_lookup_name_ptr("ip_rcv");
        if (!orig_ip_rcv) {
                printk("Get vfs_read_fn failed\n");
                return -1;
        }
        printk("orig_ip_rcv -> 0x%lx\n", orig_ip_rcv);

        /* Backup raw vfs_read opcode */
        memcpy(save_opcode, (char *)orig_ip_rcv, HOOK_SIZE);

        print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_ADDRESS,
                16, 1, save_opcode, HOOK_SIZE, true);

        /* Get hook_ip_rcv opcode */
        jump_opcode[0] = 0xe9;

        hook_offset = (int32_t)((long)hook_ip_rcv - (long)orig_ip_rcv - HOOK_SIZE);
        (*(int32_t *)&jump_opcode[1]) = hook_offset;

        print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_ADDRESS,
                16, 1, jump_opcode, HOOK_SIZE, true);

        /* Get stub ip_rcv opcode */
        stub_opcode[0] = 0xe9;

        stub_offset = (int32_t)(((long)orig_ip_rcv + HOOK_SIZE) - ((long)stub_ip_rcv + HOOK_SIZE));
        (*(int32_t *)&stub_opcode[1]) = stub_offset;

        get_online_cpus();
        text_poke_ptr((void *)stub_ip_rcv, stub_opcode, HOOK_SIZE);
        barrier();
        text_poke_ptr((void *)orig_ip_rcv, jump_opcode, HOOK_SIZE);
        put_online_cpus();

        print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_ADDRESS,
                16, 1, stub_opcode, HOOK_SIZE, true);

        print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_ADDRESS,
                16, 1, stub_ip_rcv, HOOK_SIZE, true);

        return 0;
}

static int __init poke_init(void)
{
        int ret = 0;

        ret = hook_framework_init();
        if (ret < 0) {
                printk("Init inline hook failed\n");
                return -1;
        }

        ret = text_poke_init();
        if (ret < 0) {
                printk("Hook vfs_read failed\n");
                return -1;
        }

        return 0;
}

static void __exit poke_exit(void)
{
        printk("Text_poke exit ...\n");
        text_poke_ptr(orig_ip_rcv, save_opcode, HOOK_SIZE);
}

module_init(poke_init);
module_exit(poke_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("curtis");
MODULE_DESCRIPTION("inline hook kernel function");

驱动安装之后内核打印:

[ 2291.314300] orig_ip_rcv -> 0xffffffffa53ae8c0
[ 2291.314302] raw data: 00000000115abb16: 0f 1f 44 00 00                                   ..D..
[ 2291.314302] raw data: 000000004d249135: e9 6b a7 29 1b                                   .k.).
[ 2291.314305] raw data: 00000000c7f290e8: e9 c0 58 d6 e4                                   ..X..
[ 2291.314306] raw data: 00000000b27b0427: e9 c0 58 d6 e4                                   ..X..
[ 2318.487262] This is hook ip_rcv function
[ 2318.492536] This is hook ip_rcv function
[ 2318.494959] This is hook ip_rcv function
[ 2318.495728] This is hook ip_rcv function
[ 2318.495807] This is hook ip_rcv function
[ 2318.496516] This is hook ip_rcv function
[ 2318.506860] This is hook ip_rcv function
[ 2318.506903] This is hook ip_rcv function
[ 2318.508956] This is hook ip_rcv function
GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐