【翻译】在代码指针完整性(CPI)上攻击的可能性——On the Feasibility of Attacks on Code-Pointer Integrity

摘要

尽管进行了许多研究来减轻控制流劫持攻击,但仍是一个主要的安全问题。 代码指针完整性(CPI)[2]是第一个系统保护所有此类攻击并同时保持较低性能开销的保护机制。 在即将发布的S&P’15论文中,Evans等人 [1]声称可以在x86-64和ARM体系结构上绕过CPI。 本文是对[1]中声称CPI总体上存在安全漏洞的说法的澄清回应。

如形式正确性证明[2]所示,CPI属性本身是安全的。 特定CPI实施中的错误或缺陷可能导致安全缺陷。 我们讨论了不同的实现方案,分析了它们的安全性保证和性能影响,并证明了[1]中提出的攻击仅对最简单的CPI概念验证实现有效。 提出的攻击无法颠覆其他实现方式,例如,使用硬件强制分段或软件故障隔离的方式。

1.指针完整性

代码指针完整性(CPI)[2]是一种保护机制,可以防止由内存破坏错误引起的所有控制流劫持攻击,而其性能开销较低。 CPI在编译时检测C/C++程序,从而为程序中所有直接和间接指向代码的指针提供了精确的内存安全性,从而确保了上述安全性。 CPI带有[2]中概述的正确性证明。 代码指针分隔(CPS)是CPI的简化版本,通过确保仅指向代码的直接指针的完整性,为大多数控制流劫持攻击提供实用保护。 我们在SPEC2006基准上实施CPI的性能开销平均为8.4%,而CPS的性能开销平均为1.9%。

CPI通过将内存安全实施限制为仅敏感指针(即代码的直接或间接指针)来实现较低的性能开销。关键思想是将程序存储器分成两个隔离的区域:安全区域存储所有敏感指针,而常规区域存储其他所有内容。CPI使用静态分析来识别可访问安全区域的程序指令,并通过内存安全检查对它们进行检测。CPI采用指令级隔离,以防止所有其他指令访问安全区域,即使被攻击者劫持也是如此。 为了避免更改常规区域的内存布局,CPI保留了常规区域中敏感指针通常占用的位置,并且在安全区域中,CPI维护了进行内存安全检查所需的,从这些保留位置的地址到指针值和相应元数据的映射。

CPI或CPS的实现包括:

(i)静态分析PASS,该过程将程序中的所有内存访问均分为可能访问敏感指针的访问和不能访问的内存;

(ii)检查PASS,使用安全区域并插入运行时检查以增强这些访问的内存安全性,对可能访问敏感指针的所有访问进行检查;

(iii)指令级隔离机制,可防止未使用内存安全性检查进行的所有内存访问,都无法访问安全区域,即使内存访问受到攻击者的侵害。 本文与Evans等人[1]一样着重于第三步。

我们在[2]中实现并提出了多种机制,这些机制可以使用硬件强制分割,软件故障隔离或随机化和信息隐藏有效地强制执行指令级隔离。表1总结了这些机制的安全性和性能含义,下面我们将对其进行详细讨论。 我们专注于每种机制背后的设计选择,而忽略了我们发布的原型中潜在的非设计错误。

[1]中的攻击主要针对这些机制之一,即基于信息隐藏的机制,并且仅对最简单的概念验证实施有效。

1.1基于硬件分段的实现

在支持硬件强制分段的体系结构上,CPI直接使用此功能来强制执行指令级隔离。在此类实现中,CPI专用于段寄存器以指向安全内存区域,并且它在编译时强制执行,只有通过内存安全检查检测到的指令才能使用此段寄存器。CPI配置所有其他段寄存器,这些指令将由非检查指令使用,以防止通过这些段寄存器在硬件级别上对安全区域的所有访问。

x86-32CPU支持硬件强制分段,某些x86-64 CPU也支持硬件分段(请参见Long Mode Segment Limit Enable标志),这表明向x86-64 CPU添加分段是可行的,只要可以受益的技术从中证明确实是有价值的。

CPI的这种实现是精确的,并且在不访问敏感指针的指令上施加了零性能开销。 它不易受到[1]中的攻击的影响。

1.2基于软件故障隔离的实现

在无法使用硬件强制分割的体系结构(例如ARM和大多数x86-64 CPU)上,可以使用轻量级软件故障隔离(software fault isolation,SFI)强制执行指令级隔离。 在我们的实现中,我们将内存中的安全区域对齐,以便可以通过单个bitmask操作来实现不使用别名的指针(与量级更重的SFI解决方案不同,在程序中后者通常会为每个内存访问添加额外的内存访问和/或分支) 。 此外,对安全堆栈的访问无需进行检测,因为它们可以保证安全[2]。

我们基于SFI的CPI实现非常精确,相对于硬件强制分段,SFI将开销增加了不到5%。 它不易受到[1]中的攻击的影响。

1.3基于信息隐藏的实现

实现指令级隔离的另一种方法是基于随机化和信息隐藏。这样的实现利用了CPI检查的保证,即在CPI检查的程序中,永远不会将指向安全区域的指针存储在安全区域本身之外。 当安全区域的基本位置被随机化时,以上保证意味着即使在任意内存读取漏洞的情况下,攻击者也必须依靠随机猜测才能找到安全区域。 在64位体系结构上,大多数地址空间都是未映射的,因此,大多数失败的猜测都会导致崩溃。 如果足够频繁,则可以通过其他方式检测到此类崩溃。

通过随机猜测找到安全区域的位置,所需的实际预期崩溃次数由安全区域的大小和地址空间的大小确定。 当今的主流x86-64 CPU提供 2 48 2^{48} 248字节的地址空间(而该体系结构本身预计将来会扩展到 2 64 2^{64} 264字节)。 地址内核的一半通常由OS内核占用,这为应用程序保留了 2 47 2^{47} 247个字节。

如上所述,针对每个敏感指针,安全区域存储一个映射,该映射将非CPI检查程序内存中,指针占据的位置映射到CPI检查程序中指针值及其元数据的元组。 在64位CPU上,此映射的每个条目占用32个byte,并且由于指针对齐要求,它代表程序内存的8个byte。 预期的条目数取决于程序内存的使用情况,敏感指针的分数以及用于存储此映射的数据结构。

我们发布了基于信息隐藏的CPI实现的三个版本,这些版本使用哈希表,二级查找表或线性表来组织安全区域[2]。 我们估计安全区域的大小以及为以下每个版本找到其位置所需的预期崩溃次数。 为了进行此估计,我们假设程序使用1GB内存,其中8%存储敏感指针(与[2]中的实验评估一致),总计为 1 G B ∗ 8 % / 8 b y t e s ≈ 2 23.4 1 GB * 8% / 8bytes ≈ 2^{23.4} 1GB8/8bytes223.4个敏感指针。

  • 哈希表,此实现基于线性探查表,该表具有基于位掩码移位的哈希函数,由于程序内存中敏感指针的稀疏性,在哈希表负载因子高达0.5时性能良好。 保守地假设负载因子为0.25,哈希表将占用 2 23.4 / 0.25 ∗ 32 = 2 30.4 2^{23.4}/0.25*32 = 2^{30.4} 223.4/0.2532=230.4字节的内存。 随机化散列表的位置可以提供最多 47 − 30.4 = 16.6 47 - 30.4 = 16.6 4730.4=16.6的熵,; 平均而言,需要 2 15.6 ≈ 51000 2^{15.6} ≈ 51000 215.651000次崩溃可以猜测。 在大多数系统中,可以从外部检测到许多崩溃,因此无法进行攻击。
  • 二级查找表,此实现组织类似于页表的安全区域,使用地址的高23位作为目录中的索引,而低22位作为子表中的索引(由于对齐,最低3位为零)。 每个子表占用 32 ∗ 2 22 32*2^{22} 32222字节,并描述了地址空间的 8 ∗ 2 22 8*2^{22} 8222字节区域。 假设敏感指针均匀分布在1GB的连续程序内存中,CPI将为子表分配 1 G B y t e / ( 8 ∗ 2 22 ) ∗ 32 ∗ 2 22 = 2 32 1GByte/(8*2^{22})*32*2^{22} = 2^{32} 1GByte/(8222)32222=232字节。 随机化子表位置可得到 47 − 32 = 15 47-32=15 4732=15位的熵,平均需要 2 14 2^{14} 214次崩溃才能猜测。 请注意,攻击者将在多个子表中找到随机的一个,并且在其中找到可用的代码指针需要进一步猜测。 因此,在许多实际情况下这种攻击也是不可行的。
  • 线性表,这个简单的实现为线性映射地址的安全区域分配了 2 42 2^{42} 242字节的固定大小区域。 该实现将仅给出 47 − 42 = 5 47-42=5 4742=5位熵。 可以猜测此实现中线性表的位置,而平均平均仅导致16次崩溃。

与完整的CPI不同,代码指针分隔(Code-Pointer Separation,CPS)不需要任何元数据并且敏感指针更少(平均减少8.5倍[2])。 对于基于哈希表的实现,预期崩溃的数量增加了17倍,对于其他两种实现,预期崩溃的数量增加了4倍。

使用哈希表组织安全区域的CPI的基于信息隐藏的实现提供了 2 16.6 2^{16.6} 216.6熵(或CPS为 2 20.7 2^{20.7} 220.7)的概率安全保证。 [1]中的攻击, 在检测到安全区域的位置并成功攻击之前,将导致平均发生51000次崩溃(对于CPS约为867000次)。 在检测到安全区域的位置并成功攻击之前。我们认为,可以通过外部手段自动检测到此数量的崩溃,尤其是考虑到这些崩溃的统一模式。

2.对CPI实施最简单的攻击

Evans等。 [1]提出了一种针对基于信息隐藏的CPI实现的攻击,该信息隐藏使用线性表来组织安全存储区,这是上述CPI的五个实现之一。 该攻击基于Nginx Web服务器中的远程通道信息泄漏漏洞,攻击者可以利用该漏洞以高可信度远程读取Nginx内存中的任意位置。 攻击首先使用此任意内存读取功能来探查程序内存并定位安全区域,从而利用CPI的概念验证实现的局限性进一步将崩溃次数减少到16以下。 在已知内存区域的情况下,该攻击利用了另一个内存写入漏洞,以便直接在安全区域中覆盖敏感指针的值,而CPI无法检测到该敏感指针的值。

如上一节所述,此攻击仅对表1中概述的最简单的CPI实现有效。我们认为,此攻击的存在并不会削弱CPI整体的安全性,并且阻止广泛使用CPI 对社区不利。

参考

[1] Isaac Evans, Sam Fingeret, Juli´an Gonz´alez, Ulziibayar Otgonbaatar, Tiffany Tang, Howard Shrobe, Stelios Sidiroglou-Douskos, Martin Rinard, and Hamed Okhravi. Missing the point(er): On the effectiveness of code pointer integrity. In IEEE Symp. on Security and Privacy, 2015.

[2] Volodymyr Kuznetsov, L´aszl´o Szekeres, Mathias Payer, George Candea, R. Sekar, and Dawn Song. Code-Pointer Integrity. In Symp. on Operating Systems Design and Implementation, 2014.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值