Pin是Intel维护的一款动态二进制插桩工具。
实现原理
原理图如下:
参考 https://firmianay.gitbooks.io/ctf-all-in-one/content/doc/5.2.1_pin.html
Pin由进程级的虚拟机、代码缓存和提供给用户的插桩检测 API 组成。Pin 虚拟机包括 JIT(Just-In-Time) 编译器、模拟执行单元和代码调度三部分,其中核心部分为 JIT 编译器。
当 Pin 将待插桩程序加载并获得控制权之后,在调度器的协调下,JIT 编译器负责对二进制文件中的指令进行插桩,动态编译后的代码即包含用户定义的插桩代码。编译后的代码保存在代码缓存中,经调度后交付运行。
程序运行时,Pin 会拦截可执行代码的第一条指令,并为后续指令序列生成新的代码,新代码的生成即按照用户定义的插桩规则在原始指令的前后加入用户代码,通过这些代码可以抛出运行时的各种信息。然后将控制权交给新生成的指令序列,并在虚拟机中运行。当程序进入到新的分支时,Pin 重新获得控制权并为新分支的指令序列生成新的代码。
完整的Pin分为两个部分Pin和Pintool. 一个是Pin本身,包含VM和代码换成等,是Pin的核心. Pintool是用户实现和配置的代码.
安装配置
Pin下载解压即可使用:
1 | wget https://software.intel.com/sites/landingpage/pintool/downloads/pin-3.7-97619-g0d0c92f4f-gcc-linux.tar.gz |
环境变量配置: 修改/etc/profile,添加下面的配置,计算机重启后生效.
1 | export PATH=$PATH:/p/pin-3.7-97619-g0d0c92f4f-gcc-linux/ |
usage:
pin -t64 <64-bit toolname> -t <32-bit toolname> – <application>
pin -t <32-bit toolname> – <application>
pin -t64 <64-bit toolname> – <application>
toolname是所编写的pintool,so文件或者目标文件.
Pintool helloworld
如何新建一个Pintool项目?
首先在pintool的项目中加入如下的 makefile,
1 | PIN_ROOT = /p/pin-3.7-97619-g0d0c92f4f-gcc-linux |
然后复制一份source/tools/ 下面项目的makefile.rules,并修改其中的TEST_TOOL_ROOTS TEST_ROOTS和TOOL_ROOTS ,如:
源代码是helloworld.cpp (
1 | # Tests defined here should not be defined in TOOL_ROOTS and TEST_ROOTS. |
然后编写pintool代码: 下面是helloworld.cpp 例程,统计指令数量
1 |
|
Pintool也是从main函数开始,依次调用PIN_InitSymbols
PIN_Init
来进行初始化, PIN_InitSymblos会初始化二进制文件的调试信息,pdb,导出表等.
INS_AddInstrumentFunction
是进行插桩的API, Instruction
是插桩例程.
PIN_AddFiniFunction
是插入类似析构函数的callback函数,在二进制文件运行结束时调用.
PIN_StartProgram
来启动pintool, 但是它并不会返回.
使用INS_InsertCall
在插桩例程中插入分析例程docount
整个程序会记录二进制文件中执行的指令的数量.
这里有几个坑:
- 使用
cerr
等输出并不会显示在终端上,所以代码中添加了OutFile
来输出到文件.
1 | ofstream OutFile; |
KNOB类是专门用来设置命令行的类
- 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 | IMG_AddInstrumentFunction(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 |
|
控制直行边与跳转边:
参考
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