首页
社区
课程
招聘
[翻译]通过静态分析的Use-After-Free检测的介绍
2018-4-25 20:03 5159

[翻译]通过静态分析的Use-After-Free检测的介绍

2018-4-25 20:03
5159

Use-After-Free(释放后使用)是一类有名的漏洞,经常在现代漏洞利用中被利用(参见Pwn2own 2016)。在研究项目AnaStaSec中,AMoSSYS致力于如何在二进制代码中静态检测此类漏洞。在这篇博文中,我们解释了科学社区如何建议检测此类漏洞。这种最高水平的技术的目标是定义一种全球方法论,之后会使我们建立满足我们需求的概念性证明工具。

Use-After-Free(UAF)描述

UAF的原理非常好理解。当一个程序试图访问之前已经被释放的内存区域时,产生Use-After-Free漏洞。在这种情况下,我们称为悬空(dangling)的指针被创建,并指向一个内存中被释放的对象。举个例子,下面的代码会导致UAF。如果代码被执行并且执行了错误的分支,由于ptr指向了无效的内存区域,因此可能会发生未定义的行为。

char * ptr = malloc(SIZE);
…
if (error){
    free(ptr);
}
…
printf("%s", ptr);

Figure 1: Use-After-Free示例

 

换句话说,当这三步发生时,会出现UAF漏洞:

  • 一块内存区域被分配并且有一个指针指向它。
  • 内存区域被释放但是指针是可用的。
  • 指针被使用并访问之前释放掉的内存。

大多时候,UAF漏洞能够导致信息泄漏。但是,更有趣的是UAF导致代码执行。这可通过几个步骤完成:

  • 程序分配并释放内存块A。
  • 攻击者分配内存块B,重用之前分配给内存块A的内存。
  • 攻击者写入数据到B块。
  • 程序使用释放掉的块A,访问攻击者留在那里的数据。

在C++中,这通常是在A类被释放并且攻击者成功在A的内存区域上分配B类。之后,如果A类中的一个方法被调用,攻击者装入的B类中的代码将会被执行。
既然我们已经阐明了UAF是什么,让我们深入了解社区是如何尝试检测此类漏洞的。

静态和动态分析的优缺点

分析二进制文件的方式有两种:静态和动态。动态的分析整个代码是十分困难的,因为没有一个简单的方案来生成能执行二进制文件每条路径的输入。由于这个原因,当更想关注代码覆盖时,静态方法更合适。

 

然而,尽管有这样的看法,文献[Lee15][Cab12] 说明了大多数学术关于Use-After-Free检测的学术研究关注在动态分析上。这主要是因为动态分析可以使人们轻松检查同一指针的副本,被成为别名。换句话说,当考虑到动态方法时,内存中的值是可以直接可访问的,从分析的角度来看,这是不可忽略的。当进行动态分析时,人们会获得精确的结果,但是会失去一些完整性。

 

从我们的角度看,我们仅想要关注静态分析。学术界确定了两个难题:

  1. 最大的难题是如何管理程序中的循环。的确,当计算循环中被处理变量的所有可能性时,人们需要知道循环要执行多少次。这个问题通常被认为是停止问题( halting problem)。在可计算理论中,停止问题是要知道程序是否到达终点或者一直继续执行。不幸的是,这个问题被证明是不可判定的。换句话说,没有一种通用的算法来解决所有程序带有所有可能输入的停止问题。在这种情况下,静态分析工具被迫做简化来解决这个问题。
  2. 另一个难题是表示内存的方式。一个平常的解决方案是保存一个大数组,里面存储指针的内存值。然而,这并不像看起来那么简单。例如,一个内存地址可能被填充许多值,或者一些变量有许多的地址。而且,如果有太多可能的值,保存每个单独的值是不合理的。如之前所说,对于这种内存表示方法需要做一些简化。

为了减少静态分析的复杂性,一些文献如[Ye14] 或者工具如 PolyspaceFrama-C运行在C源码级,因为这个级别包含了最多的信息。然而,人们通常不能访问要分析应用的源代码。

从二进制到中间表示

如果我们关注于二进制分析,第一步是构建相关的控制流程图(CFG)。控制流程图是定向图,它表示了程序执行过程中所有可能遍历的路径。CFG的每个节点都是一条指令。由边连起来的两个节点代表可能连续执行的两条指令。一个节点通过两条边连接到其他的节点表示条件跳转。因此,创建CFG可以讲二进制代码组织为指令的逻辑序列。一个众所周知的构建一个可执行文件的CFG的方法来是使用反汇编器IDA Pro)。

 

当处理二进制文件时,学术论文似乎总是和对UAF漏洞一样的方式处理。论文[Gol10][Fei14]详细介绍这些不同的步骤:

  • 事实似乎表明循环对Use-After-Free的存在没有很大的影响。因此,展开循环只保留第一次迭代是处理二进制文件的强制步骤。对于我们之前解释的内容,这步允许我们避免之前 1)中解释的暂停问题。
  • 为了让根本问题 2)变得容易,中间表示被构建用来表示具有独立的处理器架构。例如,x86汇编代码太复杂因为它有太多指令。因此,解决方案是对一小组指令进行分析。使用中间表示法,每条指令被转换成几条原子指令。一种IR的选择取决于人们想要分析的类型。大多情况下,使用逆向工程中间语言(REIL),但是BAP([Bru11])或者Bincoa([Bar11])是学术工作中被使用的IR。

仅仅17条不同的指令组成了REIL IR,每条指令最多只有一个结果值。从原生x86汇编转成REIL代码可以由BinNavi这样的工具提供,该工具是现在由Google(之前是Zynamics)开发的一个开源项目。BinNavi可以将IDA Pro数据库作为输入,非常方便。

符号执行与抽象解释

一旦构建完中间表达式,如今有两种方法用来分析二进制的行为:抽象解释([Gol10][Fei14])或符号执行([Ye14])。
没有特殊,通过符号执行的分析遵循指令集。符号执行使用程序中表达式和变量的符号,而不是获取实际的输入值。因此,分析不会跟踪变量的值,但会使用符号表示每个值来构建数学表达式。例如,这些计算出的表达式可以用来检查条件分支。

 

另一方面,抽象解释基于一个程序的分析可以在某种抽象的层次上完成的想法。因此,不需要跟踪每个变量的实际值。语义被抽象语义覆盖,抽象语义允许描述指令对变量的影响。例如,变量可由他们的符号定义。对于“加”指令,操作符号将被检查来设置结果符号。因此,如果操作是+,结果就是+,但是实际的变量值永远不会被计算。可以定义许多抽象域而不是符号。例如,可以通过变量在内存上的间隔值(全局,堆和栈)来跟中变量的值。比较出名的使用这种表示的分析被称为值集分析。(Value Set Analysis,VSA)。

 

作为一个实例,框架monoREIL是一个依赖于REIL IR的VSA引擎。它简化了VSA算法开发,使开发人员在自己的抽象域上执行VSA。

分析中间表示

下一个问题是如何在浏览CFG时实现分析算法。同样,有两种方法:

  • 过程内分析,限定于当前函数范围,
  • 过程间分析,具有进入子函数的能力。

不用说,过程分析要比过程间分析更容易实现。然而,当人们想探测UAF时,它必须能跟踪内存块,从他们的申请到释放。并且,这可能发生在不同函数中。

 

这就是为什么论文[Gol10]提出要先进行过程分析,然后扩大到全局过程间分析。如图 2 所示。对于每一个函数,都会创建一个块。这些块集合了函数的行为以及将他们的输出与输入相关联。因此,当分析被放大到过程间分析时,每个函数调用被之前的该函数的过程分析所替代。这种方法的主要优点是函数即使被调用多次,但仅被分析一次。而且,过程分析是在非常小的代码块上执行,因此他们更加精确。
Figure 2
Figure2:合并许多过程分析的过程间分析

 

论文[Fei14]介绍了另一种解决方案。第二种方法(如图3)包含了内联被调用程序到调用函数中。因此,函数调用不再是问题。这种解决方案可能更容易实现,但是限制在,当函数被调用两次,将会分析两次。因此,这种方法被认为更耗时和内存。
图片描述
图3:通过将函数内联到一个单一函数的过程间分析

检测UAF

在这点上,我们有方法来分析二进制代码语义和遍历控制流程图。我们现在想要检测的是UAF模式。让我们关注下面的定义:UAF的特征是两个事件的发生:

  • 创建个悬挂着的指针,
  • 访问此指针指向的内存

为了检测这种模式,论文[Fei14]在每条语句中建立了一组释放的内存堆区,并在每次使用指针时检查是否它指向了这些释放的内存之一。

 

举个例子,让我们看下面的伪代码。注意,为了简化,该例子不提供复杂的CFG。事实上,处理CFG的方式取决于选择的分析方法和它的实现。该示例的目标仅仅是展示一种方法来检测UAF当代码被分析过。

1. malloc(A);
2. malloc(B);
3. use(A);
4. free(A);
5. use(A);

该伪代码分配了两块由名称A和B引用的内存块。之后内存块A在被释放前访问(使用)。在那之后,内存块再次被访问。

 

通过定义两个域(一组分配的堆元素和一组释放的堆元素),在每个指令出更新这些集合并检查访问这些属于分配的内存块集合的块是可能的。如图4所示:
图片描述
图4:通过域检查来检测Use-After-Free

 

当内存块A再次被访问时,它在上一步中已经被注册为释放的内存块,因此分析可以产生报警:Use-After-Free被检测到。

 

[Ye14]中描述的另一种检测的想要的模式的方法是通过使用一个简单的状态机。这个想法是在申请后,指向(内存)块的指针被设置为“已分配”状态,并且在未被释放时保持这个状态。当他们被释放时,变为“释放”状态。如果一个指针在“释放”状态被使用,将会导致“Use-After-Free”状态。但是,一个指针和它的别名被删除,并且没有引用到内存块,它们是无害的并且,他们变为“结束”状态。这个简单状态机如图5所示。
图片描述
图5:检测Use-After-Free的简单状态机

 

[Gol10]提出的另一种解决方案是使用图论。在这篇论文中,作者检查使用指针的语句是在释放内存的基本组前还是后。如果是之后,他们就发现了一个UAF漏洞。
图片描述
图6:潜在Use-After-Free的图

 

在检测到悬挂指针的所有情况下,分析的最后阶段通过提取导致UAF的子图来臧否UAF漏洞。该子图必须包含让人们人工检查来避免真正(TP)的所有元素。

结语

我们介绍了几种使用静态分析方法处理二进制文件后处理检测Use-After-Free的方法。我们试图说明这种分析触发的不同的问题,并且很容易理解没有直接的解决方案来检测这种错误(的说法)。

 

我们在这项工作中也看到,只有少数研究员把他们的工作以开源项目发布。由Verimag团队的Josselin Feist开发的项目GUEB是其中之一。如果你对这个主题感兴趣(不用担心Ocam代码),我们鼓励你去查看他的Github

 

为了完成(该研究),科研项目AnaStaSecAMOSSYS提供了分析当前关于UAF检测学术成果的机会,目的是利用和增强现有的工具。即使这篇文章没有发布任何东西,我们希望在未来的几年可以做。敬请期待!

参考书目

[lee15]阻止无效悬挂的指针的释放后使用。Byoungyoung Lee, Chengyu Song, Yeongjin Jang, Tielei Wang. s.l. : NDSS Symposium 2015, 2015.
[Cab12]早期检测释放后使用和双重释放漏洞中的悬挂指针。Juan Cabaleero, Gustavo Grieco, Mark Marron, Antionia Nappa. IMDEA Software Institute, Madrid; Spain : ISSTA 2012, 2012.
[Ye14]UAF检查者:可伸缩静态检测或释放后使用漏洞。 Jiay Ye, Chao Zhang, Xinhui Han. s.l. : CCS'14, 2014.
[Gol10] Gola, Vincenzo Iosso.通过静态分析检测别名的旧指针。s.l. : Politecnico di Milano, 2010.
[Fei14]静态检测二进制代码中的释放后使用。Josselin Feist, Laurent Mounier, Marie-Laure Potet. s.l. : Journal of Computer Virology and Hacking Techniques, 201
[Bru11] Brumley, D., Jager, I., Avgerinos, T., Schwartz, E.J.: Bap:二进制分析平台。In: Proceedings of the 23rd International Conference on Computer Aided Verification. CAV’11, pp. 463–469. Springer, Heidelberg (2011)
[Bar11]Bardin, S., Herrmann, P., Leroux, J., Ly, O., Tabary, R.,Vincent, A.:二进制代码分析框架bincoa。In: Proceedings of CAV’11, pp. 165–170. Springer, Berlin (2011)

 

原文链接:http://blog.amossys.fr/intro-to-use-after-free-detection.html
编译:看雪翻译小组 SudoZhange


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回