正文
由上图中的内容可以看到,在 IA-32e 模式下,中断或者异常发生时,其栈切换工作流程和 legacy 模式没有本质区别,只是如果发生了特权级切换,那么新的SS选择子会被赋值为NULL。同时,旧SS和旧RSP会被无条件压栈。
由上图中的内容可以看到,在 IA-32e 模式下,出现了 IST 机制,可选修改 legacy 栈切换机制。如果,64位 IDT 门描述符的 3-bit IST 属性的值设置为
001 - 111
中的任意值,那么在中断或者异常触发时,将会将其处理程序使用的栈指针修改为 TSS 中对应的 IST 指针。
但是如果,3-bit IST 属性的值设置为
000
,那么就会使用上述
modified legacy stack-switching
机制。
顺带提一下,如果我们需要在中断/异常触发时,由CPU帮我们关中断,可以选择将IDT中的门描述符设置为正确的类型。如果时中断门,那么CPU会帮自动清除IF位,以关中断;反之,陷阱门则不会。
因此,想要使用当前栈来存储函数的标识信息,只需要做一件事:
将中断门/陷阱门描述符中的 3-bit IST 设置为0即可。
1.2 中断和异常
根据"intel 白皮书 Vol. 3A CHAPTER 6 INTERRUPT AND EXCEPTION HANDLING "中描述,中断的来源是:来自外部的硬件中断、Int n软件模拟的中断;异常的来源是:来自CPU在程序运行过程中的错误检查、Int n软件模拟的异常、机器检查的异常。
可以注意到,中断和异常都可以由 Int n 指令进行模拟,而且由 Int n 触发的中断不会受到 EFLAGS 寄存器中 IF 位的影响。
同时,由于异常是由 CPU 产生的,因此往往是在进程上下文同步执行的,因此异常在任何情况都应该被第一时间处理,否则 CPU 就会出现错误。
配合 Linux 中断子系统进行理解,硬件中断是异步中断,通常上半部在中断上下文进行处理(即没有固定进程上下文的进程上下文),而下半部可以在使用软中断、微任务在中断上下文进行处理,也可以使用中断线程化、工作队列在进程上下文进行处理。而异常是同步异常,在进程上下文进行处理。稍微了解一些Linux 中断子系统的实现,可以发现,对于异常特别简单的硬中断(CPU 核间中断等),其处理程序往往短、平、快,而稍微耗时、不太紧要的操作就可以延迟到软中断(允许中断的中断上下文)和进程上下文进行操作,本文实现的
基于异常的两字节 Hook 框架
参考这样的设计,在中断上下文仅仅将控制流转向 Hook 分发函数的 wrapper ,在进程上下文完成真正的 Hook 分发。
因此可以得出这样的结论,这也是本文实现 Hook 框架的核心指导:
1. 中断分为可屏蔽和不可屏蔽中断,由 EFLAGS 的 IF 位决定是否响应可屏蔽中断,Int n 模拟的中断不可屏蔽。
2. 异常必须立刻同步执行异常处理程序,但是此时CPU并不自动屏蔽中断。
1.3 可行性总结
1. 由于异常发生后会立即执行异常处理程序,因此完全可以胜任 改变执行流 的重任。
2. 64-bit 模式下,通过设置中断/陷阱门描述符的 3-bit IST 为0,在同特权级下,将不会进行栈空间的切换。因此,可以存储被 Hook 函数的信息到原执行流使用的栈中。
3. IDT 中通常都会存在空余的表项未被使用。
因此,使用 IDT hook + Inline Hook的方式,可以减小 Inline Hook 对被 Hook 函数的修改影响,可以仅使用2字节实现对控制流的劫持。