目前有两类插桩平台:静态插桩(SBI)和动态插桩(DBI)
- SBI使用二进制重写方法永久修改磁盘上的二进制文件;
- DBI不会修改磁盘上的二进制程序,而是监视二进制程序的执行状态,并在其运行时将新指令插入指令流中
一、DBI动态intel pin插桩
https://www.cnblogs.com/level5uiharu/p/16963907.html
intel Pin简要介绍及示例程序-CSDN博客
Pin可以被看做一个即时JIT编译器(Just in Time)。它可以程序运行时拦截常规可执行文件的指令,并在指令执行前生成新的代码,然后去执行生成的新的代码,并在新的代码执行完成后,将控制权交给被拦截的指令。Pin不开源,Pin的DBI引擎和Pintool都运行于用户空间,因此只能插桩用户空间进程。
intel Pin的官方介绍Pin: Pin 3.21 User Guide (intel.com)
intel Pin的API文档Pin: API Reference (intel.com)
intel Pin的下载地址Pin - A Dynamic Binary Instrumentation Tool (intel.com)
步骤:
1. 命令行选项和数据结构
Pintool可以实现特定工具的命令行选项,这些选项在Pin术语中称为开关(knob),PinAPI中包括一个专用的KNOB类,用于创建命令行选项。在下图代码中,有两个布尔选项(KNOB<bool>),分别是ProfileCalls和ProfileSyscalls,可通过将-c标志传递给Pintool来启用ProfileCalls选项,并通过传递-s标志启用ProfileSyscalls选项。
KNOB<bool> ProfileCalls(KNOB_MODE_WRITEONCE, "pintool", "c", "0", "Profile function calls");
KNOB<bool> ProfileSyscalls(KNOB_MODE_WRITEONCE, "pintool", "s", "0", "Profile syscalls");
2. 初始化pin
从main函数开始,调用的第一个Pin函数是PIN_InitSymbols,该函数表示Pin读取应用程序的符号表,接下来调用PIN_Init函数来初始化Pin
3. 注册插桩例程
Profiler需要注册3个插桩例程,其中第一个是parse_funcsyms,进行img粒度的插桩,另外两个为instrument_trace和instrument_insn,分别进行踪迹和指令粒度的插桩。
IMG_AddInstrumentFunction(parse_funcsyms, NULL);
INS_AddInstrumentFunction(instrument_insn, NULL);
TRACE_AddInstrumentFunction(instrument_trace, NULL);
4. 注册系统调用入口函数接口
Profiler使用PIN_AddSyscallEntryFunction函数注册一个名为log_syscall的函数
PIN_AddSyscallEntryFunction(log_syscall, NULL);
5. 注册fini函数
Profiler注册的最后一个回调函数是fini函数,该函数在应用程序退出时或者Pin从程序分离时被调用
6. 启动应用程序
每个Pintool初始化的最后一步都是调用了PIN_StartProgram函数来启动应用程序。
PIN_StartProgram();
7. 测试
命令行中的-c -s 用于打开函数调用和进行系统调用分析
Profiler输出关于执行指令的数量、控制转移、函数调用和系统调用的统计分析
接下来,就上述TRACE_AddInstrumentFunction插桩例程为例进行介绍,该插桩例程的第一个函数instrument_trace代表Profiler注册的踪迹粒度trace的插桩例程。以下是详细代码:
首先,instrument_trace函数使用路径的地址调用IMG_FindByAddress函数查找踪迹所属的IMG;
接下来,验证IMG是否有效且检查路径是否为主应用程序,若不是,则不插桩。因为当评测应用程序时,通常希望只计算应用程序内部的代码,而不是共享库或者动态加载器中的代码;
如果trace是有效的并且为主应用程序,那么instrument_trace循环遍历路径中的所有基本快BBL,并对每个BBL调用instrument_bb函数,该函数对BBL执行实际的插桩;
instrument_bb函数通过调用BBL_InsertCall函数对给定的BBL进行插桩
BBL_InsertCall使用分析例程来插桩基本块的Pin API函数,需接收3个必需的参数:待插桩的基本块(本例中是bb)、IPOINT_ANYWHERE)及指向待添加的分析例程的函数指针(count_bb_insns)
最终结果是,Pin用指向count_bb_insns的回调对主应用程序的实际执行的bb块进行插桩,count_bb_insns为profiler的指令计数器加上每个基本块中指令的数量。
程序代码:
static void
instrument_trace(TRACE trace, void *v)
{
IMG img = IMG_FindByAddress(TRACE_Address(trace));
if(!IMG_Valid(img) || !IMG_IsMainExecutable(img)) return;
for(BBL bb = TRACE_BblHead(trace); BBL_Valid(bb); bb = BBL_Next(bb)) {
instrument_bb(bb);
}
}
static void
instrument_bb(BBL bb)
{
BBL_InsertCall(
bb, IPOINT_ANYWHERE, (AFUNPTR)count_bb_insns,
IARG_UINT32, BBL_NumIns(bb),
IARG_END
);
}
static void
count_bb_insns(UINT32 n)
{
insn_count += n;
}
#include <stdio.h>
#include <map>
#include <string>
#include <asm-generic/unistd.h>
#include "pin.H"
KNOB<bool> ProfileCalls(KNOB_MODE_WRITEONCE, "pintool", "c", "0", "Profile function calls");
KNOB<bool> ProfileSyscalls(KNOB_MODE_WRITEONCE, "pintool", "s", "0", "Profile syscalls");
std::map<ADDRINT, std::map<ADDRINT, unsigned long> > cflows;
std::map<ADDRINT, std::map<ADDRINT, unsigned long> > calls;
std::map<ADDRINT, unsigned long> syscalls;
std::map<ADDRINT, std::string> funcnames;
unsigned long insn_count = 0;
unsigned long cflow_count = 0;
unsigned long call_count = 0;
unsigned long syscall_count = 0;
int
main(int argc, char *argv[])
{
PIN_InitSymbols();
if(PIN_Init(argc,argv)) {
print_usage();
return 1;
}
IMG_AddInstrumentFunction(parse_funcsyms, NULL);
INS_AddInstrumentFunction(instrument_insn, NULL);
TRACE_AddInstrumentFunction(instrument_trace, NULL);
if(ProfileSyscalls.Value()) {
PIN_AddSyscallEntryFunction(log_syscall, NULL);
}
PIN_AddFiniFunction(print_results, NULL);
/* Never returns */
PIN_StartProgram();
return 0;
}
二、SBI二进制静态插桩
SBI对二进制程序进行反汇编,然后按需添加插桩代码并将更新的二进制程序存入磁盘。SBI平台包括PEBIL和Dyninst,都是研究工具,没有详细的文档。SBI主要挑战是,在不破坏任何现有代码和数据引用的前提下,添加插桩代码并重写二进制程序。目前有两种流行的解决方法:
1.int3方法;
2.跳板(trampoline)方法;