Group of Software Security In Progress

GoSSIP @ LoCCS.Shanghai Jiao Tong University

Losing Control: On the Effectiveness of Control-Flow Integrity Under Stack Attacks

论文下载:https://www.ics.uci.edu/~perl/ccs15_stackdefiler.pdf

Abstract & Introduction

敌手利用内存破坏漏洞,劫持程序的控制流来执行任意代码。面对这种攻击,其中一种防护技术就是保证程序的CFI。 但是敌手可以通过获得原始程序之外的控制流来打破粗粒度的CFI策略,构造出图灵完全的代码重用攻击。

  • 所谓细粒度的CFI策略,就是只有编程者计划好的跳转目标才是被允许的;
  • 粗粒度的CFI策略更灵活一些,可以允许间接跳转指向任意函数的起始地址。

并且在防护措施的实际应用中,由于考虑到效率,安全性以及兼容性等问题,往往会做出一部分的妥协。 本文针对GCC以及LLVM上的CFI策略(LLVM的indirect function call checks,GCC的virtual table verification)提出了可实用的攻击:

  • IFCC通过一个包含针对所有可能调用地址的jump指令的跳转表,来保证间接调用的安全性。在编译的时候,有效的调用目标集就会被确定。
  • VTV only protects virtual function calls.

本文通过利用堆上的漏洞所获得的对栈上内容的读写权,来影响栈上保证控制流转换的数值,从而达到攻击应用了CFI防护的程序的目的。

Threat Model & Assumpitions

敌手的能力:

  • Memory read-write:目标程序包含内存破坏漏洞能够被敌手在运行时被利用;
  • Adversarial Computation:敌手可以在运行时进行相关的计算操作。特别的,敌手可以使用脚本引擎来生成多条执行线程。

防御的假设:

  • Non-Executable Memory:目标系统强制实行DEP;
  • Randomization:目标系统应用地址随机化;
  • Shadow Stack:虽然无法实现shadow shacks,但是文中假设它的存在。

Stackdefiler

本作提出的攻击建立在获得对栈上的数据进行修改的权限上。

  • 由于地址随机化,要获得栈的内存地址:an adversary with the ability to disclose arbitrary memory can get a stack address by recursively disclosing data pointers。
  • 利用堆上的漏洞,获得对内存数据读写的权限。

Corrupting Callee-Saved Registers

当一个函数(the caller)调用另一个函数的时候,被调用的函数(the callee)会将会被使用到的寄存器上值(或者全部寄存器上的值)压入栈中,这些被出入栈上的寄存器就被称为callee-saved registers。当被调用的函数即将返回时,会将这些callee-saved registers的值重新写会寄存器。

那么,当这些callee-registers被存在栈上时,就可以被敌手修改,特别地,如果这些还原的寄存器值被用于CFI检测,就会引起严重的问题。

尤其是在x86 32bit架构上的位置无关代码(PIC),就很容易被这种方法利用,来绕开相应的CFI检测。

Corrupting System Call Return Address

细粒度CFI保证了每一个间接跳转的目标地址:

  • 通过静态分析以及强制的label checking来决定控制流图中的前向边;
  • shadow stack被用来核实控制流中的返回边。

但是我们注意到用户态的CFI仅插桩用户模式的函数调用,而不涉及系统内核的函数调用。特别地,当从一个系统调用返回时,内核会从用户态的栈上读取返回地址,返回到原来代码的执行位置,但是CFI并不会去检测这个返回值,这就存在一个严重的漏洞。

在x86 32bit的架构里,提供了快速调用0层系统过程的指令——sysenter,由于这个指令不保存任何状态信息,在Windows会在执行sysenter前将返回地址存于用户态的栈上。这就导致了在返回地址被压到栈上和内核读取它返回用户态之间存在一个较小的窗口时间。

那么敌手就可以通过另一个并行的线程,利用这个窗口时间来重写这个栈上的返回地址,指向任意敌手所希望的地址,并绕过CFI的检测。

在64-bit x86系统上,使用了一个不同的指令syscall,它将用户态的返回地址保存在寄存器上,防止敌手修改返回地址。但是32-bit程序在64位系统上执行依旧存在这个漏洞

Disclosing the Shadow Stack Address

  • parallel shadow stack:shadow stack和normal stack之间保持一个常数偏移量,因此敌手可以通过normal stack的地址来计算shadow stack的地址。
  • 利用TLS技术的shadow stack:由于这种防御技术在公共领域的不可用性,本文只讨论了他的潜在漏洞,并没有进行实现。由于TLS通过一个段寄存器进行定位,以TLS为基础的shadow stack就会将shadow-stack的指针存于一个通用寄存器上。而且并没有证据显示在地址返回后,寄存器上的值会被清除,那么通过callee-saved registers的方法就可以获得这个shadow-stack的指针。

Stackdefiler Implementation

针对重新导入CVE-2014-3176漏洞的最新版chromium进行攻击。CVE-2014-3176能允许敌手操作在堆上的JavaScript对象的数据域。

Attacking a Web Browser

  • Information Disclosure

Fig

  • vTable Hijacking:修改 C++ object中的vTable指针,使它指向一个伪照的vTable。

Bypassing IFCC

Fig

Bypassing fine-grained CFI

  • 产生两条线程:一条线程连续地进行系统调用,另一条持续地修改系统调用的返回地址
  • 连续的系统调用,可能会破坏在栈上的ROP gadget chain,因此需要一个gadget,这个gadget能将堆栈指针指向一个不被连续修改的位置,然后我们将返回地址修改成这个gadget的起始地址。

Measurement & Case studies

Securing IFCC

在使用ebx这类PIC相关的寄存器之前,保证没有经过需要对寄存器上的值进行存储的函数调用。

Securing Stacks

一种可以防御本文攻击的策略就是把栈从常规数据内存中隔离出来。