pin学习记录

  1. 1. 实现原理
  2. 2. 安装配置
  3. 3. Pintool helloworld
  4. 4. 一些API
  5. 5. profiler
  6. 6. 参考

Pin是Intel维护的一款动态二进制插桩工具。

image-20220303155501823

实现原理

原理图如下:

参考 https://firmianay.gitbooks.io/ctf-all-in-one/content/doc/5.2.1_pin.html

img

Pin由进程级的虚拟机、代码缓存和提供给用户的插桩检测 API 组成。Pin 虚拟机包括 JIT(Just-In-Time) 编译器、模拟执行单元和代码调度三部分,其中核心部分为 JIT 编译器。

当 Pin 将待插桩程序加载并获得控制权之后,在调度器的协调下,JIT 编译器负责对二进制文件中的指令进行插桩,动态编译后的代码即包含用户定义的插桩代码。编译后的代码保存在代码缓存中,经调度后交付运行。

程序运行时,Pin 会拦截可执行代码的第一条指令,并为后续指令序列生成新的代码,新代码的生成即按照用户定义的插桩规则在原始指令的前后加入用户代码,通过这些代码可以抛出运行时的各种信息。然后将控制权交给新生成的指令序列,并在虚拟机中运行。当程序进入到新的分支时,Pin 重新获得控制权并为新分支的指令序列生成新的代码。

完整的Pin分为两个部分Pin和Pintool. 一个是Pin本身,包含VM和代码换成等,是Pin的核心. Pintool是用户实现和配置的代码.

安装配置

Pin下载解压即可使用:

1
2
wget https://software.intel.com/sites/landingpage/pintool/downloads/pin-3.7-97619-g0d0c92f4f-gcc-linux.tar.gz
tar -xf pin-3.7-97619-g0d0c92f4f-gcc-linux.tar.gz

环境变量配置: 修改/etc/profile,添加下面的配置,计算机重启后生效.

1
export PATH=$PATH:/p/pin-3.7-97619-g0d0c92f4f-gcc-linux/

usage:

  1. pin -t64 <64-bit toolname> -t <32-bit toolname> – <application>

  2. pin -t <32-bit toolname> – <application>

  3. pin -t64 <64-bit toolname> – <application>

toolname是所编写的pintool,so文件或者目标文件.

Pintool helloworld

如何新建一个Pintool项目?

首先在pintool的项目中加入如下的 makefile,

1
2
3
4
5
6
7
8
9
PIN_ROOT = /p/pin-3.7-97619-g0d0c92f4f-gcc-linux
ifdef PIN_ROOT
CONFIG_ROOT := $(PIN_ROOT)/source/tools/Config
else
CONFIG_ROOT := ../Config
endif
include $(CONFIG_ROOT)/makefile.config
include makefile.rules
include $(TOOLS_ROOT)/Config/makefile.default.rules

然后复制一份source/tools/ 下面项目的makefile.rules,并修改其中的TEST_TOOL_ROOTS TEST_ROOTS和TOOL_ROOTS ,如:

源代码是helloworld.cpp (

1
2
3
4
5
6
7
8
9
# Tests defined here should not be defined in TOOL_ROOTS and TEST_ROOTS.
TEST_TOOL_ROOTS := helloworld

# This defines the tests to be run that were not already defined in TEST_TOOL_ROOTS.
TEST_ROOTS := helloworld

# This defines the tools which will be run during the the tests, and were not already defined in
# TEST_TOOL_ROOTS.
TOOL_ROOTS := helloworld

然后编写pintool代码: 下面是helloworld.cpp 例程,统计指令数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <fstream>
#include "pin.H"
using namespace std;

ofstream OutFile;
KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool","o", "helloworld.out", "specify output file name");

UINT64 icount = 0;
// Analysis routines
void docount() {
icount++;
}

// Instrumentation routines
void Instruction(INS ins, void *v) // Pin Callback.
{
INS_InsertCall(ins, IPOINT_BEFORE,(AFUNPTR)docount, IARG_END);
}

void Fini(INT32 code, void *v)
{
cerr << "Count " << icount << endl;
OutFile.setf(ios::showbase);
OutFile << "Count " << icount << endl;
OutFile.close();
}


// 从main函数开始
int main(int argc, char * argv[]) {
PIN_InitSymbols(); // 初始化符号
PIN_Init(argc, argv); // init

OutFile.open(KnobOutputFile.Value().c_str());

INS_AddInstrumentFunction(Instruction, 0);// 添加插桩例程
PIN_AddFiniFunction(Fini, 0); // 析构函数

// start
PIN_StartProgram(); // Never returns
return 0;
}

Pintool也是从main函数开始,依次调用PIN_InitSymbols PIN_Init来进行初始化, PIN_InitSymblos会初始化二进制文件的调试信息,pdb,导出表等.

INS_AddInstrumentFunction是进行插桩的API, Instruction是插桩例程.

PIN_AddFiniFunction 是插入类似析构函数的callback函数,在二进制文件运行结束时调用.

PIN_StartProgram来启动pintool, 但是它并不会返回.

使用INS_InsertCall 在插桩例程中插入分析例程docount

整个程序会记录二进制文件中执行的指令的数量.

这里有几个坑:

  1. 使用cerr等输出并不会显示在终端上,所以代码中添加了OutFile来输出到文件.
1
2
ofstream OutFile;
KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool","o", "helloworld.out", "specify output file name");

KNOB类是专门用来设置命令行的类

  1. pintool必须是cpp文件

一些API

也是参考 ctf-all-in-one

注册不同粒度的回调函数:

TRACE(轨迹)粒度

  • TRACE 表示一个单入口、多出口的指令序列的数据结构。Pin 将 TRACE 分为若干基本块 BBL(Basic Block),一个 BLL 是一个单入口、单出口的指令序列。TRACE 在指令发生跳转时进行插入,进一步进行基本块分析,常用于记录程序执行序列。注册 TRACE 粒度插桩函数原型为:
1
TRACE_AddInstrumentFunction(TRACE_INSTRUMENT_CALLBACK fun, VOID *val)

IMG(镜像)粒度:

  • IMG 表示整个被加载进内存的二进制可执行模块(如可执行文件、动态链接库等)类型的数据结构。每次被插桩进程在执行过程中加载了镜像类型文件时,就会被当做 IMG 类型处理。注册插桩 IMG 粒度加载和卸载的函数原型:
1
2
IMG_AddInstrumentFunction(IMAGECALLBACK fun, VOID *v)
IMG_AddUnloadFunction(IMAGECALLBACK fun, VOID *v)

RTN(例程)粒度

  • RTN 代表了由面向过程程序语言编译器产生的函数/例成/过程。Pin 使用符号表来查找例程,即需要插入的位置,需要调用内置的初始化表函数 PIN_InitSymbols()。必须使用 PIN_InitSymbols 使得符号表信息可用。插桩 RTN 粒度函数原型:
1
RTN_AddInstrumentFunction(RTN_INSTRUMENT_CALLBACK fun, VOID *val)

INS(指令)粒度

  • INS 代表一条指令对应的数据结构,INS 是最小的粒度。INS 的代码插桩是在指令执行前、后插入附加代码,会导致程序执行缓慢。插桩 INS 粒度函数原型:
1
INS_AddInstrumentFunction(INS_INSTRUMENT_CALLBACK fun, VOID *val)

注册结束回调函数

  • 插桩程序运行结束时,可以调用结束函数来释放不再使用的资源,输出统计结果等。注册结束回调函数:
1
VOID PIN_AddFiniFunction(FINI_CALLBACK fun, VOID *val)

插桩函数:

  • INS

    • VOID LEVEL_PINCLIENT::INS_InsertCall(INS ins, IPOINT action, AFUNPTR funptr, ...)
  • RTN

    • VOID LEVEL_PINCLIENT::RTN_InsertCall(RTN rtn, IPOINT action, AFUNPTR funptr, ...)
  • TRACE

    • VOID LEVEL_PINCLIENT::TRACE_InsertCall(TRACE trace, IPOINT action, AFUNPTR funptr, ...)
  • BBL

    • VOID LEVEL_PINCLIENT::BBL_InsertCall(BBL bbl, IPOINT action, AFUNPTR funptr, ...)

    profiler

    profiler是二进制程序分析里面的一个示例,实现了控制流追踪,函数调用跟踪,系统调用跟踪等功能.下面是代码: 这里记录一下.

    profiler.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#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;


/*****************************************************************************
* Analysis functions *
*****************************************************************************/
// BBL计数
static void
count_bb_insns(UINT32 n)
{
insn_count += n;
}

// 控制流计数
static void
count_cflow(ADDRINT ip, ADDRINT target)
{
cflows[target][ip]++;
cflow_count++;
}

// 函数调用计数
static void
count_call(ADDRINT ip, ADDRINT target)
{
calls[target][ip]++;
call_count++;
}

// 系统调用计数
static void
log_syscall(THREADID tid, CONTEXT *ctxt, SYSCALL_STANDARD std, VOID *v)
{
syscalls[PIN_GetSyscallNumber(ctxt, std)]++;
syscall_count++;
}


/*****************************************************************************
* Instrumentation functions *
*****************************************************************************/
static void
instrument_bb(BBL bb)
{
BBL_InsertCall(
bb, IPOINT_ANYWHERE, (AFUNPTR)count_bb_insns,
IARG_UINT32, BBL_NumIns(bb),
IARG_END
);
}

// 实现基本块插桩
static void
instrument_trace(TRACE trace, void *v)
{
// 检查是否是主程序
IMG img = IMG_FindByAddress(TRACE_Address(trace));
if(!IMG_Valid(img) || !IMG_IsMainExecutable(img)) return;
// 遍历基本块 BBL
for(BBL bb = TRACE_BblHead(trace); BBL_Valid(bb); bb = BBL_Next(bb)) {
// 在基本块上实现插桩
instrument_bb(bb);
}
}

// 指令粒度插桩
static void
instrument_insn(INS ins, void *v)
{
if(!INS_IsBranchOrCall(ins)) return; // 判断是否是控制流转移指令

IMG img = IMG_FindByAddress(INS_Address(ins)); // 判断是否是主程序
if(!IMG_Valid(img) || !IMG_IsMainExecutable(img)) return;

// 在跳转边上插桩
INS_InsertPredicatedCall( // IPOINT_TAKEN_BRANCH 分支的转移边
ins, IPOINT_TAKEN_BRANCH, (AFUNPTR)count_cflow,
IARG_INST_PTR, IARG_BRANCH_TARGET_ADDR,
// 回调函数参数,回调时的指针指令值(IARG_INST_PTR),目标地址的值(IARG_FALLTHROUGH_ADDR)
IARG_END
);
// 显式检查是否为直行边
if(INS_HasFallThrough(ins)) {
INS_InsertPredicatedCall( // IPOINT_AFTER 直行边
ins, IPOINT_AFTER, (AFUNPTR)count_cflow,
IARG_INST_PTR, IARG_FALLTHROUGH_ADDR,
// IARG_FALLTHROUGH_ADDR 直行地址
IARG_END
);
}

// 函数调用判断
if(INS_IsCall(ins)) {
if(ProfileCalls.Value()) {
INS_InsertCall( // IPOINT_BEFORE 在插桩对象前
ins, IPOINT_BEFORE, (AFUNPTR)count_call,
IARG_INST_PTR, IARG_BRANCH_TARGET_ADDR,
IARG_END
);
}
}
}

// IMG粒度
static void
parse_funcsyms(IMG img, void *v)
{
if(!IMG_Valid(img)) return;

// 遍历节
for(SEC sec = IMG_SecHead(img); SEC_Valid(sec); sec = SEC_Next(sec)) {
// 遍历节中的函数
for(RTN rtn = SEC_RtnHead(sec); RTN_Valid(rtn); rtn = RTN_Next(rtn)) {
funcnames[RTN_Address(rtn)] = RTN_Name(rtn);
}
}
}


/*****************************************************************************
* Other functions *
*****************************************************************************/
static void
print_results(INT32 code, void *v)
{
ADDRINT ip, target;
unsigned long count;
std::map<ADDRINT, std::map<ADDRINT, unsigned long> >::iterator i;
std::map<ADDRINT, unsigned long>::iterator j;

printf("executed %lu instructions\n\n", insn_count);

printf("******* CONTROL TRANSFERS *******\n");
for(i = cflows.begin(); i != cflows.end(); i++) {
target = i->first;
for(j = i->second.begin(); j != i->second.end(); j++) {
ip = j->first;
count = j->second;
printf("0x%08jx <- 0x%08jx: %3lu (%0.2f%%)\n",
target, ip, count, (double)count/cflow_count*100.0);
}
}

if(!calls.empty()) {
printf("\n******* FUNCTION CALLS *******\n");
for(i = calls.begin(); i != calls.end(); i++) {
target = i->first;

for(j = i->second.begin(); j != i->second.end(); j++) {
ip = j->first;
count = j->second;
printf("[%-30s] 0x%08jx <- 0x%08jx: %3lu (%0.2f%%)\n",
funcnames[target].c_str(), target, ip, count, (double)count/call_count*100.0);
}
}
}

if(!syscalls.empty()) {
printf("\n******* SYSCALLS *******\n");
for(j = syscalls.begin(); j != syscalls.end(); j++) {
count = j->second;
printf("%3ju: %3lu (%0.2f%%)\n", j->first, count, (double)count/syscall_count*100.0);
}
}
}


static void
print_usage()
{
std::string help = KNOB_BASE::StringKnobSummary();

fprintf(stderr, "\nProfile call and jump targets\n");
fprintf(stderr, "%s\n", help.c_str());
}


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

控制直行边与跳转边:

image-20220303163114159

参考

Pin Tutorial https://www.intel.com/content/dam/develop/external/us/en/documents/cgo2013-256675.pdf

ctf-all-in-one https://firmianay.gitbooks.io/ctf-all-in-one/content/doc/5.2.1_pin.html#pintool-%E7%A4%BA%E4%BE%8B%E5%88%86%E6%9E%90

https://www.intel.com/content/www/us/en/developer/technical-library/programming-guides.html