【翻译】关于代码指针完整性(CPI)的有效性——On the Effectiveness of Code Pointer Integrity

关于代码指针完整性(CPI)的有效性

这项工作是由国防研究与工程助理部长根据空军合同#FA8721-05-C-0002赞助的。意见,解释,结论和建议是作者的观点,不一定得到美国政府的认可。

摘要

内存损坏攻击仍然是破坏现代系统的主要攻击手段。已经提出了许多针对存储器破坏攻击的防御措施,但是它们都有其局限性和弱点。较强的防御措施(例如,传统语言(C/C++)的完全内存安全性)会产生较大的开销,而较弱的防御措施(例如,实际控制流完整性CFI)已被证明是无效的。最近一种称为代码指针完整性(CPI)的技术,通过将内存安全性集中在代码指针上,有望来平衡安全性和性能,从而防止大多数控制劫持攻击,同时保持较低的开销。CPI通过将代码指针存储在指令级隔离保护的安全区域中,来保护对代码指针的访问。

在x86-32上,这种隔离是由硬件强制执行的。在x86-64和ARM上,隔离是通过隐藏信息来实现的。我们表明,对于不支持CPI依赖信息隐藏的分段的架构,可以泄漏CPI的安全区域,然后使用数据指针覆盖进行恶意修改。

我们针对Nginx实施了概念验证漏洞并成功绕过了CPI实现,依靠6秒钟内隐藏的信息以及13次观察到的崩溃。我们还提出了一种不会导致崩溃的攻击,并且能够在98小时内绕过CPI。我们的攻击证明了在安全机制中充分保护机密的重要性,以及在不保证不存在内存泄漏的情况下依靠猜测的难度的危险。

1.简介

尽管付出了巨大的努力,但内存破坏错误以及随之而来的安全漏洞仍然是非受控语言(例如C/C++)的重要问题。它们以代码注入[40]和代码重用[49,14]的形式构成了对现代系统进行攻击的基础[14]。

非受控语言(unmanaged languages)提供的功能(例如低级内存控制,显式内存管理以及对底层硬件的直接访问)使其成为系统开发的理想选择。但是,这种级别的控制会付出巨大的代价,即缺乏内存安全性。用受控语言(managed languages)重写系统代码的成功有限[24],这是因为人们意识到可能会失去诸如垃圾收集之类的机制的控制权,并且需要移植现有几百万行现有C/C++代码以提供类似的功能。不幸的是,将内存安全性改造到C/C++应用程序中可能会导致大量开销(速度降低多达4倍)[36]或可能需要注释[37,28]。

针对这些已知的缺点,研究集中在可以降低代码注入和代码重用攻击风险而又没有明显的性能开销和可用性约束的替代技术上。一种这样的技术是数据执行保护(DEP)。DEP使系统可以使用内存保护将页面标记为不可执行,这可以限制在执行过程中引入新的可执行代码。不幸的是,DEP可以使用代码重用攻击(例如面向返回的编程[11,17],面向跳转的编程[10]和返回库的攻击[56])来击败。

基于随机化的技术,例如地址空间布局随机化(ASLR)[43]及其媒介[30]和细粒度的变体[57]使代码和数据段的位置随机化,从而提供了针对代码重用攻击的概率保证。不幸的是,最近的攻击表明,即使是细粒度的内存随机化技术也可能容易受到内存泄露攻击的影响[52]。内存泄漏可以采取直接内存泄漏的形式[53](即作为系统输出的一部分),也可以采取间接内存泄漏的形式,其中使用故障或定时侧通道分析攻击来泄漏内存内容[9,47]。其他形式的基于随机化的技术包括指令集随机化(ISR)[8]或多样化编译器技术[26]。不幸的是,它们也容易受到信息泄漏攻击的攻击[53,47]。

控制流完整性(CFI)是一项经过广泛研究的运行时强制技术,可以为代码注入和代码重用攻击提供切实的保护[3,61,62]。CFI通过禁止应用程序的控制流图(CFG)中不存在的传输来提供预期的控制流传输的运行时实施。但是,精确执行CFI可能会产生较大的开销[3]。这激励了更实用的CFI变体的开发,这些变体具有较低的性能开销,但实施了较弱的限制[61,62]。例如,放宽了控制传输检查,以允许传输到任何有效的跳转目标,而不是正确的目标。不幸的是,这些实现已被证明是无效的,因为它们允许足够的有效传输以使攻击者能够构建恶意有效负载[21]。

最近对保护机制的调查[55]显示,大多数可用的解决方案要么(a)不完整,(b)使用已知攻击可绕过,(c)需要修改源代码,要么(d)施加了巨大的性能开销。

最近,一种新技术,即代码指针完整性(CPI),有望弥合安全性保证与性能/可用性之间的差距。CPI在代码指针上强制执行选择性的内存安全性(即,它不保护数据指针),无需进行任何源代码修改。CPI背后的关键思想是在单独的安全区域中隔离和保护代码指针,并提供运行时检查以验证每次控制传输时代码指针的正确性。由于修改代码指针是实施控制劫持攻击所必需的,因此CPI的作者认为,它可有效抵御大多数恶意类型的内存破坏攻击。由于代码指针只占所有指针的一小部分,因此相对于已建立的用于增强完全内存安全性的技术,CPI要有效得多(C的平均值为2.9%,C/C++的平均值为8.4%)[31]。

在本文中,我们提出了一种针对CPI的攻击,该攻击使用数据指针漏洞启动了时序侧通道,该通道泄漏了有关受保护安全区域的信息。我们的攻击利用了CPI的两个设计缺陷。首先,在不支持分段保护的体系结构(例如x86-64和ARM)上,CPI使用信息隐藏来保护安全区域。其次,为了实现低性能开销,CPI将保护重点放在代码指针上。由于安全区域与它所保护的代码保持在相同的存储空间中,为避免昂贵的上下文切换,它还容易遭受泄漏和覆盖攻击。

我们表明,攻击者可以使用定时侧信道攻击来找到安全区域,并使安全区域的位置暴露。一旦知道了安全区域中代码指针的位置,就可以修改指针的元数据以允许ROP链的位置。然后,将指针修改为指向可以成功完成劫持攻击的ROP链。

在我们对CPI实施的评估中,我们发现了许多实施缺陷,这些缺陷可以促进对CPI的攻击。在本文中,我们将重点放在利用信息隐藏来保护不提供硬件隔离的架构(例如x86-64和ARM)的安全区域的缺陷的攻击上。换句话说,对于x86-64和ARM体系结构,我们假设攻击者是最弱的假设。实际上,攻击者打破CPI的唯一必要假设就是对堆栈的控制,这与文献中的其他代码重用攻击和防御方法是一致的[49、23、57]。在本文的其余部分,当提到CPI时,我们指的是基于信息隐藏的CPI实现。

在较高级别,我们的攻击如下。

首先,通过控制堆栈,我们使用数据指针覆盖将数据指针重定向到CPI使用的内存映射(mmap)中的随机位置。使用定时侧信道攻击,我们泄漏了大部分安全区域。

然后,我们使用数据指针覆盖攻击来修改安全区域,并篡改实际有效负载所需的代码指针的基数和边界信息。这可以归纳为以下步骤:

  1. 启动定时侧信道攻击:数据指针覆盖漏洞用于控制数据指针,该数据指针随后用于影响控制流(例如,循环迭代次数),用于泄露受控制的指针的内容(即,字节值)。数据指针可以被覆盖以指向堆栈上的返回地址,从而泄露代码所在位置或代码段中的位置等关键信息,从而泄露哪些代码位于此处。
  2. 数据收集:利用数据指针漏洞,我们测量对攻击应用程序的往返响应时间,以收集定时样本。我们在最小的累积延迟斜率和字节0之间,以及最大的斜率和字节255之间创建一个映射。我们使用这两个映射来为所有可能的字节值(0-255)内插累积延迟斜率。这使我们能够高精度读取特定存储位置的内容。
  3. 定位安全区域:使用关于mmap随机位置的安全区域可能位置信息,我们从安全区域内可靠映射的位置开始搜索并遍历安全区域,直到发现表示已知库(例如,libc的库)位置的字节序列。在CPI的当前实现下,发现libc的基数使我们可以轻松计算安全区域的基地址。到目前为止,该攻击对CPI完全透明,并且可能不会导致任何崩溃或可检测到的副作用。
  4. 攻击安全区域:使用安全区域表地址,我们可以计算存储在安全区域中的任何代码指针的地址。此时,我们可以更改代码指针和任何关联的元数据,以启用控件劫持攻击(例如ROP小工具)。CPI不会将重定向检测为违规,因为我们已经修改了其安全区域,以接受新的基础并绑定了代码指针。

在CPI论文中,作者认为,泄漏大量内存或强行强制使用安全区域会导致大量崩溃,可以使用其他方法进行检测[31]。我们证明了这种假设是不正确的,实际上,可以在不导致目标进程崩溃的情况下发生大部分安全区域的泄漏。CPI中的另一个假设是,如果没有指向安全区域的指针,则其位置不会泄漏。我们证明这个假设也是不正确的。通过跳到mmap中随机选择的位置,攻击可以开始泄漏安全区域,而无需任何指向它的指针。

为了评估我们的攻击,我们在Nginx上针对受CPI保护的版本构建了概念验证攻击[45]。我们的评估表明,在具有ASLR的Ubuntu Linux中,要经过13次崩溃才能绕过CPI花费6秒。我们的分析还显示,对于CPI来说,性能最高且完全实现,可以在大约98小时内完成一次攻击而不会造成任何崩溃。此实现依赖于操作系统对ASLR的支持。

1.1贡献

本文做出以下贡献:

  • 针对CPI的攻击:我们证明,在x86-64架构上,只要仅控制堆栈,攻击者就可以击败CPI。具体来说,我们展示了如何使用数据指针覆盖来显示安全区域的位置而不会导致任何崩溃,而CPI作者认为这是不可能的。
  • Nginx的概念验证攻击:我们对流行的Nginx Web服务器的CPI保护版本实施概念验证攻击。我们证明了我们的攻击是准确而有效的(只需6秒钟即可完成13次崩溃)。
  • 实验结果:我们提供的实验结果证明了我们的攻击使用定时侧信道攻击来泄漏安全区域的能力。
  • 对策:我们提出了一些可能的CPI改进措施,并分析了它们对不同类型攻击的敏感性。

接下来,第2部分介绍了我们的威胁模型,该模型与CPI的威胁模型一致。

第3节简要介绍了CPI和了解本文其余内容所必需的旁信道攻击。

第4节介绍了我们的攻击过程及其详细信息。

第5节介绍了我们的攻击结果。

第6节描述了CPI实施中的一些缺陷。

第7节提供了一些有关CPI问题根源的见解,并讨论了可能的补丁程序修复及其含义。

第8节描述了针对我们攻击的可能对策。

第9节回顾了相关工作。

第10节总结了本文。

2.威胁模型

在本文中,我们假设一个现实的威胁模型与既往工作和CPI假设的威胁模型一致[31]。对于攻击者,我们假设存在一个可控制堆栈的漏洞(即,攻击者可以在堆栈上创建和修改任意值)。我们还假设攻击者无法修改内存中的代码(例如,内存受DEP [41]保护)。我们还假设存在ASLR [43]。由于以上假设阻止了代码注入,因此将要求攻击者构建成功的代码重用攻击。

我们还假定CPI已正确配置和正确实现。正如我们将在后面讨论的那样,CPI还有其他实现上的缺陷,使其更容易受到攻击,但是对于本文,我们将重点放在其设计决策上,即使用信息隐藏来保护安全区域。

3.背景

本节介绍了解我们对CPI的攻击所需的必要背景信息。具体而言,本节从CPI概述开始,并继续提供有关远程泄漏攻击的信息。有关更多信息,请向读者介绍CPI论文[31]和最近的远程泄漏攻击论文[47]。

3.1 CPI总览

CPI由三个主要部分组成:静态分析,检测和安全区域隔离。

  1. 静态分析:CPI使用基于类型的静态分析来确定要保护的敏感指针的集合。CPI将指向函数,复合类型(例如包含敏感类型的数组或结构),通用指针(例如void *和char *)和指向敏感类型的指针的所有指针视为敏感类型(请注意递归定义)。CPI可以防止敏感指针的重定向,该重定向可能导致控制劫持攻击。敏感性的概念本质上是动态的:在运行时,指针可能指向良性整数值(不敏感),并且在执行的其他部分也可能指向函数指针(敏感)。使用静态分析的结果,CPI存储元数据,用于检查代码指针在其安全区域中的有效性。

    元数据包括:

    • 指针的值;
    • 上下阈值;
    • 一个标识符以检查时间安全性;

    在CPI的当前实现中未实现检查时间安全性功能。请注意,静态分析有其自身的局限性和不准确性[33],其讨论超出了本文的范围。

  2. 检测:CPI添加了沿指针操作传播元数据的检查(例如指针算术或赋值)。使用该检查还可以确保只有CPI内部指令可以操纵安全区域,并且确保代码中没有指针可以直接引用安全区域。这是为了防止任何代码指针使用内存泄露攻击(针对代码指针)暴露安全区域的位置。

  3. 安全区域隔离:在x86-32架构上,CPI依靠分段保护来隔离安全区域。在不支持分段保护的体系结构(例如x86-64和ARM)上,CPI使用信息隐藏来保护安全区域。CPI在x86中隔离安全区域的方法存在两个主要缺陷。首先,随着系统迁移到64位架构和移动架构,x86-32架构逐渐被淘汰。第二,正如我们在评估中所显示的那样,CPI中实施分段保护的弱点使其可以绕开。

    为了在x86-64架构中提供保护,CPI依赖于

    • 安全区域的大小( 2 42 2^{42} 242字节);
    • 安全区域的随机性;
    • 安全区的稀疏性;
    • 没有直接指向其安全区域的指针;

    这些为提供保护的CPI依赖的因素,我们证明了它们充其量只是一个微弱的假设。

CPI作者还介绍了一个较弱但更有效的CPI版本,称为代码指针分离(CPS)。CPS将受保护的指针集限制为仅代码指针,而使指向代码指针的指针不被检查。因为CPI作者将CPI视为提供最强的安全保证,所以我们不再讨论CPS和其他安全堆栈功能。有兴趣的读者可以参考原始出版物,以更深入地了解这些功能。

3.2通过内存损坏的旁通道

使用内存破坏的侧通道攻击有两种广泛的形式:故障和时序分析。他们通常使用内存损坏漏洞(例如,缓冲区溢出),作为泄漏有关内存内容信息的基础。与传统的内存泄漏攻击相比,它们的通用性要强得多[54],因为它们可以限制崩溃,它们可以泄漏有关很大一部分内存的信息,并且只需要一个漏洞就可以破坏代码重用保护机制。

盲ROP(BROP)[9]是一种故障分析攻击的示例,该攻击使用应用程序的故障输出泄漏有关内存内容的信息(即,使用应用程序崩溃或冻结)。BROP有意使用崩溃来泄漏信息,因此可以通过监视异常数量的程序崩溃的机制来潜在地检测到。

Seibert等 [47]描述了各种定时和故障分析攻击。在本文中,我们专注于通过数据指针覆盖来使用定时通道攻击。这种类型的定时攻击可以防止意外崩溃,通过将定时分析,着力于分配的页面的分析(例如,一块内存被分配为安全区域一部分)。

考虑下面的代码。

i = 0;
while (i < ptr->value)
    i++;

如果攻击者可以覆盖ptr,修改ptr指向内存中的某个位置,则while循环的执行时间将与ptr指向的字节值相关。例如,如果ptr存储在堆栈中,则简单的缓冲区溢出会破坏其值以指向内存中的任意位置。该延迟很小(约纳秒); 但是,通过在网络上进行大量查询并保留最快的样本(累积延迟分析),攻击者可以获得字节值的准确估计值[47,16]。在我们的攻击中,我们证明了这种攻击是公开CPI安全区域的实用技术。

3.3内存熵

CPI技术作者提出的论据之一是,虚拟内存的巨大容量使猜测或蛮力攻击变得十分困难/或不可能。具体来说,他们提到x86-64中的48位可寻址空间很难破解。我们证明在实践中这种假设是不正确的。首先,攻击者面临的熵不是48位而是28位:存储CPI安全区域的mmap基址的熵是28位[39]。其次,攻击者无需知道mmap的确切起始地址。攻击者只需要将数据指针重定向到mmap内的任何有效位置。由于mmap的大部分内容已由库和CPI安全区域使用,因此攻击者很有可能进入分配的mmap页面内。在我们的评估中,我们表明,对于一般情况,此概率高达1。换句话说,由于mmap区域的大小比其起始地址中的熵大得多,因此攻击者可以有效地降落在mmap内的有效位置而不会导致崩溃。

4.攻击方法

本节介绍了对受CPI保护的应用程序执行攻击的方法。如第二节所述,对CPI的攻击假定攻击者具有与CPI文件[31]中概述的功能相同的功能。本节从对攻击方法的高级描述开始,然后继续使用该方法描述针对Nginx的详细攻击[45]。

从高层次来看,我们的攻击利用了CPI的两个设计缺陷。首先,在不支持分段保护的体系结构(例如x86-64和ARM)上,CPI使用信息隐藏来保护安全区域。其次,为实现低性能开销,CPI将保护重点放在代码指针上(即,它不保护数据指针)。本节说明可以利用这些设计决策来绕过CPI。

直观地,我们的攻击利用CPI中缺乏数据指针保护来执行定时侧信道攻击,这可能会泄漏安全区域的位置。一旦知道了代码指针在安全区域中的位置,就会修改代码指针及其元数据,以指向完成劫持攻击的ROP链。我们注意到,使用数据指针覆盖来启动定时通道以泄漏安全区域位置对于CPI可能是完全透明的,并且可以避免任何可检测到的副作用(即,它不会导致应用程序崩溃)。

该攻击执行以下步骤:

  1. 查找数据指针漏洞;
  2. 收集资料,识别统计上唯一的存储序列;收集有关数据指针漏洞的计时数据;
  3. 找到安全区域;
  4. 攻击安全区;

接下来,我们将详细描述每个步骤。

4.1脆弱性

对CPI发起攻击的第一个要求是发现受CPI保护的应用程序中的数据指针覆盖漏洞。数据指针不受CPI保护; CPI仅保护代码指针。

数据指针覆盖漏洞用于发起定时侧信道攻击[47],这反过来又可能泄漏有关安全区域的信息。更详细地讲,数据指针覆盖漏洞用于控制数据指针,该数据指针随后用于影响控制流(在我们的示例中,是循环的迭代次数),并且可以用于揭示指针的内容(即 ,字节值)通过计时信息。例如,如果数据指针存储在堆栈中,则可以使用堆栈溢出攻击将其覆盖; 如果它存储在堆中,则可以通过堆损坏攻击将其覆盖。

在没有完整的内存安全性的情况下,我们假设将存在此类漏洞。该假设与该领域[50,12]中的相关工作是一致的。在我们的概念验证漏洞中,我们使用类似于先前漏洞[1]的堆栈缓冲区溢出漏洞来重定向Nginx中的数据指针。

4.2数据收集

在存在数据指针漏洞的情况下,下一步是收集足够的数据以准确发起定时侧信道攻击,该攻击将揭示安全区域的位置。

第一步是生成一个请求,将易受攻击的数据指针重定向到内存中经过精心选择的地址(请参阅第4.3节)。接下来,我们需要收集足够的信息来准确估计所选地址取消引用的字节值。为了估算字节值,我们使用如下公式1中所述的累积延迟分析。
b y t e = c ∑ i = 1 s ( d i − b a s e l i n e ) ( 公 式 1 ) byte = c\sum_{i=1}^s (d_i-baseline)\quad\quad\quad\quad (公式1) byte=ci=1s(dibaseline)(1)
在上式中,baseline表示服务器处理字节值为零的请求所花费的平均往返时间(RTT)。 d i d_i di表示一个非零字节值的延迟采样RTT,而s表示所采样的数量。

一旦我们将 b y t e = 0 byte = 0 byte=0设置为上式,则简化为:
b a s e l i n e = ( ∑ i = 1 s d i ) / 8 baseline = (\sum_{i=1}^s d_i)/8 baseline=(i=1sdi)/8
由于网络状况带来的额外延迟,因此建立准确的baseline非常重要。从某种意义上说,baseline充当带通滤波器。换句话说,我们从公式1的di中减去baseline,以便仅测量由我们选择的环路引起的累积差分延迟。

然后,我们使用为字节255收集的一组延迟采样来计算常数c。设置 b y t e = 255 byte = 255 byte=255后,公式如下:
c = 255 / ( ( ∑ i = 1 s d i ) − s ∗ b a s e l i n e ) c = 255/((\sum_{i=1}^s d_i) - s * baseline) c=255/((i=1sdi)sbaseline)
一旦获得c(它提供了字节值和累积微分延迟之比),便可以估算字节值。

4.3找到安全区域

图1说明了x86-64体系结构上受CPI保护的应用程序的内存布局。

堆栈位于虚拟地址空间的顶部,并向下增长(向较低的内存地址),然后是堆栈间隙(stack gap)。堆栈间隙之后是mmap区域的基址(mmap_base),共享库(例如,libc)和由mmap( )系统调用创建的其他区域位于此位置。在受ASLR保护的系统中,mmap_base的位置被随机选择在max_mmap_base(紧接堆栈间隙之后)和min_mmap_base之间。最小mmap基数计算如下:
m i n _ m m a p _ b a s e = m a x _ m m a p _ b a s e − a s l r _ e n t r o p y ∗ p a g e _ s i z e min\_mmap\_base = max\_mmap\_base − aslr\_entropy ∗ page\_size min_mmap_base=max_mmap_baseaslr_entropypage_size
其中,在64位系统中,aslr_entropy为 2 28 2^{28} 228,并且页面大小被指定为操作系统参数(通常为4KB)。安全区域是在mmap基础上加载任何链接库之后直接分配的,该区域为 2 42 2^{42} 242个字节。在安全区域之后紧随其后的是内存中进行动态加载的库和基于mmap的堆分配的区域。

已知在加载所有链接库之后直接分配安全区域,并且已确定地链接了链接库,则可以通过发现链接库中的已知位置(例如,libc的库)来计算安全区域的位置,并从链接库的地址中减去安全区域( 2 42 2^{42} 242)的大小。公开任何libc地址或另一个链接库中的地址,都会轻易揭示当前CPI实现中安全区域的位置。即使我们在后面讨论,即使采取对策在随机位置分配安全区域,我们的攻击也仍然有效。

为了发现已知库(例如,libc的库)的位置,攻击需要扫描从min_mmap_base开始的每个地址,并使用上述的定时通道攻击,搜索唯一标识该位置的字节的签名。

在最坏的情况下,可能搜索的位置空间可能需要 a s l r _ e n t r o p y ∗ p a g e _ s i z e aslr\_entropy * page\_size aslr_entropypage_size扫描。由于mmap的基址是页面对齐的,一种明显的优化方法是扫描为page_size倍数的地址,从而大大减少了需要扫描到的地址数量:
( a s l r _ e n t r o p y ∗ p a g e _ s i z e ) / p a g e _ s i z e (aslr\_entropy ∗ page\_size)/page\_size (aslr_entropypage_size)/page_size
实际上,可以使这种攻击更加有效。在x86-64架构中,CPI通过分配很大的区域( 2 42 2^{42} 242个字节)来保护安全区域,该区域非常稀疏地填充了指针元数据。结果,安全区域内的绝大多数字节为零字节。这使我们能够通过对字节的零/非零值进行采样(即不需要精确的字节估计)来确定我们是在安全区域内还是在链接库中。

因为我们从安全区域开始,并且libc被分配在安全区域之前,如果我们按libc的大小退回去到内存,则可以避免崩溃应用程序。这是因为安全区域内的任何位置至少在其顶部具有libc分配的内存大小。结果,改进的攻击过程如下:

  1. 将数据指针重定向到安全区域的始终分配的部分(请参见图1)。
  2. 按libc的大小退回去到内存。
  3. 扫描一些字节。如果字节全为零,请转到步骤2。否则,扫描更多字节以确定我们在libc中的位置。
  4. 完成。

请注意,发现驻留在libc中的页面将直接暴露安全区域的位置。

使用此过程,扫描次数可以减少到:
( a s l r _ e n t r o p y ∗ p a g e _ s i z e ) / l i b c _ s i z e (aslr\_entropy ∗ page\_size)/libc\_size (aslr_entropypage_size)/libc_size
在我们的实验中,这里的libc_size约为 2 21 2^{21} 221。换句话说,内存扫描的估计数量为: 2 28 ∗ 2 12 / 2 21 = 2 19 2^{28} * 2^{12}/2^{21} =2^{19} 228212/221=219。这种不崩溃的扫描策略如图2左侧所示。

如果我们容忍对未映射到可读页面的地址解引用而导致的崩溃,则可以进一步减少内存扫描的次数。因为未映射mmap_base以上的页面,所以取消引用mmap_base以上的地址可能会使应用程序崩溃。如果应用程序在崩溃后重新启动而没有重新分配其地址空间,则我们可以使用此信息执行搜索,目的是找到地址x,以便可以安全地取消引用x,但x + libc_size会导致崩溃。

这意味着x位于链接库区域内,因此从x中减去所有链接库的大小,我们将在libc附近的安全区域中获得一个地址,并且可以简化为上述情况。请注意,并不能保证x位于链接库区域的顶部:在该区域内,有未分配的页面,也有没有读取权限的页面,如果取消引用,这会导致崩溃。

为了找到这样的地址x,二进制搜索按如下进行:如果崩溃,我们的猜测地址太高,否则我们的猜测太低。换句话说,我们保持不变,即范围内的高地址将导致崩溃,而低地址是安全的,并且当差异达到libc_size的阈值时终止。这种方法最多只需要 log ⁡ 2 2 19 = 19 \log_2{2^{19}} = 19 log2219=19次读取,最多会崩溃19次(平均9.5次)。

更一般而言,鉴于我们的扫描允许T崩溃,我们希望描述在最佳扫描策略下定位崩溃边界所需的最少页面读取次数。这样做的原因是,当T <19时,我们的二进制搜索方法无法保证在最坏的情况下找到崩溃的边界。

我们使用动态编程来找到给定T的最佳扫描策略。令f(i,j)为最佳扫描策略可以覆盖的最大内存量,从而导致i崩溃并执行j页读取。请注意,要导致崩溃,您需要执行读取。因此,我们有递归
f ( i , j ) = f ( i , j − 1 ) + f ( i − 1 , j − 1 ) + 1 f(i, j) = f(i, j − 1) + f(i − 1, j − 1) + 1 f(i,j)=f(i,j1)+f(i1,j1)+1
这种递归之所以成立,是因为在f(i,j)的最佳策略中,读取的第一页将导致崩溃或不会崩溃。

当发生崩溃时,这意味着libc在我们读取的第一页之下,因此我们必须搜索的内存量被减少为最多为f(i-1,j-1)的值。对于后一种情况(读取第一页没有发生奔溃时),我们必须搜索的数量减少为最多f(i,j-1)的值。

从递归中计算出一个数值表(如图3),我们可以使用它来制定我们大多数T崩溃所引起的扫描策略。图3显示了此策略针对不同的T值执行的读取次数。

因为我们事先知道了库区域的布局,所以当我们发现崩溃边界时,我们知道从x减去 8 ∗ l i b c _ s i z e 8 * libc\_size 8libc_size将保证地址在安全区域中,因为该数量大于所有链接库的总和。因此,最多需要8次读取才能在libc中定位地址。崩溃的扫描策略如图2右侧所示。

即使应用程序在崩溃后重新启动后确实重新分配其地址空间,我们仍然可以获得重大改进。假设我们平均可以容忍T崩溃。而不是从以下地址开始扫描:
m i n _ m m a p _ b a s e = m a x _ m m a p _ b a s e − a s l r _ e n t r o p y ∗ p a g e _ s i z e ( 公 式 2 ) min\_mmap\_base =max\_mmap\_base − aslr\_entropy ∗ page\_size\quad\quad\quad\quad (公式2) min_mmap_base=max_mmap_baseaslr_entropypage_size(2)
我们开始于:
m a x _ m m a p _ b a s e − 1 T + 1 ( a s l r _ e n t r o p y ∗ p a g e _ s i z e ) max\_mmap\_base-\frac1{T+1} (aslr\_entropy ∗ page\_size) max_mmap_baseT+11(aslr_entropypage_size)
1 T + 1 \frac1{T + 1} T+11的概率出现的情况是,mmap基数高于该地址并且我们不会崩溃,并且读取次数将减少 1 T + 1 \frac1{T + 1} T+11倍。以 1 − 1 T + 1 1 − \frac1{T + 1} 1T+11的概率,这将立即使应用程序崩溃,我们将不得不再次尝试。可以预期,此策略将在成功之前崩溃T次。

请注意,在重新随机化的情况下,任何最佳策略都将基于可以容忍的崩溃次数来选择起始地址,如果第一次扫描没有崩溃,则连续扫描的地址之间的差异最大为libc_size。如果差异大于该数字,则可能是libc被跳过,导致崩溃,并且由于重新随机化而丢失了有关安全区域的所有知识。如果连续地址x,y之间的差满足 y − x > l i b c _ s i z e y − x> libc\_size yx>libc_size,则用 y − l i b c _ s i z e y −libc\_size ylibc_size替换x并在x之前将所有地址移位 y − l i b c _ s i z e − x y−libc\_size-x ylibc_sizex会产生更好的策略,因为崩溃的风险已转移到第一次扫描 同时保持相同的成功概率。

一旦使用时序侧通道发现了mmap的基地址,就可以如下计算安全区域表的地址(table_address):
t a b l e _ a d d r e s s = l i b c _ b a s e − 2 42 table\_address = libc\_base − 2^{42} table_address=libc_base242

4.4攻击安全区

使用安全区域表地址(table_address),受CPI保护的应用程序中感兴趣的代码指针的地址ptr_address可以通过使用cpi_addr_mask(为0x00fffffffff8)进行掩码计算,然后乘以表条目的大小(即4)。

有了安全区域中代码指针的确切地址,该指针的值就可以被劫持以指向库函数或ROP链的开始以完成攻击。

4.5攻击优化

CPI的更强大实现可能会为其安全区域选择一个任意地址,该安全区域可以是在链接库的底部与mmap区域的末端之间随机选择的。我们的攻击仍然针对这样的实现,并且可以进一步优化。

我们知道安全区域的大小为 2 42 2^{42} 242个字节。因此,有 2 48 / 2 42 = 26 = 64 2^{48}/2^{42} = 26 = 64 248/242=26=64种可能的需要搜索的地方。实际上,在像Ubuntu 14.04这样的现实世界系统中,只有 2 46.5 2^{46.5} 246.5个地址可用于Ubuntu x86-64上的mmap,因此,即使有最极端的随机性假设,也有 1 25 \frac1{25} 251个机会获得正确的地址。此外,堆和动态库地址的公开将增加这种机会。我们注意到CPI安全区域表中的每一项依次包含~~(自己备注:需要看源码查验一下是不是如此)~~:

  • 一个唯一的指针值签名;
  • 一个空槽;
  • 下限和上限;

这将使攻击者可以轻松地验证到达的地址确实在安全区域中。一旦确定了安全区域内的地址,攻击者就能够确定安全地址相对于表库的偏移量只是时间问题。有许多选项可以大大减少读取次数,以准确确定我们降落在安全区域中的哪个位置。例如,我们可能会分析本地应用程序的安全区域,并找到以系统页面大小为模的最频繁填充的地址(因为安全区域的基址必须与页面对齐),然后以页面大小的间隔在安全区域中进行搜索在该偏移量。此外,如果我们将偏移量与本地参考副本进行比较,则可以立即找到偏移量(如果我们落在安全区域内唯一的任何值上)。

现在,我们可以对在搜索过程中选择要定位的目标变量进行一些常规观察。如果我们可以从程序中最大的一组指针中选择一个指针,而该指针具有与页面大小相同的地址,那么我们将能够以最快的速度进行搜索。例如,如果程序中有100个指针的地址是页面大小的1模,那么我们将大大增加在安全区域扫描期间尽早找到其中一个指针的机会。

此外,其他库任何位置的泄漏(进行强随机化假设)将有助于识别安全区域的位置。请注意,泄漏所有其他库属于CPI的威胁模型。

5.测量和结果

接下来,我们给出针对流行的Web服务器Nginx 1.6.2的第4节中描述的攻击的实验结果。我们使用clang/CPI 0.2和-flto -fcpi标志编译Nginx。Nginx通过1Gbit有线LAN连接连接到攻击者。我们在具有4 GB RAM的四核Intel i5处理器的服务器上执行所有测试。

5.1脆弱性

我们对Nginx进行了修补,以引入一个堆栈缓冲区溢出漏洞,使用户可以控制用作Nginx日志记录系统上限的参数。这类似于以前的Nginx版本[1]中攻击者可以实现的效果(CVE-2013-2028)。利用此漏洞,攻击者可以根据CPI假定的威胁模型在堆栈上放置任意值(请参阅第2部分)。我们通过有线LAN连接启动该漏洞,但是如先前的工作所示,通过无线网络也可以进行攻击[47]。

使用该漏洞,我们修改了Nginx日志记录模块中的数据指针,使其指向精心选择的地址。相关循环可以在nginx_http_parse.c的源代码中找到。

for (i = 0; i < headers->nelts; i++)

数据指针漏洞使您可以控制循环中执行的迭代次数。使用第四节介绍的时序分析,我们可以区分零页和非零页。通过这种优化,攻击可以有效地识别安全区域的末尾,其中非零页表示链接库区域的开始。

5.2定时攻击

我们通过使用Nginx测量静态网页(0.6 KB)的HTTP请求往返时间(RTT),开始计时侧通道攻击。我们收集了10,000个样本以建立平均基线延迟。对于我们的实验,平均RTT为3.2毫秒。图4和5显示了我们的字节估计实验的结果。这些图表明使用累积差分延迟的字节估计准确度在2%(±20)以内。

5.3找到安全区域

确定平均基线延迟后,我们将nelts指针重定向到地址0x7bfff73b9000和0x7efff73b9000之间的区域。正如内存分析中提到的,尽管启用了ASLR,这就是我们可以保证分配的CPI安全区域的范围。我们选择该区域的顶部作为指针的第一个值。

攻击的关键组成部分是能够通过对页面进行零字节采样来快速确定给定页面位于安全区域内还是链接库内部的能力。即使我们在安全区域内命中了一个非零地址,这将触发在libc中搜索已知签名,但我们扫描的附近字节也不会产生有效的libc签名,因此我们可以识别出误报。在我们的测试中,从安全区域的高地址空间读取的每个字节均产生零。换句话说,我们没有观察到假阳性。

如果我们在libc中采样零字节值,则会出现一种有问题的情况。在这种情况下,如果我们错误地将此地址解释为安全区域的一部分,我们将跳过libc,攻击将失败。我们可以通过选择智能扫描的每页字节偏移量来减轻这种可能性。因为我们提前知道了libc的内存布局,所以我们可以识别出具有很大一部分非零字节的页面偏移量,因此,如果我们选择libc的随机页面并读取该偏移量的字节,则很可能会读取一个非零值。

在我们的实验中,页面偏移量4048产生非零值的比例最高,在443个libc页面中,有414个页面的偏移量为非零字节。这将使我们的策略的错误率为1 − 414/443 = 6.5%。我们注意到,可以通过每页扫描两个字节而不是按照我们选择的偏移量将这个数字减小为0。在我们的实验中,如果我们在libc的任何页面中扫描偏移量1272和1672处的字节,则可以确保这些值之一为非零。这以2倍的速度降低了我们的误报率。在我们的实验中,我们发现,除了两个签名字节以外,再扫描5个额外的字节可以使用每个字节30个样本并考虑字节估计中的错误来产生100%的准确性。图6说明了我们扫描到libc的零页时所选偏移量的总和。请注意,我们跳了libc的大小,直到达到非零页面为止。图右上角的点显示第一个非零页面。

简而言之,我们扫描每个libc大小30 * 7 = 210个字节,以确定我们是在libc中还是在安全区域中。表1总结了误报的数量,即我们估计为非零的页面数量,实际上为0。数据样本和估计样本的数量,以及它们各自用于计算的最快百分位数都影响准确性。扫描5个额外的字节(除了一页的两个签名字节外)并每字节采样30次,在我们的设置中精度为100%。因此,攻击平均需要(2 + 5)* 219 * 30 = 7 * 219 * 30 = 110、100、480次扫描,而使用我们的攻击设置大约需要97个小时。

一旦在libc中有一个指向非零页面的指针,我们就会发送更多请求以高精度读取其他字节,以确定我们找到了哪个libc页面。图7说明,通过每字节发送10000个样本,我们可以实现高精度。

尽管准确性很高,但我们必须考虑估计误差。为此,我们开发了一种模糊n-gram匹配算法,该算法在给定一个有噪声字节序列的情况下,通过将估计的字节与libc的本地副本进行比较来告诉我们这些字节所在的libc偏移量。在确定零页和非零页时,每个字节仅收集30个样本,因为我们不需要非常精确的测量。登陆到libc中的非零页面后,我们确实需要更准确的度量来确定我们可能的位置。我们的测量结果表明,必须有10 000个样本才能估计每个字节在20以内。我们还确定,可靠地读取从页偏移3333开始的70个字节足以使模糊n-gram匹配算法确定我们在libc中的确切位置。通过查看libc的每一页的所有连续字节序列并选择需要最少字节以确保唯一匹配的字节序列来计算此偏移量。libc内部的这种定向会导致额外的70 * 10,000 = 700,000个请求,这将使攻击的总时间再增加一小时,总计98个小时。

在libc中确定了我们的确切位置之后,我们知道了安全区域的确切基址:
s a f e _ r e g i o n _ a d d r e s s = l i b c _ b a s e − 2 42 safe\_region\_address = libc\_base − 2^{42} safe_region_address=libc_base242

5.4崩溃时快速攻击

我们平均可以容忍12次崩溃,从而使上述攻击速度更快。改进的攻击使用二进制搜索而不是线性搜索,如4.3节中所述,在进入安全区域后找到libc。我们还使用另一种策略来发现libc的基础。我们继续遍历,直到观察到表明libc的非可读部分位置的崩溃,而不是对单个页面进行采样。这揭示了libc的确切地址。在我们的设置中,二进制搜索导致11次崩溃; 发现libc的基础需要另外2次崩溃。

5.5攻击安全区

找到安全区域后,我们然后使用相同的数据指针覆盖来更改安全区域的read_handler条目。然后,我们修改代码指针的基数和边界以保存系统调用的位置(sysenter)。由于我们可以通过在寄存器中设置适当的值来控制sysenter调用的系统调用,因此查找sysenter可以使我们实现各种实际的有效负载。此后,只需将代码指针重定向到使用系统调用的ROP链的开头,攻击就可以继续进行。CPI不会阻止重定向,因为它的代码指针条目已经被恶意修改以接受ROP链。

整个崩溃攻击需要6秒钟才能完成。

5.6总结

总而言之,我们展示了针对受CPI,ASLR和DEP保护的Nginx版本的实际攻击。该攻击使用数据指针覆盖漏洞来发起计时侧通道攻击,该攻击可在6秒内泄漏安全区域,并观察到13次崩溃。另外,此攻击可以在98小时内完成而不会崩溃。

6.CPI的实施缺陷

CPI的已发布实现(简单)为所有受支持的体系结构使用表的固定地址,在其默认配置中未提供任何保护。我们认为这是由于我们评估的CPI版本仍处于“早期预览”的事实。在整个评估过程中,我们始终牢记这一点,主要集中在使用隐藏在CPI中的信息的基本问题。话虽如此,我们发现按照当前的实施,几乎没有将重点放在保护安全区的位置上。

原文中剩下的两个替代实现(哈希表和查找表)直接使用mmap,而没有固定地址,这是一个改进,但当然依赖于mmap进行随机化。这并未针对CPI的威胁模型中的ASLR暴露提供任何保护。我们进一步注意到,安全堆栈实现还使用mmap分配页面而没有固定地址,因此同样容易受到ASLR暴露的攻击。此漏洞使安全堆栈比堆栈canary提供的保护更弱,因为任何ASLR公开内容都可以确定安全堆栈的位置,而堆栈canary则需要更有针对性的公开内容(尽管可以通过其他方式绕开)。

在默认实现(简单)中,表的位置存储在一个静态变量(__llvm__cpi_table)中,该变量在将其值移入段寄存器后不会清零。因此,通过读取数据段中的固定偏移量,攻击者几乎可以使用它。在这两种替代实现中,表的位置不为零,因为它根本不受存储在段寄存器中的任何保护。而是将其存储为局部变量。再次,这很容易受到可以读取进程内存的攻击的攻击,一旦泄露,该信息将立即损害CPI保护。注意,在存在优化编译器的情况下,调零内存或寄存器通常很难在C中正确执行[44]。

我们注意到CPI的性能数字取决于对超级页面superpage(在Linux中称为大页面huge-page)的支持。在用于性能评估的配置中,未启用ASLR(FreeBSD当前不支持ASLR,从Linux内核3.13开始,mmap中用于大型表分配的基础并未随机化,尽管此后已添加了添加了补丁的支持 )。我们注意到这是为了指出CPI性能测试与实际环境之间的差异,尽管我们没有立即的理由怀疑启用ASLR会对性能造成很大的影响。

尚不清楚发布的CPI实现如何打算在32位系统上使用段寄存器。使用%gs寄存器的simpletable实现警告,尽管可以编译,但x86不支持它。我们注意到,在Linux中,使用段寄存器可能与线程本地存储(TLS)冲突,后者在x86-32上使用%gs寄存器,在x86-64上使用%fs寄存器[18]。如前所述,默认的实现方式simpletable不支持32位系统,而其他实现方式则根本不使用段寄存器,这是前面提到的一个缺陷,因此目前尚不容易暴露该缺陷。但是,快速搜索32位libc,使用%gs寄存器发现了近3000条指令。大概可以通过在32位系统上使用%fs寄存器来解决。但是,我们注意到这可能会导致与期望%fs寄存器为免费的应用程序(例如Wine)(在Linux内核源中明确指出)[2]产生兼容性问题。

此外,如果将CPI用于保护内核模式代码(CPI方法的既定目标),则使用%gs和%fs段寄存器可能会导致冲突。Linux和Windows内核对于这些寄存器都有特殊的用法。

7.讨论

在本节中,我们讨论了一些有问题的CPI设计假设,并讨论了可能的解决方法。

7.1设计假设

  1. 强制执行机制:首先,CPI的作者专注于安全检查的提取和强制执行,但是他们没有为其执行机制提供足够的保护。这在安全性上可以说是一个难题,但是防御的有效性取决于这种保护。在已发布的CPI实施中,安全区域的保护是非常基本的,这取决于32位体系结构中的分段和64位结构中安全区域的大小。但是,由于安全区域存储在相同的地址空间中,以避免性能昂贵的上下文切换,因此这些保护是不够的,并且如我们的攻击所示,它们很容易被绕开。请注意,诸如CPI之类的技术的动机是这样的事实,即现有的内存保护防御(如ASLR)已被破坏。具有讽刺意味的是,CPI本身依靠这些防御措施来保护其执行。例如,依靠位置随机化来隐藏安全区域具有我们已经说明的ASLR的许多弱点。
  2. 检测崩溃:其次,假设泄漏大量内存需要引起大量崩溃,可以使用其他机制来检测崩溃。这实际上是不正确的。尽管诸如Blind ROP [9]和蛮力[51]之类的攻击确实会导致大量崩溃,但在当前的CPI实现中,也有可能使用边通道攻击来避免此类崩溃。这样做的主要原因是,实际上分配了许多页面,实际上,区域起始地址中的熵远小于其大小。这使攻击者可以正确地降落在分配的空间内,从而使攻击不会崩溃。实际上,CPI的实施通过分配很大的mmap区域加剧了这一问题。
  3. 内存泄漏:第三,还隐式地假定大部分内存不会泄漏。直接存储器公开技术可能有一些限制。例如,它们可以以零字节终止,或者可以限制为与缓冲区[54]相邻的区域。但是,使用悬挂数据指针的间接泄漏以及时序或故障分析攻击没有这些限制,它们可能会泄漏大量内存。
  4. 内存隔离:第四,关于安全区域不会泄漏的假设,因为没有指向它的指针是不正确的。正如我们在攻击中所展示的,mmap区域的随机搜索可用于泄漏安全区域,而无需明确指向该区域的指针。

总而言之,CPI的主要弱点在于它依赖于与受保护过程保存在同一空间中的秘密。可以说,这个问题也导致了许多其他防御的弱点[59,51,54,47]。

7.2修补CPI

我们的攻击可能会立即使您想到许多修补程序,以提高CPI。我们在这里考虑了其中一些修复程序,并讨论了它们的有效性和局限性。这样的修复将增加成功进行攻击所必需的崩溃数量,但是它们不能完全阻止对缺少分段的架构(x86-64和ARM)的攻击。

  1. 增加安全区域大小:第一个直接的想法是在更大的mmap分配区域内随机分配安全区域库的位置。但是,这没有任何好处:安全区域的基址必须严格大于返回的mmap区域的开头,从而有效地增加了大区域中浪费的数据量,但并不能阻止我们的侧信道攻击继续扫描直到它 找到安全区域。此外,必须使用附加寄存器来隐藏偏移量,然后必须使用附加指令从该寄存器中加载值,将其添加到安全区域段寄存器中,然后添加实际表偏移量。这会对性能产生负面影响。

  2. 随机化安全区域位置:第二个解决方法是使用mmap_fixed为mmap分配指定固定的随机地址。这样做的好处是,非映射内存将有更大的部分,从而增加了攻击可能会扫描这些区域之一并触发崩溃的可能性。但是,在不更改安全区域大小的情况下,攻击者只需要进行少量崩溃即可发现随机位置。此外,这种方法可能会带来可移植性问题。如mmap手册页所述,“通常,不能保证特定地址范围的可用性。” 依赖平台的ASLR技术可能会加剧这些问题。此对策还有许多其他合理的攻击方式:

    • 除非该表跨越较小的虚拟内存范围,否则仍然可能基于泄漏偏移量并知道绝对最小和最大mmap_fixed地址而进行攻击,这会降低安全区域的熵。
    • 导致大量堆分配(达到使它们由mmap支持的阈值)并泄漏其地址。当地址以安全区域的大小跳跃时,很有可能发现了该地址。这类似于堆喷洒技术,在采用强堆随机化的系统上特别有效。
    • 泄漏任何动态加载的库的地址。如果新的动态加载的库地址比先前的动态库地址增加了安全区域的大小,则很有可能找到了该区域。
  3. 对安全区域使用哈希函数:第三个修复方法是将段寄存器用作哈希函数进入安全区域的键。这可能会导致性能过高的惩罚。它也仍然容易受到攻击,因为快速散列函数在密码上并不安全。这个想法类似于使用加密机制来保护CFI [35]。

  4. 减小安全区域大小:第四个解决方法是使安全区域变小。这是合理的,但请注意,如果mmap仍然是连续的,则攻击者可以从映射的库开始并进行扫描,直到找到安全区域为止,因此,此修补程序必须与不连续的mmap结合使用。此外,使安全区域紧凑会导致额外的性能开销(例如,如果使用哈希表,则会有更多的哈希表冲突)。较小的安全区域还具有更大的空间用尽风险,以便更轻松地存储“敏感”指针。

    ​ 为了评估此建议的修复程序的可行性,我们在具有4GB RAM的Ubuntu 14.04.1计算机上编译并运行了C和C ++ SPECint和SPECfp 2006基准测试[22],并使用了几种大小的CPI哈希表。所有C基准测试都是使用-std = gnu89标志编译的(clang需要此标志才能运行400.perlbench)。在我们的设置中,没有使用CPI哈希表编译的基准在400.perlbench,403.gcc和483.xalancbmk上产生正确的输出。

    ​ 表2列出了SPECint的间接费用结果。表中的NT表示“ 8小时后未终止”。在此表中,我们列出了默认CPI哈希表大小(233)的性能。使用226的哈希表大小,CPI报告它在471.omnetpp和473.astar的哈希表中空间不足(即,它超过了线性探测的最大限制)。使用220的哈希表大小,CPI在这些测试的安全区域以及445.gobmk和464.h264ref中的空间不足。其他测试的471.omnetpp产生的平均开销为17%,最坏情况下的开销为131%。通常,减小CPI哈希表的大小会导致性能的小幅提高,但是对于某些实际应用程序,尤其是471.omnetpp之类的C ++应用程序,这些性能开销仍然不切实际。

    ​ 表3列出了SPECfp的间接费用结果。表中的IR表示“错误的结果”。对于SPECfp和CPI哈希表大小为226,两个基准测试用尽了空间:433.milc和447.dealII。此外,其他两个基准测试返回错误的结果:450.soplex和453.povray。453.povray基准测试还使用CPI的默认哈希表大小返回了错误的结果。

    ​ 为了评估方案的有效性,该方案可能会动态扩展并减小哈希表的大小,从而以未知的性能损失和某些实时保证的损失为代价来减少攻击面,我们还将SPEC基准运行在有工具的哈希表实现上发现并发驻留在哈希表中的最大键数;我们的分析表明,该数字是223个条目,占用228个字节。但是,除非哈希表大小至少为228个条目(占用233个字节),否则某些测试无法正确完成。如果没有其他mmap分配声明地址空间,我们预计246 228 = 218崩溃,预期值为217,或者246 233 = 213崩溃,预期值为212。这似乎很难保证CPI在大型程序上的安全性。代码指针的数量。例如,如果一个程序具有2GB的内存,并且使用CPI哈希表(负载因子为25%)只能发现10%的指针是敏感的,则该程序的安全区域大小为(2 * 109/8 * 8%* 4 * 32个字节)。在识别该区域之前,预期的崩溃次数将仅略大于214。此数字意味着CPI的哈希表实现无法有效地防御本地攻击者的使用,并使其无法在任何非远程系统上提供的保证受到质疑。由非本地日志记录监视。作为比较,它在盲ROP [9]攻击中导致崩溃的数量级内。

  5. 使用非连续的随机mmap:最后,第五种解决方法是使用非连续的,按分配分配的随机mmap。当前,此类非连续分配仅可使用定制内核(例如PaX [43])使用。但是,即使在非连续分配的情况下,将超级页面用于虚拟内存仍然会造成漏洞。攻击者可以强制大型对象的堆分配,这些对象直接使用mmap生成减少总熵的条目。而且,知道其他库的位置由于其较大的大小,进一步减小了安全区域的熵。结果,必须将这种技术与减小安全区域的尺寸结合起来才可行。对这种修复程序的安全性和性能进行更准确的评估将需要一个实际的实现方式,我们留给以后的工作。

CPI的可查找实现(在我们评估时不起作用)可以通过一种设计来支持此方法,该设计在运行时随机分配每个子表的地址。这将导致较小的子表在内存中的随机分散。但是,查询表的子表位置只有 2 46 32 ∗ 2 22 e n t r i e s = 2 19 \frac{2^{46}}{32 * 2^{22}entries}= 2^{19} 32222entries246=219个插槽。查找其中之一的期望是 2 19 2 K \frac{2^{19}}{2K} 2K219崩溃,其中K是引入的新代码指针的数量,这些代码导致分配了单独的子表表。如果有 2 5 2^5 25个这样的指针(在1GB的进程中,至少有一个指针跨越地址空间),那么预期该数量将达到 2 13 2^{13} 213次崩溃,如先前所论证的那样,它不能提供强大的安全保证。

我们认为,由于可识别的CPI结构,我们可以识别一个子表,并通过直接/旁道攻击对其进行搜索。尽管我们无法修改任何代码指针,但我们相信,攻击者发现启用远程代码执行的代码指针只是时间问题。

8.可能的对策

在本节中,我们讨论针对使用时序侧通道进行内存泄漏的控制劫持攻击的可能对策。

  1. 内存安全性:完全的内存安全性可以防御所有控制劫持攻击,包括本文中的攻击概述。使用CETS扩展进行软绑定[36]可以实现完整的空间和时间指针安全性,尽管付出了高昂的代价(速度降低了4倍)。

    另一方面,经验表明,权衡安全性保证的低开销机制(例如,大约[48]或部分[5]内存安全性)最终会被绕过[9,52,21,11,17]。

    幸运的是,硬件支持可以使完整的内存安全性成为现实。例如,英特尔内存保护扩展(MPX)[25]可以促进更好地执行内存安全检查。其次,胖指针方案表明,基于硬件的方法可以在非常低的开销下实现空间内存安全性[32]。标记的体系结构和基于功能的系统还可以为减轻此类攻击提供可能的方向[58]。

  2. 随机化:针对定时通道攻击的一种可能的防御方法(例如本文概述的一种方法)是在攻击者可以披露足够的有关内存布局的信息以使攻击切实可行之前,对安全区域和ASLR进行连续重新随机化。一种简单的策略是使用通过重新启动工作进程来定期重新随机化(即不仅发生崩溃)的工作程序池模型。另一种方法是通过迁移正在运行的进程状态来执行运行时重新随机化[20]。

    随机技术提供的概率保证比低开销下的完整内存安全性要弱得多。我们注意到,任何以安全性保证换取性能的安全性机制都可能容易受到将来的攻击。为了实用起见,这种短期优化是对安全系统进行多次攻击的原因之一[9、52、21、11、17]。

  3. 定时侧通道防御:克服使用侧通道泄漏内存的攻击的一种方法是消除执行定时差异。例如,可以通过使每个执行(或路径)花费相同的时间量来删除定时通道。这种方法的明显缺点是平均情况下的执行时间现在变为最坏情况下的执行时间。对于许多系统而言,预期等待时间的这种更改可能代价太高。我们在这里注意到,在程序执行中添加随机延迟不能有效地防止侧信道攻击[19]。

9.相关工作

自70年代初以来就一直使用内存破坏攻击[6],并且在现代环境中[14]仍然构成重大威胁。诸如C/C++之类的内存不安全语言容易受到此类攻击。

完整的内存安全技术,例如带有CETS扩展[36]的SoftBound技术,可以缓解内存损坏攻击,但会给执行带来很大的开销(速度降低多达4倍)。还提出了诸如CCured [37]和Cyclone [28]之类的“胖指针”技术来提供空间指针安全性,但是它们与现有的C代码库不兼容。诸如Cling [4],Memcheck [38]和AddressSanitizer [48]之类的其他工作仅提供临时指针安全性,以防止悬而未决的指针错误(如后使用)。还提出了许多硬件强制的内存安全技术,包括低脂指针技术[32]和CHERI [58],它们将内存安全检查的开销降至最低。

基于软件的完全内存安全性的高开销导致了较弱的内存防御,可以将其分为基于强制的防御和基于随机化的防御。在基于实施的防御中,通常在编译时提取的某些正确代码行为会在运行时实施,以防止内存损坏。在基于随机化的防御中,将代码或执行环境的不同方面随机化以使成功的攻击更加困难。

基于随机化的类别包括地址空间布局随机化(ASLR)[43]及其中粒度[30]和细粒度变体[57]。不同的ASLR实现在加载时将堆栈,堆,可执行文件和链接库的子集的位置随机化。中型ASLR技术(例如地址空间布局置换[30])也可以置换函数在库中的位置。ASLR的细粒度形式(例如Binary Stirring [57])使基本块在代码中的位置随机化。其他基于随机化的防御措施包括就地指令重写(例如ILR [23]),使用随机化编译器(例如多编译器技术[27])或Smashing Gadgets技术[42]进行代码多样化。不幸的是,这些防御措施很容易受到信息泄漏(内存泄露)攻击的影响[54]。已经显示,攻击者甚至可以反复使用一个这样的漏洞来绕过甚至是细粒度形式的随机化[52]。其他基于随机化的技术包括Genesis [60],Minestrone [29]或RISE [8]使用仿真,工具或二进制翻译层(例如Valgrind [38],Strata [46]或Intel PIN)实现指令集随机化[34]本身会带来很大的开销,有时甚至会导致应用程序减慢几倍的速度。

在基于强制的类别中,控制流完整性(CFI)[3]技术是最突出的技术。他们在运行时强制执行编译时提取的控制流图(CFG),以防止控制劫持攻击。CCFIR [61]和bin-CFI [62]中实现了较弱形式的CFI,它们允许将控制权转移到任何有效目标,而不是确切的目标,但是这种防御措施容易受到精心设计的控制劫持攻击的攻击,使用这些目标来实现其恶意意图[21]。Backes等人提出的技术。[7]通过将可执行页面标记为不可读来防止内存泄漏攻击。最近的一项技术[15]结合了执行(非可读内存)和随机化(细粒度代码随机化)方面,以防止内存泄露攻击。

在攻击方面,直接内存泄露攻击已为人所知[54]。间接内存泄漏,例如故障分析攻击(使用崩溃,非崩溃信号)[9]或一般而言,其他形式的故障和时序分析攻击[47],最近已得到研究。

不受CPI阻止的非控制数据攻击[13]在破坏许多安全性方面也可能非常强大。但是,由于它们不在CPI的威胁模型之内,因此我们将其评估留给以后的工作来进行。

10.结论

我们对最近提出的CPI技术提出了攻击。我们表明,使用信息隐藏来保护安全区域是有问题的,并且可以用来破坏CPI的安全性。具体来说,我们展示了如何使用数据指针覆盖攻击来发起时序侧信道攻击,该攻击公开了x86-64上安全区域的位置。我们使用针对该版本的概念验证漏洞来评估攻击 受CPI,ASLR和DEP保护的Nginx Web服务器。我们显示,可以在98个小时内无故障地绕过CPI的最有效和完整的实现(简单),如果可以容忍少量崩溃(13),则可以在6秒钟内绕过。我们还将评估绕过CPI的其他实施所需的工作因素,包括对初始实施的许多可能修复。我们表明,信息隐藏是一个弱示例,通常会导致脆弱的防御。

11.致谢

这项工作由海军研究办公室赞助,奖号为N00014-14-1-0006,题为“使用最少的硬件修改和DARPA击败代码恢复攻击”(授权FA8650-11-C-7192)。这些意见,解释,结论和建议是作者的观点,并不反映海军研究办公室或美国政府的官方政策或立场。

作者要衷心感谢William Streilein博士,Fan Long博士,CPI团队,David Evans教授和Greg Morrisett教授的支持以及有见地的评论和建议。

参考

[1] Vulnerability summary for cve-2013-2028, 2013.

[2] Linux cross reference, 2014.

[3] M. Abadi, M. Budiu, U. Erlingsson, and J. Ligatti. Control-flow integrity. In Proceedings of the 12th ACM conference on Computer and communications security, pages 340–353. ACM, 2005.

[4] P. Akritidis. Cling: A memory allocator to mitigate dangling pointers. In USENIX Security Symposium, pages 177–192, 2010.

[5] P. Akritidis, C. Cadar, C. Raiciu, M. Costa, and M. Castro. Preventing memory error exploits with wit. In Security and Privacy, 2008. SP 2008. IEEE Symposium on, pages 263–277. IEEE, 2008.

[6] J. P. Anderson. Computer security technology planning study. volume 2. Technical report, DTIC Document, 1972.

[7] M. Backes, T. Holz, B. Kollenda, P. Koppe, S. N¨urnberger, and J. Pewny. You can run but you can’t read: Preventing disclosure exploits in executable code. In Proceedings of the 2014 ACM SIGSAC Conference on Computer and Communications Security, pages 1342–1353. ACM, 2014.

[8] E. G. Barrantes, D. H. Ackley, T. S. Palmer, D. Stefanovic, and D. D. Zovi. Randomized instruction set emulation to disrupt binary code injection attacks. In Proceedings of the 10th ACM Conference on Computer and Communications Security, CCS ’03, pages 281–289, New York, NY, USA, 2003. ACM.

[9] A. Bittau, A. Belay, A. Mashtizadeh, D. Mazieres, and D. Boneh. Hacking blind. In Proceedings of the 35th IEEE Symposium on Security and Privacy, 2014.

[10] T. Bletsch, X. Jiang, V. Freeh, and Z. Liang. Jumporiented programming: A new class of code-reuse attack. In Proc. of the 6th ACM Symposium on Info., Computer and Comm. Security, pages 30–40, 2011.

[11] N. Carlini and D. Wagner. Rop is still dangerous: Breaking modern defenses. In USENIX Security Symposium, 2014.

[12] S. Checkoway, L. Davi, A. Dmitrienko, A. Sadeghi, H. Shacham, and M. Winandy. Return-oriented programming without returns. In Proc. of the 17th ACM CCS, pages 559–572, 2010.

[13] S. Chen, J. Xu, E. C. Sezer, P. Gauriar, and R. K. Iyer. Non-control-data attacks are realistic threats. In Usenix Security, volume 5, 2005.

[14] X. Chen, D. Caselden, and M. Scott. New zero-day exploit targeting internet explorer versions 9 through 11 identified in targeted attacks, 2014.

[15] S. Crane, C. Liebchen, A. Homescu, L. Davi, P. Larsen, A.-R. Sadeghi, S. Brunthaler, and M. Franz. Readactor: Practical code randomization resilient to memory disclosure. In IEEE Symposium on Security and Privacy, 2015.

[16] S. A. Crosby, D. S. Wallach, and R. H. Riedi. Opportunities and limits of remote timing attacks. ACM Transactions on Information and System Security (TISSEC), 12(3):17, 2009.

[17] L. Davi, D. Lehmann, A.-R. Sadeghi, and F. Monrose. Stitching the gadgets: On the ineffectiveness of coarsegrained control-flow integrity protection. In USENIX Security Symposium, 2014.

[18] U. Drepper. Elf handling for thread-local storage, 2013.

[19] F. Durvaux, M. Renauld, F.-X. Standaert, L. v. O. tot Oldenzeel, and N. Veyrat-Charvillon. Efficient removal of random delays from embedded software implementations using hidden markov models. Springer, 2013.

[20] C. Giuffrida, A. Kuijsten, and A. S. Tanenbaum. Enhanced operating system security through efficient and fine-grained address space randomization. In USENIX Security Symposium, pages 475–490, 2012.

[21] E. G¨oktas, E. Athanasopoulos, H. Bos, and G. Portokalidis. Out of control: Overcoming control-flow integrity. In IEEE S&P, 2014.

[22] J. L. Henning. Spec cpu2006 benchmark descriptions. SIGARCH Comput. Archit. News, 34(4):1–17, Sept. 2006.

[23] J. Hiser, A. Nguyen, M. Co, M. Hall, and J. Davidson. Ilr: Where’d my gadgets go. In IEEE Symposium on Security and Privacy, 2012.

[24] G. Hunt, J. Larus, M. Abadi, M. Aiken, P. Barham, M. F¨ahndrich, C. Hawblitzel, O. Hodson, S. Levi, N. Murphy, et al. An overview of the singularity project. 2005.

[25] intel. Introduction to intel memory protection extensions, 2013.

[26] T. Jackson, A. Homescu, S. Crane, P. Larsen, S. Brunthaler, and M. Franz. Diversifying the software stack using randomized nop insertion. In Moving Target Defense, pages 151–173. 2013.

[27] T. Jackson, B. Salamat, A. Homescu, K. Manivannan, G. Wagner, A. Gal, S. Brunthaler, C. Wimmer, and M. Franz. Compiler-generated software diversity. Moving Target Defense, pages 77–98, 2011.

[28] T. Jim, J. G. Morrisett, D. Grossman, M. W. Hicks, J. Cheney, and Y. Wang. Cyclone: A safe dialect of c. In USENIX Annual Technical Conference, General Track, pages 275–288, 2002.

[29] A. D. Keromytis, S. J. Stolfo, J. Yang, A. Stavrou, A. Ghosh, D. Engler, M. Dacier, M. Elder, and D. Kienzle. The minestrone architecture combining static and dynamic analysis techniques for software security. In SysSec Workshop (SysSec), 2011 First, pages 53–56. IEEE, 2011.

[30] C. Kil, J. Jun, C. Bookholt, J. Xu, and P. Ning. Address space layout permutation (aslp): Towards fine-grained randomization of commodity software. In Proc. of ACSAC’06, pages 339–348. Ieee, 2006.

[31] V. Kuznetsov, L. Szekeres, M. Payer, G. Candea, R. Sekar, and D. Song. Code-pointer integrity. 2014.

[32] A. Kwon, U. Dhawan, J. Smith, T. Knight, and A. Dehon. Low-fat pointers: compact encoding and efficient gatelevel implementation of fat pointers for spatial safety and capability-based security. In Proceedings of the 2013 ACM SIGSAC conference on Computer & communications security, pages 721–732. ACM, 2013.

[33] W. Landi. Undecidability of static analysis. ACM Letters on Programming Languages and Systems (LOPLAS), 1(4):323–337, 1992.

[34] C.-K. Luk, R. Cohn, R. Muth, H. Patil, A. Klauser, G. Lowney, S. Wallace, V. J. Reddi, and K. Hazelwood. Pin: building customized program analysis tools with dynamic instrumentation. ACM Sigplan Notices, 40(6):190–200, 2005.

[35] A. J. Mashtizadeh, A. Bittau, D. Mazieres, and D. Boneh. Cryptographically enforced control flow integrity. arXiv preprint arXiv:1408.1451, 2014.

[36] S. Nagarakatte, J. Zhao, M. M. Martin, and S. Zdancewic. Cets: compiler enforced temporal safety for c. In ACM Sigplan Notices, volume 45, pages 31–40. ACM, 2010.

[37] G. C. Necula, S. McPeak, and W. Weimer. Ccured: Typesafe retrofitting of legacy code. ACM SIGPLAN Notices, 37(1):128–139, 2002.

[38] N. Nethercote and J. Seward. Valgrind: a framework for heavyweight dynamic binary instrumentation. In ACM Sigplan Notices, volume 42, pages 89–100. ACM, 2007.

[39] H. Okhravi, T. Hobson, D. Bigelow, and W. Streilein. Finding focus in the blur of moving-target techniques. IEEE Security & Privacy, 12(2):16–26, Mar 2014.

[40] A. One. Smashing the stack for fun and profit. Phrack magazine, 7(49):14–16, 1996.

[41] OpenBSD. Openbsd 3.3, 2003.

[42] V. Pappas, M. Polychronakis, and A. D. Keromytis. Smashing the gadgets: Hindering return-oriented programming using in-place code randomization. In IEEE Symposium on Security and Privacy, 2012.

[43] PaX. Pax address space layout randomization, 2003.

[44] C. Percival. How to zero a buffer, Sept. 2014.

[45] W. Reese. Nginx: the high-performance web server and reverse proxy. Linux Journal, 2008(173):2, 2008.

[46] K. Scott, N. Kumar, S. Velusamy, B. Childers, J. W. Davidson, and M. L. Soffa. Retargetable and reconfigurable software dynamic translation. In Proceedings of the international symposium on Code generation and optimization: feedback-directed and runtime optimization, pages 36–47. IEEE Computer Society, 2003.

[47] J. Seibert, H. Okhravi, and E. Soderstrom. Information Leaks Without Memory Disclosures: Remote Side Channel Attacks on Diversified Code. In Proceedings of the 21st ACM Conference on Computer and Communications Security (CCS), Nov 2014.

[48] K. Serebryany, D. Bruening, A. Potapenko, and D. Vyukov. Addresssanitizer: A fast address sanity checker. In USENIX Annual Technical Conference, pages 309–318, 2012.

[49] H. Shacham. The geometry of innocent flesh on the bone: Return-into-libc without function calls (on the x86). In Proceedings of the 14th ACM conference on Computer and communications security, pages 552–561. ACM, 2007.

[50] H. Shacham. The geometry of innocent flesh on the bone: Return-into-libc without function calls (on the x86). In Proc. of ACM CCS, pages 552–561, 2007.

[51] H. Shacham, M. Page, B. Pfaff, E.-J. Goh, N. Modadugu, and D. Boneh. On the effectiveness of address-space randomization. In Proc. of ACM CCS, pages 298–307, 2004.

[52] K. Z. Snow, F. Monrose, L. Davi, A. Dmitrienko, C. Liebchen, and A.-R. Sadeghi. Just-in-time code reuse: On the effectiveness of fine-grained address space layout randomization. In Security and Privacy (SP), 2013 IEEE Symposium on, pages 574–588. IEEE, 2013.

[53] R. Strackx, Y. Younan, P. Philippaerts, F. Piessens,S. Lachmund, and T. Walter. Breaking the memory secrecy assumption. In Proc. of EuroSec’09, pages 1– 8, 2009.

[54] R. Strackx, Y. Younan, P. Philippaerts, F. Piessens, S. Lachmund, and T. Walter. Breaking the memory secrecy assumption. In Proceedings of EuroSec ’09, 2009.

[55] L. Szekeres, M. Payer, T. Wei, and D. Song. Sok: Eternal war in memory. In Proc. of IEEE Symposium on Security and Privacy, 2013.

[56] M. Tran, M. Etheridge, T. Bletsch, X. Jiang, V. Freeh, and P. Ning. On the expressiveness of return-into-libc attacks. In Proc. of RAID’11, pages 121–141, 2011.

[57] R. Wartell, V. Mohan, K. W. Hamlen, and Z. Lin. Binary stirring: Self-randomizing instruction addresses of legacy x86 binary code. In Proceedings of the 2012 ACM conference on Computer and communications security, pages 157–168. ACM, 2012.

[58] R. N. Watson, J. Woodruff, P. G. Neumann, S. W. Moore, J. Anderson, D. Chisnall, N. Dave, B. Davis, B. Laurie, S. J. Murdoch, R. Norton, M. Roe, S. Son, M. Vadera, and K. Gudka. Cheri: A hybrid capability-system architecture for scalable software compartmentalization. In IEEE Symposium on Security and Privacy, 2015.

[59] Y. Weiss and E. G. Barrantes. Known/chosen key attacks against software instruction set randomization. In Computer Security Applications Conference, 2006. ACSAC’06. 22nd Annual, pages 349–360. IEEE, 2006.

[60] D. Williams, W. Hu, J. W. Davidson, J. D. Hiser, J. C. Knight, and A. Nguyen-Tuong. Security through diversity: Leveraging virtual machine technology. Security & Privacy, IEEE, 7(1):26–33, 2009.

[61] C. Zhang, T. Wei, Z. Chen, L. Duan, L. Szekeres, S. McCamant, D. Song, and W. Zou. Practical control flow integrity and randomization for binary executables. In Security and Privacy (SP), 2013 IEEE Symposium on, pages 559–573. IEEE, 2013.

[62] M. Zhang and R. Sekar. Control flow integrity for cots binaries. In USENIX Security, pages 337–352, 2013.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值