Readactor: Practical Code Randomization Resilient to Memory Disclosure阅读笔记(二)

来源:互联网 发布:阿里云域名优惠 2017 编辑:程序博客网 时间:2024/05/20 02:22

Readactor:代码随机化应对内存泄漏问题-阅读笔记(二)

3、Readactor如何实现攻击防御

针对之前提到的这两种内存泄漏攻击,Readactor分别使用相应的技术进行防御。同时本文中还给出了针对JIT编译情况下的保护方案。

1)Readactor如何阻止基于直接内存泄漏的攻击( EXECUTE-ONLY MEMORY)

此部分介绍了Readactor如何实现在内存层面的保护。
Readactor利用了商业x86处理器的虚拟化特性将代码页和只执行许可进行映射。在EPT技术的支持下,将code page许可设置execute-only,使得攻击者在程序执行过程中不能读取和分解code page,从而来阻止直接内存泄露攻击。
Figure 5:利用Execute-only内存阻止直接内存泄露
Figure 5:利用Execute-only内存阻止直接内存泄露

以一个switch-case程序为例。从上图中可以看出,(左边)code page是可读可执行的,且LLVM编译器在jump_table中给出了对应每个case的基本块地址,可以被攻击者直接访问到。而(右边)Readactor将switch-case的表转变为可执行指令,而不是嵌套在可执行代码中的数据,并且存放在execute-only许可的code page中。攻击者无法读取。

2)Readactor如何阻止间接内存泄漏攻击(COMPILER INSTRUMENTATION)

此部分介绍了Readactor如何实现在编译器层面的保护。
本文修改了LLVM编译器框架来实现以下目标:
1)产生多样化的代码
2)阻止良性代码读取代码页中的数据
3)阻止攻击者利用代码指针执行间接溢出攻击
使用的技术包括:A细粒度的代码多样化;B代码和数据的分离;C代码指针的隐藏;
Readactor为了隐藏所有函数指针和返回地址,结合了利用EPT实现的execute-only内存技术和基于trampoline的指针隐藏技术,同时还使用了高效的细粒度的代码随机化技术。
Figure 6: 利用指针隐藏技术阻止间接内存泄露
Figure 6: 利用指针隐藏技术阻止间接内存泄露

从上图中可以看出,代码指针原本存放在data page上的vTable中,可供读写,容易被攻击者获取到Method_A和Function_B的指针。而Readactor的使用,使得攻击者只能获取到trampoline(跳板)的指针,却无法得知Method_A和Function_B的具体位置,因为trampoline(跳板)位于execute-only许可的coda page上,并且函数的布局被随机化了。

3)Readactor如何实现对JIT编译器的保护(JIT COMPILER PROTECTION)

嵌套了JavaScript代码的网页必须通过浏览器来执行,而执行JavaScript最有效的方式是通过JIT编译器,这也是大多是Web浏览器支持的。所谓JIT编译就是代码的产生发生在运行时,因此前面提出的在编译时执行的保护技术不能保护动态产生的代码。作者团队实现此类情况下的保护,使得本文提出的方法足够实用和综合。本文通过扩展execute-only内存来支持动态编译的代码。这一部分主要以V8 JavaScript引擎为例介绍了动态代码产生下的保护。当然本文提出的方法同样适用于其他JIT编译器。
作者团队向V8引擎67个不同的源代码文件中添加了1053行C++代码。在对V8引擎进行调整之后,代码缓冲区的权限随时间变化情况如下图所示:
Figure 7:执行一个JIT编译程序的时间轴
Figure 7:执行一个JIT编译程序的时间轴

可以看出执行过程在JIT编译器和产生的代码之间变换。
本文通过两步将Readactor方法应用到动态产生代码的保护中:
Step1: 调整JIT编译器在输出中实现代码和数据的分离;
Step2: 此时代码和数据已经分离在不同的页上,我们识别和调整所有要求对产生代码进行读和写的操作;
》下面对step1和Step2进行详细介绍:
首先是代码和数据的分离。调整之前V8JIT编译器一次转换一个JS函数,并把结果放在一个代码缓冲区中,这个缓冲区的权限是RWX。每个转换后的函数以V8引擎中的Code boject形式展示,如Figure8中左侧所示。每个Code Object包含了机器指令的产生序列,Code header包含了关于机器码的信息和一个指向Map object的指针。我们要做的就是把这个Code object存放在execute-only的内存页中。具体怎么做呢?我们把其中的数据内容移到隔离的数据页中,通过为Code object添加新的getter函数使得这些数据可以被访问。为了确保Code header的安全,我们将其移动到一个隔离的CodeHeader object中,位于权限是RW的内存页上。接着我们添加一个getter方法达到Code object返回一个指针(code_hdr_ptr)指向CodeHeader object。同样的,我们对Map object做相似的处理。通过这些处理,消除了对V8 Code object的所有读写访问(除了创建和回收阶段)。最终他们被存放在Execute-only的内存中。具体的转化过程如下图所示:
Figure 8:将V8 Code object转化为隔离的代码和数据
Figure 8:将V8 Code object转化为隔离的代码和数据

其次是在execute-only和RW权限间按照要求进行转换。在完成隔离之后,执行页的内容无需修改,然而JIT编译器仍然需要频繁地改变代码。就如Figure7中所示,执行过程在编译器和JS代码间轮换。想要完全消除编译器中对代码的写会引发V8的重大重构工作,同时引发性能下降。通过观察,我们发现产生的代码不是可执行的就是暂停的,因此可以被更新。在执行过程中,我们为代码分配execute-only权限,当执行过程暂停时,我们为代码重新分配RW权限。这样就综合考虑了性能和安全性两个方面。我们会尽可能最小化重新分配页的次数,已经一个页可以被访问的时间长度。因为每次一个Code objet可写,就为攻击者提供了恶意代码注入的机会。Song团队已经证明了在这个窗口下实现攻击是可行的。并且提出了一个基于进程隔离的防御方法。主要思想为:通过采用进程隔离,JIT编译器位于一个独立于其他不可信浏览器的进程中,因此只有JIT进程能够对产生的代码进行写。这样就能成功实现代码缓冲区的保护,防止代码注入攻击。但是没有能实现泄漏攻击。因此本文结合了此方法,很好的对之前实现内存泄漏防御的方法进行了补充,最后实现了一个可以保护JIT免受内存泄漏攻击和代码注入攻击的方法。


4、Readactor工作流程

下面我们从整体角度来理解Readactor的完整工作流程。其中灰色部分是Readactor的组件。
Figure 9: Readactor工作流程
Figure 9: Readactor工作流程

下面简单介绍下流程:
(0)实现execute-only支持:我们装载了一个瘦hypervisor来激活内存虚拟化和启动扩展页表(EPT)。EPT中包含了两个到物理内存的映射。一个是普通映射,一个是readacted映射。后者是调整后的操作系统用来设置页的权限为execute-only;
(1)编译:编译器输入了一个任意程序的安全代码,创建一个二进制文件。此处进行了三步工作:a)实现代码和数据的严格隔离;b)在函数顺序,寄存器分配和save slot 重新排序中应用代码随机化;c)通过创建teampoline跳板实现代码指针的隐藏;
(2)二进制文件:输出的二进制文件包含了代码,跳板和数据的不同部分。Linker为每个部分标记了合适的访问权限;
(3)装载器:装载器读取每个部分的大小和权限位,为其分配合适内存区间,按照要求的权限对其进行保护(4);
(5)代码指针隐藏:为了在运行过程中隐藏代码指针,调用指令被一条到相应跳板的jmp指令替换。而这个跳板接下来会调用原本需要被调用的函数,并且返回一个地址到栈中。
(6)然而返回地址不会指向代码部分,而是跳板部分。正如前面提到的,暴露跳板指针无法让攻击者才出代码部分的位置和布局。
(7)一旦调用的函数返回到跳板,控制流也通过另一条jmp指令返回到原始调用地址。我们通过trampolines跳板简单地实现了函数指针的保护。

而攻击者可以实行两种攻击场景
A.读取数据内存:数据区域仍然是可读可写的。因此攻击者可以获得并修改代码指针。然后,泄露的代码指针不能提供任何应用了代码随机化技术的位置信息,从而攻击者无法创建一个ROP代码片段链来实施攻击。
B.读取代码内存:代码趋于被设置为execute-only.任何视图读写这些区域的行为都会引发EPT exception。一个引发execute-only exception的应用都会之间被操作系统杀死,因为execute-only权限是在硬件中实现的,从而无法用软件绕开。


攻击模型和假设

该部分说明本文提出方法的适用条件和前提假设。本文提出的方法可以用于防御所有已知类型的ROP攻击。该方法建立在以下假设和攻击模型上:
1)目标系统提供内嵌的保护来抵抗代码注入攻击。当今,所有现代的处理器和操作系统都支持数据执行保护(DEP)来防止代码注入;
2)攻击者不能够篡改我们的Readactor的实现;
3)攻击者没有内存代码布局的先验知识。我们可以通过使用细粒度的代码多样性实现这个假设;
4)目标程序至少存在一个内存冲突漏洞,运行攻击者操纵控制流;
5)攻击者知道目标平台上的软件配置和防御,已经目标应用的源代码;
6)攻击者能够读取和分析目标进程中任何可读内存的位置;
本文的攻击模型和之前的攻击防御工作是一致的,尤其和JIT-ROP中介绍的强大模型一致。


实验结果评价

本文从安全和性能两个角度对Readactor方法进行评价。

1、安全评价

Readactor的主要目标是阻止利用直接或间接内存泄漏的代码重用攻击。因此我们基于5种不同的代码重用攻击来分析和检验该方法的有效性。
1)Static ROP
2)JIT-ROP with direct disclosure
3)JIT-ROP with indirect disclosure
4)ROP on just-in-time generated code
5)Return-to-libc
总的来看,Readactor针对所有类型的代码重用攻击都提供了高度保护。

2.性能评价

本文严格的评价了Readactor对SPEC CPU2006基准下和大型实际应用Chromium浏览器的性能影响。同时还利用标准的JavaScript基准评价了V8 JS引擎的变化。
1)SPEC CPU2006
Code-Data Separation:1.1%的性能开销
Code-Pointer Hiding:4.1%的性能开销
Hypervisor layer EPT: 2.5%的性能开销
Full Readactor:如果实行整套Readactor保护,会对产生6.4%的性能开销;
2)Chromium Browser
execute-only code protection alone:2.8%性能开销
hypervisor executeonly code pages a+ code-pointer hiding:12%性能开销
3)V8 JavaScript JIT
No Readactor:6.2%性能开销
Readactor:7.8%性能开销
详细实验结果对比如下图:
Table 1:Readactor和其他方法的防御效果及性能对比
Table 1:Readactor和其他方法的防御效果及性能对比

Table1给出了本文提出的方法和其他相关工作提出的方法对内存泄露的保护效果,以及对各类ROP攻击的安全支持,同时在静态和动态编译层面的覆盖度上进行了比较,最后比较了性能。可以看出:Full Readactor能满足所有的安全属性,且性能开销也是最小的为6.4%。


结论

很多文献都证明了代码随机化在防御代码重用攻击中是非常实用和有效的,然而,内存泄漏问题却为这类方法造成了安全威胁,使其失效。为了解决这个问题,本文提出了一种方法,在保留代码随机化的基础上,还解决了内存泄露的问题。这种方法不仅实用,而且可以灵活应对各种类型的绕开策略,实现完全防御。基于这种方法,本文建立了一个完全成熟的原型系统:Readactor,能阻止攻击者直接或间接获取代码布局信息。通过硬件实现的execute-only内存技术来防御直接内存泄漏,通过代码指针隐藏技术来放置间接内存泄露攻击。经过在现实世界软件比如Google的Chromium和V8 JS引用上实验并进行安全属性和性能等方面严格详细的评价后,证明了该方法的有效性和实用性。Readactor在直接和间接内存泄露问题上提供了综合有效的保护,同时也首次实现了静态和动态编译代码的一致保护。
总的来说本文实现了三个方面成果:
1)针对直接内存泄漏问题提供了更加综合有效的保护;
2)首次提出了对间接内存泄漏问题的防御;
3)首次提出了对静态和动态编译代码的保护;

0 0