首页
社区
课程
招聘
[原创] 通过两字节修改实现的Windows内核函数Hook框架
发表于: 2025-5-17 21:44 3121

[原创] 通过两字节修改实现的Windows内核函数Hook框架

2025-5-17 21:44
3121

​ 代码仓库:39dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6d9k6i4y4W2N6p5#2A6L8X3g2y4K9h3&6V1i4K6u0r3N6r3W2F1P5f1E0W2M7X3&6W2L8p5S2G2L8$3E0Q4c8f1k6Q4b7V1y4Q4z5p5y4Q4c8e0k6Q4z5o6c8Q4z5f1k6Q4c8e0S2Q4b7U0m8Q4b7e0u0Q4c8e0g2Q4b7e0c8Q4b7e0N6Q4c8e0g2Q4b7f1g2Q4b7U0k6Q4c8e0k6Q4z5f1c8Q4b7e0g2K6N6r3q4J5i4@1f1K6i4K6R3H3i4K6R3J5

​ Inline Hook是r0/r3通用的hook方法,其原理是修改目标函数的前n个字节,常用的 Inline Hook 的替换指令为jmp qword ptr ds:[xxx]; hook_func_addrmov rax,hook_func; push rax; ret ,前者二进制码长度为 14 字节,后者二进制码长度为 12 字节。这么多字节的 Hook 带来了一些问题:有没有其它函数跳转到这些字节的中间呢?

​ 同时,在实现 Hook 后,我们还需要一种手段来修复 Hook,以实现对原始函数的调用,但是内核和应用程序不一样,应用程序的内存空间是独立的,Hook 了某个应用程序中的函数,那么只会影响到该应用程序,那么先 Unhook 再 Hook 回去也来得及;内核空间是共享的,需要保证高并发下 Hook 也稳定。因此,最好的方法是将被替换的原函数代码拷贝一份,每次函数被调用时,先执行这份拷贝,再跳回原执行流。由于在新的内存空间中执行函数,会导致相对寻址指令出错,因此这也是头疼的一点。

image-20250517193703095

​ 使用 IDA 脚本,以14字节为限度,来查看内核模块中哪些函数因为上面的两个原因而不能 Hook。

image-20250517200220096

​ 如果仅使用2字节来实现 Inline Hook 的话,由于上面两个原因而不能被 Hook 的函数会减少很多。

image-20250517200319619

​ 可以看到,相对地址导致不能 Hook 的函数数量从8064减少到了610;由于外部函数引用导致的不能 Hook 的函数数量从705减少到了1;由于函数不足14字节长度导致734个函数无法被 Hook,也变成了由于函数不足2字节长度导致59个函数无法被 Hook。可以看到缩短 Inline Hook 的长度确实能有效增加可 Hook 函数的数量。

​ 当然,除了减少 Hook 字节数以外,还有其它方法来解决上述的问题,比如@smallzhong_驱动挂钩所有内核导出函数来进行驱动逻辑分析一文中提到的将相对寻址修改为绝对地址、将代码拷贝到同模块空白地址处等。本文介绍的 基于异常的两字节 Hook 框架 是对同一事物另一思考,在此之前也有不少利用调试异常、缺页异常做 Hook 的实现,在此只是分享一下自己的实现、抛砖引玉,希望对大家有帮助,如果文章中有实质性的错误,欢迎大家指正~!

​ Hook函数不是对单一函数进行Hook,如果要为每一个Hook都准备一个中断向量号,一是可hook的函数总数受限,二是不够经济。因此,最好是使用一个中断向量号来分发Hook,这样的话就需要解决“哪个函数导致的Hook?”这一问题。

​ 我们知道,CPU响应异常或中断后,硬件会压栈SS\RSP\RFLAGS\CS\RIP到栈中,RIP是极佳的选择。但是我不想在中断上下文做太多事情,因此我选择在异常触发后复用当前栈来存储RIP的值。下面是 Intel 白皮书中对如何使用当前栈来存储RIP值的介绍。

image-20250517105151002

​ 由上图的内容可以看到,在 IA-32 模式下,当中断或者异常发生时,如果特权级没有改变,那么并不会切换被使用的栈。

image-20250517104318050

image-20250517105827160

​ 由上图中的内容可以看到,在 IA-32e 模式下,中断或者异常发生时,其栈切换工作流程和 legacy 模式没有本质区别,只是如果发生了特权级切换,那么新的SS选择子会被赋值为NULL。同时,旧SS和旧RSP会被无条件压栈。

image-20250517110301589

image-20250517102910851

​ 由上图中的内容可以看到,在 IA-32e 模式下,出现了 IST 机制,可选修改 legacy 栈切换机制。如果,64位 IDT 门描述符的 3-bit IST 属性的值设置为 001 - 111 中的任意值,那么在中断或者异常触发时,将会将其处理程序使用的栈指针修改为 TSS 中对应的 IST 指针。

​ 但是如果,3-bit IST 属性的值设置为 000,那么就会使用上述modified legacy stack-switching机制。

image-20250517104125803

​ 顺带提一下,如果我们需要在中断/异常触发时,由CPU帮我们关中断,可以选择将IDT中的门描述符设置为正确的类型。如果时中断门,那么CPU会帮自动清除IF位,以关中断;反之,陷阱门则不会。

​ 因此,想要使用当前栈来存储函数的标识信息,只需要做一件事:将中断门/陷阱门描述符中的 3-bit IST 设置为0即可。

​ 根据"intel 白皮书 Vol. 3A CHAPTER 6 INTERRUPT AND EXCEPTION HANDLING "中描述,中断的来源是:来自外部的硬件中断、Int n软件模拟的中断;异常的来源是:来自CPU在程序运行过程中的错误检查、Int n软件模拟的异常、机器检查的异常。

​ 可以注意到,中断和异常都可以由 Int n 指令进行模拟,而且由 Int n 触发的中断不会受到 EFLAGS 寄存器中 IF 位的影响。

image-20250517201614445

​ 同时,由于异常是由 CPU 产生的,因此往往是在进程上下文同步执行的,因此异常在任何情况都应该被第一时间处理,否则 CPU 就会出现错误。


[培训]科锐逆向工程师培训第53期2025年7月8日开班!

收藏
免费 6
支持
分享
最新回复 (11)
雪    币: 3556
活跃值: (6110)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
2
前排支持!
2025-5-17 22:12
0
雪    币: 3556
活跃值: (6110)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
3
顶顶
2025-5-17 23:38
0
雪    币: 55
活跃值: (914)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2025-5-19 03:23
0
雪    币: 0
活跃值: (120)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
稳定性如何会不会丢呢
2025-5-19 13:51
0
雪    币: 400
活跃值: (85)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
wx_q 稳定性如何会不会丢呢
稳定性还好,hook了NtCreateFile和ZwCreateFile,能稳定跑到pg检查idt被修改;具体函数可自测
2025-5-19 15:38
0
雪    币: 400
活跃值: (85)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
smallzhong_ 前排支持!
谢谢你,大侠
2025-5-19 15:40
0
雪    币: 400
活跃值: (85)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
东城狂人 感谢分享
2025-5-19 15:40
0
雪    币: 4916
活跃值: (1840)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
9
学习
2025-5-20 14:20
0
雪    币: 2257
活跃值: (4520)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
学习
2025-5-27 14:30
0
雪    币: 0
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
restme 稳定性还好,hook了NtCreateFile和ZwCreateFile,能稳定跑到pg检查idt被修改;具体函数可自测
pg检测到就蓝屏了,这个方案意义在哪里呢?
2025-5-31 14:18
0
雪    币: 265
活跃值: (1355)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
学习
2025-6-3 19:14
0
游客
登录 | 注册 方可回帖
返回