往linux内核函数挂钩子

本文详细介绍了在Linux X86架构下,如何实现内核函数的Hook,通过修改内存中的函数指针,使执行流转向自定义逻辑,再调用原始函数。探讨了Hook的意义、原理及限制,展示了具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

ARM平台下该方案不可行,仅适用于X86

本文讲解替换一个已经在内存中的函数,使得执行流流入我们自己的逻辑,然后再调用原始的函数。比如有个函数叫做funcion,而你希望统计一下调用function的次数,最直接的方法就是如果有谁调用function的时候,调到下面这个函数就好了。

void new_function()

{

count++;

return function();

}

钩子存在的意义

当内核程序已经在运行过程中,如果需要对某个内核函数做出小的改动,原始方法是修改内核源码或驱动程序,重新编译在加载二进制文件,这样的工作量相对比较大。只有当动态加载驱动程序时修改才比较方便。为什么不对应用程序hook呢?因为这样意义不大,改一下源码重启服务会好很多,内核重新编译、重启设备代价非常大。

钩子原理

在x86架构与linux系统平台,每个函数编译后地址的前5个字节都是callq function+0x5(即是默认指向下一条指令,注意图中的地址是一个相对地址概念,实际地址跟你运行的进程有关),图 1是文章后面用到的HerokHook.ko经过反编译得到的,从图 1中可以清晰看到跳转到函数的第一条指令是callq,指向下一条指令,紧接着是堆栈。

图 1

图 2是本次实验的流程图,orig_ptr指向linux内核需要hook的函数,当内核调用orig_ptr指向函数时候,首先会执行第一条指令,在我们的函数中修改callq orig_ptr +0x5 为jmp Hook_ptr-5,在我们的函数中执行一系列操作后,在通过return ptr_tmp调用中间辅助函数,将ptr_tmp函数的前5字节xxx修改成jmp orig_ptr+0x5,这里必须跳过orig_ptr的前5字节,因为这5个字节函数已经被我们修改,不然就进入死循环。

图 2

中间辅助函数存在的意义,如果在hook_ptr中直接返回调用orig_ptr函数,那么没有绕过前5个字节就会进入死循环。在hook_ptr函数末尾不能添加jmp跳转指令,因为你不知道那些字节是保留,以及堆栈平衡情况。所以需要添加中间辅助函数。

内核钩子接口

读者可能认为现在已经具备注册钩子的条件,其实不是这样的,在早期Linux内核版本中,如果具备上述流程就可以通过memcpy和jmp buffer(buffer存放指令)挂钩子,由于一些不符合常规的做法已经影响正常的业务逻辑,所以Linux内核做了如下限制:

  1. 可执行代码段不可写:这个措施便封堵住了你想通过简单memcpy的方式替换函数指令的方案。
  2. 内存buffer不可执行:这个措施便封堵住了你想把执行流jmp到你的一个保存指令的buffer的方案。
  3. stack不可执行:避免缓冲区溢出、栈溢出。

查阅Linux内核资料,发现Linux内核已经提供了text_poke_smp和kallsyms_lookup_name函数接口。

钩子必然可挂载原理

大家都知道,x86平台采用的是冯诺依曼体系结构,冯诺依曼结构采用统一存储,即指令与数据采用相同总线传输,那么在操作系统层我们必然可以随意解释内存空间的含义。不管是通过内核接口还是自定义接口(申请权限,重新映射当前连续page页)都可以更改内存空间含义,所以很多不正常操作计算机的原理都是基于如此。早期的单机游戏可以搜索内存数据变化来确定状态值,进而重新映射当前page权限进行重新赋值操作。

代码编写

hook驱动程序

hello.c是原驱动程序,代码中编写最简单的hello驱动程序,Makefile,驱动程序。

test.c

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/device.h>

#include <linux/miscdevice.h>

#include <linux/delay.h>

#include <asm/irq.h>

#include <asm/io.h>

#include <asm/uaccess.h>

#include <mach/regs-gpio.h>

#include <mach/hardware.h>

#include <linux/device.h>

#include <linux/gpio.h>

#define DEVICE_NAME "hello"

static struct class *hello_class;

static int hello_open(struct tty_struct * tty, struct file * filp)

{

printk("open is successd!\n");

return 0;

}

static int hello_read(struct file * file,char __user * userbuf,size_t bytes,loff_t * off)

{

unsigned char buf[4];

buf[0]=0x11;

buf[1]=0x33;

buf[2]=0x44;

buf[3]=0x55;

copy_to_user(userbuf,buf,sizeof(buf));

return(sizeof(buf));

}

static struct file_operations hello_fops = {

.owner = THIS_MODULE,

.read = hello_read,

.open =hello_open,

};

static int major;

static int hello_init(void)

{

major= register_chrdev(0, DEVICE_NAME, &hello_fops);

hello_class = class_create(THIS_MODULE, DEVICE_NAME);

device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");

printk(KERN_ALERT "init is scussed!\n");

return 0;

}

static void hello_exit(void)

{

unregister_chrdev(major, DEVICE_NAME);

device_destroy(hello_class,MKDEV(major, 0));

class_destroy(hello_class);

printk(KERN_ALERT "Goodbye, cruel world\n");

}

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE("Dual BSD/GPL");

MODULE_AUTHOR("Herok");

MODULE_DESCRIPTION("A simple hello world module");

MODULE_ALIAS("A simplest module");

hook驱动程序的测试程序

test.c

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#include <poll.h>

int main(int argc,char **argv)

{

int fd;

unsigned char buf[4];

fd = open("/dev/hello", O_RDWR);

if(fd<0){

printf("open is error!\n");

return -1;

}

read(fd,&buf,4);

printf("%x\n",buf[0]);

printf("%x\n",buf[1]);

printf("%x\n",buf[2]);

printf("%x\n",buf[3]);

close(fd);

}

​​​​​​​hook驱动程序

HerokHook.c

#include <linux/kallsyms.h>

#include <linux/cpu.h>

#include <linux/kprobes.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/device.h>

#include <linux/miscdevice.h>

#include <linux/delay.h>

#include <asm/io.h>

#include <asm/uaccess.h>

#include <linux/device.h>

#define OPTSIZE 5

char saved_op[OPTSIZE]={0};

char jump_op[OPTSIZE]={0};

int (*ptr_tmp_hello_read)(struct file * file,char __user * userbuf,size_t bytes,loff_t * off);

int (*ptr_orig_hello_read)(struct file * file,char __user * userbuf,size_t bytes,loff_t * off);

int stub_hello_conntrack_in(struct file * file,char __user * userbuf,size_t bytes,loff_t * off)

{

printk("hook stub conntrack\n");

return 0;

}

int hook_hello_read(struct file * file,char __user * userbuf,size_t bytes,loff_t * off)

{

printk(KERN_EMERG "hook conntrack herok\n");

return ptr_tmp_hello_read(file,userbuf, bytes,off);

}

static void *(*ptr_poke_smp)(void *addr, const void *opcode, size_t len);

static __init int replace_function__init(void)

{

s32 hook_offset, orig_offset;

// 这个poke函数完成的就是重映射,写text段

ptr_poke_smp = kallsyms_lookup_name("text_poke");

if (!ptr_poke_smp) {

printk(KERN_INFO "err");

return -1;

}

//找到需要hook的函数

ptr_orig_hello_read = kallsyms_lookup_name("hello_read");

printk(KERN_EMERG "ptr_orig_hello_read=%#x\n",ptr_orig_hello_read);

if (!ptr_orig_hello_read) {

printk("err");

return -1;

}

jump_op[0] = 0xe9; //jmp指令

// 计算目标hook函数到当前位置的相对偏移

hook_offset = (s32)((long)hook_hello_read - (long)ptr_orig_hello_read - OPTSIZE);

// 后面4个字节为一个相对偏移

(*(s32*)(&jump_op[1])) = hook_offset;

saved_op[0] = 0xe9;

// 计算目标原始函数将要执行的位置到当前位置的偏移

orig_offset = (s32)((long)ptr_orig_hello_read + OPTSIZE - ((long)stub_hello_conntrack_in + OPTSIZE));

(*(s32*)(&saved_op[1])) = orig_offset;

get_online_cpus();

// 替换操作!

ptr_poke_smp(stub_hello_conntrack_in, saved_op, OPTSIZE);

ptr_tmp_hello_read = stub_hello_conntrack_in;

printk(KERN_EMERG "ptr_tmp_hello_read=%#x\n",ptr_tmp_hello_read);

barrier();

ptr_poke_smp(ptr_orig_hello_read, jump_op, OPTSIZE);

put_online_cpus();

return 0;

}

static __exit void replace_function_exit(void)

{

get_online_cpus();

ptr_poke_smp(ptr_orig_hello_read, saved_op, OPTSIZE);

ptr_poke_smp(stub_hello_conntrack_in, jump_op, OPTSIZE);

barrier();

put_online_cpus();

}

module_init(replace_function__init);

module_exit(replace_function_exit);

MODULE_DESCRIPTION("hook test");

MODULE_LICENSE("GPL");

MODULE_VERSION("1.1");

​​​​​​​Makefile程序

代码如图 3。

图 3

测试

编译生成hello.ko和HerokHook.ko,依次加载这两个驱动程序,并且编译并执行测试程序,从程序运行结果发现,程序将先调用我们的hook函数,然后在调用原函数。图 4可以看到函数的地址空间,也可以通过cat /proc/modules得到所以内核的地址空间范围。

图 4

结语

至于在Linux应用程序中如何编译与加载驱动程序读者可以自行百度,这个相对简单。在centos平台需要安装linux-headrs库,kernel-headers.x86_64和kernel.x86_64两个库,安装完成后再/usr/src/kernels目录下会出现内核文件,在Makefile中指定该路径就可以正常编译。

hook怎么在内核中玩完全由读者决定,最好的是与tcp这个代码分支比较多的糟糕代码一起玩,这样玩花样比较多,后期带领大家领略linux中TCP世界。

<think>好的,我现在需要回答用户关于在Linux内核中注册钩函数的问题。首先,我需要回忆一下相关的知识。用户提到了引用[1],里面提到了Linux系统调用钩和中断描述符表,这可能涉及到系统调用钩的注册方法。另外,引用[3]提到了sk_buffer结构,这可能和网络层钩有关。 首先,我应该确定用户问的是哪种类型的钩函数注册,因为Linux内核中有多种钩机制,比如系统调用钩、网络钩(如Netfilter)、文件操作钩等。用户的问题比较笼统,可能需要分情况讨论。 然后,我需要回忆不同钩的注册方法。比如系统调用钩通常通过修改系统调用表来替换函数指针,而Netfilter钩使用nf_register_net_hook等函数。同时,还需要注意内核版本的不同,因为方法可能有所变化,比如旧版本可能直接替换系统调用表,而新版本可能有更安全的机制或者需要使用kprobes。 接下来,我需要组织回答的结构,分点说明不同的钩类型及其注册步骤。每个步骤需要给出具体的函数或代码示例,比如系统调用钩可能需要获取系统调用表的地址,替换对应的函数指针,并保存原函数。同时要提到注意事项,比如内核内存保护(如CR0写保护)的处理,以及模块的安全卸载。 另外,用户提供的引用[1]中的链接可能涉及到系统调用钩的具体实现,需要确保描述的方法符合该引用中的内容。同时,引用[3]中的sk_buffer结构可能在网络钩中使用到,说明Netfilter钩处理数据包时涉及的结构。 最后,生成相关问题时要涵盖不同钩类型、安全机制、调试工具和实际应用场景,这样用户可以根据需要进一步探索。 需要确保回答中的LaTeX格式正确,例如函数名或变量用$...$包裹,代码块用反引号标注。同时,引用标识要正确添加,比如在提到系统调用钩时引用[^1],在Netfilter部分引用[^3]。 现在整理这些思路,形成结构清晰的中文回答,并附上相关问题。</think>在Linux内核中注册钩函数的具体实现方式取决于目标系统,常见的钩注册方法包括系统调用钩、Netfilter网络钩、文件操作钩等。以下是三种典型场景的实现方法: ### 1. 系统调用钩注册 通过修改系统调用表实现,适用于内核版本<=4.xx: ```c // 1. 获取系统调用表地址 unsigned long *sys_call_table = (unsigned long*)kallsyms_lookup_name("sys_call_table"); // 2. 保存原始函数指针 static orig_open = (orig_open_t)sys_call_table[__NR_open]; // 3. 关闭写保护 write_cr0(read_cr0() & (~0x10000)); // 4. 替换函数指针 sys_call_table[__NR_open] = (unsigned long)hooked_open; // 5. 恢复写保护 write_cr0(read_cr0() | 0x10000); ``` 该方法通过修改CR0寄存器关闭内存写保护后直接替换系统调用表项。 ### 2. Netfilter网络钩 用于拦截网络数据包,通过$nf_register_net_hook()$注册: ```c static struct nf_hook_ops nfho = { .hook = hook_func, .pf = NFPROTO_IPV4, .hooknum = NF_INET_PRE_ROUTING, .priority = NF_IP_PRI_FIRST }; nf_register_net_hook(&init_net, &nfho); ``` 钩函数原型: ```c unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { // 处理sk_buffer结构[^3] return NF_ACCEPT; } ``` ### 3. Kprobes动态钩 更安全的内核跟踪机制: ```c static struct kprobe kp = { .symbol_name = "sys_open", .pre_handler = handler_pre, }; register_kprobe(&kp); ``` 预处理函数: ```c static int handler_pre(struct kprobe *p, struct pt_regs *regs) { printk("拦截sys_open调用\n"); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值