【无标题】

【无标题】,第1张

内存战争 摘要

:在用C或c++等低级语言编写的软件中,内存破坏漏洞是计算机安全中最古老的问题之一。这些语言缺乏安全性,因此攻击者可以改变程序的行为,或者通过劫持程序的控制流来完全控制程序。这个问题已经存在了30多年,已经提出了大量潜在的解决方案,但内存破坏攻击仍然构成严重的威胁。现实世界的漏洞显示,所有当前部署的保护措施都可以被击败。
本文通过描述在当今系统上成功的攻击来阐明这一问题的主要原因。通过建立内存破坏攻击的通用模型,对现有的各种保护技术的知识进行了系统化。使用这个模型,我们展示了哪些策略可以阻止哪些攻击。该模型识别了目前部署的技术的弱点,以及其他实施更严格政策的拟议保护措施。
我们分析了实施更严格政策的保护机制没有部署的原因。为了获得广泛采用,保护机制必须支持大量特性,并且必须满足大量需求。性能尤其重要,因为经验表明,只有开销在合理范围内的解决方案才会得到部署。
比较不同的可执行策略有助于新的保护机制的设计者在有效性(安全性)和效率之间找到平衡。我们确定了一些开放的研究问题,并提供了建议,以提高新技术的采用。

介绍

内存破坏漏洞是计算机安全中最古老的问题之一。用低级语言(如C或c++)编写的应用程序很容易出现这类bug。这种语言中内存安全性(或类型安全性)的缺乏使攻击者能够通过恶意地改变程序的行为甚至完全控制控制流来利用内存bug。最明显的解决方案是避免使用这些语言,并用类型安全的语言重写易受攻击的应用程序。不幸的是,这是不现实的,不仅因为现有的数十亿行C/ c++代码,而且因为性能关键程序(例如 *** 作系统)需要底层特性。

记忆战争的一方是开发新的攻击和恶意攻击的进攻性研究人员,另一方是开发新的保护措施的防御性研究人员和试图编写安全程序的应用程序程序员。记忆战争实际上是进攻和防守之间的军备竞赛。根据MITRE的[1]排名,内存损坏错误被认为是最危险的三个软件错误之一。谷歌Chrome是用c++编写的最安全的网络浏览器之一,在2012年的Pwn2Own/Pwnium黑客竞赛中被攻击了四次。

在过去的30年里,已经开发了一套针对内存损坏攻击的防御系统。它们中的一些被部署在商品系统和编译器中,保护应用程序免受不同形式的攻击。堆栈cookie[2],异常处理程序验证[3],数据执行预防[4]和地址空间布局随机化[5]使得利用内存破坏漏洞变得更加困难,但是在所有这些当前部署的基本保护设置下,一些攻击向量仍然有效。returnorented Programming (ROP)[6],[7],[8],[9],[10],[11],信息泄露[12],[13]以及用户脚本和即时编译[14]的普遍使用,允许攻击者执行几乎任何攻击,尽管有所有的保护。

为了克服一个或多个可能的攻击向量,提出了多种防御机制。然而,由于以下一个或多个因素,它们中的大多数都没有在实践中使用:该方法的性能开销超过了潜在的保护,该方法与所有当前使用的特性不兼容(例如,在遗留程序中),该方法不健壮,提供的保护不完整,或者,该方法依赖于编译器工具链或源代码中的更改,而工具链不是公共可用的。

对于所有不同的攻击和拟议的防御,很难看出不同的解决方案有多有效,它们之间的比较如何,以及主要的挑战是什么。本文的目的是对先前提出的方法进行系统化和评价。通过建立内存破坏漏洞和利用技术的通用模型,对系统进行了系统分析。防御技术是根据它们减轻的exploit和它们试图阻止的exploit的特定阶段来分类的。评估基于健壮性、性能和兼容性。使用此评估,我们还讨论了成功部署新软件防御所需满足的通用标准。

一些相关的工作已经涵盖了不同的内存损坏攻击[15],提供了历史概述[16]或列出了不同的保护机制[17]。这篇知识论文的系统化扩展了相关的调查论文,开发了一种新的内存破坏攻击通用模型,并使用一套新的标准评估和比较了提出的防御机制,该标准还纳入了现实世界的采用。该文件的目的不是涵盖或提及每一个提出的解决方案,而是系统地识别和分析主要方法,梳理最有希望的建议,并指出根本问题和未解决的挑战。

•开发了一个内存破坏攻击的通用模型,并在此模型的基础上确定了不同的可执行安全策略;•通过将其执行策略与不同的攻击阶段相匹配,阐明当前使用的和先前提出的保护措施未受保护的攻击向量;•评估和比较提出的解决方案的性能、兼容性和健壮性;•讨论为什么许多提出的解决方案在实践中没有被采用,以及新解决方案的必要标准是什么。

本文的组织结构如下。第二节建立了攻击的主要模型,并根据其执行的策略对保护进行了分类。第三节讨论了目前部署的保护措施及其主要弱点。我们的评估标准在第四节中建立,并通过以下四节所涵盖的防御方法的分析来使用。第IX节通过比较分析进行总结,第X节对本文进行总结。

II.attacks

为了解决基于内存错误的攻击问题,我们首先需要了解执行这种攻击的过程。在本节中,我们建立了一个逐步的内存利用模型。我们将以讨论保护技术及其在此模型上执行的策略为基础。图1显示了利用内存错误的不同步骤。每个白色矩形节点代表成功利用的基石,底部的椭圆节点代表成功攻击。每个菱形代表了一个通向目标的可选路径的决定。虽然控制流劫持通常是攻击的主要目标,但也可以利用内存损坏来执行其他类型的攻击。

A. Memory corruption

由于本文中讨论的所有漏洞的根本原因都是内存损坏,因此每个漏洞都是从触发内存错误开始的。图1中的攻击的前两个步骤涉及初始内存错误。第一步使指针无效,第二步解除对指针的引用,从而触发错误。指针可以通过超出其所指向对象的边界或对象被释放而失效。指向已删除对象的指针称为悬空指针。对越界指针解引用会导致所谓的空间错误,而对悬空指针解引用会导致时间错误。

在第1步中,攻击者可能会利用各种编程错误迫使指针超出界限。例如,通过触发未检查的分配失败,该指针可以变成空指针(在内核空间中,nullpointer解引用是可利用的[18])。如果在循环中对数组指针进行过度递增或递减运算而没有进行适当的绑定检查,就会发生缓冲区溢/下溢。如果攻击者控制了数组的索引,但边界检查缺失或不完整,则会导致索引错误,指针可能指向任何地址。索引错误通常是由整数相关的错误引起的,比如整数溢出、截断或签名错误,或不正确的指针转换。最后,我们讨论的内存错误可能会损坏指针。这由图1中的向后循环表示。

作为一种替代方法,攻击者可以使指针“悬空”,例如,利用不正确的异常处理程序释放对象,但不重新初始化指向对象的指针。临时内存错误被称为use - after-free漏洞,因为悬空指针指向的内存区域被返回(释放)给内存管理系统后,悬空指针被解除引用(使用)。大多数攻击的目标是堆中分配的对象,但是当给全局指针赋值时,指向局部变量的指针也可以从局部作用域“逃逸”。当函数返回并删除堆栈上的局部变量时,这些转义的指针将悬空。

接下来,我们将展示在第2步中读取或写入无效指针时,如何利用越界指针或悬空指针执行利用模型中的第三步。第三步是一些内部数据的损坏或泄漏。
当攻击者通过解除对指针的引用将值从内存读入寄存器时,该值可能被破坏。考虑下面的跳转表,其中定义下一个函数调用的函数指针从一个数组中读取,而不进行边界检查。
攻击者可以使指针指向他或她控制下的位置,并转移控制流。任何其他间接读取的变量都可能受到攻击。

除了数据损坏,通过攻击者指定的指针读取内存泄漏信息,如果该数据包含在输出中。这种攻击的典型例子是printf格式字符串错误,其中格式字符串由攻击者控制。通过指定格式字符串,攻击者创建无效的指针,并读取(或写入)任意的内存位置。
如果攻击者控制的指针被用来写入内存,那么任何变量,包括其他指针甚至代码,都可以被覆盖。缓冲区溢出和索引错误可以被用来覆盖敏感数据,例如返回地址或虚拟表(vtable)指针。破坏虚函数表指针是图1中向后循环的一个例子。假设在第一轮中,缓冲区溢出使数组指针出界,在第二轮中(在第3步中)破坏了内存中附近的虚函数表指针。当损坏的虚函数表指针被解引用时(在步骤2中),一个虚假的虚函数指针将被使用。重要的是要看到,对于一个内存错误,会通过破坏其他指针而引发越来越多的内存错误。还可以利用攻击者控制的指针调用free()来执行任意的内存写入[19]。写解引用也可能被用来泄漏信息。例如,攻击者可以通过破坏err_msg指针泄露上述代码行的任意内存内容。

当悬浮指针在第2步中被解引用时,时间错误可以类似于空间错误被利用。可利用时间错误的一个约束是,已分配对象(旧对象)的内存区域被另一个对象(新对象)重用。新旧对象之间的类型不匹配会允许攻击者访问非预期的内存。

让我们首先考虑读取带有旧对象类型但指向由攻击者控制的新对象的悬空指针。当调用旧对象的虚函数并查找虚函数指针时,新对象的内容将被解释为旧对象的虚函数表指针。这允许虚函数表指针的损坏,类似于利用空间写错误,但在这种情况下,悬浮指针只在读取时被解引用。这种攻击的另一个方面是,新对象可能包含敏感信息,当通过旧对象类型的悬空指针读取时,这些信息可能被泄露。

同样,通过悬空指针进行写入也可以作为越界指针,破坏新对象中的其他指针或数据。当悬空指针是指向局部变量并指向堆栈的转义指针时,它可能被用来覆盖敏感数据,例如返回地址。Double-free是释放后使用漏洞的一种特殊情况,悬浮指针被用来再次调用free()。在这种情况下,攻击者控制的新对象的内容将被错误地解释为堆元数据,这也可用于任意内存写入[19]。

内存错误通常允许攻击者以意想不到的方式读取和修改程序的内部状态。我们展示了在我们的内存利用模型中,前两个步骤的任何组合都可以用来破坏内部数据和泄漏敏感信息。此外,损坏其他指针可能会触发更多的内存错误。使这些错误成为可能的编程错误,例如缓冲区溢出和双释放,在C/ c++中很常见。在使用这种低级语言进行开发时,边界检查和内存管理都完全由程序员负责,这是非常容易出错的。

上述错误违反了内存安全策略。C和c++本身就是内存不安全的。根据C/ c++标准,编写超出界限的数组、解引用空指针或读取未初始化的变量会导致未定义的行为。由于发现和修复所有的编程错误是不可行的,我们需要自动解决方案来在现有程序中实施内存安全或在其后期阶段停止攻击。减轻一组给定攻击步骤的策略在图中由白色方框周围的彩色区域表示。图中还显示了讨论执行给定策略的方法的节号,在策略名称的上方。在第VI节中,我们讨论了在第1(2)步中通过加强内存安全来阻止任何攻击的方法。在接下来的小节中,我们将讨论不同攻击路径的步骤,并确定减轻给定步骤的策略。如图所示,一些策略包括其他较弱的策略。

B. Code corruption attack
修改程序执行的最明显的方法是使用上面提到的错误之一来覆盖内存中的程序代码。代码完整性策略强制不能编写程序代码。如果所有包含代码的内存页都被设置为只读,则可以实现代码完整性,这是所有现代处理器都支持的。不幸的是,代码完整性不支持自修改代码或即时(JIT)编译。如今,每个主流浏览器都包含了用于JavaScript或Flash的JIT编译器。对于这些用例,不能完全强制执行代码完整性,因为有一个时间窗口,在此期间生成的代码位于可写页面上。

C. Control-flow hijack attack
大多数情况下,内存破坏利用人员试图通过转移程序的控制流来控制程序。如果代码完整性是强制的,那么这个替代选项尝试使用内存错误破坏步骤3中的代码指针。代码指针完整性策略旨在防止代码指针的损坏。我们将在第VIII-A节讨论该策略的潜在代码指针目标和局限性。
假设攻击者可以由于缓冲区溢出而访问和修改返回地址。对于一个成功的攻击,攻击者还需要知道正确的目标值(即有效负载的地址)。我们将其表示为图1中的第4步。如果控制流劫持的目标(要跳转到的代码地址)不是固定的,攻击者不能指定目标,攻击在这一步失败。这一特性可以通过使用地址空间随机化在内存地址中引入熵来实现。我们将在第V-A节讨论随机化地址空间的技术。
假设一个代码指针(例如,一个函数指针)在前四个步骤中被成功破坏。第五步是执行需要将损坏的指针加载到指令指针中。指令指针只能通过执行间接控制流传输指令来间接更新,例如,间接函数调用、间接跳转或函数返回指令。从源代码定义的控制流转移执行违反了控制流完整性(CFI)策略。在第8 - b节中,我们讨论了通过在间接控制转移中发现腐败来执行不同的CFI政策的保护措施。
控制流劫持利用的最后一步是执行攻击者指定的恶意代码。典型的攻击将所谓的shellcode注入到内存中,并将执行转移到这段代码。非可执行数据策略可以防止这种利用,该策略可以使用内存页的可执行位强制执行,从而使数据内存页(如堆栈或堆)不可执行。非可执行数据和代码完整性的组合导致W⊕X (Write XOR Execute)[4]策略,表示页面可以是可写的,也可以是可执行的,但不能同时是可写的。实际上所有的现代CPU都支持设置不可执行的页面权限,所以结合不可写的代码,执行W⊕X是廉价和实用的。但是在JIT编译或自修改代码的情况下,W⊕X不能完全执行。出于完整性的考虑,我们注意到另一种随机化方法,指令集随机化(ISR)也可以通过加密来减少注入代码的执行或现有代码的破坏。但是由于对页面权限的支持,速度慢得多的ISR变得不那么重要了,由于篇幅有限,我们将不在本文中详细介绍它。
为了绕过非可执行数据策略,攻击者可以重用内存中的现有代码。重用的代码可以是现有的函数(“return-to-libc”攻击),也可以是代码中随处可见的小指令序列(gadget),它们可以被链接在一起执行有用的(恶意的) *** 作。这种方法称为面向返回编程(Return Oriented Programming, ROP),因为攻击使用结束返回指令将函数或小工具的执行链起来。面向跳转编程(JOP)是这种攻击的一般化,它利用间接跳转和链接。此时没有策略可以阻止攻击,因为无法阻止有效和已经存在的代码的执行。最近的研究集中在减少代码重用的技术上。研究人员提出了通过编译器[20]或二进制重写[21]从代码中消除有用代码块(用于ROP)的技术。虽然这些解决方案增加了ROP难度,但它们并没有消除所有有用的小工具,也没有阻止对完整功能的重用。由于这些原因,我们不会更详细地介绍这些技术。
一旦攻击者指定的代码开始执行,我们就认为控制流劫持攻击成功。要进行有意义的攻击,攻击者通常需要进行系统调用,可能还需要高级权限(例如,文件访问)。我们将不讨论仅限制攻击者访问的高级策略,包括权限、强制访问控制或由SFI[22]、XFI[23]或Native Client[24]强制实施的沙箱策略。这些策略可以限制不可信程序(或插件)或攻击者在危及可信程序后可能造成的损害。我们的重点是防止受信任程序的入侵,通常是通过广泛的访问(例如,ssh/web服务器)。

D. Data-only attack
劫持控制流并不是成功攻击的唯一可能性。一般来说,攻击者的目标是恶意地修改程序逻辑以获得更多的控制、获得特权或泄漏信息。这个目标可以在不修改与控制流明确相关的数据的情况下实现。例如,考虑在没有管理员特权的情况下登录到系统后,通过缓冲区溢出对isAdmin变量的修改。
这些特定于程序的攻击也被称为“非控制数据攻击”[25],因为代码和代码指针(控制数据)都没有损坏。破坏的目标可以是内存中的任何安全关键数据,例如,配置数据、用户标识或密钥的表示。

此次攻击的步骤与前一次相似,只是针对的是腐败。在这里,目标是破坏步骤3中的一些安全关键变量。由于安全关键是一个语义定义,因此必须保护所有变量的完整性,以阻止这一步骤中的攻击。我们称这种策略为数据完整性,它自然包括代码完整性和代码指针完整性。数据完整性方法试图通过强制执行一些近似于内存安全的方法来防止数据损坏。我们将在VII-A中介绍执行此类策略的技术。

在代码指针的情况下,攻击者需要知道应该用什么来替换损坏的数据。通过使用数据空间随机化将熵引入所有数据的表示,可以防止获取这一知识。数据空间随机化技术扩展和概括了地址空间随机化,我们将在V-B节介绍它们。

类似于代码指针损坏,一旦损坏的变量被使用,仅数据攻击就会成功。使用正在运行的示例,if (isAdmin)语句必须在不检测损坏的情况下成功执行。作为控制流完整性的推广,使用任何损坏的数据都是对数据流完整性的违反。我们在第VII-B节中讨论了该政策的执行。

E. Information leak
我们展示了任何类型的内存错误都可能被用来泄漏内存内容,否则将从输出中排除这些内容。这通常用于规避基于随机和秘密的概率防御。真实的漏洞利用信息泄露[13],[26]绕过ASLR。除了内存安全之外,唯一可能减少信息泄漏的策略是完全数据空间随机化。我们将在第五节讨论数据空间随机化的有效性,以及信息泄漏如何被用来绕过其他建立在秘密之上的概率技术。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/926058.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存