目前有两类插桩平台:静态插桩(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)方法;