拦截二进制函数

        Detours库能够在执行过程中动态拦截函数调用。detours将目标函数前几个指令替换为一个无条件跳转,跳转到用户定义的detour函数。被拦截的函数保存在trampoline函数中。trampoline保存了目标函数移除的指令和一个无条件跳转,能够跳转到目标函数的执行体部分(未被移除的部分)。

        当运行到目标函数的时候,直接跳转到用户提供的detours拦截函数。拦截函数開始运行自己的代码。

detour函数能够直接返回或调用trampoline函数,将流程返回到拦截前。当目标函数运行完以后,再将控制交给detour函数。detour函数运行适当的代码返回。下图分别表示没有拦截和拦截以后的运行流程:


      detours库通过在目标进程二进制映像中入指令进行拦截。

对于目标函数。detours实际上写入两个函数。目标函数和trampoline函数,以及一个函数指针pointer(怀疑文档中有错误)。trampoline函数由detours动态分配。拦截之前,trampoline仅仅包括一条跳转到目标函数的语句。拦截以后。trampoline包括目标函数的初始几条语句和跳转到目标函数剩余内容的跳转指令。

        目标指针最初被初始化为指向目标函数。

用detour依附(attach)到目标函数以后。目标指针就被改动为指向trampoline函数。当detour从目标函数分离(detach)以后,目标指针像開始一样指向目标函数。


        上图展示了detours拦截过程。为了拦截目标函数,首先为动态trampoline函数分配内存(假设没有静态的trampoline),然后改动目标函数和trampoline为可写。

拦截的第一步,detours从目标函数中复制至少5字节指令到trampoline(足够一条无条件跳转指令)。假设目标函数小于5字节,detours退出并返回一个错误码

        复制指令的过程中,detours採用了一种简单的表格驱动反汇编器。detours在trampoline最后加入一条跳转命令,跳转到目标函数第一条没有被复制的指令处。detours在目标函数的第一条指令处写入一条无条件跳转指令,跳转到detour函数中。最后,detours将目标函数和trampoline函数恢复为原始状态,然后通过调用借口FlushInstructionCache刷新cpu指令缓存。

使用Detours

为了detour目标函数,须要一个指向目标函数的指针和一个detour函数。为了可以正确拦截目标函数,detour函数和目标指针的调用规则须要一致,包含參数和调用规则。调用规则一致确保寄存器能正确保存。detour和目标函数的栈可以合理分配。

以下的代码描写叙述了detours库的用法,用户必须包括头文件detours.h,连接过程包括库detours.lib







  1. #include <windows.h>  
  2. #include <detours.h>  

  3. static LONG dwSlept = 0;  

  4. // Target pointer for the uninstrumented Sleep API.  
  5. //  
  6. static VOID (WINAPI * TrueSleep)(DWORD dwMilliseconds) = Sleep;  

  7. // Detour function that replaces the Sleep API.  
  8. //  
  9. VOID WINAPI TimedSleep(DWORD dwMilliseconds)  
  10. {  
  11.     // Save the before and after times around calling the Sleep API.  
  12.     DWORD dwBeg = GetTickCount();  
  13.     TrueSleep(dwMilliseconds);  
  14.     DWORD dwEnd = GetTickCount();  

  15.     InterlockedExchangeAdd(&dwSlept, dwEnd - dwBeg);  
  16. }  

  17. // DllMain function attaches and detaches the TimedSleep detour to the  
  18. // Sleep target function.  The Sleep target function is referred to  
  19. // through the TrueSleep target pointer.  
  20. //  
  21. BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)  
  22. {  
  23.     if (DetourIsHelperProcess()) {  
  24.         return TRUE;  
  25.     }  

  26.     if (dwReason == DLL_PROCESS_ATTACH) {  
  27.         DetourRestoreAfterWith();  

  28.         DetourTransactionBegin();  
  29.         DetourUpdateThread(GetCurrentThread());  
  30.         DetourAttach(&(PVOID&)TrueSleep, TimedSleep);  
  31.         DetourTransactionCommit();  
  32.     }  
  33.     else if (dwReason == DLL_PROCESS_DETACH) {  
  34.         DetourTransactionBegin();  
  35.         DetourUpdateThread(GetCurrentThread());  
  36.         DetourDetach(&(PVOID&)TrueSleep, TimedSleep);  
  37.         DetourTransactionCommit();  
  38.     }  
  39.     return TRUE;  
  40. }  



拦截目标函数通过与detour交互,调用DetourAttach实现。与detour的交互通过调用DetourTransactionBegin和DetourTransactionCommit实现。DetourAttach包括2个參数:目标函数指针的地址和detour函数地址。目标函数不能直接作为參数传入。由于须要传入一个目标指针。         交互过程中DetourUpdateThread更新全部线程。确保全部拦截点都能正确更新。         DetourAttach为调用目标函数分配并准备好一个trampoline。detour执行以后,目标函数和trampoline会被重写。目标指针被更新。指向trampoline函数。         一旦目标函数被detour,目标函数的调用会被转到detour函数。目标函数通过trampoline被运行的时候。detour函数将參数复制过来,由于如今目标函数变成了detour函数的一个子程序。         要想移除拦截。能够调用detoursDetach。与DetourAttach类似,DetourDetach包括两个參数:目标函数指针地址和detour函数指针。函数运行以后。目标函数被重写为其原来的状态。trampoline函数被删除,目标函数指针恢复为原来的目标函数。         假设须要拦截的程序没有源码。detour函数须要打包为一个dll。将dll载入到一个新的进程中。进程启动的时候调用DetourCreateProcessWithDllEx载入dll。假设通过调用DetourCreateProcessWithDllEx插入dll,DllMain函数必须调用DetourRestoreAfterWith函数。假设希望dll可以在32位和64为机器上使用,DllMain必须调用DetourIsHelperProcess函数。Dll必须导出DetourFinishHelperProcess函数. 注意:微软不保证或支持不论什么被改动的微软或第三方的代码,无论是採用detour改动还是其他方式改动。 detours自带样例withdll採用DetourCreateProcesswithDllEx函数启动一个新进程加载一个命名dll Payloads和DLL导入编辑         除了连接和分离detours函数,detours包还提供了注入其它数据段的API,如二进制文件,或改动dll导入表,叫做payloads。Detours中的二进制改动API是全然可逆的,Detours在二进制中保存了恢复信息。使得全部的改动都能够还原。 windowsPE格式的二进制文件结构如上图所看到的。PE格式的windows二进制文件时COFF格式(通用文件格式)的一个扩展。windows二进制文件包括一个DOS兼容的头,PE头。包括代码的text段,包括初始化数据的data段,包括导入的dll和函数的导入表,包括代码导出函数的导出表,调试信息。二进制文件必须包括两个头,其他的段都是可选的。         为了改动windows二进制文件,Detours创建了一个.detours段,在导出表和调试符号中间,如上图所看到的。windows二进制文件必须把调试符号放在最后。新的段包括一个detours头。和原始PE头的一份拷贝,假设改动导入表,detours创建一个新的导入表,将其放在复制的PE头后面,然后改动原始的PE头,将其指向新的导入表。最后,Detours在.detours段的最后写入用户的payloads,然后加上调试信息完毕文件。改动后的windows二进制文件能够非常easy恢复:从.detours段中恢复PE头。移除.detours段。上图表示的是Detours改动过的windows二进制文件格式。         创建一个导入表有2个目的。首先是保持原始的导入表不变,以便以后恢复文件,其次,新建的导入表包括重命名的导入DLL和函数,或者是整个dll和函数。比如。detours保的样例中setdll.exe这个程序。讲一个用户dll插入到目标二进制程序中。因为新的导入表在应用程序中第一个位置。所以用户的dll在程序中会首先被运行。         Detours提供了非常多实用接口。包含编辑导入表(DetourBinaryEditImports)。加入payloads(DetourBinarySetPayload)。枚举payloads(DetourBinaryEnumeratePayloads),移除payloads(DetourBinaryPurgePayloads)。DetourEnumerateModules能够枚举一个地址空间中的二进制文件。DetourFindPayload能够定位二进制文件里映射的payloads。每个payload通过一个128位的guid表示。payloads能够将程序配置数据注入到二进制程序中。 DetourCopyPayloadToProcess能够直接将Payloads拷贝到目标进程 Detour 32位和64位进程 注意:仅仅有专业版的Detours支持64位程序。非商业版,express版本号仅仅支持32位x86系统 detours最常见的使用情况是在不改动原始二进制程序的情况下的改变函数运行情况。这时。用户提供的detour函数打包到一个dll中,在程序运行開始的时候调用DetourCreateProcesswithDll载入该dll。父进程调用DetourCreateProcesswithDll,通过插入一个导入表来改变程序内存中的拷贝。新的插入表使得系统在程序開始的时候,程序逻辑代码还没有运行的时候载入dll。这样deoturDLL就能够hook目标进程中的目标函数。 在64位的处理器上。windows支持32位或64位的应用程序。为了支持32位或64位程序。须要创建32位和64位的detourDLL。调用DetourCreateProcessWithDll的地方须要改为DetourCreateProcessWithDllEx。DetourCreateProcessWithDllEx会依据系统选择合适的Dll来注入目标程序。 须要这样做: 为了在一个系统上支持32位和64位程序,须要创建2个DLL。一个包括32位代码,还有一个包括64位代码。两个DLL放在同一个文件夹中而且起同样的名字。并加上对应的后缀“32”和“64”,比方foo32.dll和foo64.dll。 调用DetourCreateProcessWithDllEx来启动一个包括对应DLL的进程。另外,你的DLL须要: 1.导出DetourFinishHelperProcess 2.在DllMain中调用DetourIsHelperProcess。假设DetourIsHelperProcess返回TRUE。则立即返回TRUE。 3.通过调用DetourCreateProcessWithDllEx而不是DetourCreateProcessWithDll来创建新的进程。 工作原理 假设目标进程跟dll的父进程同为32位或同为64位,DetourCreateProcessWithDllEx执行过程类似DetourCreateProcessWithDll。 假设父进程与目标进程不同,一个是32位,一个是64位,DetourCreateProcessWithDllEx会创建一个辅助进程。将DLL载入到rundll32.exe进程,然后调用DetourFinishHelperProcess。这个API通过使用正确的32位或64位代码来修补导入表。 应用演示样例 以下測试辅助进程,首先在32位环境中编译Detours样例。然后在64位环境中编译64位样例。然后进入样例中\tryman文件夹,在64位环境中输入"nmake size64",这样能够递归执行进程。包含32位和64位进程。