【终结篇】使用二进制分析使黑盒变为白盒

  • A+
所属分类:安全闲碎

【终结篇】使用二进制分析使黑盒变为白盒

前言

在当下对应用软件进行漏洞挖掘主要有两种方法:静态分析动态分析。现在大家更倾向于结合使用这两种方法,每种方法解决不同的问题。但有时候,静态分析的使用是很受限制的,比如:没有源码的情况下。在本文中,我们将介绍一种很罕见但非常有用的技术,该技术结合了静态分析和动态分析的优点——可执行代码的静态分析

McAfee公司称,2017年网络犯罪在全球所造成的损失约为6000亿美元,相当于全球GDP的0.8%!我们生活在信息技术时代,全球网络和互联网技术在人类活动的各个领域中快速的更新换代。现在网络犯罪已经不再与众不同,统计数据显示当下网络犯罪正呈指数级增长。

现在应用软件的漏洞已经成为了一个严重的问题:根据美国国土安全局的统计,超过90%的成功网络攻击是利用应用程序中的各种漏洞实现的。以下是最著名的漏洞利用方式:

  • SQL注入

  • 缓冲区溢出

  • 跨站脚本

  • 使用不安全的配置

针对存在未声明功能(NDV)和漏洞的软件进行分析是确保应用程序安全性的主要技术。

谈到分析软件NDV(符合信息安全要求)和漏洞的经典且成熟的技术,可分为:

  • 静态代码分析(Static Application Security Testing)

  • 动态代码分析(Dynamic Application Security Testing)

然而,还有另一种技术—IAST(交互式分析),它本质上是动态分析的一种(在分析过程中,附加辅助代理会观察应用程序执行期间发生的情况)。RASP技术(运行时应用程序自我防御),时常在许多分析工具中提到,但它更像是一种保护工具。

动态分析(黑盒分析法)是一种在程序运行时进行检测的方法。

  优点:

  1. 由于漏洞存在于可执行程序中,在程序运行该漏洞时便可检测到错误。所以动态分析产生的误报会少于静态分析。

  2. 无需源代码即可执行分析。

  缺点:

  1. 对代码的覆盖力度不够,所以存在发现不了漏洞的风险。例如,动态分析无法找到使用了弱加密或书签(如:”临时炸弹”)这类的相关漏洞。

  2. 必须运行应用程序,在某些情况下可能很困难。比如有些应用程序的启动需要复杂的配置以及各种集成的配置。而且,为了使结果尽可能的精确,有必要重现“真实环境”,但在不损害程序的前提下实现这一点是很困难的。

静态分析(白盒分析法)是一种不需要运行程序的分析方法。

  优点:

  1. 全面覆盖代码,可以搜索更多的漏洞。

  2. 分析不依赖于程序执行的环境。

  3. 在没有可执行文件的情况下为模块或程序编写代码的初期进行测试。将类似的解决方案灵活地集成到开发初期的SDLC(软件开发生命周期)。

 唯一不足:

存在误报:所以需要评估分析器是发生了真正的错误,还是出现了误报。

综上所述,两种分析方法各有千秋。我们能否给出一种既能包含两者的优点又能最小化其缺点的方法呢?二进制分析就可以做到——通过静态分析搜索可执行文件中的漏洞

二进制分析技术

【终结篇】使用二进制分析使黑盒变为白盒

二进制分析允许在没有源代码的情况下使用静态分析技术,比如在第三方的情况下。此外,与动态分析相比,代码的覆盖是完整的。使用二进制分析,可以验证在没有源代码的开发过程中使用的第三方库。此外,使用二进制分析,可以通过比较存储库中的源代码分析结果和来自作战服务器的可执行代码来执行发布的控制检查。

在二进制分析过程中,二进制形式会被转变成一种中间形式(内部形式或者代码模型)以便进一步分析。然后,对内部形式进行静态分析,以便补充进一步监测漏洞和未声明功能(NDV)所需的信息。在下一阶段,使用漏洞和未声明功能(NDV)规则进行搜索。

我们在这篇文章(https://habr.com/ru/company/solarsecurity/blog/439286/)中详细介绍了静态分析。与使用编译原理的元素(词法及句法分析)进行建模的源代码分析不同,二进制分析使用的是反向编译(反汇编,反编译,反混淆)来构建模型。

术语

我们正在谈论的是对没有调试信息(debug info)的可执行文件进行分析。存在调试信息会使任务将会被大大简化。

本文中,我们将对java字节码的分析称为二进制分析,虽然这有一点不严谨。当然分析JVM字节码要比对C/C++,Objective-C/Swift进行二进制分析简单。但字节码和二进制码的分析流程是相似的,本文中描述的主要困难涉及到二进制代码的分析。

反编译——是根据二进制代码恢复源代码的过程。反向转换的元素——反汇编(从二进制形式中获得汇编代码),将汇编代码转换为三地址代码或者其他表达形式,从而恢复源代码级别的结构。

混淆——是一种保留源代码功能的转换,可以使反编译更加的困难,也能使二进制代码变得更加难以理解。

反混淆——是对混淆的一种逆变换,可以应用在源代码上,也可以用在二进制代码上。

如何分析结果

让我们先从后往前,即从二进制代码开始,因为对得到的二进制代码进行分析一般都会放在第一位。

对于从事二进制代码分析的人而言,将漏洞和未声明的功能(NDV)映射到源码上是非常重要的。为了实现映射,便要采用上面所说的技术:如果目标使用了混淆变换,那么我们便启动反混淆并将二进制代码反汇编成源代码。也就是说,最后可以在反编译后的代码上发现漏洞。

在反汇编过程中,即便我们反编译的是JVM字节码,有些信息也无法正确恢复,所以要在比较接近二进制代码的表示方式中进行分析。这便出现了一个问题:如何找到二进制代码中的漏洞,并在源码中定位它?针对JVM字节码我们给出了一个解决方案(https://habr.com/ru/company/solarsecurity/blog/312056/)。这对二进制代码也是类似的,只是一个技术问题。

此处我们再重申一遍——没有调试信息我们是在没有调试信息的情况下进行二进制代码分析的。

让我们再来看另一个问题——反编译代码是否足以发现和定位漏洞?

让我们来逐一列举:

  1. 对于JVM字节码来说,反编译就足够了,反编译在实践中总是能够找到漏洞。

  2. 定位漏洞的难点是类名和函数的重命名。但在实际应用中显示,了解漏洞比定义漏洞在哪个文件中更重要。当需要修补漏洞时便需要定位漏洞,但在这种情况下开发人员可以通过反编译的代码就知道漏洞位置。

  3. 对于二进制代码而言(比如C++),一切都会复杂的多。没有工具可以完全恢复随机的C++代码。但是,我们不需要全编译代码,需要的是那些能够让我们理解漏洞的高质量代码。

  4. 通过高质量的反编译来理解漏洞是可行的,但是为了实现它必须要解决许多复杂的问题。

  5. 对于C/C++而言,定位漏洞会更加的复杂——在编译过程中,字符名称会以多种方式丢失且无法恢复。

  6. 对于Objective-C而言定位漏洞会简单一点,因为在Objective-C中有函数名称。

  7. 混淆问题很特别,有许多复杂的变换可能会使漏洞的反编译和映射变得更难处理。但实际上,事实证明,一个好的反编译器可以应对大多数令人困惑的转换(要记住,我们需要高质量的代码来了解漏洞)。

 结论 ——通常情况下反编译都可以用来了解和验证漏洞。

二进制分析的复杂性和细节问题

现在让我们来分析真正的二进制代码,这里我们将用C/C++,Objective—C和Swift的分析为例。

分析二进制代码的难点在反汇编阶段。此处非常重要的一步是——将二进制形式分化分为相应的子程序。之后在子程序中挑选出汇编指令——这是个技术问题。关于此细节可以在这篇文章中找到(https://cyberrus.com/wp-content/uploads/2016/02/53-60-114-16_8.-%D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B0%D0%BD%D0%B4%D1%80%D0%BE%D0%B2.pdf),这里我们只简要的说明一下。

让我们将x86架构当作例子。在x86中指令并没有固定的长度。在二进制形式中并没有明确的区分代码和数据:导入表,虚函数表可以在代码段中。转换表可以位于基函数块之间。所以相应的,我们需要将代码从数据中分离出来,要了解代码段的开始位置和结束位置。

一般最常见的有两种方法来确定子程序的起始地址。在第一种方法中子程序的起始地址是有标准prolog来确定(在x86中——prolog为push ebp; move ebp,esp)。第二种方法会从程序入口处递归的遍历代码来识别子程序的调用指令。通过识别转移指令来完成遍历。有时也可以将两种方法进行结合:当找到prolog起始地址时开始递归遍历。

实际上,事实证明这样的方法识别代码的效率很低,因为并非所有的函数都有标准prolog,并且存在对函数的间接调用和转移。

通过以下的启发我们可以来改进这些算法:

  1. 在大型的测试平台上寻找更为精确的prolog表(新的prolog或者prolog标准版本)。

  2. 自动查找虚拟函数表,并通过该表来查找子程序的起始位置。

  3. 可以基于与异常处理机制相关联的二进制代码的部分来寻找子程序的起始地址和一些其他构造。

  4. 从调用指令中确定子程序的起始地址。

  5. 递归调用子程序来识别起始地址。间接转移和无返回的函数会增加困难度,但可以通过分析导入表和辨别swith结构来解决。

在反向转换期间还有一件重要的事:为了在之后的步骤中能够正常查找漏洞,需要在二进制形式中正确的识别标准函数。标准函数可能是静态链接,甚至可能是内联。这里基本的识别算法是:通过标签及其变体来搜索,可以选择合适的AC自动机(Aho-Corasick)算法来搜索。为了收集标签,需要在不同的条件下预先对库进行分析,并分配给它们一些不变的字节。

接下来

 我们已经研究了二进制形式的反向转换的初始阶段——反汇编。在此阶段可能会丢失部分代码,这将对分析结果造成巨大的影响。

【终结篇】使用二进制分析使黑盒变为白盒

接下来发生了很多有趣的事情。简要说说主要任务,不会详谈。我们无法在此明确写出技术诀窍,也没有非常有趣的技术和解决方案。

  1. 将汇编代码转换成可分析的形式。可以使用不同的字节码,对于c语言我们可以选择LLVM。LLVM由社区支持和开发,它对于静态分析很有用。在这个阶段中我们需要注意大量的细节。例如,为了简明分析结果,需要检测哪些变量出现在了堆栈中。需要在字节码指令中配置最佳的汇编指令集。

  2. 对高层次的结构进行恢复(如循环,分支等)。若从汇编代码中恢复的原始结构越精确,那么最后的分析结果便越好。这些结构的恢复可以借助于CFG(控制流图)或者其他的图形式程序来完成。

  3. 执行静态分析算法。总的来说,我们从源头或二进制码中获得的内部表示不是很重要——我们还是需要建立CFG,使用数据流分析算法或者其他静态分析的算法。

结论

我们已经讲述了如何在没有源码的条件下进行静态分析。根据我们与客户交流的经验,事实证明该技术非常受欢迎。然而此项技术却很少见:因为二进制分析很特别,它需要很多复杂且先进的静态分析算法以及反向编译技术。

译文申明:

本文是翻译文章,文章原作者:yaleksar。

原文地址:

https://habr.com/ru/company/solarsecurity/blog/460949/

译文仅供参考,具体内容表达以及含义原文为准,未经许可,禁止转载。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: