1. KHOOK的用法
用户的项目代码中引入khook头文件。
#include "khook/engine.c"
在项目的kbuild/makefile中添加声明。
ldflags-y += -T$(src)/khook/engine.lds
使用khook_init()和khook_cleanup()进行挂钩的初始化和退出。
内核函数分为两种,对应KHOOK框架中两种不同的挂钩方式:
-
.h头文件中已经声明的函数
-
.c文件内部使用的函数
第一种内核函数,通过下面的方式进行挂钩。
第二种未在头文件中声明的函数,通过下面方式进行挂钩。
2. KHOOK原理分析
未使用Khook挂钩之前的正常执行流程:
挂钩之后的执行流程,替换被挂钩的内核函数的前几个字节为跳转指令,使得X函数开始执行时,跳转到框架自定义的STUB代码部分,STUB再调用用户自定义的钩子函数。然后又会执行原先被跳转指令覆盖的指令,最后回到被挂钩的内核函数的正常执行逻辑:
X的第一条指令被替换成JUMP的跳转指令,执行流程中多了3个部分:STUB.hook、HOOK.fn、STUB.orig。
其含义分别为:
-
STUB.hook:框架自定义的钩子函数模板,有4部分,除了引用的维护,还有(3)一条跳转,(8)一条返回。(3)是跳转到HOOK.fn
-
HOOK.fn:使用者自定义的钩子函数,在上面的流程中,这个函数被定义成khook_inode_permission、khook_load_elf_binary。(4)是KHOOK_ORIGIN,钩子替换的原函数地址,一般来说,自定义的钩子函数最后也会调用原函数,用来保证正常的执行流程不会出错
-
STUB.orig:框架自定义的钩子函数模板,由于X的第一条指令被替换成JUMP的跳转指令,要正常执行X,则需要先执行被替换的几个字节,然后回到X,也就是图中的过程(5)
3. KHOOK结构体
khookengine.h定义了,khook_t结构体,表示一个钩子,我们需要对函数的内容进行挂钩,将这个函数的内容映射到一个可以访问的虚拟地址,addr_map就是这个虚拟地址,后面覆盖为jump就需要向这个地址写入函数地址。
用户自定义钩子函数的入口,KHOOK和KHOOK_EXT。
格式规定:假设原函数名字为fun则自定义的钩子函数名字必须为khook_fun
在内核中的函数有两种:
-
.h头文件中已经声明的函数,使用KHOOK()自定义钩子函数。
-
.c文件内部使用的函数,使用KHOOK_EXT()自定义钩子函数。
KHOOK()就是做了一个格式规定,然后保证这个结构被分配到.data.khook节中,KHOOK_EXT()加入一个函数声明,未声明的函数就可以被使用
设置函数属性。
在钩子函数中,定义了KHOOK_ORIGIN宏,传入原函数的名字和参数,KHOOK_ORIGIN保存原函数地址。
4. 链接脚本
将engine.lds写入链接脚本。
ldflags-y += -T$(src)/khook/engine.lds
链接脚本定义了两个变量表示钩子函数表的起始和结束地址,KHOOK_tbl和KHOOK_tbl_end
所有的钩子函数都被分配到.data.khook节中,.data.khook放在.data节之中,.这个字符表示的是当前定位器符号的位置,KHOOK_tbl指向的是.data.khook起始位置,KHOOK_tbl_end指向的是.data.khook的结束位置。
以下脚本将输出文件的text section定位在0×10000,data section定位在0×8000000:
5. STUB结构体
每个钩子函数会有一个STUB。
STUB会被初始化为stub.inc或stub32.inc。
6. 内核insn_init函数
两个内核中操作指令的函数,insn_init和insn_get_length,这两个函数功能分别为获取某个地址的指令,用struct insn表示获取这个指令的长度。
获取这两个内核函数的地址。
获取地址p的指令的长度,先调用insn_init获得insn结构,然后调用get_length得到指令长度,结果存放在insn的length字段。
7. 查找内核符号
在内核中,可以使用函数kallsyms_on_each_symbol来查内核符号,这个函数被KHOOK封装为两个部分。
利用kallsyms_on_each_symbol查询内核符号地址。
需要给内核符号执行分配一个虚拟地址。
8. init流程
使用KHOOK框架,首先调用khook_init函数,它定义在engine.c中,khook_init函数主要作用为:
-
分配所有STUB需要用到的内存
-
查找内核符号表,获得所有需要挂钩的函数的地址,然后建立虚拟地址的映射
-
执行khook_sm_init_hook,建立好STUB和khook的关联,保证跳转逻辑
查找内核符号的地址函数:
建立映射:
khook_sm_init_hook,建立STUB和khook的关联,保证跳转逻辑,功能由khook_arch_sm_init_one函数实现。
khook_arch_sm_init_one函数,设置stub的内容
-
先是用khook_stub_template的内容填充stub,生成stub.inc
-
第3步中stub是需要跳转到自定义钩子函数的,stub_fixup填充这个地址
-
保存函数的前一部分内容,这一部分必须大于5个字节
-
设置返回到原函数的地址
-
用跳转指令覆盖原函数的内容
其中使用的辅助函数:
8. cleanup流程
退出时,调用khook_cleanup函数。
khook_sm_cleanup_hooks函数判断当前地址是否为已被挂钩的函数地址,若是,则调用khook_arch_sm_cleanup_one恢复原函数执行流程。
khook_arch_sm_cleanup_one函数将保存的原函数字节码复制到目标地址,恢复原执行流程。
kernel_write_enter改cr0寄存器,取消写保护。
reference
https://github.com/milabs/khook
https://www.cnblogs.com/likaiming/p/10970543.html
原文始发于微信公众号(TahirSec):Linux | Khook 内核挂钩框架
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论