二进制插桩:静态插桩和动态intel pin插桩

发布时间:2023年12月22日

目前有两类插桩平台:静态插桩(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的官方介绍https://software.intel.com/sites/landingpage/pintool/docs/98749/Pin/doc/html/index.html

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;
}

示例代码:

IMG_AddInstrumentFunction可以获取目标程序全局变量

IMG_Name(img)文件名

RTN rtn = RTN_FindByName

RTN_Open(rtn);

RTN_InsertCall(rtn, IPOINT_BEFORE, (AFUNPTR)myFunc,

IARG_FUNCARG_ENTRYPOINT_VALUE, 0,

IARG_END);

插好IMG桩

TRACE级,统计指令

TRACE_AddInstrumentFunction(MyTraceInstrument, 0);

VOID TraceInstrument(TRACE trace, VOID *v) {

// INS_InsertVersionCase更新version时重新打桩,根据标志状态决定是否打桩

if (instrumentFlag == INSTRUMENT_START ||true) {

ADDRINT address = TRACE_Address(trace);

if ((address >= 目标最小地址 && address < 目标最大地址空间)) { //目标的代码段被trace

for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl);

bbl = BBL_Next(bbl)) {

BBL_InsertCall(bbl, IPOINT_ANYWHERE, (AFUNPTR)CountMyBblIns,

IARG_UINT32, BBL_NumIns(bbl), IARG_END);

}

}

}

}

static VOID CountMyBblIns(UINT32 numIns) { insCount += numIns; }

for (SEC sec = IMG_SecHead(img); SEC_Valid(sec); sec = SEC_Next(sec)) {

目标最小地址?= SEC_Address(sec);

目标最大地址空间 = 目标最小地址 + SEC_Size(sec);

实例二:

根据TRACE级别,判断分支语句 的文件名,行号,指令名

TRACE_AddInstrumentFunction(TranceMyBranchMainProc, 0);

VOID TranceBranchMyMainProc(TRACE trace, VOID *v)

{

// INS_InsertVersionCase更新version时重新打桩,根据标志状态决定是否打桩

for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))

{

TraceMyBranchInst(&bbl);

}

}

static VOID TraceMyBranchInst(BBL *bbl)

{

for(INS ins= BBL_InsHead(*bbl); INS_Valid(ins); ins = INS_Next(ins))

{

if (INS_IsBranch(ins) )

{

ADDRINT instAddr = INS_Address(ins);? ?//遍历bbl中的每条指令,如果是分支指令则处理

std::string funcName= RTN_FindNameByAddress(instAddr);

}

实例三:pin提供了二进制反汇编库

extras/xed-intel64

assembleStr返回反汇编字符串

static void myBinDisassemble(int start, int stop, char *assembleStr)

{

int pc = start;

xed_state_t myDstate;

xed_syntax_enum_t mySyntax = XED_SYNTAX_INTEL;

xed_decoded_inst_t myXedd;

if (sizeof(ADDRINT) == 4)

{

xed_state_init(&myDstate, XED_MACHINE_MODE_LEGACY_32, XED_ADDRESS_WIDTH_32b, XED_ADDRESS_WIDTH_32b);

}else

{

xed_state_init(&myDstate, XED_MACHINE_MODE_LONG_64, XED_ADDRESS_WIDTH_64b, XED_ADDRESS_WIDTH_64b);

}

xed_decoded_inst_zero_set_mode(&myXedd, &myDstate);

int len = 15;

if (stop - pc < 15) len = stop-pc;

if (XED_ERROR_NONE == xed_decode(&myXedd, reinterpret_cast<const UINT8*>(pc), len))

{

xed_decoded_inst_get_length(&myXedd);

xed_format_context(mySyntax, &myXedd, instBuffer, DSIASSEMBLE_STR_SIZE, pc, 0, 0);

}

}

预测指令(predicated?instructions)

Intel Pin 工具生成分支列表

  1. 所有条件分支的地址

  2. 目标

  3. taken/not taken

1/2。这可以通过使用指令级检测来实现。使用?INS_AddInstrumentFunction(Instruction, 0)为了允许Instruction(INS ins, VOID *v)每次要执行一条新指令时插入调用。?Instruction()功能,使用if(INS_IsBranch(ins) && INS_HasFallThrough(ins))判断当前指令是否为条件分支的表达式。如果是,存储它的地址?INS_Address(ins)连同它的目标?INS_DirectBranchOrCallTargetAddress(ins)?.也许您可以出于调试目的打印其反汇编?INS_Disassemble(ins)?.

3.为了打印出决策,您必须在每个条件分支之前插入一个分析例程。使用上面的 Instruction 函数,在?if(INS_IsBranch(ins) && INS_HasFallThrough(ins))?中,使用此 API 调用:

INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)<YOUR FUNCTION NAME>, IARG_INST_PTR, IARG_BRANCH_TAKEN, IARG_END)

使用它,您可以创建一个分析例程,该例程将在每次动态执行条件分支时运行。从那里使用 IARG_BRANCH_TAKEN 参数,你可以做一个简单的检查来确定分支是否被采用。将决定存储在 map 或类似 map 的东西中,以便稍后打印出来。警告:不要在分析例程中打印出任何东西(打印出指令的动态轨迹从来都不是一个好主意)。另请注意,条件分支可能会针对不同的已token/not token决定运行不止一次,因此您可能需要跟踪多个决定。

二、SBI二进制静态插桩

SBI对二进制程序进行反汇编,然后按需添加插桩代码并将更新的二进制程序存入磁盘。SBI平台包括PEBIL和Dyninst,都是研究工具,没有详细的文档。SBI主要挑战是,在不破坏任何现有代码和数据引用的前提下,添加插桩代码并重写二进制程序。目前有两种流行的解决方法:

1.int3方法;
2.跳板(trampoline)方法;

文章来源:https://blog.csdn.net/feelinglikeyou/article/details/134527037
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。