首页
社区
课程
招聘
15
有毒的学Pin记录(一)
发表于: 2021-10-9 17:18 38048

有毒的学Pin记录(一)

2021-10-9 17:18
38048

目录

 

version: 3.20

前言

本系列主要是对Pin进行全面的学习,实现从小白到小菜。主要资料是以官方文档为主,然后辅助自己的实践,更新时间不定,看忙的程度吧。

 

前面两篇会先挑选官方文档中重要的部分汇总以下,后期可能会加上自己的开发吧。随缘~

 

当别人都要快的时候,你要慢下来。

1. Introduction

Pin 是一个动态二进制插桩工具,支持 Linux*, macOS* 和 Windows* 操作系统以及可执行程序。Pin可以通过pintools在程序运行期间动态地向可执行文件的任意位置插入任意代码(C/C++),也可以attach到一个正在运行的进程。

 

Pin 提供了丰富的 API,可以抽象出底层指令集特性,并允许将进程的寄存器数据等的上下文信息作为参数传递给注入的代码。Pin会自动存储和重置被注入代码覆盖的寄存器,以恢复程序的继续运行。对符号和调试信息也可以设置访问权限。

 

Pin内置了大量的样例插桩工具的源码,包括基本块分析其、缓存模拟器、指令跟踪生成器等,根据自己的实际需求进行自定义开发也十分方便。

2. Instrument with Pin

1. Pin

对 Pin 的一个最合适的理解是可以将 Pin 当作一个 JIT 编译器,只是它的输入不是字节码,而是可执行文件。Pin 会拦截可执行文件的第一条指令,然后对从该指令开始的后续的指令序列重新“compile”新的代码,然后控制权限转移到新生成的代码。生成的代码与原始代码几乎一致,但是 Pin 会保证在分支退出代码序列时重新获得控制权限。重新获得控制权后,Pin 会基于分支生成更多的代码,然后继续运行。Pin 将所有生成的代码都保存在内存中,这样可以实现代码重用。

 

在这种 JIT 模式下,执行的是生成的代码,原始代码仅作为参考。当生成代码时,Pin 会给到用户注入自己想执行的代码(插桩)的机会。

 

Pin 对所有实际执行的代码进行插桩,不管代码具体处于哪个 section 。虽然对于一些条件分支会存在异常,但是如果指令没有被执行过,就一定不会被插桩。

2. Pintools

在概念上,插桩主要包含两部分内容:

  • 插桩机制(instrumentation code)

    在什么位置插入什么样的代码

  • 分析代码(analysis code)

    在插桩点执行的代码

这两部分内容都通过 Pintool 这个可执行文件来实现。Pintool 可以看作是 Pin 中可以实现修改代码生成过程的插件。

 

Pintool 会向 Pin 注册插桩回调例程,每当需要生成新代码时, Pin 会调用这些回调例程。回调例程承担了检测插桩内容的作用,它会检查要生成的代码,检查其静态属性,并决定是否以及在何处注入对分析函数的调用。

 

分析功能收集有关应用程序的数据。Pin 确保根据需要保存和恢复整数和浮点寄存器状态,并允许将参数传递给函数。

 

Pintool 还可以为诸如线程创建或 fork 之类的事件注册通知回调例程,这些回调通常用于收集数据或工具初始化和清理。

 

因为 Pintool 采用的是类似插件的工作机制,所以必须运行在和 Pin 及插桩的可执行文件相同的地址空间内,所以 Pintool 可以访问可执行文件的全部数据,还会与可执行文件共享 fd 和其他进程信息。

 

在 Pintool 的开发过程中,分析代码的调优比插桩代码更重要,因为插桩代码只执行一次,但是分析代码会调用很多次。

3. Instrumentation Granularity

1. trace instrumentation

在一个代码序列第一次执行前进行插桩,这种粒度的插桩称为“trace instrumentation”。在这种模式下,Pintool 一次“trace”执行一次检查和插桩,“trace”是指从一个 branch 开始,以一个无条件跳转 branch 结束,包含 call 和 return。

 

Pin 会保证每个 trace 只有一个顶部入口,但是可能包含多个出口。如果一个分支连接到了一个 trace 的中间位置,Pin 会生成一个以该分支作为开始的新的 trace 。Pin 将 trace 切分成了基本块,每个基本块称为“BBL”,每个 BBL 是一个单一入口、单一出口的指令序列。如果有分支连接到了 BBL 的中间位置,会定义一个新的 BBL 。通常以 BBL 为单位插入分析调用,而不是对每个指令都插入,这样可以降低分析调用的性能消耗。trace instrumentation 通过 TRACE_AddInstrumentFunction API 调用。

 

因为 Pin 是在程序执行时动态发现程序的执行流,所以 BBL 的概念与传统的基本块的概念有所不同,说明如下:

1
2
3
4
5
6
7
8
swtich(i){
  case 4: total++;
  case 3: total++;
  case 2: total++;
  case 1: total++;
  case 0:
  default: break;
}

在 IA-32 架构下,会生成如下类似的指令:

1
2
3
4
5
6
7
8
.L7:
        addl    $1, -4(%ebp)
.L6:
        addl    $1, -4(%ebp)
.L5:
        addl    $1, -4(%ebp)
.L4:
        addl    $1, -4(%ebp)

传统基本块的计算方式是会把每个 addl 指令作为一个单独的指令基本块,但是对于 Pin 来说,随着执行不同的 switch cases,Pin 会在 .L7 作为入口(从 .L7 依次向下执行)的时候生成包含所有4个指令的 BBL,在 .L6 输入的时候生成包含3个指令的 BBL,依此类推。所以,在 Pin 的统计方式里,如果代码分支走到了 .L7 ,只会计算一个 Pin BBL,但是4个传统概念上的基本块都被执行了。

 

Pin 在遇到一些特殊指令的时候会直接作为 trace 的结束位置,生成一个 BBL, 比如 cpuid, popf 以及 REP 为前缀的指令。REP 为前缀的指令都被当作隐式循环处理,在处理完第一次的迭代后,后面的每次迭代都作为一个单独的 BBL ,因此这种情况下,会看到比传统基本块统计方式统计出更多的 BBL。

2. instruction instrumentation

Pintool 会在可执行文件的每条指令都进行插桩,这种模式使得开发者不必过多关注 trace 内部的迭代循环指令,因为如上面所说,包含循环的 trace 内部的特定的 BBL 和指令可能产生多次。instruction instrumentation 通过 INS_AddInstrumentFunction API 进行调用。

3. image instrumentation

通过“caching”插桩请求实现,会有额外的内存空间要求,属于一种“提前插桩”。image instrumentation 模式下,Pintool 在 IMG:Image Object第一次加载时,对整个 imgaes 进行检查和插桩, Pintool 可以遍历 image 的 sections:SEC:Section Object, 可以是 section 中的 routine:RTN:Routine,还可以是一个 routine 中的 instructions:INS。插入位置可以是例程或指令的前面或后面,都可以实现,使用的 API 为 IMG_AddInstrumentFunction

 

image instrumentation 需要有调试信息来确定 routine 的边界,所以在调用 PIN_Init 之前,需要先初始化符号信息 PIN_InitSysmbols

4. routine instrumentation

通过“caching”插桩请求实现,会有额外的内存空间要求,属于一种“提前插桩”。routine instrumentation 模式下,Pintool 在 image 首次加载时就对整个 routine 进行检查和插桩,对 routine 中的每条指令都可以插桩,但是没有充分的信息可以将指令划分为 BBL。插入位置可以是执行例程或指令的前后。这种模式其实更大程度上属于 image instrumentation 的替代方法,使用的 API 为 RTN_AddInstrumentFunction

 

需要注意的是,在 image 和 routine instrumentation 模式下,插桩时并不确定 routine 是否会被执行,但是通过识别 routine 的开始指令,可以遍历出执行过的 routine 的指令。

4. Symbols

Pin 通过symbol object 来访问函数名, 但是 symbol 对象只能提供程序中的函数符号相关的信息,对于数据符号之类的信息必须通过其他工具获取。

 

Windows下,可以通过 dbghelp.dll 文件获取,但是可能出现死锁问题;Linux下可以通过 libelf.solibdwarf.so 文件获取符号信息。

3. Examples

本章主要是通过运行一些 Pin 内置的样例 Pintool,来实际感受一下 Pin 的插桩过程。实践出真知。

1. Building the example tools

ia32 架构的样例:

1
2
$ cd source/tools/ManualExamples
$ make all TARGET=ia32

ia64 架构的样例:

1
2
$ cd source/tools/ManualExamples
$ make all TARGET=intel64

编译并运行某个样例:

1
2
$ cd source/tools/ManualExamples
$ make inscount0.test TARGET=intel64

编译某个样例但不运行:

1
2
3
$ cd source/tools/ManualExamples
$ make obj-intel64/inscount0.so TARGET=intel64
# $ make obj-ia32/inscount0.so TARGET=ia32

2. Simple Instruction Count (指令插桩)

功能:统计执行过的指令的总数。

 

运行和查看输出:

1
2
3
4
5
6
7
8
$ ../../../pin -t obj-intel64/inscount0.so -o inscount.out -- /bin/ls
Makefile          atrace.o     imageload.out  itrace      proccount
Makefile.example  imageload    inscount0      itrace.o    proccount.o
atrace            imageload.o  inscount0.o    itrace.out
$ cat inscount.out
Count 422838
 
# 输出文件存在默认名称,可以使用-o参数指定输出文件名。

原理:在每个指令前插入对 docount 的调用,并将结果保存在 inscount.out 文件中。

 

源码 source/tools/ManualExamples/inscount0.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
#include
#include
#include "pin.H"
 
using std::cerr;
using std::endl;
using std::ios;
using std::ofstream;
using std::string;
ofstream OutFile;
// The running count of instructions is kept here
// make it static to help the compiler optimize docount
static UINT64 icount = 0;
 
// 这里就是我们调用的桩代码
VOID docount() { icount++; }
 
// Pin calls this function every time a new instruction is encountered
// 遇到一条新指令,调用一次该函数
VOID Instruction(INS ins, VOID* v)
{
    // Insert a call to docount before every instruction, no arguments are passed
    // 指定调用的桩代码函数,执行插入操作,没有对桩代码函数进行传参
    INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);
}
 
// 处理输出文件,默认文件名为“inscount.out”
KNOB< string > KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool", "o", "inscount.out", "specify output file name");
 
// This function is called when the application exits
VOID Fini(INT32 code, VOID* v)
{
    // Write to a file since cout and cerr maybe closed by the application
    // 将输出保存到文件
    OutFile.setf(ios::showbase);
    OutFile << "Count " << icount << endl;
    OutFile.close();
}
 
/* ===================================================================== */
/* Print Help Message                                                 */
/* ===================================================================== */
INT32 Usage()
{
    cerr << "This tool counts the number of dynamic instructions executed" << endl;
    cerr << endl << KNOB_BASE::StringKnobSummary() << endl;
    return -1;
}
/* ===================================================================== */
/* Main                                                                  */
/* ===================================================================== */
/*   argc, argv are the entire command line: pin -t -- ...    */
/* ===================================================================== */
int main(int argc, char* argv[])
{
    // Initialize pin 初始化
    if (PIN_Init(argc, argv)) return Usage();
    OutFile.open(KnobOutputFile.Value().c_str());
 
    // Register Instruction to be called to instrument instructions
    // 注册插桩函数
    INS_AddInstrumentFunction(Instruction, 0);
 
    // Register Fini to be called when the application exits
    // 注册程序退出时的处理函数
    PIN_AddFiniFunction(Fini, 0);
 
    // Start the program, never returns
    // 开始执行
    PIN_StartProgram();
    return 0;
}

3. Instruction Address Trace(指令插桩)

功能:打印执行的指令的地址

 

运行和查看输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ ../../../pin -t obj-intel64/itrace.so -- /bin/ls
Makefile          atrace.o     imageload.out  itrace      proccount
Makefile.example  imageload    inscount0      itrace.o    proccount.o
atrace            imageload.o  inscount0.o    itrace.out
$ head itrace.out
0x40001e90
0x40001e91
0x40001ee4
0x40001ee5
0x40001ee7
0x40001ee8
0x40001ee9
0x40001eea
0x40001ef0
0x40001ee0
$

原理:在调用分析程序时,Pin 允许传递指令指针、寄存器当前值、内存操作的有效地址、常量等数据给分析程序。完整的可传递的参数的类型如下:IARG_TYPE。将指令计数程序中的参数更改为 INS_InsertCall 来传递即将执行的指令的地址,将 docount 更改为 printip 来打印指令的地址,最后将输出写入到文件 itrace.out 中。

 

源码`source/tools/ManualExamples/itrace.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
#include
#include "pin.H"
FILE* trace;
// 在每条指令执行前都会被调用,打印出当前指令的地址
VOID printip(VOID* ip) { fprintf(trace, "%p\n", ip); }
// 遇到一条新指令调用一次
VOID Instruction(INS ins, VOID* v)
{
    // 在每条指令前插入对 printip 函数的调用,并传递 ip 参数
    INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)printip, IARG_INST_PTR, IARG_END);
}
// 结束函数
VOID Fini(INT32 code, VOID* v)
{
    fprintf(trace, "#eof\n");
    fclose(trace);
}
/* ===================================================================== */
/* Print Help Message                                                    */
/* ===================================================================== */
INT32 Usage()
{
    PIN_ERROR("This Pintool prints the IPs of every instruction executed\n" + KNOB_BASE::StringKnobSummary() + "\n");
    return -1;
}
/* ===================================================================== */
/* Main                                                                  */
/* ===================================================================== */
int main(int argc, char* argv[])
{
    trace = fopen("itrace.out", "w");
    // 初始化
    if (PIN_Init(argc, argv)) return Usage();
    // 桩指令注册
    INS_AddInstrumentFunction(Instruction, 0);
    // 结束逻辑注册
    PIN_AddFiniFunction(Fini, 0);
    // 开始执行,不返回
    PIN_StartProgram();
    return 0;
}

4. Memory Reference Trace (指令插桩)

功能:内存引用追踪(只对读写内存的指令插桩)


[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!

最后于 2021-10-9 17:20 被有毒编辑 ,原因:
收藏
免费 15
支持
分享
赞赏记录
参与人
雪币
留言
时间
Jtian
+10
感谢你的积极参与,期待更多精彩内容!
2024-8-23 16:19
Youlor
感谢你的贡献,论坛因你而更加精彩!
2024-7-6 03:39
伟叔叔
为你点赞~
2023-3-18 05:59
PLEBFE
为你点赞~
2022-7-28 00:11
tank小王子
为你点赞~
2022-3-25 10:34
estream
为你点赞~
2022-2-17 19:56
无名侠
为你点赞~
2021-10-12 20:11
yichen115
为你点赞~
2021-10-11 14:05
MTRush
为你点赞~
2021-10-11 09:23
pureGavin
为你点赞~
2021-10-10 22:24
v0id_
为你点赞~
2021-10-9 18:58
pupububu8
为你点赞~
2021-10-9 18:47
Max_hhg
为你点赞~
2021-10-9 17:44
jmpcall
为你点赞~
2021-10-9 17:32
ogli324
为你点赞~
2021-10-9 17:20
最新回复 (8)
雪    币: 15619
活跃值: (16982)
能力值: (RANK:730 )
在线值:
发帖
回帖
粉丝
2
有些内容可能需要一定的前置知识,对于描述不清楚的地方,大家可以在评论区直接讨论。
2021-10-9 17:23
0
雪    币: 8511
活跃值: (5131)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
3
mark
2021-10-9 18:58
0
雪    币: 2121
活跃值: (7324)
能力值: ( LV11,RANK:180 )
在线值:
发帖
回帖
粉丝
4
2021-10-11 10:17
0
雪    币: 75
活跃值: (88)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
请问,第7个pintool:Procedure Instruction Count (Routine Instrumentation)中,为什么-- 后面跟的是/bin/grep proccount.cpp Makefile 而不是和前面的实例一样直接-- 可执行文件呢?/bin/grep proccount.cpp Makefile 怎么理解呢?
2022-3-25 10:02
0
雪    币: 15619
活跃值: (16982)
能力值: (RANK:730 )
在线值:
发帖
回帖
粉丝
6
estream 请问,第7个pintool:Procedure Instruction Count (Routine Instrumentation)中,为什么-- 后面跟的是/bin/grep proccount. ...
这个是为了执行一下 grep 这个程序,/bin/grep proccount.cpp Makefile 的意思就是在 Makefile 中查找 proccount.cpp 字符串。没有其他特殊的含义,只不过这里给 grep 程序传入了参数。
2022-3-25 16:23
0
雪    币: 75
活跃值: (88)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
有毒 这个是为了执行一下 grep 这个程序,/bin/grep proccount.cpp Makefile 的意思就是在 Makefile 中查找 proccount.cpp 字符串。没有其他特殊的含义 ...

好的,/bin/grep proccount.cpp Makefile 其实是一个需要输入的可执行文件,完全可以使用/bin/ls或其它不需要输入的可执行文件。

最后于 2022-3-28 11:01 被estream编辑 ,原因:
2022-3-25 21:56
0
雪    币: 677
活跃值: (1034)
能力值: ( LV4,RANK:42 )
在线值:
发帖
回帖
粉丝
8
mark
2022-4-2 14:07
0
雪    币: 631
活跃值: (3026)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
9
mark
2022-9-13 09:07
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册