Intel Pin根据颗粒度可以划分为指令级插桩(INS)、基本块级插桩(TRACE)、函数级插桩(RTN)、镜像级插桩(IMG)。
基础函数
PIN_InitSymbols函数
用于初始化程序的符号表,对于RTN和IMG两个颗粒度的插桩分析时这个函数不可缺少。
Fini函数
PIN_AddFiniFunction(Fini, 0);
VOID Fini(INT32 code, VOID* v)
{
fprintf(trace, "#eof\n");
fclose(trace);
}
Usage函数
if (PIN_Init(argc, argv)) return Usage();
INT32 Usage()
{
PIN_ERROR("This Pintool prints the IPs of every instruction executed\n" + KNOB_BASE::StringKnobSummary() + "\n");
return -1;
}
Knob类
KNOB< string > KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool", "o", "inscount.out", "specify output file name");
分析函数的参数
字段 | 作用及意义 |
---|---|
IARG_INST_PTR | 指令的地址 |
IARG_REG_VALUE | 寄存器的值 |
IARG_CONTEXT | |
IARG_FUNCARG_ENTRYPOINT_VALUE | 传递给RTN的参数,需要额外参数表示RTN的第几个参数,第一个参数从0开始 |
IARG_FUNCRET_EXITPOINT_VALUE | RTN的返回值 |
分析函数的参数
https://software.intel.com/sites/landingpage/pintool/docs/98650/Pin/doc/html/group__INST__ARGS.html#gga089c27ca15e9ff139dd3a3f8a6f8451da623ad95758bce14fcb9427beef53736a
插入点的位置
插入点 | 分析回调 | 有效性 |
---|---|---|
IPOINT_BEFORE | 在插桩对象之前 | 总是有效 |
IPOINT_AFTER | 在直行边(分支或“规则的”指令) | INS_HasFallthrough为真则有效 |
IPOINT_ANYWHERE | 在插桩对象的任何位置 | 只对TRACE或BBL有效 |
IPOINT_TAKEN_BRANCH | 在分支的转移边 | INS_IsBranchOrCall为真则有效 |
编写Pintool常用的语法
BBL遍历
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
BBL_InsertCall(bbl, IPOINT_BEFORE, (AFUNPTR)docount, IARG_UINT32, BBL_NumIns(bbl), IARG_END);
}
指令类型判断
// 查找将值从内存移动到寄存器的指令
例如:Mov eax,[0x77d154h]
if ( INS_Opcode (ins) == XED_ICLASS_MOV && INS_IsMemoryRead (ins) && INS_OperandIsReg (ins, 0) && INS_OperandIsMemory (ins, 1))
INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(DoLoad), IARG_UINT32, REG(INS_OperandReg(ins, 0)), IARG_MEMORYREAD_EA,IARG_RETURN_REGS, INS_OperandReg(ins, 0), IARG_END);
首先if语句的判断确定这必须是一条mov寄存器,内存这样的地址。因此IARG_MEMORYREAD_EA获取内存的地址,然后使用IARG_RETURN_REGS指定返回值保存位置,也就是当前指令的第一个寄存器,其中INS_OpeandReg(ins,0)能够获取指令的第0个操作数(也就是寄存器)。
获取malloc函数在IMG中的函数映射地址
RTN mallocRtn = RTN_FindByName(img, MALLOC);
如果在多线程应用程序上运行时,pintool在回调中打开文件,则可能会发生死锁。要解决此问题,请在 main中打开一个文件,并使用线程 ID 标记数据。请参阅 source/tools/ManualExamples/buffer_windows.cpp 作为示例。
修改原始指令
INS_Delete()删除原始指令;
插入直接或间接分支(使用INS_InsertDirectJump和INS_InsertIndirectJump);
这样可以更轻松地模拟更改控制流的指令。
dump所有nop指令的地址及汇编指令反汇编
VOID dump_ins(UINT64 insAddr, std::string insDis) {
printf("%lx\t%s\n", insAddr, insDis.c_str());
}
if (INS_IsNop(ins)) {
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)dump_ins, IARG_ADDRINT,
INS_Address(ins), IARG_PTR, new string(INS_Disassemble(ins)),
IARG_END);
}
一个C++程序执行完毕大概是12亿左右条指令。
跟踪“CALL 0”和“JMP 0”指令
VOID CALL_reg(ADDRINT ip, ADDRINT op, THREADID threadid)
{
PIN_GetLock(&lock, threadid+1);
log_info (ip, "CALL", op);
PIN_ReleaseLock(&lock);
}
VOID CALL_mem(ADDRINT ip, ADDRINT *op_addr, unsigned int op_size, THREADID threadid)
{
PIN_GetLock(&lock, threadid+1);
ADDRINT op;
PIN_SafeCopy(&op, op_addr, op_size);
log_info (ip, "CALL", op);
PIN_ReleaseLock(&lock);
};
...
// this function executed only during startup, so no need to optimize anything here:
VOID Instrument_all(INS ins, VOID* v)
{
// CALL reg
// N.B.: this doesn't work: if ((INS_Mnemonic(ins) == "CALL") && (INS_OperandIsReg(ins, 0)))
if (INS_IsCall(ins) && (INS_OperandIsReg(ins, 0)))
{
INS_InsertCall(ins,
IPOINT_BEFORE,
AFUNPTR(CALL_reg),
IARG_INST_PTR,
IARG_REG_VALUE, REG(INS_OperandReg(ins, 0)),
IARG_THREAD_ID,
IARG_END);
}
// CALL mem
if (INS_IsCall(ins) && INS_OperandIsMemory(ins, 0))
{
INS_InsertCall(ins,
IPOINT_BEFORE,
AFUNPTR(CALL_mem),
IARG_INST_PTR,
IARG_MEMORYREAD_EA,
IARG_MEMORYREAD_SIZE,
IARG_THREAD_ID,
IARG_END);
}
官方提供Pintool及其作用
Malloctrace.cpp | 跟踪传递给malloc函数的参数值或返回值 |
Imageload.cpp | 加载模块及image详情 |
Proccount.cpp | 函数过程调用及次数统计 |
参考链接
https://software.intel.com/sites/landingpage/pintool/docs/98650/Pin/doc/html/index.html#WindowsNotes