背景说明
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,1
,ADD EAX,2
和CMP EAX,5
。如果EAX
等于5,程序会跳转到基本块2。 -
基本块2:包含指令
SUB EAX,1
。无条件跳转指令JMP block3
是基本块2的出口。 -
基本块3:包含指令
MOV EBX,EAX
和ADD EBX,4
。这个基本块没有跳转指令,因此它的出口是程序的自然结束点。
-
-
-
-
插桩(Instrumentation)
-
指的是在基本块或其他代码段中插入自定义代码,以实现监控或修改程序行为的目的。
-
-
回调函数
-
由DynamoRIO在特定事件发生时调用的函数;
-
常见的回调函数包括基本块生成、指令执行等。
-
基本组成
此插桩平台主要包含以下内容:
-
DynamoRIO:负责解释并执行目标程序;提供丰富的跨平台API接口;
-
Client:通过API自定义分析操作来拓展DynamoRIO;
-
DynamoRIO Extensions:主要指drmgr、drsyms、drwrap等官方提供的接口。
DynamoRIO中有一个重要的概念,事件。
-
应用程序事件:应用程序在动态执行时的事件,包括进程创建、模块加载、系统调用等;
-
DynamoRIO事件:包括基本块、轨迹流的创建等;
-
事件回调函数的注册:
dr_register_xx_event
、dr_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环境
-
下载和安装DynamoRIO
-
从DynamoRIO官网下载适用于Windows环境的版本;
-
解压缩至一个目录,例如
C:\dynamorio
。
-
-
设置环境变量
-
打开命令提示符(或PowerShell);
-
设置
DYNAMORIO_HOME
环境变量;set DYNAMORIO_HOME=C:\dynamorio
-
将DynamoRIO的bin32或bin64目录添加到
PATH
环境变量。set PATH=%DYNAMORIO_HOME%\bin64;%PATH%
-
-
编译客户端
-
打开Visual Studio开发者命令提示符;
-
编译客户端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"
-
-
运行客户端
drrun -c basic_count.dll -- your_application.exe
Linux环境
-
下载和安装DynamoRIO
-
从DynamoRIO官网下载适用于Linux环境的版本;
-
解压缩至一个目录,例如
/opt/dynamorio
。
-
-
设置环境变量
-
在终端中设置DYNAMORIO_HOME环境变量:
export DYNAMORIO_HOME=/opt/dynamorio
-
将DynamoRIO的bin64路径添加到PATH环境变量。
export PATH=$DYNAMORIO_HOME/bin64:$PATH
-
-
编译客户端
使用gcc编译(建设客户端代码文件名为
basic_count.c
):gcc -shared -o basic_count.so basic_count.c -I$DYNAMORIO_HOME/include -L$DYNAMORIO_HOME/lib64/release -ldrmgr -ldynamorio
-
运行客户端
drrun -c basic_count.so -- your_application
系统化的流程
使用DynamoRIO进行程序监控和修改时,需要一个系统化的流程来确保插桩代码的正确性和适用性。这涉及对目标程序的了解、插桩代码的设计和实现,以及对插桩效果的测试与验证。
以下是一个典型的流程,包括各个步骤的详细说明:
-
了解目标程序
-
分析需求:明确需要监控或修改的目标,包括性能指标、安全检查、功能检测等;
-
程序结构理解:熟悉程序的控制流或数据流,找出可能需要插桩的位置,例如特定的函数、基本块或指令。
-
-
设计插桩逻辑
-
确定插桩点:根据需求选择合适的插桩点。例如,在函数入口或出口处插桩,或者在特定的条件分支中插桩;
-
定义插桩内容:决定在插桩点插入哪些代码,如日志记录、性能计数器、安全逻辑检查等。
-
-
实现插桩代码
-
使用DynamoRIO API:利用DynamoRIO 提供的API来实现插桩逻辑。需要注册回调函数,以便在程序执行过程中调用自定义的插桩代码;
-
编写回调函数:这些函数在基本块或特定指令执行时被调用。开发者可以使用这些回调函数中使用
dr_insert_clean_all
等API插入自定义代码。
-
-
编译和构建
-
编译客户端:将插桩代码编译成动态链接库(DLL或SO),以便与DynamoRIO一起加载和执行;
-
构建环境设置:确保开发环境中包含了必要的编译器、库和头文件。
-
-
测试和验证
-
功能测试:运行被插桩的程序,验证插桩代码是否按预期工作;
-
性能测试:评估插桩对程序性能的影响,确保插桩代码不会导致显著的性能下降;
-
错误检测:监控程序执行中的错误和异常,确保插桩代码的健壮性。
-
-
部署和维护
-
在目标环境中部署被插桩的程序,并监控其运行状况;
-
持续维护:根据需求变化和程序更新,定期检查和更新插桩代码。
-
示例代码
在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) {
// 指令级别的自定义逻辑
}
外部依赖项
-
dr_api.h
-
DynamoRIO的核心API头文件,提供了与DynamoRIO框架交互的基础功能,如注册回调函数、访问基本块和指令列表等。
-
-
drmgr.h
-
DynamoRIO提供的一个更高级的API,简化了客户端开发。dymgr(DynamoRIO Manager)提供了事件注册和管理功能,可以更方便地处理插桩和分析逻辑。
-
库文件
在编译DynamoRIO客户端时,需要链接以下库文件:
-
DynamoRIO核心库
-
对于32Bit应用程序,通常是
dynamorio.lib
(Windows)或libdynamorio.so
(Linux); -
对于64Bit应用程序,通常是
dynamorio64.lib
(Windows)或libdynamorio64.so
(Linux)。
-
-
DynamoRIO管理器库
-
对于使用drmgr的客户端,需要链接
drmgr.lib
(Windows)或libdrmgr.so
(Linux)。
-