往linux内核函数挂钩子

概述

本文讲解替换一个已经在内存中的函数,使得执行流流入我们自己的逻辑,然后再调用原始的函数。比如有个函数叫做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_smp");

         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世界。

 

Never lock up your dreaming box, and the greatest peril to the soul is that one is likely to get precisely what he is seeking.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HeroKern

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值