Group of Software Security In Progress

GoSSIP @ LoCCS.Shanghai Jiao Tong University

Nail: A Practical Tool for Parsing and Generating Data Formats

  • 出处:OSDI 2014
  • 作者:Julian Bangert, Nickolai Zeldovich

不同版本

Motivation

本文的主要目的:解决臭名昭著的Binary data输入格式解析(parsing)问题。

输入格式的解析导致的软件漏洞比比皆是,最近著名的如Android上stagefright的系列漏洞。 论文中给出的数据显示,在论文发表的最近三年间,有15个关于ZIP文件格式的CVE被登记,其中11个是memory corruption相关,4个是parser的不一致性(inconsistency),即相同的输入存在不同的解析方式(Android master key是一个著名的例子,Java层和Native层上的解析差异性导致了恶意的APK安装包能够通过校验)。 由于文件格式解析这个业务需求广泛存在于大量的系统中,又是相对独立的一个功能模块,如果能够用一些方法产生更为健壮安全的parser,则有望极大提升当前系统的安全性。

Solution

类似编译器前端生成的工作流程(such as lex and yacc),通过提供一个文件格式描述的Domain Specific Language(DSL)来自动化生成Parser,这样就可以保证少犯错误或者不犯错误。

当前的主要问题:

  1. 不要手写parser!不要手写parser!不要手写parser!
  2. 已有的一些DSL只能将输入转换成抽象语法树(AST),而AST离程序内部使用的数据描述结构(特别是C语言这种memory unsafe的情况)之间还有很多差异性,如果不能把这个gap填起来那么还是容易出错。
  3. 已有的文件结构描述方式不完善:
    • 不能表达复杂的文件结构;
    • 文件结构内部本身的冗余(比如checksum,是可以通过数据计算出来的,就没必要单独描述它);
    • c)当output和input是一样的格式的时候,最好能够直接重用input的格式而不是再把parsing过程反过去做一遍.

作者设计了一个叫做Nail的parser generator,这个generator做到了:

  • 规定了一套grammer可以方便地描述大部分常见的binary data format,而且最为重要的是,这个描述既是抽象的格式描述,同时又和low level的layout保持很高的兼容性,也就是作者说的using a single grammar to define both the external

Format and the internal representation

  1. 通过引入dependent fields和transformations两个重要的概念,极大地简化了输入格式的描述
  2. 自动化地产生出parser,提供良好的结果,将文件格式解析问题隔离为一个独立的操作,极大降低了安全风险
    • Using Nail, we implement an authoritative DNS server in C in under 300 lines of code and grammar, and an unzip program in C in 220 lines of code and grammar, demonstrating that Nail makes it easy to parse complex real-world data formats.

解读

视频解读

作者给出了一个非常清晰的presentation,介绍Nail是如何被用于一套数据格式处理的toolchain

Nail的设计目标涵盖了数据格式定义、parser生成以及generator生成三个点。Nail提供了一套grammer描述常见的数据格式,这里它的格式和C比较类似,但是增加了很多约束条件来保证每个域的安全。Nail能根据数据格式直接生成parser和generator,parser用来将binary input转换成程序内部的数据结构,而generator可以反过来将程序内部的数据结构转换成binary output(这里假定input和output的格式一致)

Nail抓住了real world input的重要特征,并用了三大特性来对应之:第一,Nail定义的dependent fields很好地描述了binary format里面那些length field、checksum field;第二,Nail将文件视为多个stream,很好地处理了复杂层级结构的文件格式;第三,Nail用了一个抽象的transformation概念去统一描述那些binary format里面的复杂数据类型(并且告诉你可以引入外部插件来解析他们,而Nail不去直接处理这些数据)

Evaluation

测试对象:

一些网络协议格式(DNS、Ethernet、ARP、IP、UDP和ICMP)包 ZIP格式 作者用了非常少量的描述代码就重写了这些包的格式描述,并自动化生成的解析代码;生成的ZIP处理器不再受到11个已知的CVE影响,而生成的DNS解析程序经过两天的fuzzing test也没有任何异常

Performance也很好,和其它程序如Bind比起来NailDNS的效率高许多(当然作者在作报告的时候也承认和其它程序比不太公平,因为他自己生成的是一个prototype simple parser,并没有做复杂的功能)

For Developers

Github(然而已经两年没有更新,感觉后续没维护了)

CVE-2016-3078 PHP ZipArchive Integer Overflow 分析

这个漏洞的影响范围是PHP 7.0.6版本以前的所有PHP 7.x 版本。PHP的源码可以在这里下到,https://github.com/php/php-src/

PHP源码架构

  • PHP源码里的核心库是在Zend目录下。负责php脚本的解析,执行等核心功能。
  • TSRM目录下是关于PHP多线程的库。
  • ext目录下是实现各种PHP扩展功能的代码。如:ftp,ssl,xml等,也包括这次主要要分析的zip解析的功能。

漏洞详情

关于CVE-2016-3078,在社区上有人发过:http://seclists.org/bugtraq/2016/Apr/159

主要问题是,当PHP在x86的机器上编译的时候,其中的zend_ulong类型会被编译成不同的长度。

Fig

以上代码来自Zend/zend_long.h。可以看到,如果是在x64环境下编译的话,zend_ulong是64位长度的;如果是x86下的话,就是32位长度的。然后,在php_zip_get_from()函数里,会把一个64位的长度赋值给一个zend_ulong类型的变量,形成整型溢出,然后是堆溢出,通过合理构造输入可以达到任意地址写。

执行流程

一个可以触发漏洞的简单的php脚本如下:

Fig

在php脚本里,解析zip文件时,先调用ZipArchive::open()来把zip文件读到内存里。在php源码里对应的代码在php_zip.c里:

Fig

其中,open()初始化了一个_ze_zip_object结构体:

Fig

其中的za指向了一个zip结构体,这个结构体存放的是与被解析的zip文件的内容相关的东西。

Fig

其中的zip_source_t *src指向的结构体与zip文件里的数据相关的东西。

Fig

其中的cb是一个union结构体,里面放的是在解压zip压缩包里的文件时调用的回调函数。在open()函数里,这个回调函数会被初始化成read_data()

Fig

再转回php脚本。open()完成之后,然后调用getFromIndex()或者getFromName()读取zip压缩包里的具体文件数据。在php源码里,这两个函数的都直接调用了php_zip_get_from()函数,而这个就是存在漏洞的函数。

在php_zip_get_from()函数里,会先从Executor Globals里把传入的参数读出来。然后会解析zip文件的dirEntry对应的文件目录,然后更新一个zip_stat结构,存放结果。

Fig

其中size是uint64的,对应zip Entry里的UncompressedSize位。之后会检查从php脚本里传入的参数len是不是小于1,如果是就会把这个size赋值到len。注意,这里len的类型是zend_long。

Fig

然后,调用zip_fopen_index()来解析zip结构,然后更新zip结构体的数据。在解析过程中,这个函数会对zip文件的压缩方式做区分。分别是encrypted,compressed,和store。

encrypted是加密,需要password;compressed是压缩;store是不压缩,直接存放原始文件数据。然后会在原来的zip结构体之上再重新封装一层zip结构体,并把新的结构体的回调函数注册成相应的解密、解压、crc检查的函数。并返回这个新的结构体的指针。

接下来,回到php_zip_get_from()函数。zend_string_alloc()函数是触发整型溢出的点。然后下面的zip_fread()函数是堆溢出的点。

Fig

在zend_string()里会先对len做一个边界对齐,会在原始len的大小上加上0x14然后再mask 0xFFFFFFFC。攻击中可以把UncompressedSize设成0xFFFFFFFE,然后会分配一个0x10大小的堆。

Fig

这里面,调用了pemalloc分配堆块。它是php内部实现的一个Memory Allocator。源码在zend_alloc.c里,具体就不展开了。它里面对小的堆块的分配做了优化,所有小堆块都连续分布在一个或多个连续的内存页上,每个空闲的小堆块都用一个单向链表的形式组织起来。第一个空闲的小堆块的地址会放在全局的chunk head的free_slot里。

Fig

当要分配小堆块的内存的时候,会先从free_slot里拿出那个地址,然后根据链表,把下一个free的block地址放到free_slot里。代码如下:

Fig

所以,这就给堆溢出带来了机会。可以先溢出改写下一个free block的头,然后再分配一次,把构造的free block头写入free_slot,然后再分配一次,构造出任意地址写。

实际的堆溢出发生在后面的zip_fread()函数里。他会递归地调用之前提到的zip结构体里注册的回掉函数。于是,就会先调用之前提到的decrypt/inflate/crc check,然后再调用在open()的时候注册的read_data()函数。这里基本上可以看作是memcpy。

ASLR on the Line: Practical Cache Attacks on the MMU

论文下载

这篇文章发表在2017年NDSS上,作者是Vrije Universiteit Amsterdam的Ben Gras和Kaveh Razavi。文章中使用了一种EVICT+TIME的旁路攻击方式(AnC)来泄漏内存地址,用来降低ASLR的熵。项目网站:https://vusec.net/projects/anc

旁路攻击的条件比较苛刻,要求计时准确,内存访问的控制要求也很高,感觉应用上确实更适合浏览器攻击。

攻击原理

Fig

虚拟地址寻址时,会使用4级页表(PTL)来找到对应的物理地址。每级页表中存放指向下一级页表物理地址的PTE(Page Table Entry)。最后一级页表(PTL1)存放到目的物理地址的PTE。出于性能考虑,MMU和Cache中会缓存一部分最近经常访问的PTE,从而可以通过旁路攻击泄漏地址。

对64位的虚拟地址,12-20位确定PTL1中的PTE,21-29位确定PTL2中的PTE,30-38位确定PTL3中的PTE,39-46位确定PTL4中的PTE。64位系统中ASLR随机化了最多35bit熵,即虚拟地址的12-45位是随机的。

攻击时通过分配多个内存地址上连续(或者临近)的内存块,来暴破待泄漏内存地址的偏移。由于PTL对应的虚拟地址的分段特性,可以分配不同大小的内存块分段暴破。 攻击前需要清空cache中的PTE的缓存,通过多次访问一个偏远的内存空间来清cache。然后访问待泄漏的内存地址,因为cache已经清掉了,所有会进行一次Page Table Walk,目标虚拟地址对应的四级PTL都会装入cache。这时候再访问预先分配的相邻(或者临近)的内存块中的地址,并记录时间。因为,这两个内存块的虚拟地址高位都一样,只有低位不同,因此可以通过(比如)PTL1的cache miss来旁路泄漏出目标地址的偏移。然后逐次访问更远的内存,来泄漏更高位的(e.g. PTL4)的地址。

Challenge

  1. 准确测量时间:Firefox上用performance.now() 计时,精度是每5µs一次;chrome上因为performance.now()不太准,所以用shared memory counter来计时。
  2. 内存排列:以2GB为单位分配内存块。使用不同的步长来分段泄漏地址。使用步长4KB来泄漏PTL1,2MB泄漏PTL2,8GB泄漏PTL3,4TB泄漏PTL4。浏览器中,Firefox因为mmap的时候没有用MAP_POPULATE,所以可以顺利地分配到4TB的内存。但在Chrome中,mmap的时候会同时分配物理内存,所以攻击PTL3和PTL4时会有一定概率无法成功。

实验

作者在实验中对Chrome和Firefox进行了攻击。实验环境是Intel Skylake i7- 6700K CPU, 16 GB 内存,操作系统是 Ubuntu 16.04.1 LTS

Fig

上图攻击是成功率的图,Chrome因为内存排列随机化的原因,会有失败的概率。Firefox PTL4攻击时,4TB内存会和其他对象共享,产生噪声。

Fig

上图中,AnC攻击可以在50s内有效地降低ASLR的熵。

Fig

上表列举了可以被AnC攻击的CPU型号。

防御方法

攻击检测

可以针对攻击者使用的计时器进行检测防御,但是有可能有误报。

Cache分区

可以对每个程序划分LLC(Last Level Cache)进行进程隔离,防止其他进程受到攻击。但是这种方法可能会对计算机系统带来性能上的影响。

计时器修改

可以降低计时器的精度,来防止攻击者进行旁路信息收集。但是问题是,攻击者有很多其他的手段来构造出计时器,进行攻击。无法完全防御所有的计时器。

Cache隔离

可以加一个独立的cache把PTE都放进去,阻止旁路信息泄漏。但是这样的问题在于成本太高。

MARX: Uncovering Class Hierarchies in C++ Programs

论文下载

INTRODUCTION

  • 设计与实现了 MARX,一个可以直接重构没有RTTI信息和使用任意编译选项编译的二进制文件的class hierarchies 的框架
  • 使用超过80M的二进制代码进行实验,vtables可以以很高的精确率恢复出来
  • 实现了两个安全相关的应用:vtable protection; type-safe object reuse

TECHNICAL BACKGROUND

  • RTTI: 指向metadata数据结构的指针
  • Offset-to-Top: 多继承的时候需要的选项
  • vcall 结构 in Itanium C++ ABI on x86-64

    mov RDI, thisptr call [vtblptr + x]

Fig

Fig

ANALYSIS APPROACH

Vtable Extraction

采用了以下六个特征帮助提取vtable:

  • H-1 Vtables 在只读段上
  • H-2 只有最前面的function entries会被vtblptr引用
  • H-3 Offset-to-Top 有一个范围,而且其不是relocation entry
  • H-4 RTTI 指向数据段或者是0
  • H-5 function entry 指向代码段或者是 relocation entry
  • H-6 前两个function entries 可能是0

Static Analysis

Overwrite Analysis

  • 构造时,基类A的构造函数率先调用,之后C的构造函数调用,所以A的vtblptr会被C的vtblptr重写
  • 析构时,C的析构函数县调用,所以C的vtblptr会被A的vtblptr重写

利用以上的特点:

  • 通过观察一个vtblptr是否被另一个vtblptr重写,来判断两个类的关系。
  • 观察 构造函数和析构函数

Fig

Vtable Function Entries

  • 虚函数并不需要重写
  • 所以对于基类和继承类的vtables,可能有多个entries指向同一个函数
  • 通过判断vtables是否指向同一个函数,来判断两个类是否关联
  • 由于编译器优化,可能导致误报

Inter-Procedural Data Flow

  • Forward Edge
    • 虚函数通常是间接调用
    • 通过上下文关系来解析间接调用指令
    • 如果一个vcall被不同的vtable的对象引用,那么这些vtable是相关联的
  • Backward Edge
    • 考虑返回值,可能返回不同的对象

Fig

Inter-Modular Data Flow

首先分析共享库,并根据返回值和vtblptr重写关系建立数据流信息。如果一个共享库的函数被调用,那么这些信息被添加到当前相关的context中辅助分析。

IMPLEMENTATION

  • VEX-IR from the Valgrind project
  • table extraction: IDAPython CFG
  • static analysis: data tracking engine (基于中间语言)

确定入口点

Path Creation and Convergence

  • 值得深入分析的basic block(interesting block),满足三个条件中的一个:

    • an indirect call (i.e., a possible vcall)
    • a (direct) call to a new operator
    • an instruction operating on a vtblptr
  • 设立阈值t,一个函数有t个及以上的interesting block,生成路径深入分析

  • 保证循环只访问一次

Virtual Callsite Identification

  • conservative mode: an indirect call is only identified as vcall if the thisptr holds a known object and a vtable is involved when computing the target address

  • non-conservative mode: an indirect call is considered to be a vcall simply if the vtable is involved in the computation of the target address

APPLICATIONS

VTable Protection for Binaries

类似于CFI

在vcall之前会插入一个标签检查,检查函数类型是否一致

  • Dynamic Analysis
  • Slow Path:当函数类型检查失败时,深入分析

使用Dyninst进行插桩

Type-safe Object Reuse

  • an allocator with type-safe object reuse support
  • a library to instrument object allocations

保证同一个类型才能重用相同类型的内存pool

预先分析建立(location, size, type)信息,在runtime时并保存在hash table里,(location, size)为key,劫持new new[]对象

EVALUATION

  • GCC 4.8.5
  • Ubuntu 14.04 LTS
  • Intel Core i7- 2600 CPU with 16 GB of RAM.

Class Hierarchy Reconstruction

the ground truth is obtained by parsing the RTTI of the target application

Fig

Virtual Callsite Targets

使用 VTV (Virtual Table Verification) GCC pass 生成ground truth,MySQL Server 和 VBoxManage 在编译的时候失败,因此没有数据

Fig

VTable Protection And Type-safe Object Reuse

Fig