Group of Software Security In Progress

GoSSIP @ LoCCS.Shanghai Jiao Tong University

Automating Patching of Vulnerable Open-Source Software Versions in Application Binaries

作者:Ruian Duan, Ashish Bijlani, Yang Ji, Omar Alrawi, Yiyuan Xiong , Moses Ike, Brendan Saltaformaggio, Wenke Lee

单位:Georgia Institute of Technology, Peking University

会议:NDSS 19

链接:PDF


本文主要讲了针对移动端上的大量使用第三方开源软件(库)(Open-Source Software,OSS)的应用程序,当第三方库推出安全更新时往往得不到开发者注意的问题,提出了OSSPATCHER这样一个工具,用来在不需要应用程序开发者参与的情况下自动化地根据OSS提供的补丁源码来对应用了该开源库的应用程序打patch。在这当中作者们也设计了很多关键性的技术用来解决其中遇到的问题,如:patch可行性分析、源代码-二进制文件比对以及内存级的修复(in-memory patching)

作者们使用39个OSS和1000个使用了这些有漏洞版本的安卓应用程序。OSSPATCHER生成了675个函数级的补丁,在不影响原有功能的情况下修复了受影响的移动应用程序。之后作者还评估了在热门软件中的10个漏洞(如Android Chrome),使用公开的exploit来攻击,而OSSPATCHER能够在应用之后抵御这些攻击。

Intro

作为开发人员,理应是时刻关注自己使用的第三方库在发展过程中暴露出的一些安全问题。在安卓平台,谷歌已经启动了应用安全改进计划(ASIP)来通知开发人员正在使用的易受攻击的第三方库。

安卓的开发人员主要使用Java和C/C++库,而Derr等人的研究表明易受攻击的java库可以在库级别提供修复,但更多的记录在NVD中的C/C++形式的漏洞则还没有被解决。这块已经有一些工作进行,但其受到错误类型和编译补丁的可用性的限制。

因此更有效的方法应该是从源代码出发进行修补。这一块有一些挑战,如识别目标应用程序的构建配置,将源代码与二进制代码匹配以查找缺少的调试符号,以及寻址静态链接库。除了这些挑战之外,自动修补可能会引入阻碍目标移动应用程序的side-effect。但是基于最近Li等人的安全研究,安全补丁相比于非安全的补丁而言更加的本地化(即更局限于单个函数的小部分地方),也有更小的side-effect可能性,这就为本文的工具提出提供了一定基础

Challenges

OSSPATCHER遇到的挑战主要有如下几个方面:

Configurable OSS Variants

由于C/C++中的特殊编译指令#ifdef #else #endif之类的存在,最终编译出来的二进制文件与源码可能由于一些编译选项的开关而有所不同,为了生成准确的二进制补丁,作者们就需要找到这些编译选项是否在target目标程序中的开关状态。但是又由于很多目标软件都是闭源的,无法确定编译选项的开关情况。因此作者提出了两种方案:一种是把每种可能的二进制文件都编译出来然后逐个比较,当然这个的消耗将是指数级的增长;另一种则是对源码到二进制文件的转变进行变化感知分析。作者选用后一种,采用对OSS构建variability-aware abstract syntax tree(变化感知语法分析树,VAST)的方法来进行源码-二进制分析

image-20190307111814192

静态链接库

应用程序和开源库之间的构建依赖关系可以模糊库的边界,如一些库本身静态链接了另外的库,然后才最终链接到目标文件上,那么如果还想像之前那样根据库边界来确定更换哪个库就显然不大行,那么基于Li等人的研究,作者们想到可以进行更细粒度的函数层级的patch。即识别函数边界并替换函数。

stripped的二进制文件

最近的一项研究表明,Android应用程序中98.9%的本机库被stripped掉了符号,只有导出的符号(非静态)仍然允许其他程序动态链接到它们。其他符号(例如静态函数和变量)不可见,因此需要付出额外的努力来定位它们。此外,当app开发人员将多个库静态链接在一起时,甚至可以隐藏非静态符号(-fvisibility = hidden

那么OSSPATCH就会在二进制文件中执行一系列匹配分析来找到目标函数,那么找到之后可以使用执行时的内存patch和二进制重写的patch两种方式。作者采用前者,这样就可以在例外情况下恢复patch,同时也方便了debug

Design

Goals and Assumptions

OSSPATCHER被设想为一个自动化系统,使用公开的源补丁来修复app二进制文件中的n-day OSS中的漏洞。OSSPATCHER必须考虑OSS变体并执行功能级匹配,而无法访问app二进制文件中的调试符号。在OSSPATCHER原型设计的同时,其专注于修复用于Android应用程序的C / C ++编写的易受攻击的OSS的使用,但该设计是通用的,也适用于其他基于Linux的应用程序和编程语言,例如Java。

最终的系统由两部分组成:

  1. 服务端:自动适应目标程序并编译patch
  2. 客户端:下载并应用patch到目标程序上

OSSPATCHER假设应用程序的源代码不公开,开发人员直接从其发行版本编译OSS,而不会篡改OSS源代码。 OSSPATCHER还假定来自NVD的信息(例如指定的易受攻击版本和相应的修补提交)是准确的。为此,作者给定了以下目标:

  • OSSPATCHER可以准确识别易受攻击的函数及其与补丁相关的配置选项以进行修补。
  • OSSPATCHER可以自动生成二进制补丁并执行无side-effect的补丁注入。

OSSPATCHER的工作流程如下图所示。为了实现上述目标,其在OSSPATCHER中设计了三个主要组件:

  • Analyzer分析源补丁的可行性,并将可以patch的函数转化为对应的VAST
  • Matcher执行变化感知的源到二进制比较,以识别函数地址,配置选项和变量地址。
  • Patcher从源补丁生成修补库并执行内存中的补丁注入。

Feasibility Analysis

在这一环节当中主要判断源码,判断其作出的改动是否只是在一个函数内的改动,过滤掉具有大范围代码更改的非本地化程序patch(例如,更改结构定义)。在evaluation环节中显示,OSSPatcher这种可行性的选择方式可以包含他们从公开OSS repo中爬取的60%以上的patch

简单的正则表达式匹配能完成上述任务,但是由于注释和预处理器指令,这可能容易出错。因此,作者设计了一个系统的可行性分析器来执行多通道的源代码范围分析。

给定OSS补丁提交,作者使用默认配置解析受影响的文件。由于源代码是有条件编译的,因为编译时选项可能会跳过某些部分,因此作者收集语义信息以及跳过的源范围。

  • 如果补丁中的代码更改与跳过的源范围不重叠,那么作者直接检查它们是否在函数内部以报告可行性。
  • 如果代码更改在跳过的源范围内,作者使用基于SMT(Satisfiability Modulo Theories,可满足模理论)的表达式分析器来查找配置组合,该组合启用跳过的范围并重新解析源文件。

最后将合格的补丁应用于旧版本,并通过执行多个检查来确保它们是兼容的,例如补丁上下文匹配和功能签名验证。作者将可行性分析分为三个相对独立的任务,即Source Range Analysis, Expression Analysis, Version Analysis

image-20190306191459464

Source Range Analysis

源代码范围分析找到补丁中代码更改的语义上下文,基于此来确定补丁是否可行。具体来说,作者认为以下更改类型及其组合是可行的:1)添加,删除或修改函数,2)添加,删除或修改注释和空行,3)添加或删除extern条目,宏定义,结构体,和include指令。但是,此列表是初步的,可以根据需要逐步添加其他类型。

要执行源代码范围分析,OSSPATCHER首先clone OSS并检出补丁commit。由于之前提到的指数爆炸问题,OSSPATCHER从许多OSS变体中的任何一个开始,收集跳过的源范围,并构建相应的AST。然后OSSPATCHER检查语义上下文中的补丁中的代码更改以确定可行性。如果更改在跳过的源范围内,OSSPATCHER将执行表达式分析以启用此范围,并再次调用源范围分析以确定其可行性。

Expression Analysis

在OSS中使用条件预处理器指令来启用/禁用源代码的某些部分。作者将这些指令中的条件称为表达式。如果补丁中的代码更改与跳过的源范围重叠,作者需要找到一个配置以启用跳过的部分以进行进一步的源范围分析。

然而,#if和#elif等条件指令中的表达式可能非常复杂。现有的编译器能够解析表达式但是其只判断true/false而不会留有OSSPATCHER所需的全部信息。因此作者又设计了satisfiability modulo theories(SMT)来分析表达式,其生成抽象语法树(Abstract Syntax Tree,AST),再将AST转化为中间代码并最终用于启用到跳过的源代码范围

Version Analysis

OSSPATCHER还需要检查源补丁更改是否与旧的易受攻击的OSS版本兼容。因此,作者的版本分析器检查以下属性:

  1. 上下文行匹配(默认为3行,如图1中的4-6行)
  2. 参数类型和函数的返回类型相同
  3. 引用的数据结构和函数签名是相同的。

要执行版本分析,作者首先运行git apply将补丁应用于易受攻击的版本。然后作者将修补的文件解析为AST并检查这些属性。如果有漏洞的函数中的代码更改与跳过的源范围重叠,则执行表达式分析以确保跳过的部分不违反这些属性。

如果补丁通过了版本的可行性分析,作者认为这个版本是可行的。

Variability Analysis

由于应用程序开发人员可能使用不同的OSS变体,OSSPATCHER必须正确推断与易受攻击的函数相关的配置选项 – 使用相同的配置生成正确的二进制补丁。在前面的可行性分析中已经可以解决函数内的变化问题,但是其仍然不能解决函数对外的一些依赖:如一些typedef的一些变量类型声明等,可能随着使用平台的不同而变化。

作者借鉴了TypeChef提出的变化感知词法分析器和解析器来构建变化感知AST(VAST)来解决这个问题。下图显示了TypeChef的工作流程。 VAST中的节点(如函数,字符串或表达式)与启用它们的条件相关联。

image-20190307073454085

其中lexer为词法分析器,parser为解析生成器。lexer生成词法规则,parser生成词法分析树

然而,TypeChef并不是一个全自动的分析软件,而是需要手动输入平台相关的文件头、open feature和partial configurations来完成软件分析。平台相关的文件头比较好解决,但是后两者作者则还进行了一些如下布置:

Open Feature Analysis

open feature包括开发者可以使用configure脚本进行打开/关闭的一些可配置的特性。

由于条件指令由预处理器进行评估以选择性地启用代码块,因此作者设计了一个基于clang的分析器,它只执行预处理,并收集源文件中条件指令使用的表达式和包含头文件。

同时,作者们还递归地从跳过的代码块收集表达式。然后,表达式分析器(§III-B)解析收集的表达式以提取条件宏。这些宏构成了typechef所需的open feature输入。

Partial Config Analysis

partial configure与open feature不同。通常包含一些预定义的宏,这些宏一般不可以被开发者所配置。但同样的partial configure也必须包含一定的规则来解决一些定义上的冲突情况。

除了条件指令,宏还用于设置某些OSS属性,如timeout或版本字符串。此外,某些feature的组合是不允许的,并且可能导致VAST的生成失败,因为它们在句法或语义上不正确。而TypeChef需要这些信息来生成VAST

因此,作者们构建了一个工具来收集宏定义,并利用配置脚本来提取feature之间的约束。虽然其分析还不完整,可能仍然会遗漏makefile中嵌入的约束或源代码的其他部分,但作者发现这两个分析器大大缩短了typechef输入的准备时间。

Source vs Binary Matching

在函数级别修补应用程序二进制文件需要定位易受攻击的函数、推断配置选项和修复外部引用。为了实现这些任务,作者们设计了三个独立的模块:

  • 函数匹配标识易受攻击的函数及其外部函数引用的地址
  • 配置推断找出如何从源代码编译易受攻击的函数,并生成配置组合以进行准确的复制
  • 变量匹配标识易受攻击的函数中的外部变量引用。

由于应用程序二进制文件被stripped,OssPatcher应该利用源代码和二进制文件中的可用功能进行源代码到二进制比较。

Feature Extration

对于源文件,系统解析它们的VAST以提取语法和语义特征,例如引用的字符串文本、常量、函数调用和全局变量(由Variability Analysis得到)信息。作者在这里只选择支持简单的语法和语义功能,因为相比于复杂的控制流提取比较,除了在编译后的二进制文件中可用之外,这些功能还可以抵御常见的编译器优化,并且易于提取。

对于二进制文件,其使用静态分析和基于angr[70]的符号引擎执行集成,对二进制文件中的每个函数执行符号汇总。具体地说,系统对每个函数进行多路径探索,目的是发现对一组预先确定的feature的引用的执行路径。

由于只在一个函数内探索路径,因此系统不在正在探索的函数内执行函数调用,也不执行系统或API调用,只关注一次提取一个函数中的所有相关feature引用。这样一来对整个二进制文件的分析速度就还是不至于太差。

Function Matching

为了在库中定位有漏洞的函数,作者利用了VAST的特性并检查它们是否存在于二进制文件中。在搜索有漏洞的函数时,系统将其标记为可选的,因为可能其所在的文件不会被编译;并且由于条件指令,这些函数中的不同部分也可以是可选的。matching分三步:

  1. 在动态符号表中搜索函数名开始匹配。如果有姓名,系统会报告匹配的地址。
  2. 通过引用/调用关系描述候选函数,包含可选的VAST节点。然后使用angr总结二进制文件中的所有函数,并与源函数进行比较,以确定最接近的匹配。

如果有漏洞的函数具有丰富的句法和语义特征,则上述算法可以很好地工作。为了支持这一假设,作者们在Evaluation中表明,大多数OSS的易受攻击功能平均有100多行代码。

除了易受攻击的函数外,其还匹配二进制文件中的大型函数,以便于进行配置推断。

Config Inference

为了能够复制具有相同配置的漏洞点的功能,其检查二进制文件中是否存在与配置相关的特性,并解决它们的条件以推断配置选项的值。关键思想是识别与配置选项相关的特性。

如下图所示,系统首先收集要在大量有漏洞的函数中要推断的配置选项。然后检查这些配置选项中的每一个是否与易受攻击的函数中的语法和语义特性相关。然后,其检查二进制函数中是否存在这些特性,并根据从特性到配置选项表达式的映射生成约束。使用SMT解算器来求解这些约束,以找到相应的配置

image-20190307080028560

Variable Matching

与函数匹配类似,可以通过查找动态符号表来匹配导出的变量。对于没有导出符号的变量引用,作者通过将变量引用与目标函数的程序依赖关系图中的句法和语义特征相关联来解决这个问题。

Patch Generation and Injection

一旦可行的源补丁与应用程序二进制文件匹配,OssPatcher就会编译补丁函数并将它们注入到易受攻击的应用程序中。为了使OssPatcher适用于各种Android系统,其尽量减少运行时开销,避免更改Android系统。在PatchDroid[42]的启发下,其设计了OssPatcher,在应用程序启动时就执行内存修补(而不是二进制重写或者运行时的热patch)

为了在修补过程中最小化对易受攻击应用程序的更改,OssPatcher将易受攻击的函数分割成单独的文件,并将它们编译到单个共享库中。然后,OssPatcher将库注入到易受攻击的进程中,并将易受攻击的函数重新导向,以调用注入库中的已修补函数。

这里还需要考虑对其他外部函数、变量的引用问题。对于无符号的函数/变量引用,作者采用重构源代码来创建存根函数/变量,将它们编译成修补过的动态库的依赖性存根库,最后再将存根库的我引用修改到正确的原来地址上。最终patch过之后的示例执行流程如下:

image-20190307211035048

对于原来有漏洞的OSS,其中的vuln_func是要被patch的目标函数。该函数引用了四个函数外部的变量/函数,其中两个有导出符号,另外两个没有(也就是说,有导出符号的可以被ld.so寻址找到,而没有导出符号的则只能由OSSPatcher自己找地址填入引用)。而这也就导致了对有无符号的函数/变量的区别处理。

系统首先对vuln_func生成两份源代码example_stub.c和example_patch.c,其中stub.c只有对无符号函数/变量的一个存根,patch.c中则包含patch过的函数以及其引用的所有变量/函数,这些全部视为extern。

那么将这两个源文件分别编译成两个库文件在程序运行时加载到内存后,OSSPatcher会将原来的example.so中对应的无导出符号的引用地址填到example_stub.so中作为该库中的外部引用地址,程序再调用vuln_func时首先跳转到另一个库(example_patch.so)中的外部函数patch_func中,然后其中所有的有符号外部引用都直接交给ld.so处理,无符号的外部引用则是交给ld.so链接到example_stub.so中的变量后再间接引用到原来example.so中的外部引用。

Patch Generation

如图8所示,OSSPATCHER生成两种类型的源文件,这些源文件被进一步编译到不同的共享库中。此设计允许OSSPATCHER修复修补函数中的引用并将它们指向正确的位置。

给定由Matcher(§III-D)识别的易受攻击函数及其引用的地址,作者首先通过检查app二进制文件中的动态符号表部分(.dynsym)来检查引用的可见性。对于隐藏函数或变量引用(可以是静态函数或非静态函数),系统为它们生成存根(stun func),然后调用ClangMove [75],这是一个能够从一个文件移动各种定义(包括函数,变量和类)的工具。进入另一个,将修补的函数移动到补丁文件中,并将存根函数和变量转换为存根文件。然后,系统从存根文件和补丁文件中编译一个存根库,其中包含对存根库和原始易受攻击库的依赖性。由于Android需要一个特殊的工具链来构建ARM体系结构的二进制文件,作者通过将CC或CXX设置为Android NDK [20](例如arm-linux-androideabi-gcc)的相应编译器来执行交叉编译。这两个库包含系统生成的补丁二进制文件。

Patch Injection

使用生成的存根库和补丁库,OSSPATCHER在应用程序启动时执行内存中的修补。当在Android中启动应用程序时,首先由Zygote fork出该应用程序的进程,然后通过dlopen(使用open和mmap系统调用在内部将库映射到内存中)加载一些第三方库并调用其构造函数(其中就包括有漏洞的第三方库)。 OSSPATCHER使用ptrace确定修补最可行的时间窗口,在库加载之后和库代码执行之前来进行修补。OSSPATCHER使用ptrace跟踪Zygote进程以分析新进程并在子进程中跟踪open和mmap调用。

一旦将有漏洞的库映射到内存中,OSSPATCHER检查点就会使用Criu进行处理,并将带有可选存根库的补丁库注入其中。注入后,修补程序库中的外部引用指向存根库或原始应用程序二进制文件。然后,OSSPATCHER执行基于detour的修补,将app二进制文件中的易受攻击的函数重新导向到patch动态库中的patch程序,并通过覆盖GOT条目修改patch程序库的外部引用以更正位置。修补应用程序后,OSSPATCHER将从目标应用程序中分离出来,并且该进程本机运行而不会产生任何开销。

Implementation

OssPatcher建立在几个现有的工具之上。例如数据收集器建立在cve search上以查找漏洞,并建立在osspolice上以查找易受攻击的应用程序。所有源代码分析和重构工具都是使用libtoolg作为独立的clang工具实现的。variability分析建立在typechef之上,二进制分析基于angr[70]。patch注入在内部使用CRIU[69]。在这里,作者简要描述了图2中描述的每个组件的实现。

Collector

首先是讲了如何进行数据的收集和准备的,这就包括收集有漏洞的OSS和使用这些OSS的app

Vulnerability Database

爬虫运行(2018年1月)扫描了NVD上所有95K个CVE,并识别出至少有一个提交哈希相关链接的5793个CVE。由于OssPatcher专注于修补用户空间中的应用程序,因此其忽略了Linux内核和uboot等系统操作系统。然后,其克隆剩下的619个OSS并尝试提取出相应有签名的commit,最终有与2723个CVE相关的3047个有效的提交。

在这3047个commit中,2045个来自307个由C/C++编写的的OSS,如OpenSSL,42个来自22个Java OSS,例如Apache Struts,这意味着在NVD中有补丁的C/C++ OSS漏洞记录的数量远远超过Java。作者把这307个C/C++的OSS称为$OSS_{nvd}$。

Compile Commands

基于LibTooling的clang工具需要编译命令才能工作,作者直接利用包含大量OSS构建脚本的OSSFuzz工具,其包括了大量的OSS的编译脚本。那么作者只需要hookOSSFuzz工具的编译过程,就可以得到每个OSS所编译 用的具体命令是什么了。

OSSFuzz在被作者使用时(2018年4月)包含125个OSS的编译脚本。作者取OSSnvd与OSSFuzz的交集,得到39个OSS,涉及到1111个cves和1140个patch,称为$OSS{eval}$。作者在评估中使用$OSS{eval}$作为最终用来作为评估的目标OSS集合。

Vulnerable Applications

那么找到了用来评估的OSS,接下来就是找有哪些APP使用了这些OSS了,目前已经提出了若干研究来识别应用程序中易受攻击的Java和C/C++库的工作。由于OSS的重用检测不是本论文的重点,作者直接联系了OSSPolice的作者以获取标记的易受攻击的应用程序列表。获得的列表包含10万个独特的Android应用程序列表$App$

但是由于$OSS{eval}$中不是每个OSS都是那么的流行,因此其最终选用了其中10个较为流行的OSS,又从$App$中随机每个OSS挑了100个使用了该OSS的app,最终得到的1000个App称为$App{eval}$作为最终用来评估的目标APP

Analyzer

如前文所述,Analyzer分为可行性的分析器和OSS变体的分析器。可行性分析器包含三个独立的部分:范围分析工具、表达式分析工具和版本分析工具。范围分析器和版本分析器作为clang工具实现,并在内部使用ASTMatcher来匹配和操作源代码。表达式分析器包括前端AST生成和后端符号建模。作者基于Boost Spirit实现了表达式分析器中的前端lexer和parser,并使用CVC4对它们进行了符号化的表示和求解。

作者们基于TypeChef实现了变化分析器。由于当前的TypeChef需要手动设置,因此作者还设计了Open Feature分析器和Partial Config分析器作为clang工具来实现TypeChef在新的OSS上的半自动输入设置。

Matcher

为了从二进制文件中提取特性,作者首先使用ida pro识别函数地址。

  1. 首先从有导出符号的函数开始,如果有导出了符号的函数名和源代码对应patch的函数名一致则直接match。
  2. 否则,如果有漏洞的函数并没有导出符号,那么系统进入每个函数并提取相应的feature。具体来说,系统提取的feature包括每个函数中的字符串文本、常量、函数调用和全局变量使用数,并与源进行比较,以帮助识别非导出函数。

在源到二进制的匹配算法方面,作者自己实现了函数匹配,并使用Z3解算器来解决配置问题,因为它具有方便的python接口。为了便于变量匹配,系统根据推断的配置编译易受攻击的函数,使用angr提取变量,并为vex ir实现正向和反向切片。

Patcher

作者将补丁生成器实现为一个clang工具,它首先为隐藏的引用生成存根,然后调用Clang-Move来创建补丁和存根文件。同时作者还重用了原来有漏洞的文件中的编译指令来加快编译时的编译选项判断速度。其最终是使用对应平台上的编译器(如Ubuntu上的GCC,移动端上的arm-linux-androideabi-gcc等等)进行编译并生成对应的动态库文件。

为了准确捕获库加载的时间窗口并执行内存修补,作者将补丁注入器实现为一个守护进程,它监视合子进程的分叉,并跟踪分叉的应用程序进程使用ptrace加载易受攻击的库的时间。一旦它们被加载,系统就使用CRIU[69]检查相应的进程并执行内存修补。一旦完成,系统将恢复执行并从这些进程中分离,以避免跟踪开销。

同时,为了避免patch的false positive的问题(即patch了但是是无效或者有可能crash的patch),作者在注入动态链接库的前后添加一个enter和exit的计数器。如果app crash,检查enter和exit是否一致,如果不一致则回滚这个patch并重新执行app

Evaluation

作者的评估包括如下几步:

  1. 使用$OSS_{eval}$对系统的feasibility&variability分析进行评估
  2. 使用标定的数据集评估系统的函数匹配、配置推断和变量推断,并进一步使用$App_{eval}$中的实际市场上的程序来验证这些算法的有效性
  3. 在有了对配置的推断和二进制中的对应函数的地址之后,系统运行patch生成器和注入器来让app在加载vuln动态库的时候进行patch。
  4. 作者进一步运行这些修补的app,发现修补所需的空间和时间overload可忽略不计。
  5. 最后收集了10个有可行补丁和公开exp的漏洞的app用来验证修补有效性,发现都得到了缓解

评估主要在运行Android 5.0(lrx21o)的Nexus 5手机和带有8核Intel Xeon CPU W3565@3.20GHz和24GB内存的Ubuntu 16.04台式机上进行。

Code Analysis Statistics

作者对39例OSS进行了feasibility&variability analysis。分析表明,1140个patch中的675个是可以被用在本系统中的。作者有选择地在表一中显示了10个OSS的结果,因为它们被$APP_{eval}$中的应用程序所大量使用。

image-20190307094450798

据报告,在表的顶部,ffmpeg和openssl有大量的cve、补丁和易受攻击的版本,表明它们是评估osspatcher的最佳目标,之后作者也主要围绕这两个OSS进行描述。相比之下,zlib只有一个易受攻击的版本,可能无法帮助显示OssPatcher的跨版本可移植性。

Feasibility Analysis

  • $#FPs$显示了至少一个脆弱版本可行的可行补丁的数量
  • $#FVs$显示了至少一个补丁适用的可行版本的数量

这两列显示了patch的特性和OSSPATCHER的补丁可行的能力。

例如,77%的ffmpeg和83%的openssl补丁是本地化的,可以被OSSPatcher自动应用。同样,97%的ffmpeg和75%的OpenSSL易受攻击的版本可以进行修补,这表明它们的源代码是稳定的,很少有有漏洞的函数在被修复之前有跨版本修改。相比之下,12%可修补curl的脆弱版本,表明curl对易受攻击的函数进行了相关的跨版本更改,让OssPatcher难以适应跨版本的补丁。

  • $#VFs$显示所有CVE/补丁中涉及到的有漏洞的函数之和,
  • $#EVFs$显示其中导出(非静态)的函数
  • $#LOC_{FP}​$显示在可行的patch中的平均修改的行数
  • $#LOC_{VF}​$显示有漏洞的函数的平均大小
  • $#Feats_{VF}$显示有漏洞的函数中的独有的特征的平均个数(用来识别函数)。

从表中可以看出,在ffmpeg中的193个可行补丁中有197个函数发生了修改。同样在openssl中的80个patch中有145个函数被修改。这表明一个漏洞可能存在于OSS的多个不同函数中。

这两个OSS的$#LOC{VF}$分别为102和153,意味着安全漏洞位于中大型函数中。$#Feats{VF}$表明,这些函数包含大量的特性。此外,$#LOC_{FP}$表明补丁是局部的,只改变了相应的脆弱函数的小部分。


除了对补丁的全面描述,作者还详细介绍了OpenSSL和ffmpeg的跨版本分析和代码大小分析。图9a显示了可行版本计数和有漏洞版本计数的累积分布函数(CDF)。它揭示了80%的补丁被标记为不到40个易受攻击的版本,并且可以应用于不到15个可行的版本。为了了解每个补丁的功能,作者计算了可行版本计数与易受攻击版本计数(fv/vv)的比率,并在图9b中显示了其CDF。

图中显示,50%的补丁在ffmpeg和openssl中的fv/vv比率都高于35%,这也就意味着OSSPatcher可以在当前环境下的1/3有漏洞的版本中,对其涉及到的一半的补丁进行修复工作,进一步检查可行版本的发布日期与补丁的不可行版本发现可行版本是新版本,而不可行版本在补丁披露前几年发布。这意味着这些补丁更可能适用于较新版本的OSS。

image-20190307100900414

此外,为了更好地理解补丁及其被包含的,函数的变化,作者在图10中显示了补丁的变化行的CDF和脆弱函数的代码行。图10a显示,80%的补丁在openssl和ffmpeg中的变化分别小于40和10行,验证了Li等人的观点。[34]安全补丁的本地化特性使其涉及到的代码长度很短。图10b显示了50%的有漏洞函数在OpenSSL和ffmpeg中分别有90行和70行以上的代码。有漏洞函数的适当大小允许OssPatcher收集大量的语法特性,以便进行源到二进制的匹配。但是,它也表明修补可能会导致一些内存开销,因为这些函数被编译并注入到正在运行的应用程序中。

image-20190307101401045

对于不可行的patch,作者还研究了它们对OSSPatcher潜在的改进。如前文所述所述,feasibility分析可变类型、上下文和引用兼容性,以确定补丁的可行性。因此,补丁可能由于三个原因而不可行,即非功能性更改、上下文不匹配和不兼容的引用。在465个不可行的修补程序中,27%由于非功能性更改而失败,64%没有匹配的上下文行,9%具有不兼容的引用,例如具有修改的签名的新类或函数。

Variability Analysis

  • $#MIVFs$指在有漏洞的函数中使用到的条件宏的个数,由feasibility analyzer分析得到
  • $#MRVFs$指和有漏洞的函数有关的条件宏的个数,是$#MIVFs$的超集,其还考虑了与数据结构相关的间接条件宏或变量类型,由variability analyzer分析得到

如上表所示,不同的OSS在可变性方面具有非常不同的行为(即条件指令的使用)。 定制度较高的源代码中通常含有更多的条件宏,也给了开发者更大的开发自由空间

Matching Algorithm

由于缺少标记的数据集,为了在Matcher中评估源代码与二进制文件匹配时的匹配算法,作者自己构建了一个具有不同OSS变体的$OSS_{eval}$中6个OSS的数据集。选择的6个OSS包括curl、ffmpeg、libpng、libxml2、openssl和wireshark,其选择具有不同代码库大小的可配置OSS,从小型libxml2到大型wireshark,以便对他们提出的算法进行全面评估。

由于枚举所有可能的OSS变体不太现实(例如,对于wireshark有268种可能),因此我们构建了它们变体的一个子集作为groundtruth。特别地,我们从默认配置开始,一次指定一个特性选项来构建这些OSS(如wireshark的1+68)。因此作者们最终得到了6个OSS共174个不同的二进制文件$BIN_{eval}$,它们的调试信息包括符号地址和配置选项,并将它们作为评估所提出算法的基础。

Accuracy Groundtruth

6个OSS的有漏洞的函数在大小上相对都较大,并且包含大量用于匹配的语法特征。

对于在最新版本中仍然存在的有漏洞的函数,作者使用其提出的算法将它们定位在$BIN_{eval}$中的文件里,并推断它们的配置选项并识别其外部引用的地址。

通过调整匹配阈值,如功能等效性测试中匹配特征比(如匹配分数大于0.95意味着等价),作者表明系统能够在源到二进制匹配有82%被召回(recall,召回重新判断?)时达到95%的精度。(?)

By tuning matching threshold numbers, such as ratio of matched features for function equivalence testing (e.g. matching score greater than 0.95 means equivalence), we are able to achieve a precision of 95% at a recall of 82% in source-to-binary matching.

进一步的优化:Matcher有三步:定位易受攻击的函数及其函数引用,计算配置选项,并匹配变量引用。匹配过程可能在三个步骤中的每个步骤中失败,这导致false negative。例如,由于函数内联(不匹配)或模糊候选(多重匹配),函数匹配可能会失败,由于缺少与配置相关的功能,可能无法进行配置推断,并且由于缺少可变引用而无法区分依赖特征。我们的检查显示,这三个步骤分别引入了35%,58%和7%的假阴性,这意味着更丰富的编译feature

Matching Real World Applications

虽然源代码的可变性很常见,但仍然不清楚应用程序开发人员是否在实践中采用非默认设置。要了解真实应用程序正在做什么,我们选择使用版本1.0.1e的OpenSSL的应用程序中的所有2,340个应用程序。我们针对这些应用程序运行匹配算法,专注于匹配函数ssl3_get_key_exchange()来得到开发者在编译这个函数的时候使用了什么样的config,并在表II中显示细分。

image-20190307104029018

表II中的每列表示可以使用宏(例如OPENSSL_NO_RSA)可选地排除的特征,其中仅排除PSK的第一行是默认值。结果显示,其中17%使用非默认配置,表明可变性感知分析是OSSPATCHER中的重要组成部分。

Runtime Testing

那么接下来是测试系统的运行开销,作者预先选择了$App_{eval}​$中的1000个APP进行匹配算法并保存其识别出的地址和配置用来接下来的测试。

通过收集$App_{eval}$的地址和配置,作者运行补丁生成器和注入器,以便在加载相应的易受攻击的库后立即修复这些应用程序中的漏洞。另外,作者运行monkey来运行被修补过的应用程序10分钟,以确保这些应用程序的正常运行。根据Choudhary等[10]的研究结果,这个测试期实际上足够长,大多数Android自动化测试工具随着测试进展5到10分钟就已经接近他们的最大覆盖范围。在作者的测试过程中,32%的应用程序至少调用了一个修补的易受攻击的功能。

另外,作者记录了OSSPATCHER引入的内存和性能开销。在运行时测试期间,所有已修补的应用程序仍可正常运行。内存开销主要来自Patcher生成的补丁库和存根库,如下图所示,OSSPATCHER对80%的应用程序产生的内存开销不到80KB(0.1%)

image-20190307104519664

在性能开销方面,它可以分为两部分:修补前(加载)和修补后(运行时)。在实际的测试期间,所有应用程序在启动时加载易受攻击的库,并且对于80%的应用程序,修补程序会导致小于350毫秒的加载延迟。至于运行时开销,因为应用程序使用共享库本地修补,使用正常输入运行,并且在测试期间保持功能,系统在响应性方面几乎没有延迟。由于我们使用基于绕行的修补,因此运行时开销只是jmp指令。因此,类似于其他工作(例如PatchDroid [42]),作者凭经验得出结论,运行时开销可以忽略不计。

Exploitation and Correctness Verification

为了验证OSSPATCHER的正确性,最好使用以前运行的漏洞来攻击已修补的应用程序,以检查漏洞是否已被修复。由于$App_{eval}$中的应用程序是封闭源,并且基于漏洞来自动生成漏洞并不是本文的考虑点,因此作者使用具有公开漏洞的应用程序来验证OSSPATCHER的正确性,如下表所示。

image-20190307105251062

其使用6个Android应用程序和4个Linux应用程序验证了OSSPATCHER,以显示OSSPATCHER修补Android应用程序和其他基于Linux的应用程序。对于收集的漏洞,作者首先验证它们是否适用于易受攻击的版本,并且在较新版本的OSS库中被修复。然后作者再运行OSSPATCHER将源补丁编译到共享库中,并将它们修补到易受攻击的应用程序中。最终的评估显示OSSPATCHER已成功阻止所有这些漏洞。

讨论了三个案例:Android Chrome, Stagefright和Heartbleed

Android Chrome

作为一个大型开源项目,Chrome还重用了许多其他OSS,例如FFmpeg,Libxml2和WebRTC。在Android上,Chrome将它们编译成一个名为crazy.libchrome.so的巨型库,并使用包装器与这些功能进行交互。为了提高Chrome的安全性,Google启动了漏洞赏金计划,以鼓励安全研究人员使用漏洞测试和提交漏洞。其中作者确定了Chrome浏览器使用含CVE-2017-15412的Libxml2,可以使用构造的xml文件攻击。然后,我们使用OSSPATCHER通过定位必要的地址并推断其编译选项来修补函数xmlXPathCompOpEvalPositionalPredicate。注入此补丁后,我们发现该漏洞被有效挫败。

Stagefright

Libstagefright是Android的内置系统库之一,系统服务以及第一方应用程序(如Hangouts)可用于处理多媒体文件。臭名昭着的stagefright bug有几个漏洞,我们在Exploit-DB [45]中使用exploit 38124来演示OSSPATCHER,它使用恶意mp4文件。我们使用运行Android 5.0的Nexus 5手机,受此漏洞影响进行实验。在修补之前,漏洞可以通过环聊启动反向shell。在使用OSSPATCHER修补SampleTable :: setSampleToChunkParams之后,停止利用漏洞。

Heartbleed

Apache Web服务器Httpd使用OpenSSL通过https托管网站。 Heartbleed漏洞允许攻击者窥视服务器的内存。我们在Docker容器中使用1.0.1f版本的OpenSSL设置Httpd并打开https。利用32745,我们可以转储Web服务器的内存。通过修复函数dtls1进程心跳和tls1进程心跳在运行时修补服务器守护程序后,攻击者无法再利用服务器。

Conclusion

小结:这篇文章主要讲了在开发者不关心其使用的第三方库(一般为OSS,开源软件)的安全性情况下,如何能对市面上的程序(本文主要针对APP和Ubuntu上运行的二进制程序)中应用的第三方库自动打patch的问题。文章将解决问题的范围缩小到局部性较强的安全补丁上,并根据实际评估和前人工作总结认为许多的安全补丁都是发生在有丰富语法特征的函数中的小部分代码中。

因此,基于如上的假设,作者设计了可行性分析、变化分析、源-二进制比较和最后的补丁生成和注入这几部分来设计了OSSPatcher这样一款工具。

  • 可行性分析:主要解决判断该OSS的patch是否符合如上假设的问题。其又分为三个部分

    • 源代码范围分析:即判断发生修改的源代码是否在一个函数当中(即不是跨函数地联动地去修改)
    • 表达式分析:由于一个函数的编译可能还受到一些条件宏的影响,因此作者设计表达式分析来求解一个配置组合来让被修改的源代码能够包含在编译范围内
    • 版本分析:判断这个patch能够用在该第三方库的哪些版本上,这一块做的粒度比较大,有较大的 优化空间
  • 变化分析:主要解决对在平台架构、开发者配置等的影响下生成的代码的结构问题。其主要生成了VAST,使用一些平台配置相关的变量来构成抽象语法树,从而方便之后的源-二进制的比较工作

    也就是说,由于目标的二进制文件一定是由一个确定的配置生成得到的,那么我用我得到的VAST去和二进制文件中的函数去比较,就可以得到VAST中的一些变量的取值,也就可以进一步得到目标二进制文件是在什么样的配置环境下得到的了

  • 源-二进制文件比较:主要就是拿之前两部分的分析结果来和目标的二进制文件进行比较,来最终确定系统是否可以为该二进制文件打patch以及该使用什么样的配置来生成这个patch的问题,那么在真正确定前,有几步准备工作要做:

    • 对目标函数的源代码的特征提取,特征包括如函数中引用的字符串、外部变量、调用关系等,用来在目标二进制文件中定位目标函数的位置
    • 如果目标函数有导出符号,那么直接使用符号找到函数地址;否则,使用angr和静态分析的工具来获得二进制文件中的所有函数的执行流等其他特征并和源代码中的目标函数的特征进行比较找到最相近的认为是目标函数
    • 由于前一步中的特征并不一定完全一样,那么此时再对VAST中的变量进行推断,得到一组配置使得二者的相似性尽可能大,保存此配置
    • 对目标函数所引用的一些外部函数、变量等做一些特殊处理,从而避免如地址随机化导致的引用固定地址时出错的问题
  • 最后则是真正的patch生成和注入阶段

    • 生成比较容易,系统根据之前匹配得到的配置进行源代码的编译,对目标函数专门生成一个动态库文件用来进行内存注入
    • 注入则需要系统修改的权限,Zygote fork出app的进程之后,OSSPatcher需要有对app进行ptrace的能力,其监控app open和mmap目标函数所在的动态库的行为,并在有漏洞的库映射到内存后对目标函数进行修改,内存注入patch的动态库文件并将原目标函数跳转到patch动态库中修过的目标函数上。之后OSSPatcher detach,完成整个修补过程
总体而言,作者试图解决多年来困扰软件安全研究者的一个问题,同时也提出了很多新颖的方法和假设来解决这一些问题。在最后的评估环节也可以看到,作者所设计的OSSPatcher在解决语法丰富的函数的一些局部性代码patch上有较好的效果,而这无疑也为相关的工作创造了一个良好的开端。