目的:记录程序的分支跳转地址,比如:jump、call、ret等直接或间接、无条件或条件转移指令的跳转地址(从哪来,跳到哪去)
使用动态插装工具:PIN
分析:
首先,因为需要记录指令的地址,并且区分转移指令,因此需要对程序进行指令级插装。由PIN提供指令级插装API : INS_AddInstrumentFunction(func,0) 注册指令级插装回调函数func.
其次,并不是对全部指令进行插装,而是仅插装分支转移指令。故需要在插装例程中判断当前指令是否为分支转移指令或者Call指令(一般为函数调用)。PIN提供API:INS_IsBranchOrCall(ins),来判断当前指令ins是(true)否(false)为分支转移指令或者Call指令,并返回bool值。
然后,在判断插装点之后,则调用分析例程插装,记录当前指令地址以及跳转的目标地址。由PIN提供API:INS_InsertCall(INS ins,IPOINT action,AFUNPTRfuncptr...)来条用分析例程funcptr函数。因为我们仅记录分支跳转且跳转成功的指令地址,所以调用funcptr函数的条件即:当前转移指令跳转成功。PIN提供API: IPOINT_TAKEN_BRANCH,来判断一个分支指令是否跳转成功。由于要记录跳转地址的源地址和目的地址,所以需要通过INS_InsertCall向分析例程funcptr传递当前分支指令地址和分支目的地址。分别由枚举类型的IARG_TYPE中的IARG_INST_PTR、IARG_BRANCH_TARGET_ADDR指定。
最后,在分析例程funcptr中记录分支跳转指令地址(源地址+目的地址)。
按照以上分析思路,编写分支记录pintool:branch.cpp,如下:
#include<iostream>
#include<fstream>
#include<stdlib.h>
#include"pin.H"
using namespace std;
ofstream OutFile;
//指定输出文件
KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE,"pintool",
"o", "branch.out","specify output file name");
//分析例程
void recording(VOID*ip,ADDRINT addr)
{
OutFile<<hex<<"From:"<<ip<<" To:"<<(void*)addr<<endl;
}
//插装回调函数
void instruction(INSins,VOID *v)
{
//判断当前指令是否是转移指令或者Call调用指令
if(INS_IsBranchOrCall(ins))
{
INS_InsertCall(ins,IPOINT_TAKEN_BRANCH, (AFUNPTR)recording, IARG_INST_PTR,IARG_BRANCH_TARGET_ADDR,IARG_END);
}
}
VOID Fini(INT32 code,VOID *v)
{
OutFile.close();
}
int main(int argc,char * argv[])
{
PIN_InitSymbols();
PIN_Init(argc, argv);
OutFile.open(KnobOutputFile.Value().c_str());
INS_AddInstrumentFunction(instruction,0);
PIN_AddFiniFunction(Fini, 0);
PIN_StartProgram();
return 0;
}