Inline Hook在之前文章有较多概念性描述,本文则侧重介绍如何去实现一个Inline Hook,并且关注一些实现过程中会遇到的困难与挑战。
1.1 实现原理
HOOK是什么,通过前面几章的介绍,相信读者都有个比较具体的了解。通俗点理解,就是拦截指定函数或者具体函数某条汇编指令的功能。注入GOT HOOK之类的方法,只能针对函数头进行HOOK。如果需要针对函数的特定位置进行HOOK,那么Inline Hook则可以派上用场。
Inline Hook直接修改要HOOK位置的指令,让其跳转到桩函数中。在桩函数中,会处理寄存器等信息,并调用相应HOOK点用户自定义的桩函数。在处理完用户自定义的桩函数后,则会跳转执行原指令。
图1. Inline Hook原理图 如图1所示,Inline Hook的核心原理。这里说明一下几个关键点:
(1) 跳转指令的构建、从原指令跳转过去的底层桩函数,涉及系统汇编层,和Inline Hook的平台关系较大,既是ARM和THUMB、X86等均都有所不同;64位和32位也有所不同。本文实现以32位的ARM为样例实现。
(2) 从底层桩函数跳转回原函数,既原理图中第3步跳转,去执行原指令2的时候,有个关键的点:如果原指令2涉及到PC操作,则需要进行指令修复。比如说是ADD R3, PC, R3, 两处指令的PC完全不一样,肯定不能直接复制,需要针对修复相应PC值。本文为了方便读者入门,没有针对指令进行修复操作,既是有个限制:HOOK点位置,原指令2不能有涉及到PC的操作。如果读者后续对指令修复有兴趣,可能自行实现,本文的代码框架能很好支持这个扩展。
(3) Inline Hook的指令覆盖顺序,原指令2的覆盖建议留在最后实现。既是完成了底层桩函数构造、原函数构造后,再一次性填写跳转指令覆盖原指令2。这样的好处是防止HOOK一些频繁执行的函数可能导致的崩溃。在Inline Hook的过程中走入了HOOK逻辑,而桩函数或者原函数可能未构造成功导致崩溃。
大概的原理知道后,下面给出一个实现的流程图,如图2。通过该流程图,读者可以快速了解到Inline Hook的实现框架流程主要是这么3个:构造stub、构造原函数、覆盖原指令。图3示意了样例要HOOK的IBored中的一条函数指令,对应图4则示意了Inline Hook实现之后的底层汇编指令结构。
1.2 实现流程
图2. InlineHook实现流程图
图3. HOOK位置的指令
图4. Inline Hook实现后的逻辑
1.3 实现代码
按照实现流程图,这里按照流程讲解下4个流程代码。
首先是备份hook点的信息,如下代码所示,主要是原指令的备份
接着是构造stub。底层stub采用了汇编编写,整片代码在ihookstub.s中。如下代码所示,主要是malloc一块内容用于填充shellcode,同时针对用户自定义的_hookstub_function_addr_s函数地址进行修改填充。备份相应代码地址到INLINE_HOOK_INFO中。(代码为了粘贴美观,去掉相应注释,在实际代码文件里面,会有相应注释片段。)
然后是构建原来的指令块,这里也需要malloc一块空间来填充指令。前8个bytes则是原指令直接填充。本文一开始提到的指令修复,如果需要则是在这个位置进行扩展。后8个bytes用于填充跳转指令,跳转地址则是HOOK地址+8。通过BuildArmJumpCode函数构建简单的指令跳转函数,
LDR PC, [PC, #-4]
Addr
该跳转指令范围是32位,不过对于32位系统来说,则是全地址跳转。构造原指令的同时,将原指令地址,填充到stub中使之可实现示意图中的第3步跳转。
最后则是Inline Hook的最后一步,重构HOOK位置的指令,直接填充一个跳转指令。该跳转指令是跳转到BuildStub构建的stub中。
1.4 小结
本文介绍了Inline Hook的原理,并通过流程图和代码直观地描述和说明Inline Hook的执行过程,配合IBored校验代码的正确性和应用场景的举例。本篇幅重点是让读者了解到Inline Hook的思想和原理,针对THUMB、X86等平台上的Inline Hook扩展,有兴趣的读者基于本篇幅的了解去实现相信难度不会太大。