赞
踩
概述
本文讲解替换一个已经在内存中的函数,使得执行流流入我们自己的逻辑,然后再调用原始的函数。比如有个函数叫做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内核做了如下限制:
查阅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.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。