内容简介:“Karta”(在俄语中的意思是“map”)是IDA(一个静态反编译程序)的源代码辅助二进制对比插件,该插件的开发是为了对比一个非常大的二进制文件(通常是固件文件)中的开源库的符号。对于那些每天都要处理固件文件的人来说,重复逆转net-snmp(一个免费的、开放源代码的SNMP实现,以前称为UCD-SNMP)非常浪费时间的。所以,人们急需一种对比插件工具,来识别使用过的开源码,并在IDA中自动对比它们对应的符号。人们最初的开发重点是放在了快速对比的过程上,但在实践中,就发现即使他们试图逆向的二进制文件包含超
前言
“Karta”(在俄语中的意思是“map”)是IDA(一个静态反编译程序)的源代码辅助二进制对比插件,该插件的开发是为了对比一个非常大的二进制文件(通常是固件文件)中的开源库的符号。对于那些每天都要处理固件文件的人来说,重复逆转net-snmp(一个免费的、开放源代码的SNMP实现,以前称为UCD-SNMP)非常浪费时间的。所以,人们急需一种对比插件工具,来识别使用过的开源码,并在IDA中自动对比它们对应的符号。
人们最初的开发重点是放在了快速对比的过程上,但在实践中,就发现即使他们试图逆向的二进制文件包含超过10万个函数,也仅仅会对比一个包含300个函数的库,而光这个过程就需要等待几个小时。
事实证明,出于性能原因而部署的启发式算法对对比结果也有很大影响。Karta的假阳性率非常低,而真阳性率很高。这使得它甚至可以用于对比中小型二进制文件。
因此,有人认为Karta可以成为开发人员 工具 箱中的一个重要工具,并应用于以下场景:
1.侦察阶段:确定二进制文件中使用的开放源代码(包括它们的版本);
2.优化Clutter:对比使用的开源的符号,从而为逆向工程节省时间;
3.查找用例:使用已用过的开放源代码列表,其中的符号已经在二进制文件中对比,以便在可执行或固件文件中轻松查找用例。
KARTA
如前所述,Karta是IDA*的源代码辅助二进制对比插件,该插件有两个重要的用途:
1.标识:对静态编译的开源代码的确切版本进行标识;
2.对比:对比已标识的开源代码的符号;
Karta现在是开源的,我可以在 Github 中找到。
由于在不同系统结构上编译开源库是非常复杂的事情,因此人们通常会通过让插件独立于系统运行来消除此复杂的过程。例如,如果我想要对比libpng开源的1.2.29版本(这是我在HP OfficeJet固件中使用的版本),我所要做的就是从Github复制它并在(本文使用的x86)设备上编译它。编译完成后,Karta可以生成描述库的规范.json配置文件。使用此配置,即使固件已编译为Big-Endian ARM Thumb模式,Karta插件现在也可以在固件中成功找到库。
Karta由模块组成, IDA反汇编模块可以被任何其他反汇编模块替换 。
寻找用例
虽然目前,我已经为介绍了几个插件用例,但在热门程序中查找用例可能是最有趣的用例。以下是我在研究过程中发现的两个真实用例。
HP OfficeJet固件
由于在对HP传真固件研究期间,我需要将用例用作调试漏洞( CVE-2017-9765 )。该漏洞允许攻击者远程破坏 SOAP Web 服务后台进程,并在受害者设备上执行任意代码。在完成Karta的开发之后,我返回固件并检查了Karta是如何帮助我进行漏洞研究的。
标识符插件会告诉我固件中使用的开源库是:
· libpng: 1.2.29
· zlib: 1.2.3
· OpenSSL: 1.0.1.j
· mDNSResponder: unknown
·gSOAP: 2.7
我可以看到确实使用了gSOAP(编译工具提供了一个SOAP/XML 关于C/C++ 语言的实现,从而让C/C++语言开发web服务或客户端程序的工作变得轻松了很多),快速的CVE搜索显示它包含一个关键的漏洞: CVE-2017-9765 。
在快速编译了此版本的gSOAP的配置之后,我运行了对比器并导入了对比的符号。如下图所示,我可以看到易受攻击的代码函数soap_get_pi与Karta对比。
反编译的soap_get_pi函数,与Karta对比
这对Karta插件来说是一个非常好的消息,这意味着它可以在真实的场景中正常工作(可惜我也只是在调试漏洞完成后才知道这一点)。
普通的闭源程序:TeamViewer
虽然在固件中轻松找到用例很方便,但在Windows PC上使用的日常程序中,实现这一点就很难了,为什么?在阅读Project Zero在WebRTC上的博客文章时,我发现他们在一个名为libvpx的库中发现了一个漏洞: CVE-2018-6155 ,它可以攻击VP8视频编码技术源代码,从而影响了VP8库libvpx而不是WebRTC中的代码。因此,该漏洞有可能影响使用除 WebRTC 之外的所有其他库的程序。
这看起来很有趣,因为Project Zero特别指出这个漏洞“有可能影响使用除WebRTC之外的所有其他库的程序”。由于我在计算机上安装了TeamViewer,看起来它应该使用相同的开源库,让我来看看。
为此,我在IDA中打开了TeamViewer.exe,并在分析过程中开始运行它。为此,我还专门下载了最新版本的libvpx(1.7.0),为它编写了一个简单的标识符,并将其添加到Karta中。由于IDA完成这个分析的时间太长了,我直接将其停止,并运行Karta的标识符插件。从中确定的开放源代码包括:
· zlib: 1.2.5
· mDNSResponder: unknown
· libjpeg: 8b
· libvpx: 1.6.1
TeamViewer不仅使用libvpx(编解码器开发包),而且都是2017年1月的旧版本。
以下是Google发布的补丁,其中我感兴趣的函数是vp8_deblock,如下所示。
vp8_deblock函数的代码片段,易受CVE-2018-6155攻击
现在让IDA恢复分析进程,然后继续编译libvpx版本1.6.1的Karta配置。配置完成后,在IDA完成对二进制文件的分析之后,我运行了Karta的对比器插件。如下所示,对比器发现了易受攻击的函数:
Karta的对比结果显示它与易受攻击的函数相对比(用蓝色突出显示)
在我将结果导回IDA之后,我可以通过数字常量清楚地验证这是正确的对比。
如IDA Pro所示,易受攻击的函数与我的插件相对比
此时,我已经成功在TeamViewer程序中发现了一个漏洞,甚至还知道了在调试时确切地放置断点的位置。
整个漏洞发现过程大约需要2个小时,因为IDA的分析非常耗时,原因在于TeamViewer是一个非常大的程序,包含超过143000个函数。
Karta是如何运行的?
101个函数的二进制对比
简而言之,二进制对比可以归结为以下这个过程:如果我们想检查两个函数(一个来自已编译的开源,另一个来自二进制)是否表示同一个函数,为了能够比较这两个函数,我们需要将它们转换成用统一的方法表示的内容,通常称为“规范表示”。这种表示通常包括我们从函数中提取的一组特性:数字常量列表、字符串列表、汇编指令的数量等等。
当我们尝试对比一组相关函数时,例如,在一个已编译的开源项目中,我们将多余的信息存储在规范表示中,以便对函数之间的关系进行编码:被调用函数列表(被调用者),列表调用我的函数(调用者)等。使用此信息,我可以尝试根据控制流图(CFG)中的规则或位置对比两个函数。
此时,我们可以使用一些传统的二进制对比工具,如BinDiff或Diaphora。虽然每个对比工具都有自己独特的巧妙对比启发式算法,但它们都基于相同的规则,以比较两个规范表示并对结果进行评分,这意味着二进制对比工具首先将所有函数转换为它们的规范表示形式。
避免内存崩溃
当分析一个包含大约65000个函数的二进制文件时,比如我使用的OfficeJet打印机的固件,为所有函数构建规范表示的过程根本无法扩展。它需要很长时间(通常超过一个小时),并且可以在磁盘空间中消耗超过3GB的空间。这意味着,稍后将此数据集加载到内存中常常会导致对比程序崩溃。
如果我们想要对比巨大的二进制文件中的任何内容,就需要改变对比策略。由于开源库相对较小,我可以将问题描述为:
· M——我的开源函数数量;
· N——我的二进制文件中的函数数量;
由于我希望在大小为N的二进制中对比数量为M的函数,其中M << N,是使用依赖于M而不是N的内存。
二进制地址空间的图解,我试图在其中对比我的库
链接器位置的寻找
如果我将稍后讨论的特殊情况排除在外的话,我可以跳过编译过程直接使用以下步骤进行链接:
1.编译器独立编译每个源文件,并创建一个对比的二进制文件(.o用于gcc,.obj用于visual studio);
2.链接器将所有二进制文件组合成一个二进制大对象(一个可以存储二进制文件的“容器”);
3.在链接阶段,此大对象将按原样插入最终程序或固件;
这导致两个重要的结果:
1.编译后的源代码包含在固件或可执行文件中的一个连续的大对象中;
2.一旦我们找到了这个大对象的一个标志(被称为锚点),我们就可以根据这个大对象中应该包含的函数的数量来推测二进制中这个大对象位置的下限和上限。
其实,这也是Karta所依据的基本规则。
构建函数关系地图
Karta是一个源代码辅助工具,通过利用源代码中的信息,我可以构建一个映射:哪些函数包含在哪个文件中,以及该库包含哪些文件。
以下是对比二进制文件库的过程:
1. 从一个基本标识符脚本开始,该脚本检查二进制文件是否使用了该库,以及使用的版本——O(N)时间和O(1)内存消耗;
2.识别后,扫描二进制文件以搜索锚函数——O(N)时间和O(1)内存消耗;
3. 使用定位锚函数来放大二进制函数的推测范围,这些函数可能是库的一部分—O(1)时间和O(1)内存消耗;
4.此时,所有逻辑都将出现在聚焦范围内,其大小为O(M)。
以下是具体的示例:
1.我有一个含有322个函数的库,利用此方法,我找到了5个锚函数;
2.最低的锚点是二进制文件中的函数#2622;
3.最高的锚点是二进制文件中的函数#2842;
4.锚点之间包含的范围包括221(2842 – 2622 + 1 = 221)个函数;
5.我们还需要找到101(322 – 221 = 101)个函数;
6.为了安全起见,在我的方法中,我会在第一个锚点之前包含101个函数,在最后一个锚点之后也包含101个函数。
总的来说,函数的总数量: 423(101 + 221 + 101 = 423)个函数<< 65000个完整的二进制文件。
我现在要做的只是为聚焦功能构建规范表示,从而大大提高了我从这一点开始的性能。
注意:这个映射可以为构建函数关系地图提供进一步的帮助,因为来自文件a.c的foo()函数应该只与a.c中的函数对比,这样就不需要将它与我们已经标识为驻留在不同文件中的函数进行比较。
选择锚点
从本质上讲,这些锚点函数在规范表示之前都是对比的,这就限制了我在搜索时可以使用的特性。此外,我希望锚点成为库的唯一地标识,这样,我正在处理的二进制文件中的其他库就不会有任何误报。
在不知道所有开放源代码的情况下,为复杂的特性确定标准非常困难。尽管如此,我还是编写了一些在实践中似乎很有效的启发式算法。
IDA Pro中的OpenSSL函数SHA224_Init
我选择这个函数是因为它有惟一的数值常量,且可配置确切的规则,你可以在其中找到src/config/anchor_config.py文件。
对比步骤过程
现在我们已经知道了Karta对比引擎背后的主要逻辑是什么,下来就让我完整介绍对比的详细步骤。
标识符
每个受支持的库都有一个标识符,我试图在二进制文件中找到它。由于大多数开源代码都包括唯一字符串,且它们通常都具有完整版本详细信息,因此大多数标识符都是基于字符串的,并且是为他们试图识别的库配置的。一旦找到库,标识符就尝试提取版本信息,并对可执行或固件使用的确切版本进行指纹识别。
开源项目是不可能在编译后的二进制文件中试图隐藏的,这些库不仅通常包含一个简短的Google搜索,以识别字符串所在原始库的唯一线索。不过,它们通常包含不必要的信息。以下是一个来自libpng库的版权声明的示例,它是一个使用二进制文件编译的字符串。
编译后的二进制文件中包含libpng的版权字符串
如你所见,在大多数用例中,识别可执行程序中是否存在开源库相对比较容易。
锚点搜索
使用标识符中的信息,就可以加载特定库版本的配置(基于.json *)。第一步是扫描二进制文件,查找与库的锚函数对比的惟一数值常量和惟一字符串。如果没有锚点,我无法扩展库并继续对比过程。
对比开始后,整个配置都会加载到内存中。由于我们不需要对配置发出查询,这样就不需要使用更流行的 sqlite 数据库了,这种从sqlite到json的转换,会导致配置文件的大小大幅减小。
根据绘制的文件映射关系图,我可以精确定位包含对比锚函数的每个文件的位置,并估计其下限和上限(使用前面描述的相同逻辑)。
对比结果的示例图
文件提示
由于许多开源项目往往包含源文件名称的调试或跟踪字符串,通常,这些字符串位于上述源文件的函数中,这意味着我们可以使用它们作为文件提示。绘制的文件映射关系图后,我可以使用这些提示来确定其他文件的位置。
定位代理
由于代理是本地唯一的函数,它也可以称为本地锚。在它的文件中,它是一个锚,但它包含的常量弱于全局锚所需的常量,每个定位的文件都会尝试对比自己的代理。
优化链接器
在上面的讲解中,我假设了一种前提,就是开源将被编译为单个连续的大对象,并且内部文件不会彼此模糊处理。不幸的是,实际情况并非如此。
实际上,当我最初尝试在Adobe PDF的二进制文件(2d.x3d)中对比libtiff时,得到的结果并不理想:只有176/500个函数对比。经过调查,该链接器似乎与具有相同二进制结构的函数混淆了。例如,用不同的名称或不同的名称范围(来自不同文件的静态函数)实现了两次的函数。
来自libtiff的两个相同的函数,其实它们位于不同的文件中
来自IDA Pro的分析,显示使用了左边的函数而不是右边的函数
虽然这种优化减少了可执行文件的大小,但它由于会进行模糊处理,且改变了控制流程图。且稍后进行几次快速检查,我发现这种优化也会破坏其他二进制对比工具的对比结果。
我决定像处理链接器那样解决这个问题,在编译开源库的规范表示时,我对每个函数的链接器视图进行哈希,并将其存储为唯一的函数ID。 起初我自己对字节进行了哈希处理,但是当两个函数引用相同的全局变量并且该变量驻留在每个文件中的不同偏移量时,会引入一些错误。参见下图,我通过对大多数操作码的字节进行哈希,并在引用导出的全局变量时对指令的文本进行哈希,解决了这个问题。
文件tif_dirwrite.c中的函数TIFFClampDoubleToFloat()
文件tif_dir.c中的函数TIFFClampDoubleToFloat()
在对比过程中,对比器会查找任何可能合并的线索。当对比器在控制流图中发现两个函数是相同函数时,合并就会发生。此时,二进制函数就知道它所代表的源函数的数量,并且将保存它对比的合并源函数的列表。
使用此优化,我现在可以修复控制流图中的异常,因为每个检测到的合并都有效地将控制流图扩展到链接器优化之前的原始状态。在相同的二进制文件(2d.x3d)上再次测试时,结果明显优化:对比了248/500个函数,对比度提高了41%。
可以看到,Karta可以让链接器对函数_TIFFNoFixupTags的优化:
来自Karta的对比结果,成功地标识了链接合并函数
对比结果
现在是测试Karta如何处理原始的OfficeJet固件的时候了,我在计算机上的虚拟机(VM)中测试了这个插件,结果如下:
测试的OfficeJet固件上的对比结果
可以看到,不到30秒就可以对比一个包含300个函数(如libpng)的开源程序。此外,我还能够对比所有引用的库函数。
注意事项
1.当Karta处理函数的规范表示时,它与架构无关;
2.因为我的对比是从对比的开源库的角度进行的,所以我还可以推导出关于“外部”函数的信息,这些函数不是库的一部分,但是可以从库中调用。例如,libpng使用zlib。
在libpng对比过程中对比的外部zlib函数
此外,在大多数情况下,我能够对比标准库中的函数,例如:memcpy,memcmp,malloc等。
与已知的BinDiff工具的比较
由于我无法比较所有现有的二进制对比工具,所以我选择关注以下常用的工具:
BinDiff:BinDiff是二进制文件的比较二进制对比工具,可以帮助漏洞研究人员和工程师快速找到反汇编代码中的差异和相似之处。使用BinDiff,你可以识别并隔离供应商提供的修补程序中的漏洞修复程序。你还可以在同一二进制文件的多个版本的反汇编之间借用符号和注释,或使用BinDiff收集代码被盗或专利侵权的证据。
以下,我会对它们是否开源 、是否支持大型二进制文件、是否有源代码辅助、是否标识版本等方面进行比较。
对比结果表
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。