拦截二进制函数
Detours库支持拦截函数调用。拦截代码在运行时动态应用。Detours将目标函数的前几个指令替换为无条件跳转到用户提供的detour函数。来自目标函数的指令保存在 trampoline函数中。 trampoline 由从目标函数中删除的指令和无条件地转移到目标函数的其余部分组成。
当执行到达目标函数时,控制直接跳转到用户提供的detour函数。detour函数执行任何适当的拦截预处理。detour函数可以将控制权返回给源函数,也可以调用trampoline函数,后者调用目标函数而不进行拦截。当目标函数完成时,它将控制权交还给detour函数。detour函数执行适当的后处理并将控制权返回给源函数。图1显示了有或没有拦截的函数调用的控制逻辑流。
图1所示。控制没有Detours和有Detours的调用流。
Detours库通过重写目标函数的进程内二进制图像来拦截目标函数。对于每个目标函数,Detours实际上重写了两个函数,目标函数和匹配的trampoline函数,以及一个函数指针,目标指针。 trampoline函数由Detours动态分配。在插入一个Detour路由之前, trampoline只包含一个到目标函数的跳转指令。插入后, trampoline包含来自目标函数的初始指令和到目标函数其余部分的跳转。
用户初始化目标指针以指向目标函数。在将一个Detour路由附加到目标函数之后,目标指针被修改为指向 trampoline函数。当detour路由与目标函数分离后,返回目标指针指向原目标函数。
图2。 trampoline和目标函数,在插入绕道之前(左边)和之后(右边)。
图2显示了绕道的插入。要使目标函数绕行,Detours首先为动态trampoline函数分配内存(如果没有提供静态trampoline),然后对目标函数和trampoline都启用写访问。从第一条指令开始,Detours将指令从目标复制到 trampoline,直到至少复制了5个字节(对于无条件跳转指令来说足够了)。如果目标函数小于5字节,Detours将终止并返回错误代码。
要复制指令,Detours使用一个简单的表驱动反汇编程序。Detours为目标函数的第一个非复制指令添加了一条从 trampoline末端开始的跳转指令。Detours向detour函数写入一条无条件跳转指令,作为目标函数的第一条指令。最后,Detours恢复目标函数和 trampoline函数的原始页面权限,并通过调用FlushInstructionCache API刷新CPU指令缓存。