目前,有两种主要的方法来搜索应用程序中的漏洞-静态和动态分析。 两种方法各有利弊。 市场得出的结论是,必须同时使用两种方法-它们可以解决略有不同的问题,并且结果不同。 但是,在某些情况下,静态分析的使用会受到限制-例如,在没有源代码的情况下。 在本文中,我们将讨论一种非常罕有但非常有用的技术,它使您可以结合使用静态方法和动态方法的优点-可执行代码的静态分析。
远方来吧根据McAfee防病毒公司的数据,2017年全球网络犯罪造成的损失总计约6,000亿美元,相当于全球GDP的0.8%。 我们生活在信息技术时代,其具体特征一直是全球网络和Internet技术在人类活动的所有领域中的快速集成。 现在,网络犯罪不再与众不同。
统计数据显示,网络犯罪呈指数级增长。
应用程序的漏洞已成为一个严重的问题:根据美国国土安全部的数据,超过90%的成功网络攻击是使用应用程序中的各种漏洞进行的。 最著名的漏洞利用方法是:
- SQL注入
- 缓冲区溢出
- crossite脚本
- 使用不安全的配置。
分析软件(软件)中是否存在未声明的功能(NDV)和漏洞是确保应用程序安全性的主要技术。
谈到用于分析软件漏洞和NDV(符合信息安全要求)的经典且行之有效的技术,我们可以区分:
- 静态代码分析(静态应用程序安全性测试);
- 动态代码分析(动态应用程序安全性测试)。
有IAST(交互式分析),但是它本质上是动态的(在分析过程中,附加的代理会观察应用程序执行期间发生的情况)。 RASP(运行时应用程序自防御)(有时在许多分析工具中也提到过)很可能是一种保护工具。
动态分析(“黑匣子”方法)是在执行过程中进行的程序检查。 可以从该方法中区别以下优点。
- 由于漏洞在可执行程序中,并且可以通过其操作检测到错误,因此误报的生成少于静态分析的生成。
- 无需源代码即可执行分析。
但是也有缺点。
- 该代码的覆盖范围不完整,因此存在遗漏漏洞的风险。 例如,动态分析无法找到与使用弱加密技术或“临时炸弹”之类的书签相关的漏洞。
- 需要运行应用程序,这在某些情况下可能很困难。 启动应用程序可能需要复杂的配置以及各种集成的配置。 此外,为了使结果尽可能准确,有必要重现“战斗环境”,但是要在不损害软件的情况下完全实现这一目标是困难的。
静态分析(“白盒”方法)是一种程序测试,其中的程序不执行。
我们列出了好处。
- 完整覆盖了代码,这导致寻找更多漏洞。
- 不依赖于将在其中执行程序的环境。
- 在没有可执行文件的情况下,在为模块或程序编写代码的初始阶段实现测试的能力。 这样一来,您就可以在开发开始时就将类似的解决方案灵活地集成到SDLC(软件开发生命周期软件开发生命周期)中。
该方法的唯一缺点是存在误报:需要评估分析仪指示的是真正的错误,还是可能出现这种误报。
如我们所见,两种分析方法都有优点和缺点。 但是,有可能以任何方式利用这些方法的优点,同时将缺点最小化吗? 是的,如果您应用二进制分析-通过静态分析在可执行文件中搜索漏洞。
二进制分析或可执行文件分析技术
二进制分析允许在没有源代码的情况下进行静态分析,例如,在第三方承包商的情况下。 此外,与动态分析方法的应用相比,代码覆盖范围将是完整的。 使用二进制分析,您可以验证在开发过程中使用的没有源代码的第三方库。 同样,使用二进制分析,您可以对发布进行控制检查,比较存储库中源代码和作战服务器中可执行代码的分析结果。
在二进制分析过程中,将二进制图像转换为中间表示形式(内部表示形式或代码模型)以进行进一步分析。 之后,将静态分析算法应用于内部表示。 结果,当前模型被补充了进一步检测漏洞和NDV所需的信息。 在下一阶段,将应用规则搜索漏洞和NDV。
我们
在上一篇文章中写了更多有关静态分析方案
的文章 。 与使用编译理论元素(词法分析,句法分析)构建模型的源代码分析不同,二进制分析使用反向翻译理论对模型进行反汇编,反编译和反混淆。
关于条款的一点点
我们正在谈论分析没有调试信息的可执行文件。 使用调试信息,可以大大简化任务,但是,如果有调试信息,则很有可能是源代码,并且任务变得无关紧要。
在本文中,我们称Java字节码分析也为二进制分析,尽管这并不完全正确。 我们这样做是为了简化文本。 当然,分析JVM字节码的任务比分析二进制C / C ++代码和Objective-C / Swift更简单。 但是对于字节码和二进制码,一般的分析方案是相似的。 本文中描述的主要困难特别涉及二进制代码的分析。
反编译是从二进制代码恢复源代码的过程。 您可以谈论逆向翻译的要素-分解(从二进制映像获得汇编代码),将汇编程序转换为三地址代码或其他表示形式,恢复源代码级别的构造。
模糊处理-保留源代码功能的转换,但是使反编译和理解生成的二进制图像变得困难。 去模糊是逆变换。 混淆可以应用于源代码级别和二进制代码级别。
如何看结果?
让我们从结尾开始,但是通常首先要问查看二进制分析结果的问题。
对于分析二进制代码以将漏洞和NDV映射到源代码的专家而言,这一点很重要。 为此,如果应用了令人困惑的转换,并且最终将二进制代码反编译为源代码,那么在最后阶段,反混淆(解散)过程将开始。 也就是说,可以在反编译的代码上演示漏洞。
在反编译过程中,即使我们反编译JVM字节码,也无法正确还原某些信息,因此分析本身是在接近二进制代码的表示形式上进行的。 因此,出现了一个问题:如何找到二进制代码中的漏洞,将其本地化? 关于JVM字节码的问题的解决方案
在我们有关搜索Java字节码中的漏洞的文章中进行了描述。 二进制代码的解决方案是相似的,即一个技术问题。
让我们重复重要的警告-我们正在谈论没有调试信息的二进制代码分析。 在存在调试信息的情况下,任务将大大简化。
我们被要求显示结果的主要问题是反编译代码是否足以理解和定位漏洞。
以下是有关此主题的一些想法。
- 如果我们谈论的是JVM字节码,那么通常的答案是“是”-字节码的反编译质量很好。 几乎总是可以找出漏洞是什么。
- 可能干扰该漏洞的定性本地化的原因是简单的混淆,例如重命名类名和功能。 但是,实际上,通常会发现,了解漏洞比确定漏洞在哪个文件中更为重要。 当有人可以修复漏洞时,需要进行本地化,但是在这种情况下,开发人员还将从反编译的代码中了解漏洞的位置。
- 当然,当我们谈论二进制代码(例如C ++)的分析时,一切都变得更加复杂。 没有工具可以完全恢复随机的C ++代码。 但是,我们这种情况的特殊之处在于,我们以后不需要编译代码:我们需要足够的质量来理解漏洞。
- 通常,您可以获得足以理解发现的漏洞的反编译质量。 为此,您需要解决很多复杂的问题,但是您可以解决它们(下面我们将简要讨论)。
- 对于C / C ++,本地化漏洞甚至更加困难-字符名称在编译过程中会以多种方式丢失,因此无法还原。
- Objective-C中的情况要好一些-那里有函数名,并且更容易定位漏洞。
- 混淆问题是分开的。 有许多复杂的转换可使漏洞的反编译和映射复杂化。 实际上,事实证明,一个好的反编译器可以处理大多数令人困惑的转换(请记住,我们需要足够的代码质量来了解此漏洞)。
作为结论-大多数情况下,结果表明该漏洞可以被理解和验证。
二元分析的复杂性和细节
这里我们将不讨论字节码:上面已经讲过有关字节码的所有有趣的事情。 最有趣的是对实际二进制代码的分析。 这里我们将以C / C ++,Objective-C和Swift为例进行分析。
即使在拆卸时也会出现很大的困难。 最重要的阶段是将二进制映像划分为子程序。 接下来,在子例程中选择汇编程序指令-一个技术问题。 我们
在《网络安全问题第一期(14)-2016》一文中对此
进行了详细介绍,在此我们将对其进行简要介绍。
作为示例,我们将讨论x86体系结构。 其中的说明没有固定的长度。 在二进制映像中,没有明确划分代码和数据部分:导入表,虚拟功能表可以位于代码部分中,转换表可以位于代码部分中基本功能块之间的间隔中。 因此,您需要能够将代码与数据分离,并了解例程在何处开始以及例程在何处结束。
最常见的两种方法是解决确定子程序的起始地址的问题。 在第一种方法中,子程序的地址由标准序言确定(对于x86体系结构,它为push ebp; mov ebp,esp)。 在第二种方法中,在识别子例程调用指令的情况下,从入口点递归遍历一段代码。 旁路是通过识别分支指令来完成的。 当从序言找到的起始地址开始递归遍历时,也可以使用所描述方法的组合。
实际上,事实证明,由于并非所有函数都具有标准的序言,并且存在间接调用和转换,因此此类方法给出的识别代码所占的百分比很小。
基本算法可以通过以下启发式方法进行改进。
- 在大量的图像测试基础上,找到更准确的序言列表(新序言或标准序言的变体)。
- 您可以自动找到虚拟功能表,并从中选择子程序的起始地址。
- 子程序的起始地址和其他一些构造可以在与异常处理机制相关的二进制代码部分中找到。
- 您可以通过在图像中搜索这些地址并识别呼叫说明来验证起始地址。
- 要搜索边界,可以通过识别起始地址中的指令来对子例程进行递归遍历。 间接转换和不返回功能存在困难。 分析导入表和识别开关结构可以提供帮助。
为了在以后正常查找漏洞,在反向转换期间需要完成的另一项重要工作是识别二进制映像中的标准函数。 标准函数可以静态链接到图像,甚至可以内联。 主要的识别算法是按签名搜索并带有变体;对于该解决方案,您可以提供经过修改的Aho-Korasik算法。 要收集签名,您需要预先分析在不同条件下收集的库图像,并将其选择为不变字节。
接下来是什么
在上一节中,我们检查了二进制图像反向翻译的初始阶段-反汇编。 的确,这个阶段是初步的,但很确定。 在此阶段,您可能会丢失一些代码,这将对分析结果产生巨大影响。
然后发生了很多有趣的事情。 简单说一下主要任务。 我们将不进行详细讨论:细节中可能包含我们无法在此处明确编写的专有技术,或者不是非常有趣的技术和工程解决方案。
- 将汇编代码转换为可以执行分析的中间表示形式。 您可以使用各种字节码。 对于C语言,LLVM似乎是一个不错的选择。 LLVM得到了社区的积极支持和开发,其基础结构(包括对静态分析有用的)目前令人印象深刻。 在此阶段,您需要注意很多细节。 例如,您需要检测堆栈上要寻址的变量,以免在结果视图中乘以实体。 您需要在字节码指令中配置汇编程序指令集的最佳显示。
- 恢复高级结构(例如循环,分支)。 从汇编代码中恢复原始构造的准确性越高,分析的质量就越好。 使用CFG上的图论元素(控制流程图)和程序的某些其他图形表示形式,可以恢复这种构造。
- 进行静态分析算法。 有细节。 通常,是从源头还是从二进制文件获取内部表示,并不是很重要-我们都还需要构建CFG,应用数据流分析算法和其他典型的静态算法。 分析从二进制文件获得的视图时,有一些功能,但是它们更具技术性。
结论
我们讨论了没有源代码时如何进行静态分析。 根据与客户沟通的经验,事实证明该技术非常有需求。 但是,该技术很少见:二进制分析问题并非微不足道,其解决方案需要复杂的高科技静态分析和反向翻译算法。
本文是与Solar appScreener分析师Anton Prokofiev合作撰写的