diff --git a/doc/5.2_pin.md b/doc/5.2_pin.md index 91c5662..3bfb684 100644 --- a/doc/5.2_pin.md +++ b/doc/5.2_pin.md @@ -1,11 +1,11 @@ # Pin 动态二进制插桩 - [插桩技术](#插桩技术) -- [Pin 简介](#Pin-简介) -- [Pin 的基本用法](#Pin-的基本用法) -- [Pintool 示例分析](#Pintool-示例分析) -- [Pintool 编写](#Pintool-编写) -- [Pin 在 CTF 中的应用](#Pin-在-CTF-中的应用) +- [Pin 简介](#pin-简介) +- [Pin 的基本用法](#pin-的基本用法) +- [Pintool 示例分析](#pintool-示例分析) +- [Pintool 编写](#pintool-编写) +- [Pin 在 CTF 中的应用](#pin-在-ctf-中的应用) ## 插桩技术 @@ -140,7 +140,7 @@ Pin 是一个闭源的框架,由 Pin 和 Pintool 组成。Pin 内部提供 API 程序运行时,Pin 会拦截可执行代码的第一条指令,并为后续指令序列生成新的代码,新代码的生成即按照用户定义的插桩规则在原始指令的前后加入用户代码,通过这些代码可以抛出运行时的各种信息。然后将控制权交给新生成的指令序列,并在虚拟机中运行。当程序进入到新的分支时,Pin 重新获得控制权并为新分支的指令序列生成新的代码。 -通常 Pintool 中包含了两部分: +通常插桩需要的两个组件都在 Pintool 中: - 插桩代码(Instrumentation code) - 在什么位置插入插桩代码 - 分析代码(Analysis code) @@ -148,16 +148,12 @@ Pin 是一个闭源的框架,由 Pin 和 Pintool 组成。Pin 内部提供 API Pintool 采用向 Pin 注册插桩回调函数的方式,对每一个被插桩的代码段,Pin 调用相应的插桩回调函数,观察需要产生的代码,检查它的静态属性,并决定是否需要以及插入分析函数的位置。分析函数会得到插桩函数传入的寄存器状态、内存读写地址、指令对象、指令类型等参数。 +- **Instrumentation routines**:仅当事件第一次发生时被调用 +- **Analysis routines**:某对象每次被访问时都调用 +- **Callbacks**:无论何时当特定事件发生时都调用 + ## Pin 的基本用法 -Pin 的三种插桩模式: -- Instruction level(Ins) - - 在每一条原始程序指令前(或后)插入自己的代码,即调用这个函数。 -- Function level(RTN) - - Pin 通过符号表信息来找到这些需要插入的位置,要使用这种模式的代码插入,首先得调用 pin 内置的初始化符号表函数,即 `PIN_InitSymbols()` -- Basic block level(BBL) - - 基本调用块级别的插入模式,只在 trace 的时候可用。 - 在 Pin 解压后的目录下,编译一个 Pintool,首先在 `source/tools/` 目录中创建文件夹 `MyPintools`,将 `mypintool.cpp` 复制到 `source/tools/MyPintools` 目录下,然后 `make`: ``` $ cp mypintools.cpp source/tools/MyPintools @@ -265,5 +261,65 @@ int main(int argc, char * argv[]) ## Pintool 编写 +#### main 函数的编写 +Pintool 的入口为 `main` 函数,通常需要完成下面的功能: +- 初始化 Pin 系统环境: + - `BOOL LEVEL_PINCLIENT::PIN_Init(INT32 argc, CHAR** argv)` +- 初始化符号表(如果需要调用程序符号信息,通常是指令粒度以上): + - `VOID LEVEL_PINCLIENT::PIN_InitSymbols()` +- 初始化同步变量: + - Pin 提供了自己的锁和线程管理 API 给 Pintool 使用。当 Pintool 对多线程程序进行二进制检测,需要用到全局变量时,需要利用 Pin 提供的锁(Lock)机制,使得全局变量的访问互斥。编写时在全局变量中声明锁变量并在 `main` 函数中对锁进行初始化:`VOID LEVEL_BASE::InitLock(PIN_LOCK *lock)`。在插桩函数和分析函数中,锁的使用方式如下,应注意在全局变量使用完毕后释放锁,避免死锁的发生: + ``` + GetLock(&thread_lock, threadid); + // 访问全局变量 + ReleaseLock(&thread_lock); + ``` +- 注册不同粒度的回调函数: + - TRACE(轨迹)粒度 + - TRACE 表示一个单入口、多出口的指令序列的数据结构。Pin 将 TRACE 分为若干基本块 BBL(Basic Block),一个 BLL 是一个单入口、单出口的指令序列。注册 TRACE 粒度插桩函数原型为: + : + ``` + TRACE_AddInstrumentFunction(TRACE_INSTRUMENT_CALLBACK fun, VOID *val) + ``` + - IMG(镜像)粒度 + - IMG 表示整个被加载进内存的二进制可执行模块(如可执行文件、动态链接库等)类型的数据结构。每次被插桩进程在执行过程中加载了镜像类型文件时,就会被当做 IMG 类型处理。注册插桩 IMG 粒度加载和卸载的函数原型: + : + ``` + IMG_AddInstrumentFunction(IMAGECALLBACK fun, VOID *v) + IMG_AddUnloadFunction(IMAGECALLBACK fun, VOID *v) + ``` + - RTN(例程)粒度 + - RTN 代表了由面向过程程序语言编译器产生的函数/例成/过程。Pin 使用符号表来查找例程。必须使用 `PIN_InitSymbols` 使得符号表信息可用。插桩 RTN 粒度函数原型: + ``` + RTN_AddInstrumentFunction(RTN_INSTRUMENT_CALLBACK fun, VOID *val) + ``` + - INS(指令)粒度 + - INS 代表一条指令对应的数据结构。INS 是最小的粒度,插桩 INS 粒度函数原型: + ``` + INS_AddInstrumentFunction(INS_INSTRUMENT_CALLBACK fun, VOID *val) + ``` +- 注册结束回调函数 + - 插桩程序运行结束时,可以调用结束函数来释放不再使用的资源,输出统计结果等。注册结束回调函数: + ``` + VOID PIN_AddFiniFunction(FINI_CALLBACK fun, VOID *val) + ``` +- 启动 Pin 虚拟机进行插桩: + - 最后调用 `VOID PIN_StartProgram()` 启动程序的运行。 + +#### 插桩、分析函数的编写 +在 `main` 函数中注册插桩回调函数后,Pin 虚拟机将在运行过程中对该种粒度的插桩函数对象选择性的进行插桩。所谓选择性,就是根据被插桩对象的性质和条件,选择性的提取或修改程序执行过程中的信息。 + +各种粒度的插桩函数: +- **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, ...)` + +其中 `funptr` 为用户自定义的分析函数,函数参数与 `...` 参数列表传入的参数个数相同,参数列表以 `IARG_END` 标记结束。 + ## Pin 在 CTF 中的应用