intel Pin:动态二进制插桩的安装和使用,以及如何开发一个自己的Pintool
先贴几个你可能用得上的链接
intel Pin的官方介绍Pin: Pin 3.21 User Guide (intel.com)
intel Pin的API文档Pin: API Reference (intel.com)
intel Pin的下载地址Pin - A Dynamic Binary Instrumentation Tool (intel.com)
Pin的介绍
Pin可以被看做一个即时JIT编译器(Just in Time)。它可以程序运行时拦截常规可执行文件的指令,并在指令执行前生成新的代码,然后去执行生成的新的代码,并在新的代码执行完成后,将控制权交给被拦截的指令。
就好像在指令前插了一根桩完成了其他的操作之后再执行程序正常的操作。
Pin支持多平台(Windows、Linux、OSX、Android)和多架构(x86,x86-64、Itanium、Xscale,好像支持的也不是很多。。)
Pin不开源
Pintool
Pin只是一个开发框架,在真正对程序进行动态插桩时,需要使用通过Pin编译而来的Pintool。
Pintool是一个动态链接库,在使用Pin时需要通过参数载入Pintool对选定的二进制文件进行插桩分析。
(Pin和Pintool的关系,呃,有点类似于LLVM 和LLVM Pass)
Kali安装Pin
我使用的环境是安装在vmware中的Kali2021.
首先去官网下载地址(见上面)下载对应平台的最新版本,例如Linux,然后点击第一行的kit栏中的内容开始下载
下载完成后将下载的文件放入Kali中并解压,解压后的文件中有一个名为Pin的二进制文件,此时就可以直接用了。
然而Pin只是一个动态插桩的开发框架,还需要Pintool才能完成对可执行文件的插桩,intel Pin提供了一些具有特定功能的Pintool,并且已经打包在压缩包当中,但是还需要编译。
首先进入ManualExamples文件夹
cd pin-3.25-98650-g8f6168173-gcc-linux/source/tools/ManualExamples/
这里存放了许多Pintool的源代码,它们使用C++开发。现在开始编译
编译ManualExamples中的所有Pintool:
# 编译可供32位可执行文件使用的pintool
make all TARGET=ia32
# 编译可供64位可执行文件使用的pintool
make all TARGET=intel64
编译完成后,生成的pintool的动态链接库(.so文件)就存放在/ManualExamples/obj-intel64(obj-ia32)
如果只想编译某个pintool(例如你开发了一个自己编写的pintool):
# 编译可供32位可执行文件使用的pintool
make obj-ia32/inscount0.so TARGET=ia32
# 编译可供64位可执行文件使用的pintool
make obj-intel64/inscount0.so TARGET=intel64
inscount0.so对应的源代码文件名应为inscount0.cpp
如果没有cmake的话,使用下面的指令安装就行
sudo apt-get install make
使用Pintool对二进制文件插桩
有了Pintool之后,就可以对二进制文件进行插桩分析了,这里以intel pin官方的pintool:inscount0.so为例
inscount0.so会在每一条指令前插桩,然后执行对某个全局变量加1的操作,最后会将全局变量的值写到inscount0.out里面(如果你不指定文件的话),因此它的功能是计算程序在运行时执行了多少条指令。由于是动态插桩,因此被多次执行的指令会被重复统计(所以它不能用于统计程序的指令条数)
可以使用以下指令来使用inscount(假设你现在的工作目录是ManualExamples):
../../../pin -t obj-intel64/inscount0.so -o inscount0.log -- /bin/ls
这条指令就会对/bin/ls这个可执行文件进行插桩分析,并将结果输出在inscount0.log中(而不是默认的inscount0.out,你可以通过-o参数设置路径)
查看inscount0.log,结果如下:
┌──(kali㉿kali)-[~/pin-3.25-98650-g8f6168173-gcc-linux/source/tools/ManualExamples]
└─$ cat inscount.log
Count 718889
一共执行了718889条指令
intel pin还提供了许多pintool,可以在这个连接中查到它们的作用Pin: Pin 3.21 User Guide (intel.com)
在你需要动态插桩时,通常可能会希望“桩”执行一些比较复杂的操作,因此就不一一说明pintool的作用了(但在介绍如何开发pintool时会以其中几个为例)。
开发自己的Pintool
插桩的颗粒度
Pin有四种插桩的模式,也叫颗粒度,它们的区别在于“何时进行插桩”:
- INS instrumentation:指令级插桩,即在每条指令执行时插桩
- TRACE instrumentation:基本块级插桩,即在每个基本块执行时插桩
- RTN instrumentation:函数级插桩,即在每个函数执行时插桩
- IMG instrumentation:镜像级插桩,对整个程序映像插桩
其中,TRACE和传统的基本块有所区别,在Pin中,trace从一个branch开始,以一个无条件跳转(jmp call ret)结束。因此会形成一个单一入口,单一出口的指令序列,因此如果按照传统基本块的定义概念去计算基本块数量,结果可能与预期的不一致。
Pintool的基本框架
仍以inscount0.cpp为例,该文件可以在ManulExamples中找到。
首先来看它的main函数
main函数中,首先调用了PIN_Init,这是Pin的初始化函数,暂时不需要了解。如果初始化失败,则会执行return Usage(),Usage的内容如下:
INT32 Usage()
{
cerr << "This tool counts the number of dynamic instructions executed" << endl;
cerr << endl << KNOB_BASE::StringKnobSummary() << endl;
return -1;
}
一般来说,Usage用于提示一些帮助信息(不影响pintool的功能)。
然后是打开了一个文件流OutFile,其中KnobOutputFile与pin的一个类KNOB有关,它是管理调用pintool时传入的参数的,暂时不用了解(例如在inscount0中就参与-o参数指定结果输出文件的这块逻辑)
接下来,调用回调函数INS_AddInstrumentFunction(Instruction,0),函数INS_AddInstrumentFunction表示这会作用在每一条指令上,Instruction则是对每条指令执行的操作,第二个参数0,官方文档的解释如下:
passed as the second argument to the instrumentation function
也即作为插桩函数Instruction的第二个参数,然而官方提供的pintool中基本上都没有用这个参数,因此意义不明,开发时弄个0上去就行。
下面是函数Insrtuction的定义:
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);
}
VOID是pin定义的一个全局宏,与C++的void并不完全一致(呃,不管了,记得全大写就行)
在函数Instruction,第一个参数INS ins就是当前指令,第二个参数v应该就是之前提到的那个0
然后在函数内部,使用了INS_InsertCall函数进行插桩,并向该函数传递了一个分析函数docont,而真正实现pintool逻辑(统计执行指令的次数)的函数就是docont,它每执行一次(意味着有一条指令被执行了),就会让全局变量icount加1.
INS_InsertCall的参数很讲究,它是一个变长参数,第一个参数为ins,传入插桩处的指令,第二个参数有四种选择:
- IPOINT_BEFORE:在插桩对象执行之前插桩(即执行docount函数)
- IPOINT_AFTER:在插桩对象执行之后插桩,对于inscount0来说,使用这个和IPOINT_BEFORE没区别,但对于其他逻辑,或者其他颗粒度的插桩(例如函数级插桩,只在函数执行完成后执行docount)区别很大
- IPOINT_ANYWHERE:在插桩对象内部的任何地方插桩,例如可以理解为在一个函数中启用IPOINT_BEFORE,也就是局部的全局插桩
- IPOINT_TAKEN_BRANCH:在插桩对象为判断语句后获取程序控制权的指令处插桩,例如if语句后的指令或else处的指令(二者选其一)
第三个参数为分析函数的函数指针,请注意要使用(AFUNPTR)来完成强制转换
之后则是分析函数的参数,这些参数可以在官方API文档中的IARG_TYPE中查看,inscount0的分析函数docount没有参数和返回值,因此这部分稍后换一个例子来说明。
最后,则是使用IARG_END来表示参数列表的结束。
因此INS_InsertCall的参数为:(待插桩对象,执行模式,分析函数的指针,分析函数的参数和返回值,IARG_END)。
然后在main函数中,调用了PIN_AddFiniFunction,当pintool即将结束时调用Fini函数,完成一些收尾工作,例如inscount0就在Fini函数中完成了将结果写入文件inscount0.out中
最后调用PIN_StartProgram函数,用于启动程序。
因此一个pintool的大致结构为:初始化Pin->回调函数->结束前的操作->启动程序
分析函数的参数和返回值
接下来以safecopy这个pintool为例,来说明如何为分析函数传递参数和返回值
safecopy的main函数与inscount0差别不大,只是多了一行PIN_InitSymbols()它用于初始化程序的符号表,在更大颗粒度的插桩分析时这个函数不可缺少,这个后续再说。
safecopy中,向INS_AddInstrumentFunction传递的函数EmulatedLoad和分析函数DoLoad分别如下:
这个pintool的功能是删除程序中从内存取值并转移到寄存器的语句,并用PIN_SafeCopy这个函数来替代,这个函数更加安全,能够保证即使内存或寄存器是部分或完全不可访问的,这种数据转移也能安全地返回到调用方。
首先来看DoLoad,它需要两个参数,类型分别是REG和ADDRINT,代表着原指令的寄存器和内存地址。
而INS_InsertCall中,通过IARG_UINT32,REG(INS_OperandReg(ins,0))传递第一个参数,也就是寄存器,其中INS_OpeandReg(ins,0)能够获取指令的第0个操作数(也就是寄存器),而IARG_UINT32是这个寄存器的类型,也急速UINT32(无符号32位整型),这样的类型还有如下几个(均可在IARG_TYPE中查到):
- IARG_ADDRINT
- IARG_PTR
- IARG_BOOL
- IARG_UINT32
- IARG_UINT64
分别表示地址、指针、布尔类型、无符号32位整型、无符号64位整型。并且在官方的API文档中,它们的描述最后都有一句(additional arg required),也就是说还需要在它们之后紧跟一个具体的参数的值(在上面的例子当中,就是REG(INS_OperandReg(ins,0)))
此外,还有一些常用的IARG_TYPE:
- IARG_INST_PTR:指令ins的地址,是一个语法糖,等价于传递两个参数IARG_ADDRINT, INS_Address(ins)
- IARG_CONTEXT:当前指令的上下文
- CALL_ORDER:用于指定分析函数的调用顺序,如果你有多个分析函数的话
- IARG_MEMORYREAD_EA(2):读内存指令中的第一个(第二个,例如cmp mem1,mem2)内存的地址
- IARG_RETURN_REGS:保存分析函数返回值的寄存器,也需要额外参数
此外还有许多有关内存和寄存器还有函数的IARG_TYPE,可以自行查阅官方API文档,需要注意它们的使用有时会有一些条件,例如只能在IPOINT_BEFORE下使用等等。
现在INS_InsertCall剩下的参数就不难理解了,首先if语句的判断确定这必须是一条mov 寄存器,内存这样的地址。因此IARG_MEMORYREAD_EA获取内存的地址,然后使用IARG_RETURN_REGS指定返回值保存位置,也就是当前指令的第一个寄存器,通过INS_OperandReg(ins,0)来获取。
最后调用INS_Delete函数将原指令删除(因为在DoLoad中实现了更安全的数据转移,原指令实现的数据转移就不需要了)
其他颗粒度的pintool
之前都是指令级INS的pintool,现在来说说其他颗粒度的pintool。
这次以malloctrace(对应文件为malloctrace.cpp)为例,它的作用是打印出程序中调用malloc时传递的参数和返回值,以及调用free时传递的参数。
首先查看main函数
可以发现,这次是一个IMG级的插桩,因此在整个程序运行期间,IMG_AddInstrumentFunction只会被执行一次(相应的,其他颗粒度的插桩,AddInstrumentFunction的开头分别为INS_、TRACE_、RTN_)
此外,在Pin初始化时,多调用了PIN_InitSymbols,这是初始化符号表,对于RTN和IMG两个级别来说,这是必不可少的。
函数Image的内容如下
这里MALLOC和FREE是定义的两个宏,分别为字符串"malloc"和“free”
首先会通过RTN_FindByName获取函数malloc的RTN,它获取的实际上是映像中malloc这个函数的内容,而不是程序调用malloc的那一条语句。
之后使用RTN_Valid来判断获取的RTN是否合法
然后使用RTN_Open函数,其具体作用API文档没有说明,只是说明了必须在调用RTN_InsHead()、RTN_InsertCall()和RTN_InsHeadOnly()前调用
接下来使用RTN_InsertCall进行插桩,并且是在mallocRTN的前后插了两个桩,分别获取malloc的参数和返回值,因此这个pintool虽然是作用在映像上的,但实际上是在对RTN进行插桩,分析函数的参数不详细解释了,这里说说新出现的两个IARG_TYPE:
- IARG_FUNCARG_ENTRYPOINT_VALUE:传递给RTN的参数,需要额外参数表示RTN的第几个参数,第一个参数从0开始(上面的例子表示函数malloc的第1个参数)
- IARG_FUNCRET_EXITPOINT_VALUE:RTN的返回值
之后,RTN_Close,与RTN_Open成对使用
然后是对free的处理,与malloc相同,只是free没有返回值,因此少插一个桩。
分析函数Arg1Before和MallocAfter只是简单的打印出参数和返回值:
其他
在进行pintool开发时注意两点:
- main中回调函数和插桩函数的作用范围
main中的回调函数,(INS_,TRACE_,RTN,_IMG_)AddInstrumentFunction(funptr,0)是表示它会将函数funptr作用在其规定的颗粒度上,但这个函数并没有完成插桩的操作。真正的插桩操作是通过(INS_,TRACE_,RTN,_IMG_)InsertCall来完成的。因此如果需要对大颗粒度下某个特定小颗粒的的目标插桩,有两种办法:
- 使用小颗粒度的回调函数,判断每个目标是否符合特定条件,符合的话就插桩,就像inscount0做的那样
- 使用大颗粒度的回调函数,筛选出符合特定条件的目标进行插桩,就像malloctrace做的那样
2.不同颗粒度的回调函数会传递不同颗粒度的插桩对象,例如INS_AddInstrumentFunction(funptr,0)就会向函数funptr传递当前的指令ins,通过这个ins可以获取有关该指令的信息,以便于进行筛选插桩,对于TRACE、RTN、IMG也是同理,并且pin 提供了许多有关它们的API,可以在官方的API文档查询
intel Pin:动态二进制插桩的安装和使用,以及如何开发一个自己的Pintool的更多相关文章
- 插桩 inline hook 动态二进制插桩的原理和基本实现过程
插桩测试 https://source.android.google.cn/compatibility/tests/development/instrumentation https://zhuanl ...
- Intel pin 2.14/CentOS 6 X86-64/安装
环境:Intel Pin 2.14 CentOS 6 X86-64 --linux.tar.gz 进入 ./source/tools/ManualExamples make all TARGET=in ...
- 二进制程序分析工具Pin在Windows系统中的安装和使用方法
这篇日志其实很弱智,也是因为换了新电脑,实验环境不全(当然,做这个实验我是在虚拟机里,因为接下来想拿些恶意代码的数据),所以这里记录一下在Windows下怎么安装和使用Pin这个程序分析领域最常用的工 ...
- Intel Pin基础
参考:http://software.intel.com/sites/landingpage/pintool/docs/62732/Pin/html/ http://blog.nruns.com/bl ...
- 手淘架构组最新实践 | iOS基于静态库插桩的⼆进制重排启动优化 抖音研发实践:基于二进制文件重排的解决方案 APP启动速度提升超15% 编译期插桩
抖音研发实践:基于二进制文件重排的解决方案 APP启动速度提升超15% 原创 Leo 字节跳动技术团队 2019-08-09 https://mp.weixin.qq.com/s/Drmmx5JtjG ...
- 方案设计:基于IDEA插件开发和字节码插桩技术,实现研发交付质量自动分析
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 如何保证代码质量? 业务提需求,产品定方案,研发做实现,测试验流程.四种角色的相互配 ...
- Flymeos插桩适配教程
插桩适配前提,安装Ubuntu或者其他linux系统. 安装JDK7 sudo apt--jdk Ubuntu 16.04与基于它的版本,需要添加源 sudo add-apt-repository p ...
- Java Instrumentation插桩技术学习
Instrumentation基础 openrasp中用到了Instrumentation技术,它的最大作用,就是类的动态改变和操作. 使用Instrumentation实际上也可以可以开发一个代理来 ...
- 开发 IDEA Plugin 引入探针,基于字节码插桩获取执行SQL
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 片面了! 一月三舟,托尔斯泰说:"多么伟大的作家,也不过就是在书写自己的片 ...
- APK修改神器:插桩工具 DexInjector
本文介绍了一个针对Dex进行插桩的工具,讲解了一下直接修改Dalvik字节码和Dex文件时遇到的问题和解决方法 作者:字节跳动终端技术-- 李言 背景 线下场景中,我们经常需要在APK中插入一些检测代 ...
随机推荐
- 使用kubeoperator自带的nginx-ingress-controller设置服务的ingress规则进行访问
情况说明 当使用kubeoperator安装k8s集群的时候,在组件设置部分选择的ingress 类型是nginx-ingress yaml文件 k8s集群安装后,可以在节点的master主机的这个目 ...
- Elasticsearch:使用 GeoIP 丰富来自内部专用 IP 地址
转载自:https://blog.csdn.net/UbuntuTouch/article/details/108614271 对于公共 IP,可以创建表来指定 IP 属于哪个城市的特定范围.但是,互 ...
- 关于linux的一点好奇心(五):进程线程的创建
一直以来,进程和线程的区别,这种问题一般会被面试官拿来考考面试者,可见这事就不太简单.简单说一点差异是,进程拥有独立的内存资源信息,而线程则共享父进程的资源信息.也就是说线程不拥有内存资源,所以对系统 ...
- NSIS皮肤插件:vcl-styles-plugins
vcl-styles-plugins简介 NSISVCLStyles plugin (dll)是一款应用于nsis安装程序的皮肤插件,插件大小约为1.6MB,实际应用在安装程序中压缩后约为550 ...
- 云原生下基于K8S声明式GitOps持续部署工具ArgoCD实战-上
@ 目录 概述 定义 工作原理 主要组件 核心概念 环境准备 概述 安装Kubekey 创建K8S 安装K9S OpenLB 安装ArgoCD 安装 ArgoCD CLI 从Git库中创建一个应用程序 ...
- 使用Pytorch进行多卡训练
当一块GPU不够用时,我们就需要使用多卡进行并行训练.其中多卡并行可分为数据并行和模型并行.具体区别如下图所示: 由于模型并行比较少用,这里只对数据并行进行记录.对于pytorch,有两种方式可以进行 ...
- ssh端口映射 解决服务器使用tensorboard的问题
有时会在服务器上使用tensorboard,然而本地无法直接访问tensorboard结果网页.这时候使用端口映射即可.比如tensorboard上占用的是 6006 端口,也就是说结果在服务器的 l ...
- 手写自定义springboot-starter,感受框架的魅力和原理
一.前言 Springboot的自动配置原理,面试中经常问到,一直看也记不住,不如手写一个starter,加深一下记忆. 看了之后发现大部分的starter都是这个原理,实践才会记忆深刻. 核心思想: ...
- NLP之基于TextCNN的文本情感分类
TextCNN @ 目录 TextCNN 1.理论 1.1 基础概念 最大汇聚(池化)层: 1.2 textCNN模型结构 2.实验 2.1 实验步骤 2.2 算法模型 1.理论 1.1 基础概念 在 ...
- JWT中token的理解
今天我们来聊一聊关于JWT授权的事情. JWT:Json Web Token.顾名思义,它是一种在Web中,使用Json来进行Token授权的方案. 既然没有找好密码,token是如何解决信任问题的呢 ...