Linux | Khook 内核挂钩框架

admin 2023年7月12日02:58:08评论33 views字数 2582阅读8分36秒阅读模式

1. KHOOK的用法

用户的项目代码中引入khook头文件。

#include "khook/engine.c"

在项目的kbuild/makefile中添加声明。

ldflags-y += -T$(src)/khook/engine.lds

使用khook_init()和khook_cleanup()进行挂钩的初始化和退出。

内核函数分为两种,对应KHOOK框架中两种不同的挂钩方式:

  • .h头文件中已经声明的函数

  • .c文件内部使用的函数

第一种内核函数,通过下面的方式进行挂钩。

Linux | Khook 内核挂钩框架

第二种未在头文件中声明的函数,通过下面方式进行挂钩。

Linux | Khook 内核挂钩框架

2. KHOOK原理分析

未使用Khook挂钩之前的正常执行流程:

Linux | Khook 内核挂钩框架

挂钩之后的执行流程,替换被挂钩的内核函数的前几个字节为跳转指令,使得X函数开始执行时,跳转到框架自定义的STUB代码部分,STUB再调用用户自定义的钩子函数。然后又会执行原先被跳转指令覆盖的指令,最后回到被挂钩的内核函数的正常执行逻辑

Linux | Khook 内核挂钩框架

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就需要向这个地址写入函数地址。

Linux | Khook 内核挂钩框架

用户自定义钩子函数的入口,KHOOK和KHOOK_EXT。

格式规定:假设原函数名字为fun则自定义的钩子函数名字必须为khook_fun

Linux | Khook 内核挂钩框架

在内核中的函数有两种:

  • .h头文件中已经声明的函数,使用KHOOK()自定义钩子函数。

  • .c文件内部使用的函数,使用KHOOK_EXT()自定义钩子函数。

KHOOK()就是做了一个格式规定,然后保证这个结构被分配到.data.khook节中,KHOOK_EXT()加入一个函数声明,未声明的函数就可以被使用

Linux | Khook 内核挂钩框架

设置函数属性。

Linux | Khook 内核挂钩框架

在钩子函数中,定义了KHOOK_ORIGIN宏,传入原函数的名字和参数,KHOOK_ORIGIN保存原函数地址。

Linux | Khook 内核挂钩框架

4. 链接脚本

将engine.lds写入链接脚本。

ldflags-y += -T$(src)/khook/engine.lds

链接脚本定义了两个变量表示钩子函数表的起始和结束地址,KHOOK_tblKHOOK_tbl_end

所有的钩子函数都被分配到.data.khook节中,.data.khook放在.data节之中,.这个字符表示的是当前定位器符号的位置,KHOOK_tbl指向的是.data.khook起始位置,KHOOK_tbl_end指向的是.data.khook的结束位置。

Linux | Khook 内核挂钩框架

以下脚本将输出文件的text section定位在0×10000,data section定位在0×8000000:

Linux | Khook 内核挂钩框架

5. STUB结构体

每个钩子函数会有一个STUB。

Linux | Khook 内核挂钩框架

STUB会被初始化为stub.inc或stub32.inc。

Linux | Khook 内核挂钩框架

Linux | Khook 内核挂钩框架

6. 内核insn_init函数

两个内核中操作指令的函数,insn_init和insn_get_length,这两个函数功能分别为获取某个地址的指令,用struct insn表示获取这个指令的长度。

Linux | Khook 内核挂钩框架

获取这两个内核函数的地址。

Linux | Khook 内核挂钩框架

获取地址p的指令的长度,先调用insn_init获得insn结构,然后调用get_length得到指令长度,结果存放在insn的length字段。

Linux | Khook 内核挂钩框架

7. 查找内核符号

在内核中,可以使用函数kallsyms_on_each_symbol来查内核符号,这个函数被KHOOK封装为两个部分。

Linux | Khook 内核挂钩框架

利用kallsyms_on_each_symbol查询内核符号地址。

Linux | Khook 内核挂钩框架

需要给内核符号执行分配一个虚拟地址。

Linux | Khook 内核挂钩框架

8. init流程

使用KHOOK框架,首先调用khook_init函数,它定义在engine.c中,khook_init函数主要作用为:

  • 分配所有STUB需要用到的内存

  • 查找内核符号表,获得所有需要挂钩的函数的地址,然后建立虚拟地址的映射

  • 执行khook_sm_init_hook,建立好STUB和khook的关联,保证跳转逻辑

Linux | Khook 内核挂钩框架

查找内核符号的地址函数:

Linux | Khook 内核挂钩框架

建立映射:

Linux | Khook 内核挂钩框架

khook_sm_init_hook,建立STUB和khook的关联,保证跳转逻辑,功能由khook_arch_sm_init_one函数实现

Linux | Khook 内核挂钩框架

khook_arch_sm_init_one函数,设置stub的内容

  • 先是用khook_stub_template的内容填充stub,生成stub.inc

  • 第3步中stub是需要跳转到自定义钩子函数的,stub_fixup填充这个地址

  • 保存函数的前一部分内容,这一部分必须大于5个字节

  • 设置返回到原函数的地址

  • 用跳转指令覆盖原函数的内容

Linux | Khook 内核挂钩框架

其中使用的辅助函数:

Linux | Khook 内核挂钩框架

8. cleanup流程

退出时,调用khook_cleanup函数。

Linux | Khook 内核挂钩框架

khook_sm_cleanup_hooks函数判断当前地址是否为已被挂钩的函数地址,若是,则调用khook_arch_sm_cleanup_one恢复原函数执行流程。

Linux | Khook 内核挂钩框架

khook_arch_sm_cleanup_one函数将保存的原函数字节码复制到目标地址,恢复原执行流程。

Linux | Khook 内核挂钩框架

kernel_write_enter改cr0寄存器,取消写保护。

Linux | Khook 内核挂钩框架

reference

https://github.com/milabs/khook

https://www.cnblogs.com/likaiming/p/10970543.html


原文始发于微信公众号(TahirSec):Linux | Khook 内核挂钩框架

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年7月12日02:58:08
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Linux | Khook 内核挂钩框架http://cn-sec.com/archives/1867517.html

发表评论

匿名网友 填写信息