【二进制插桩】基于DynamoRIO的代码插桩方案

背景说明

DynamoRIO是一种流行的动态二进制插桩平台,能够实时跟踪二进制程序的动态执行过程,在动态分析领域具有广泛的应用;允许开发者在程序运行时插入和修改代码,以实现性能监测、安全检查、自动测试等功能。

DynamoRIO提供了丰富的API接口,开发者可以利用这些接口实现对指令、基本块、线程、系统调用等监控,从而实现二进制分析插件的开发。

基本概念

  • 动态二进制插桩(Dynamic Binary Instrumentation)

    • 一种在程序运行时插入代码的方法,可以在不修改源代码的情况下对程序进行分析和优化;

    • DynamoRIO提供了一种框架,使开发者能够在程序的各个执行点(如函数、基本块)上插入自定义代码。

  • 客户端(Client)

    • 客户端是由用户编写的、与DynamoRIO一起运行的插件,负责定义插入的逻辑和位置;

    • 每个客户端通常会实现一组回调函数,这些函数会在程序的不同执行阶段被调用。

  • 基本块(Basic Block)

    • 基本块是一段程序代码,它在没有分支或跳转的情况下连续执行;

    • 在DynamoRIO中,基本块是插桩的基本单位,客户端可以再基本块的入口、出口或中间插入代码。

      • 基本块示例

        有以下伪汇编代码为例,展示程序如何分解为基本块。

        ; 基本块 1
        MOV EAX, 1
        ADD EAX, 2
        CMP EAX, 5
        JE block2    ; 如果 EAX 等于 5,跳转到基本块 2
        ​
        ; 基本块 2
        SUB EAX, 1
        JMP block3
        ​
        ; 基本块 3
        MOV EBX, EAX
        ADD EBX, 4
        • 基本块1:包含指令MOV EAX,1ADD EAX,2CMP EAX,5 。如果EAX等于5,程序会跳转到基本块2。

        • 基本块2:包含指令SUB EAX,1。无条件跳转指令JMP block3是基本块2的出口。

        • 基本块3:包含指令MOV EBX,EAXADD EBX,4。这个基本块没有跳转指令,因此它的出口是程序的自然结束点。

  • 插桩(Instrumentation)

    • 指的是在基本块或其他代码段中插入自定义代码,以实现监控或修改程序行为的目的。

  • 回调函数

    • 由DynamoRIO在特定事件发生时调用的函数;

    • 常见的回调函数包括基本块生成、指令执行等。

基本组成

此插桩平台主要包含以下内容:

  1. DynamoRIO:负责解释并执行目标程序;提供丰富的跨平台API接口;

  2. Client:通过API自定义分析操作来拓展DynamoRIO;

  3. DynamoRIO Extensions:主要指drmgr、drsyms、drwrap等官方提供的接口。

DynamoRIO中有一个重要的概念,事件

  1. 应用程序事件:应用程序在动态执行时的事件,包括进程创建、模块加载、系统调用等;

  2. DynamoRIO事件:包括基本块、轨迹流的创建等;

  3. 事件回调函数的注册:dr_register_xx_eventdr_ungister_xx_event等。

DynamoRIO Extensions

  • drmgr模块

    • drmgr模块是DynamoRIO中的一个重要组件,它提供了一个更高层次、更易于使用的接口,用于管理代码插桩、回调注册和事件处理等功能。drmgr(Dynamic Runtime Manager)模块简化了插件的开发,提供了许多底层细节,使得开发者可以更专注于实际的插桩逻辑。

    • drmgr模块的功能

      • 回调管理:提供了管理不同类型回调函数的机制,包括基本块插桩、模块加载、线程创建和退出、应用退出等事件。

      • 顺序保证:允许插件指定回调函数的优先级,以控制多个插件之间的回调执行顺序,避免回调之间的冲突和不一致。

      • 扩展性:通过提供一致的API接口,使得新的回调类型能够轻松集成到系统中,增强了插件的扩展能力。

      • 多插件协调:可以协调多个插件之间的交互,确保插件能够安全地共享DynamoRIO的资源和信息。

  • drsyms模块

    • drsyms模块是DynamoRIO提供的一个用于符号解析的辅助库。它简化了对程序中符号信息的访问,使得开发者可以更容易地获取函数名、变量名、源代码行号等调试信息。这对于动态分析、插桩和调试工具的开发非常有用。

    • drsyms提供了对二进制文件中符号信息的访问接口,支持多种符号格式,包括DWARF(用于Linux和MacOS)和PDB(用于Windows)。使用该模块可以解析模块中符号的位置,获取符号的名称,甚至可以将地址映射回源代码行号。

    • drsyms模块的功能

      • 符号查找:通过符号名称查找其对应的内存地址,或者通过地址查找其对应的符号名称;

      • 源代码信息解析:解析符号对应的源代码文件和行号,这对于插桩和调试非常有用;

      • 多平台支持:支持Windows、Linux和MacOS等主流操作系统的符号格式。

  • drwrap模块

    • drwrap模块是DynamoRIO提供的一个用于函数级插桩的工具,极大地简化了函数调用拦截和监视的过程。该模块允许开发者轻松地在函数入口和出口插入自定义逻辑,适用于函数级的性能分析、监控和调试。

    • drwrap模块的功能

      • 函数入口和出口的拦截:允许在函数被调用之前和返回之后执行自定义逻辑,提供对函数执行的细粒度控制;

      • 参数和返回值访问:在函数入口和出口处,开发者可以获取和修改函数的参数和返回值;

      • 多平台支持:支持在Windows、Linux、MacOS等操作系统上的函数插桩。

编译环境设置流程

DynamoRIO-Windows-10.93.19944文件层级结构

Windows环境

  1. 下载和安装DynamoRIO

    1. 从DynamoRIO官网下载适用于Windows环境的版本;

    2. 解压缩至一个目录,例如C:\dynamorio

  2. 设置环境变量

    1. 打开命令提示符(或PowerShell);

    2. 设置DYNAMORIO_HOME环境变量;

      set DYNAMORIO_HOME=C:\dynamorio
    3. 将DynamoRIO的bin32或bin64目录添加到PATH环境变量。

      set PATH=%DYNAMORIO_HOME%\bin64;%PATH%
  3. 编译客户端

    1. 打开Visual Studio开发者命令提示符;

    2. 编译客户端DLL(假设客户端代码文件名为basic_count.c)。

      cl /nologo /LD /MD basic_count.c /I"%DYNAMORIO_HOME%\include" "%DYNAMORIO_HOME%\lib64\release\drmgr.lib" "%DYNAMORIO_HOME%\lib64\release\dynamorio.lib"
  4. 运行客户端

    drrun -c basic_count.dll -- your_application.exe

Linux环境

  1. 下载和安装DynamoRIO

    1. 从DynamoRIO官网下载适用于Linux环境的版本;

    2. 解压缩至一个目录,例如/opt/dynamorio

  2. 设置环境变量

    1. 在终端中设置DYNAMORIO_HOME环境变量:

      export DYNAMORIO_HOME=/opt/dynamorio
    2. 将DynamoRIO的bin64路径添加到PATH环境变量。

      export PATH=$DYNAMORIO_HOME/bin64:$PATH
  3. 编译客户端

    使用gcc编译(建设客户端代码文件名为basic_count.c):

    gcc -shared -o basic_count.so basic_count.c -I$DYNAMORIO_HOME/include -L$DYNAMORIO_HOME/lib64/release -ldrmgr -ldynamorio
  4. 运行客户端

    drrun -c basic_count.so -- your_application

系统化的流程

使用DynamoRIO进行程序监控和修改时,需要一个系统化的流程来确保插桩代码的正确性和适用性。这涉及对目标程序的了解、插桩代码的设计和实现,以及对插桩效果的测试与验证。

以下是一个典型的流程,包括各个步骤的详细说明:

  1. 了解目标程序

    1. 分析需求:明确需要监控或修改的目标,包括性能指标、安全检查、功能检测等;

    2. 程序结构理解:熟悉程序的控制流或数据流,找出可能需要插桩的位置,例如特定的函数、基本块或指令。

  2. 设计插桩逻辑

    1. 确定插桩点:根据需求选择合适的插桩点。例如,在函数入口或出口处插桩,或者在特定的条件分支中插桩;

    2. 定义插桩内容:决定在插桩点插入哪些代码,如日志记录、性能计数器、安全逻辑检查等。

  3. 实现插桩代码

    1. 使用DynamoRIO API:利用DynamoRIO 提供的API来实现插桩逻辑。需要注册回调函数,以便在程序执行过程中调用自定义的插桩代码;

    2. 编写回调函数:这些函数在基本块或特定指令执行时被调用。开发者可以使用这些回调函数中使用dr_insert_clean_all 等API插入自定义代码。

  4. 编译和构建

    1. 编译客户端:将插桩代码编译成动态链接库(DLL或SO),以便与DynamoRIO一起加载和执行;

    2. 构建环境设置:确保开发环境中包含了必要的编译器、库和头文件。

  5. 测试和验证

    1. 功能测试:运行被插桩的程序,验证插桩代码是否按预期工作;

    2. 性能测试:评估插桩对程序性能的影响,确保插桩代码不会导致显著的性能下降;

    3. 错误检测:监控程序执行中的错误和异常,确保插桩代码的健壮性。

  6. 部署和维护

    1. 在目标环境中部署被插桩的程序,并监控其运行状况;

    2. 持续维护:根据需求变化和程序更新,定期检查和更新插桩代码。

示例代码

在DynamoRIO中,基本块由指令列表(instrlist_t)组成,开发者可以通过回调函数和API函数来遍历和操作这些基本块。

以下是一个简单的示例,展示如何在DynamoRIO中定义基本块,并进行插桩:

#include "dr_api.h"  // 包含DynamoRIO的API
#include "drmgr.h"   // 包含DynamoRIO管理器的API,用于简化客户端的实现

// 退出事件的回调函数
static void event_exit(void) {
    drmgr_exit();
}

// 基本块生成和插桩回调函数
static dr_emit_flags_t event_bb_instrumentation(void *drcontext, void *tag,
                                                instrlist_t *bb, instr_t *instr,
                                                bool for_trace, bool translating,
                                                void *user_data) {
    instr_t *first_instr = instrlist_first(bb);
    
    // 在基本块的入口插入一个清除调用
    dr_insert_clean_call(drcontext, bb, first_instr, my_clean_call, false, 0);
    
    // 遍历基本块中的每个指令
    for (instr_t *instr = first_instr; instr != NULL; instr = instr_get_next(instr)) {
        if (is_target_instruction(instr)) {
            // 在特定指令之前插入代码
            dr_insert_clean_call(drcontext, bb, instr, my_instr_clean_call, false, 0);
        }
    }
    
    return DR_EMIT_DEFAULT;
}

// 客户端主函数
DR_EXPORT void dr_client_main(client_id_t id, int argc, const char *argv[]) {
    drmgr_init();
    dr_register_exit_event(event_exit);
    drmgr_register_bb_instrumentation_event(NULL, NULL, event_bb_instrumentation, NULL);
}

// 自定义清除调用函数
void my_clean_call(void) {
    // 插入的自定义逻辑
}

// 检查是否为目标指令
bool is_target_instruction(instr_t *instr) {
    // 检查指令类型或属性
    return true; // 示例,实际需要根据需要判断
}

// 自定义指令清除调用函数
void my_instr_clean_call(void) {
    // 指令级别的自定义逻辑
}

外部依赖项

  1. dr_api.h

    • DynamoRIO的核心API头文件,提供了与DynamoRIO框架交互的基础功能,如注册回调函数、访问基本块和指令列表等。

  2. drmgr.h

    • DynamoRIO提供的一个更高级的API,简化了客户端开发。dymgr(DynamoRIO Manager)提供了事件注册和管理功能,可以更方便地处理插桩和分析逻辑。

库文件

在编译DynamoRIO客户端时,需要链接以下库文件:

  1. DynamoRIO核心库

    • 对于32Bit应用程序,通常是dynamorio.lib(Windows)或libdynamorio.so(Linux);

    • 对于64Bit应用程序,通常是dynamorio64.lib(Windows)或libdynamorio64.so(Linux)。

  2. DynamoRIO管理器库

    • 对于使用drmgr的客户端,需要链接drmgr.lib(Windows)或libdrmgr.so(Linux)。

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值