博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Detour开发包介绍(1):概述
阅读量:3557 次
发布时间:2019-05-20

本文共 6240 字,大约阅读时间需要 20 分钟。

微软研究院Detours 开发包是一个用来在二进制级别上对程序中的函数 (Function) 或者过程 (Procedure) 进行修改的工具库。一般我们将这种技术称为 Hook 。在现实中,这种技术可以应用在很多场景下。比如截获某些 Windows API ,在实际调用到系统函数前进行一些过滤工作;软件中使用到了一些没有源代码的第三方库,但是又想增强其中某些函数的功能;修改函数返回值;为调试以及性能测试加入附加的代码;或者截获函数的输入输出作研究;破解使用。等等。 Detours 是在 Windows 的二进制 PE 文件基础上进行 API 截获。对于 Linux 平台,作这件事情将会非常的简单,由于最初的操作系统设计者引入了 LD_PRELOAD 。如果你设置 LD_PRELOAD=mylib.so ,那么应用程序在载入系统动态链接库时,会先查看 mylib.so 的符号表,在重定位 (relocation) 的时候会优先使用 mylib.so 里的 symbol 。假如你在 mylib.so 里有个 printf()  ,那么这个 printf 就会替代 libc 的 printf 。而在 mylib.so 里的这个 printf 可以直接访问 libc.so 里的 printf 函数指针来获得真正的 printf 的入口地址。这样,所有的 API 拦截在 loader 加载系统库时就已经完成,非常自然,和平台相关的部分全部交给 loader 去处理。

Detours的下载地址为 http://research.microsoft.com/en-us/projects/detours/ ,最新为 2.1 版。 Detours Express 2.1 可免费使用(用于非商业目的),只支持 32 位的 x86 代码。 Detours Professional 2.1 需要购买版权,它提供的 API 接口其实与 Detours Express 是一样的,只不过它还可以支持 x64 和 IA64 处理器,以此为基础编写的代码拥有更强的可移植性。安装完 Detours Express 后,包括 Detours 库和使用示例的源代码,需要 Visual C++ 环境来进行编译。打开 Visual Studio 的命令行提示符界面,切换到 Detours Expressr 的安装目录,用 "nmake" 命令即可完成构建。编译完后,我们要用到时的有四个文件,有四个文件,分别是 detoured.dll 、 detoured.lib 、 detours.lib 、 detours.h 。

1、截获二进制函数

众所周知,WINDOWS NT 实现了虚拟存储器,每一 WIN32 进程拥有 4GB 的虚存空间, 关于 WIN32 进程的虚存结构及其操作的具体细节请参阅 WIN32 API 手册, 以下仅指出与 Detours 相关的几点:

(1) 进程要执行的指令也放在虚存空间中。

(2) 可以使用 QueryProtectEx 函数把存放指令的页面的权限更改为可读可写可执行,再改写其内容,从而修改正在运行的程序。

(3) 可以使用 VirtualAllocEx 从一个进程为另一正运行的进程分配虚存,再使用 QueryProtectEx 函数把页面的权限更改为可读可写可执行,并把要执行的指令以二进制机器码的形式写入,从而为一个正在运行的进程注入任意的代码。

Detours定义了三个概念:

(1) Target函数:要拦截的函数,通常为 Windows 的 API 。

(2) Trampoline函数: Target 函数的部分复制品。因为 Detours 将会改写 Target 函数,所以先把 Target 函数的前 5 个字节复制保存好,一方面仍然保存 Target 函数的过程调用语义,另一方面便于以后的恢复。

(3) Detour函数:用来替代 Target 函数的截获函数。

Detours库使得截获函数调用更容易,截获代码是运行时动态加载的。 Detours 使用一个无条件转移指令来替换目标函数的最初几条指令,将控制流转移到一个用户提供的截获函数。而目标函数中的一些指令被保存在一个被称为“ trampoline ”的函数中,这些指令包括目标函数中被替换的代码以及一个转移到目标函数的无条件分支。

当程序执行到达目标函数的时候,会直接跳转到一个用户支持的截获函数。截获函数来执行适当的预处理。截获函数可以直接返回到原来的函数,或者它可以调用“trampoline ”函数,后者可以按照截获以前的方式来调用目标函数。当目标函数执行完以后,它将控制返回到截获函数。而截获函数将执行恰当的收尾工作并将控制返回到源函数调用处。下图显示了被截获和未被截获的调用在逻辑上的控制流。

 

 

图1  有 Detours 和没有 Detours 的调用控制流对比

Detours库通过重写目标函数在进程中的二进制映像达到截获目标函数的目的。对每一个目标函数而言, Detours 实际上重写了两个函数:目标函数和与之相匹配的 trampoline 函数。 trampoline 函数可以静态或者动态的创建。一个静态创建的 trampoline 函数总是不需要截获就可以调用目标函数。在之前的用于截获的插入中,静态 trampoline 函数保存了到目标函数的一个简单跳转。这个调整插入以后, trampoline 函数保存了目标函数的初始化指令,以及到目标函数的跳转指令。

Detours在 Target 函数的开头加入 JMP Address_of_ Detour_ Function 指令(共 5 个字节)把对 Target 函数的调用引导到自己的 Detour 函数, 把 Target 函数的开头的 5 个字节加上 JMP Address_of_ Target _ Function+5 共 10 个字节作为 Trampoline 函数。如下图显示了截获过程的插入前后。要截获一个目标函数, Detours 首先为动态 trampoline 函数分配内存(如果没有提供静态的 trampoline 函数),然后会让目标和trampoline 函数可写。在开始了第一条指令之后, Detours 会从目标函数拷贝至少五个字节的指令到 trampoline  函数(五个字节足够放下一条无条件转移指令)。如果目标函数少于 5 个字节, Detours 会终止执行并返回一个错误码。为了拷贝指令, Detours 使用一个简单的表驱动的反汇编引擎。 Detours 会在 trampoline 函数的执行尾部添加一条跳转指令,这样执行完 trampoline 函数后,程序会跳转到目标函数没有拷贝的剩余部分继续执行。 Detours 会在截获函数中写入一条无条件跳转指令作为到目标函数的第一条指令。最后, Detours 将保存目标函数和 trampoline 函数的原始的页面权限,并使用 Flush  Instruction  Cache 函数将 CPU 的指令缓冲区清空。

 

 

图2  目标函数和跳板函数代码(左边没有插入 detour ,右边插入了 detour )

 

目标函数:函数体(二进制)至少有5 个字节以上。按照微软的说明文档 Trampoline 函数的函数体是拷贝前 5 个字节加一个无条件跳转指令的话(如果没有特殊处理不可分割指令的话),那么前 5 个字节必须是完整指令,也就是不能第 5 个字节和第 6 个字节是一条不可分割的指令,否则会造成 Trampoline  函数执行错误,一条完整的指令被硬性分割开来,造成程序崩溃。对于第 5 字节和第 6 个字节是不可分割指令需要调整拷贝到杂技函数 (Trampoline) 的字节个数,这个值可以查看目标函数的汇编代码得到。此函数是目标函数的修改版本,不能在 Detour 函数中直接调用,需要通过对 Trampoline 函数的调用来达到间接调用。

Trampoline函数:此函数默认分配了 32 个字节,函数的内容就是拷贝的目标函数的前 5 个字节,加上一个 JMP Address_of_ Target _ Function+5 指令 , 共 10 个字节。此函数仅供您的 Detour 函数调用,执行完前 5 个字节的指令后再绝对跳转到目标函数的第 6 个字节继续执行原功能函数。

Detour函数:此函数是用户需要的截获 API 的一个模拟版本,调用方式,参数个数必须和目标函数相一致。如目标函数是 __stdcall ,则 Detour 函数声明也必须是 __stdcall, 参数个数和类型也必须相同,否则会造成程序崩溃。此函数在程序调用目标函数的第一条指令的时候就会被调用(无条件跳转过来的),如果在此函数中想继续调用目标函数,必须调用 Trampoline 函数( Trampoline  函数在执行完目标函数的前 5 个字节的指令后会无条件跳转到目标函数的 5 个字节后继续执行),不能再直接调用目标函数,否则将进入无穷递归(目标函数跳转到  Detour 函数, Detour函数又跳转到目标函数的递归,因为目标函数在内存中的前 5 个字节已经被修改成绝对跳转)。通过对 Trampoline 函数的调用后可以获取目标函数的执行结果,此特性对分析目标函数非常有用,而且可以将目标函数的输出结果进行修改后再传回给应用程序。

这是一种执行时注入的方式,Detours 是执行时被插入的。即修改的是内存中的目标函数代码,而不是在硬盘上的 DLL 文件,因而可以在一个很好的粒度上使得截获二进制函数的执行变得更容易。例如,一个应用程序执行时加载的 DLL 中的函数过程可以被插入一段截获代码( detoured ),与此同时,这个 DLL 还可以被其他应用程序按正常情况执行(也就是按照不被截获的方式执行,因为 DLL 二进制文件没有被修改,所以发生截获时不会影响其他进程空间加载这个 DLL )。不同于 DLL 的重新链接或者静态重定向, Detours 库中使用的这种中断技术确保不会影响到应用程序中的方法或者系统代码对目标函数的定位。

如果其他人为了调试或者在内部使用其他系统检测手段而试图修改二进制代码,Detours 将是一个可以普遍使用的开发包。据我所知, Detours 是第一个可以在任意平台上将未修改的目标代码作为一个可以通过“ trampoline ”调用的子程序来保留的开发包。而以前的系统在逻辑上预先将截获代码放到目标代码中,而不是将原始的目标代码作为一个普通的子程序来调用。独特的“ trampoline ”设计对于扩展现有的软件的二进制代码是至关重要的。

出于使用基本的函数截获功能的目的,Detours 同样提供了 DLL 注入的方式,它可编辑任何 DLL 或 EXE 导入表的功能,达到向存在的二进制代码中添加任意数据节表的目的,向一个新进程或者一个已经运行着的进程中注入一个 DLL 。一旦向一个进程注入了 DLL ,这个动态库就可以截获任何 Win32 函数,不论它是在应用程序中或者在系统库中。

2、有效负荷和 DLL 导入表的编辑

Detours库提供了被称为有效负荷( payloads )的功能,它可以对 Win32 二进制文件附加任意数据节表的可逆支持(译注:可以添加,并卸栽)以及编辑 DLL 导入表。

下图显示了Win32 的 PE 二进制文件的基本结构。 PE 格式的 Win32 二进制文件是 COFF (普通对象文件格式)的一种扩展。一个 Win32 二进制文件包括一个对 DOS 兼容的文件头,一个 PE 头,一个包含了程序代码的 text 节表,一个数据节表保存了初始化数据,一个列出导入的 DLL 和函数的导入表,一个列出导出函数代码的导出表,以及调试符号。除了两个文件头以外,文件的每个节表都是可选的,二进制文件可以不包含它们。

 

 

图3 Win32 PE 可执行文件的结构

为了修改一个Win32 二进制文件, Detours 在导出节表和调试符号之间生成了一个新的 .detours 节表。注意调试符号必须永远处于 Win32 二进制文件的最后面。这个新节表保存了一个截获文件头的记录和原始的 PE 头,如果修改了导入表, Detours 会生成一个新的导入表,并将它附着到拷贝的 PE 头上,然后修改原始的 PE 头,让它的内部指向新的导入表。

最后,Detours 会将一些其他信息写到 .detours 节表的最后并将调试信息附加到文件的最后面。 Detours 可以将二进制文件恢复到被它修改以前的状况,因为它可以恢复在 .detours 节表中保存的原始的 PE 文件头,并删除 .detours 节表。下图显示了一个被 Detours 修改过的 Win32 二进制文件的格式。

 

图 4 一个被 Detours 修改过的二进制文件的格式

生成一个新的导入表有两个目的。第一,它保留了原始的导入表,这样万一程序员想恢复到修改前的状况就不会出现问题。第二,新的导入表可以包含被更名的导入DLL 和函数或者全新的 DLL 和函数。例如 Detours 包中的 setdll.exe 示例程序可以把一个用户 Dll 的初始化入口表插入到目标应用程序中,以作为目标应用程序导入表中的第一个入口,这样在应用程序地址空间中第一个运行的动态库总是这个用户 Dll (这是指动态库加载时运行 DllMain 函数)。

Detours提供了 API 用来编辑导入表( DetourBinaryEditImprots ),添加有效负荷( DetourBinarySetPayload ),枚举有效负荷( DetourBinaryEnumeratePayloads ),删除有效负荷( DetourBinaryPurgePayloads ),再绑定动态库的函数。 Detours 同时还提供了 API 用来枚举映射到地址空间中的二进制文件( DetourEnumerateModules ),以及定位这些被映射的二进制文件中的有效负荷( DetourFindPayload )。每一个有效负荷被用一个 128 位的全局唯一标识符( GUID )标识出来。有效负荷可以用来将每个应用程序的配置信息关联到应用程序的二进制代码中。有效负荷还能直接被拷贝到目标进程中( DetourCopyPayloadToProcess )。

 

    一旦有任何截获行为需要在不修改二进制文件的情况下被插入到应用程序中,Detours 提供了函数来将 DLL 注入到一个新的或者是已经存在的进程。为了注入一个 DLL , Detours 使用 AllocEx 和 WriteProcessMemory 这些 API 在目标进程中写入一个 LoadLibrary 的调用代码,并使用 CreateRemoteThread 来进行这个调用(指使用一个新线程来调用写入的代码,包括 LoadLibrary ,在 DLL 的加载过程中, DllMain 函数得以执行)。

转载地址:http://gosrj.baihongyu.com/

你可能感兴趣的文章
Java问题百度/Google记录 2020-2-16
查看>>
【PADS9.5】9,对比ECO核心板,Router移动元件后布线消失,Router找不到自动布线策略文件丢失或损坏
查看>>
【STM32+w5500汇总】23,HTTP_Client 连接到ONENET上传了一段数据之后会断开,数据上传格式的设置
查看>>
【STM32+W5500+MQTT】24,所有功能都可以通过API函数的调用来实现;HTTP接入ONENET,API开发手册和打包函数,串口软件HTTP连接服务器上传数据,2018年12月28日
查看>>
【STM32+W5500+HTTPClient】25,路由器DHCP租赁IP时间为2h,NetBios可以很好的解决IP变化的问题,DNS,2018年12月25日
查看>>
【STM32+MQTT+ONENET】26,MQTT协议接入OneNET
查看>>
【STM32+W5500+MQTT+ONENET】27,MQTT协议接入OneNET实际编程操作 2018年12月27日
查看>>
【STM32Cube+FreeRTOS 】28,KEIL5的F12不起作用;***JLink Error: Can not read register x while CPU is running
查看>>
【STM32CubeMX+FreeRTOS 】29,prtinf卡死;4任务只运行了3个;W5500联网失败(堆栈不能太大或者太小)
查看>>
【STM32+FreeRTOS +W5500移植要点】30,RTOS中断;从TIM2,主TIM3;RTOS主要用在LCD中;RT-Thread;标志重定义问题 2019年01月22日
查看>>
【STM32+FPGA+FSMC】31,FSMC熟练掌握;KEIL5生成bin文件;SDRAM的使用;IAP检验码 2019年04月10日
查看>>
【IC1】【转 非常好】运算放大器使用的六个经验
查看>>
【IC-ADC 3】ADC的选型
查看>>
2019年03月18日 查看数据手册的注意点,极限参数、电气参数、推荐参数
查看>>
HiKey960/970用户手册;HiKey960 Development Board User Manual
查看>>
【书籍推荐】FPGA,xilinx
查看>>
N9-SQL注入(union注入)
查看>>
N10-sql注入(information_schema注入)
查看>>
N1-Kali虚拟机中SQLmap
查看>>
N11-sql注入(http头注入)
查看>>