英文原文全文 http://deniable.org/reversing/binary-instrumentation
译转自https://www.4hou.com/binary/13026.html和https://www.4hou.com/binary/13116.html 翻译并不全
感谢翻译作者luochicun
目录
前言
动态二进制插桩(dynamic binary instrumentation ,DBI)技术是一种通过注入插桩代码,来分析二进制应用程序在运行时的行为的方法。
动态二进制插桩技术,可以在不影响程序动态执行结果的前提下,按照用户的分析需求,在程序执行过程中插入特定分析代码,实现对程序动态执行过程的监控与分析。目前,应用广泛的动态二进制分析平台有Pin,DynamoRIO和Frida等。
最常用动态二进制插桩框架的平台
这篇文章的目的是对动态二进制插桩的原理和基本实现过程进行全面的介绍,其中,我会选择一些最知名和最常用的动态二进制插桩框架进行具体说明,其中包括Pin,DynamoRIO和Frida。而这三个里面,我会主要关注Intel公司的Pin平台,该平台提供了丰富的编程接口,开发者可以通过调用编程接口方便的获取程序动态执行期间的指令、内存和寄存器等信息,实现细粒度的动态监控,因此本文选取Pin平台进行动态分析。
Pin提供了指令、基本块和系统调用等多个层次插桩分析,其中,RTN是函数级的插桩机制,能够自动识别API函数;Trace是轨迹层次的插桩机制,能够自动识别单一入口、多出口的指令轨迹块。
不过使用动态二进制插桩框架的可不止Pin,DynamoRIO和Frida,还有比如Valgrind,Triton(使用Pin),Q动态二进制插桩,BAP,Dyninst以及许多其他框架。其中有些更成熟,有些则不太成熟。有些功能更多,有些功能更少。尽管Valgrind是最广为人知且较为成熟的动态二进制插桩框架之一,但它仅适用于Linux。所以,我根本不会用它作为分析对象。
在我插桩发现漏洞的过程中,我一直比较专注Windows上的漏洞。不过,我在本文提供的有关Windows平台的代码,你一样可以在Linux上构建,应该非常简单。而反过来则不可以,原因是在Windows上构建Pin或DynamoRIO时会失败。请注意,我写本文的目的是让你学会编写references。
为什么我只选择动态二进制插桩进行分析
根据维基百科的介绍,插桩指的是监控或测量产品性能水平、诊断错误和编写跟踪信息的能力。程序员以代码指令的形式实现插桩,监控系统中的特定组……当应用程序包含插桩代码时,可以使用管理工具对其进行管理。必须使用插桩来检查应用程序的性能,插桩方法可以有两种类型:源插桩(Source instrumentation)和二进制插桩。
顾名思义,源插桩会要求你掌握软件应用程序的源代码,否则无法进行插桩。而二进制插桩,可以与任何软件应用程序一起使用。事实证明,你在Windows操作系统上运行的大多数程序源代码都是封闭的。这意味着,在这篇文章中,我将只谈论二进制插桩。二进制插桩通常又被称为动态二进制插桩或动态二进制修改(Dynamic Binary Modification)。
在单行语句中,动态二进制插桩是一种将插桩代码注入正在运行的进程中的技术,这意味着插桩代码对注入的应用程序来说是完全透明的。
使用动态二进制插桩框架,我们可以一步一步地对目标二进制执行过程进行拆解。但请注意,分析仅适用于已执行的代码。
为何我只选择动态程序分析
目前总共有两种类型的程序分析:静态程序分析和动态程序分析。在不运行程序的情况下进行的分析就是静态分析,当我们运行程序时所进行的分析就是动态分析。
根据维基百科的介绍,动态程序分析是通过在真实或虚拟处理器上执行程序,来达到分析目的。而要使动态程序分析有效,必须让测试程序进行多次且多样的行为运行,以全方位分析。这时,你可以使用诸如代码覆盖之类的软件测试办法,以确保观察到程序的所有行为。
如前面提到的框架那样,动态二进制修改工具会在正在运行的程序和底层操作系统之间重新引入了一个层,该层会提供一些在程序执行时检查和修改用户级程序指令的机会。
不管是多么复杂的内部系统,只要通过API让其简单实用化,允许任何用户快速构建大量工具来辅助软件分析,而这正是我将在这篇文章中尝试展示的内容。
我们经常会出于各种原因来观察和修改程序的运行行为,这其中就包括软件或硬件开发人员、系统工程师、漏洞捕获者、恶意软件分析师、终端用户等。动态二进制插桩框架可以提供对每个执行的用户级指令的访问,除了可能发生的少量运行时刻和内存开销之外,该程序将与本机执行相同地运行。
静态分析的主要优点是它可以确保100%的代码覆盖率,通过动态分析,为了确保代码覆盖率更高,我需要多次运行程序,并使用不同的输入,以便分析采用不同的代码路径。但是,在某些情况下,由于软件应用程序太大,执行静态分析的成本太高。
正如我之前提到的,动态二进制插桩框架会直接在二进制文件或可执行文件中运行,这样我们就不需要程序的源代码,也不需要重新编译或重新链接程序,这个优势就允许我们分析许多软件。
动态二进制系统不但会和“guest” 执行的程序同时运行,还会在运行中执行所有请求或所需的修改。这种动态方法还可以处理动态生成代码的程序(其分析的工作量相当大),即自修改代码(self-modifying code)。如果你上网搜一下,就会发现很多情况下,动态二进制插桩框架被用于分析具有自修改代码的恶意软件。例如,请查看Blackhat Europe 2017的一篇演示文稿,或者,关于如何用Pin解压Skype的帖子。
动态二进制插桩框架被用于解决计算机体系结构问题,大量用于软件工程、程序分析和计算机安全。比如,软件工程师希望用它来深入了解他们开发的软件,系统的分析其性能和运行行为。动态二进制插桩框架的一个常见用途是模拟新的CPU指令。由于动态二进制系统在执行之前可以访问每条指令,因此硬件工程师实际上可以使用这些系统来测试当前不受硬件支持的新指令。他们可以模拟新的指令行为,而不是执行特定的指令。同样,他们也可以使用相同的方法来替换错误指令,并正确模拟所需行为。无论如何,从计算机安全的角度来看,动态二进制插桩系统可用于流程分析、污点分析、模糊测试、代码覆盖、生成测试历程、逆向工程、调试、漏洞插桩,甚至是修补漏洞以及自动利用。
使用动态二进制系统的两种主要方式和三种执行模式
使用动态二进制系统有两种主要方式,其中第一个方式是最常见的,至少在计算机安全中,是在动态二进制系统的控制下从头到尾执行程序。当我们想要实现完整的系统模拟或仿真时,我由于需要完全控制并进行代码覆盖,就要在动态二进制系统的控制下从头到尾执行程序。第二个方式就是动态二进制系统可以被附加到一个已经运行的程序中,且以完全相同的方式被调试器从正在运行的程序中附加或分离。如果我们想知道某个程序在特定时刻正在做什么,那么第二个方式就会非常有用。
此外,大多数动态二进制插桩框架都有三种执行模式:解释模式( Interpretation mode)、探测模式(probe mode)和JIT模式(just-in-time mode)。JIT模式是最常见的实现方式,也是最常用的模式,即使动态二进制插桩系统支持多种执行模式。在JIT模式下,原始二进制文件或可执行文件实际上从未被修改或执行过。因为,此时二进制文件被视为数据,修改后的二进制文件副本将在新的内存区域中生成(但只针对二进制文件的执行部分,而不是整个二进制文件),此时执行的就是这个修改后的文件副本。而在解释模式中,二进制文件也被视为数据,每条指令都被用作具有相应功能的替代指令的查找表(由用户实现) 。在探测模式中,二进制文件实际上是通过使用新指令来覆盖旧的指令,来达到修改目的的,不过这会导致运行开销增大,但在某些体系结构(如x86)中,该方式却很好用。
无论采用哪种执行模式,一旦我们通过动态二进制插桩框架控制了程序的执行,就能够将插桩添加到执行程序中。我们可以在代码块之前和之后插入想要的代码,甚至也可以完全替换它们。
你可以在下图中看到执行过程:
其中还包含不同程度的运行权限级别:
1.指令级(Instruction level);
2.基本块级(Basic block level);
3.函数级(Function level);
可以看出,运行权限级别的选择将决定你对程序执行所控制的程度。显然,这会对性能的分析产生直接影响。另外,请注意,在大多数情况下,对整个程序进行插桩是不切实际的。
动态二进制修改对运行性能的影响
现在就来讲讲动态二进制修改会对正在运行的程序的性能产生什么影响,根据我的经验,通常观察到的性能开销,实际上取决于各种实际运行因素。不过一般情况下,用户实现的修改都会造成性能开销的变化。无论如何,使用作为动态二进制插桩时,你首先要做的决定就是确定所需的代码覆盖率,进而在衡量性能开销是否合理。
Pin
Pin是由英特尔公司开发的动态二进制插桩框架,它允许我们为Windows,Linux和OSX构建称为Pintools的程序分析工具。我们可以使用这些工具来监控、修改和记录程序运行时的行为。
点此链接就可以免费下载和使用Pin,除了文档和二进制文件之外,Pin还包含大量Pintools的样本源代码。在开发任何Pintool之前,这些都是我们必须考虑且必须阅读的宝贵材料。
在我看来,Pin是最容易使用的动态二进制插桩框架,至少我觉得深入了解它的API比使用DynamoRIO更容易。虽然除了这两个以外我没有花太多时间去深入学习其他API,但像Valgrind,Triton,Dyninst和Frida,总体上是不如Pin的。
不过选择什么,还得看你的实际用途。如果你想创建一个商业工具并发布它的二进制版本,Pin将不是一个好的选择。根据我的测试,Pin的特点就是稳定可靠。我在某些动态二进制插桩框架下运行某些程序时经常会遇到了一些问题,主要是大型程序,如Office套件,游戏和杀毒软件引擎。其中一些动态二进制插桩框架甚至在运行某些小型应用程序时,也会发生崩溃。
设置Pin(Windows)
Linux中的Pin设置非常简单,但是,在Windows系统上,情况则完全不同。如果你想尝试我在本文里的测试,可以按着以下方式快速设置。
从这里获取Pin的最新版本,并将其解压缩到C:\驱动器或任何你想要的位置。为简单起见,我通常使用C:\pin,同时我建议你也这样做。
Pin的压缩文件中包含source/tools下的大量Pintools样本。你在实践中将会看到,此API非常易于阅读和理解。
我喜欢用Microsoft Visual Studio(简称VS),我将使用它来构建本文中提到的所有工具。有一个Pintool样本几乎可以用Visual Studio构建,你只需要调整其中的几个设置而已。但是,每次当我想创建一个新的Pintool工具时,我都不想手动复制和重命名文件,为此我创建了一个已经调整好了的样本,你可以连同下面的python脚本一起放在C:\pin\source\tools下。不过,由于较新版本的Visual Studio保存设置的方式已更改,因此我不得不重新编写了一个全新的脚本。
因此,每次要使用Visual Studio构建新的Pintool时,只需执行以下操作。
cd\
cd pin
python create_pintool_project.py -p <name_of_your_project>
然后,你只需点击Pintool的解决方案文件,即可使用Visual Studio构建Pintool。我使用的是Visual Studio Professional 2015,但它也适用于Visual Studio 2017,我使用Visual Studio 2017 Enterprise进行了几次构建测试,都没有任何问题。
Pin Visual Studio集成
我们可以将我们制作的Pintools作为外部工具集成到Visual Studio中,这样我们就可以在不使用命令行的情况下运行和测试制作的Pintool。配置非常简单,从“工具”菜单中,选择“外部工具”,此时将出现一个对话框。单击“添加”按钮,然后参照下图,填写对应的文本。
在标题输入框中,输入你想要的任何内容。在命令输入文本框中,输入pin.exe的完整路径,如果你在c:\pin下安装它,请输入c:\pin\pin.exe。在参数中,你必须包含要发送给Pintool的所有参数,至少需要像上图中指定的那样。-t用于指定Pintool的位置, — 后跟的是你要插桩的目标程序。
设置完成后,你只需从工具菜单中运行你的Pintool即可,如下图所示。
点击确定,既可以开始运行了。
Visual Studio的输出窗口,将显示写入stdout的Pintool输出。
DynamoRIO
DynamoRIO也是另一个使用动态二进制插桩框架的平台,最初是由惠普的Dynamo优化系统与麻省理工学院的Runtime Introspection and Optimization(RIO)研究小组合作开发的。它允许我们为Windows和Linux构建称为客户端的程序分析工具,我们可以使用这些工具来监控、修改和记录程序运行时的行为。
DynamoRIO于2002年首次作为单独的二进制工具被包发布,后来在2009年开源了BSD许可证。与Pin一样,它还附带了多个客户端样本的源代码。这些是让我们开始并使用其API的非常宝贵的例子。
DynamoRIO是一个运行时刻代码( runtime code)操作系统,它允许我们在程序运行时对程序的任何部分进行代码转换,它可以作为应用程序和操作系统之间的中间平台。
正如我之前所说,我并没有发现DynamoRIO的API好用。但是,如果你计划制作商业版本或发布二进制版本,DynamoRIO可能是最佳选择。它的一个优点是获得了BSD许可,这意味着它是个免费软件。
另请注意,通常认为DynamoRIO比Pin更快,然而,Pin比DynamoRIO更可靠,这是我在运行大型软件程序时的亲身感受。
设置DynamoRIO(Windows)
要在Windows上安装DynamoRIO,只需从此处下载最新的Windows版本,类似于我们对Pin所做的设置,只需将其解压缩到C:\dynamorio下即可。
要在Windows上构建自己的DynamoRIO工具,可能有点困难。你可以尝试按照这个说明以及这个说明进行操作即可,或者直接套用我下面讲的DynamoRIO Visual Studio模板。
正如我之前所说,我喜欢Visual Studio。所以我用它创建了一个样本,该样本已经修改了所需的所有include和libs(前提是你已经解压缩了DynamoRIO文件),具体可在点此处获得。然后,就像使用Pin一样,我们也需下载python脚本。由于这两个工具的文件结构有点不同,我无法直接使用我之前编写的脚本,因此我不得不创建一个针对于DynamoRIO的新脚本。
因此,每次要使用Visual Studio构建新的DynamoRIO客户端时,只需执行以下操作。
python create_dynamorio_project.py -p <name_of_your_project>
请注意,上面的命令所提到提到的Python脚本和样本都必须在同一个文件夹中。
然后,你只需点击解决方案文件,即可使用Visual Studio构建DynamoRIO客户端。我使用的是Visual Studio Professional 2015,但它也适用于Visual Studio 2017。我使用Visual Studio 2017 Enterprise进行了几次构建测试,没有遇到任何问题。
DynamoRIO Visual Studio集成
我们还可以将DynamoRIO与Visual Studio集成,由于设置过程和Pin完全相同我就不赘述了,看图便知。
Frida
Frida是一个主要由Ole André V. Ravnås开发的动态二进制插桩框架,该工具在网络社区中变得非常受欢迎,并获得了相当多志愿者的积极修改。Frida目前可以支持OSX,Windows,Linux和QNX,并且有一个可用于多种语言的API,如Python,C#,Swift,Qt\QML和C.,就像上面提到的动态二进制插桩框架一样,我们可以使用Frida和脚本在程序运行时监控、修改和记录程序的行为。
Frida是免费的,非常容易安装。安装之后,还有详细的案例教程,你可以系统性进行学习。Frida目前已经将谷歌的V8引擎注入到它的进程中,这样Frida内核就会与Frida的代理(进程端)进行通信,并使用V8引擎运行JavaScript代码(创建动态hook)。
随着Web相关技术的发展,JavaScript所要承担的工作也越来越多,早就超越了“表单验证”的范畴,这就更需要快速的解析和执行JavaScript脚本。V8引擎就是为解决这一问题而生,在node中也是采用该引擎来解析JavaScript。
Frida的API由两大功能块组成:JavaScript API和bindings API。我并没有对它们进行深入研究,只是使用了我认为最受欢迎的JavaScript API,我发现它易于使用,非常灵活,我可以用它来快速编写一些内省(introspection)工具。
尽管Pin和DynamoRIO是比较流行的动态二进制插桩框架,并且最成熟,但Frida具有一些优势。如上所述,它具有更多语言的优势,并且配备了快速开发工具。不过与其他框架相比,它还有一些缺点,成熟度较低,文档较少,粒度较小,因此缺乏某些功能。
Frida的设置(Windows)
Frida的设置非常简单,只需下载它即可,然后运行:
python get-pip.py
并且,实际安装Frida类型如下。
cd\
cd Python27\Scripts
pip.exe install frida