BootStomp: 关于手机设备bootloader的安全 -- 6 BootStomp

来源:互联网 发布:英雄在美国的票房数据 编辑:程序博客网 时间:2024/05/25 23:26

(英文原文链接: BootStomp: On the Security of Bootloaders in Mobile Devices)

6 BootStomp

        BootStomp 的目的是自动鉴别安全缺陷(vulnerability),这些缺陷和误用(misuse)攻击者控制的(attacker-controlled)非易失的(non-volatile)存储有关,这些存储被bootloader的代码所信任。特别是,我们预想(envision)使用我们的系统作为一个自动的系统,给一个bootloader作为输入,输出一些可以表示安全缺陷存在的警告。然后,人工分析师能分析(analyze)那些警告并且快速确定(detemine)突出的函数是否的确构成(constitute)一个安全威胁。

        Bootloaders 在关于目的和执行环境上,完全不同于普通的程序(regular programs) ,并且用现有的工具分析他们是特别有挑战性的。尤其是,这些挑战包括:

        动态分析是不可实行的(infeasible)。因为bootloader的一个主要的责任是初始化硬件,bootloader的任何具体(concrete)的执行都需要这个硬件。

        bootloader通常缺乏(lack)可获得的源码,或者甚至也缺乏调试符号(debugging symbols).因此,包含发现程序入口在内的基本任务,变得更加困难。

        因为bootloader 在OS之前运行,依赖于这个OS的系统调用和标准库,被避免使用,这导致所有通用的函数,甚至包括像memcpy这样的函数,被从零开始(from scratch)重新实现,因此这使得基于标准签名函数鉴别(identification)的方案(scheme)变得无效(ineffective)。

        为了迈出克服这个问题的第一步(To take the first step at overcoming these issues),我们开发了一款叫BOOTSTOMP的工具,  结合不同的静态分析和动态符号执行(dynamic symbolic execution, DSE)引擎,来实现一种污点(taint)分析引擎。据我们所知(to the best of out knowledge),我们是第一个计划这个可追踪的脱机(也就是,不需要在真实的硬件上运行)污点分析技术,它完全以动态符号执行为基础。其他的工作,像[24] [33] 计划在二进制之上的完全脱机的污点分析。与我们的工作对比(in contrast to), 他们实现静态污点分析,并且因此而没有基于动态符号执行技术。

        这种类型的方法的主要问题是,虽然听起来没什么(though sound),他们可能呈现高比率的误报,分析员(human analyst)必须通过手动检查过滤掉(filter out)这些。注意, 

在污点分析上下文,误报的结果是一个被错误的考虑为污染的路径。而且(furthermore),        

使用静态污点分析方法产生一个代表污点路径的踪迹,是没有用符号执行那样简单(as simple as)。

        另一方面,我们的方法基于DSE, 并非听起来那样(也就是,一些污染的路径可能未像7.4节解释的那样被检测到), 它提出一个返回可追踪的输出的技术,带有低的误报率, 这意味着,只要最初的污点被正确的应用和传播,我们检测到作为污点的路径就确实是污点。注意,在谈及污点分析(taint analyses)和漏洞检测(vulnerability detection),在它们之间的误报有本质(substantial)的差别。然而我们的工具,可能会返回一些误报在术语检测的漏洞(detected vulnerabilities), 就像在第7章节看到的; 在污点路径上的误报是极少的(在我们的实验中还没有发现),因为我们的工具是基于DSE。关于BOOTSTOMP获得结果的更深的讨论,请参考7.4小节 。

        考虑到这些注意事项,由于我们分析的输出被假定要被人来鉴别分类,我们选择一个基于DSE的污点分析。

        这一章节讨论BOOTSTOMP 的目标,设计特性和实现细节。


6.1 Design

        我们的系统目的在于发现两种类型的缺陷:被攻击者控制的存储的使用导致的内存损坏缺陷,和被攻击者控制的存储使用导致的bootloader解锁(the unlocking of the bootloader)。虽然这两种类型的bug在概念上(conceptually)不同,我们能够用在本质上相同的分析技术发现两种缺陷 。

        我们系统的核心是一种污点分析引擎(taint analysis engine),它追踪程序中的数据流。他检索出程序的路径,在路径中,污点的源头(a seed of taint)(比如被攻击者控制的存储)能够影响(influence) 污染的下沉(a sink of taint)(比如一个敏感的内存操作)。工具为每一个易受攻击的路径发出一个警告。人类分析员然后能处理这些警告,并且确定是否这些数据流能够被利用(exploitable)。

        我们的系统开始(proceed)于以下步骤,正如图2解释的。

Seed 识别。 我们系统的第一个时期,涉及收集污点的seed。 我们开发了一个自动化分析步骤,用来发现程序中所有从任何非易失存储中读数据的函数,在定位存储破坏缺陷(memory corruption vulnerabilities)时,这些函数被作为seeds使用。然而,如果seeds在语义上不能被自动识别,比如bootloader的解锁机制,BOOTSTOMP 允许分析员手工共规格的seeds。当源码是可获得的,这个特性特别方便, 因为分析员能够依赖它手工的提供污点的seeds。

Sink 识别。 然后,我们执行一个自动分析来定位污点的sinks,它们代表攻击者能利用的代码模式,例如大块内存操作(bulk memory operations)。 此外 (moreover),向设备的存储中写也被认为是定位潜在攻击者控制的解锁机制的sink。

Taint 分析。一旦污点的seeds被收集完,我们认为这些函数包污点的seed,并且从他们的入口开始,执行一个基于未限定的符号执行的多标签(multi-tag)污点分析,以此来发现seeds到达sinks的路径(paths where seeds reach sinks)。这会创建一些包含详细上下文信息的警告给分析员检查,这些信息可能对确定缺陷的存在和可利用性(exploitability)有用。

        在这一章节的剩下部分(in the remainder of),我们将探索(explore)这些步骤的每一个的细节。


6.2 Seed 识别


表 1: 通过扫描read_emmc的没一个调用点(call site),BOOTSTOMP 推断(infers)第一个参数是一个字符串,第三个能被假设值为0,并且返回值类型是一个整型。


        为了发现存储损坏缺陷,我们的系统支持自动识别污染的seeds。我们使用和以往的工作(例如,[27])相似的方法。我们依赖于错误日志(error logging), 因为有很多不同的机制(mechanism)可能从非易失存储器,或者不同类型的存储器(plain flash memory vs. eMMC)中读,并且这些错误日志字符串(error log strings)  给我们语义上的线索(semantic clues) 来帮助发现他们。 我们的系统是通过使用像mmc, oeminfo, read 和 fail 这样的关键字, 并且避免像memory和 write这样的关键字,来寻找错误日志函数。这种方法对识别那些以某种方法从设备存储中检索出内容的函数有用。然而,由于这些函数的签名(signature)是未知的,识别这些函数的哪一个参数存储接收缓冲区(the receiving buffer)是有挑战性的。为了确定要被 污染的参数,我们使用了一种加类型推理(inference)的方法。

        理想情况,污点应该只被应用于seed的指向读取的数据被保存的存储位置的参数。由于辨别指针和整型是一个不可判定的问题[31],在应用污点的过程中,我们的分析可能解引用(dereference)一个整型,这将可能导致误报率巨大(huge)。尽管如此(nonetheless),在研究期间,我们惊讶地观察到,字符串可能不会总是被引用传入到一个函数,但是数值不是这样。在我们分析期间,我们用以上提及的方法检查每一个我们恢复的函数的调用点,并且检查每一个传递参数的实体存在。如果一个参数是由可打印的ASCII字符组成,我们假定它是一个字符串,并且我们认为对相同的函数,每一个其他的调用的这个相同的参数都是一个字符串。当寻找应用 污点的存储单元(memory locations)时,我们考虑这个信息来过滤掉这些参数。我们同样不污染那些传递的值为零的参数,因为它们可能代表值NULL。

        作为一个例子,考虑表1。 首先,BOOTSTOMP 通过分析第18行错误日志,检索出(retrieve)函数read_emmc 作为一个可能的seed函数。然后,它扫描每一个 read_emmc的调用点,并且推断出返回值是一个整型(因为它和一个整型变量比较),第一个参数是一个字符串,并且第三个参数能假定值为零。因为read_emmc是一个候选的seed函数,它必须保存从非易失存储器读到的内容到一个有效的被非空指针指向的缓冲区(buffer),因此,BOOTSTOMP 只应用污点到read_emmc的第二个参数(a2 和 b2) 。注意到这一点,由于接收缓冲区能被一个seed函数返回,如果返回值的类型不能被推断,它被分配的变量也是污染的。注意到这,当一个污染的指针被间接引用的,我们污染它指向的整个内存页(the entire memory page)。

        至于(in the case of) 定位解锁相关的缺陷,这没有独立于bootloader的方式来定位解锁函数,因为实现细节显著地变化(significantly vary)。因此,BOOTSTOMP 也支持手动提供seeds: 一个分析员能因此执行逆向工程(reverse-engineering)来定位哪一个函数实现了"unlock"功能,并且,给我们的分析系统手动指定它们。然而,这并不是一个简单明了的处理,有一个分析师可以依赖的明确的格式:fastboot的 主要命令处理者通常包括一个基础的命令行解析器,它用来决定执行哪个功能,并且相关的字符串通常足够快速精确的找到 (pinpoint)哪一个函数事实上实现"unlock"功能。       

6.3 Sink 识别

我们的sink自动识别策略是设计为定位4中不同类型的sinks:

类似memcpy的函数。 BOOTSTOMP 通过寻找涉及移动存储单元,未改变,从源头到目的地(from a source to a destination)的语义,来定位类似memcpy的函数(例如, memcpy, strcpy)。正如上文提及的(as mentioned above),没有调试符号,并且基于标准函数特征的方法不起作用。基于这个原因,我们依赖一种启发式方法(heuristic),这个方法考虑包含在每个函数中的基本的块来定位渴望的(desired)行为。尤其是,一个函数被认为是类似memcpy的,如果它包含一个满足以下条件的基本块:1)从内存中转载数据;2)保存这个相同数据到内存中;3)增加一个单位的值(一个自,一个字节,等)。此外,由于对bootloader来说,依赖于封装函数是普遍的,我们也标记直接引用一个(并且只有一个)包含一个满足(satisfy)以上条件的块的函数的函数。

        我们注意到,可能有几个虽然满足那些条件,但是没有执行类似memcpy的行为的函数。因此,我们依靠一个额外的观察(observation),就是memcpy和strcpy存在于bootloader的大多数引用的函数中,因为bootloader的功能的多数涉及到一大片内存的操作(manipulation)。我们因此将程序中所有函数以它们的引用计数来排序(sort),考虑前50个作为可能的候选者(candidate)。我们注意到,以经验为主地(empirically) ,我们发现memcpy这样的函数经常属于(fall within)前五个被引用最多的函数。

攻击者控制的间接引用(attacker-controlled dereferences)。BOOTSTOMP 考虑内存被攻击者作为sinks间接引用。事实上,如果攻击者控制的数据到达一个间接引用,这是一个攻击者控制的任意内存操作的高度象征。

攻击者控制的循环(attacker-controlled loops)。我们把任何用在循环控制中的表达式作为一个sink。显然,任何能控制一个循环中迭代次数的数值的攻击者,都能够安装(mount)一个拒绝服务攻击(denial-of-service attack)。

向设备存储中写(writes to the device's storage)。当考虑解锁漏洞时,我们只把任何向设备存储中的写操作用作 sink。这暗含了这种观念,一个保存它的安全状态在设备存储中的解锁机制可能被攻击者控制。为了识别这样的sink,我们采用相同的用来识别污点的seed的基于关键字的方法(也就是,通过使用在错误日志信息中相关的关键字)。



6.4 污点跟踪

虽然我们不能具体地(concretely)执行bootloader,像我们以上讨论的,我们能象征性地(symbolically)执行它们。我们感兴趣的是数据进入的从一个 seed移动到sink的路径 ,并且基于路径的符号执行让我们推出(reason about)这些,当暗中地(implicitly)处理污染的传播(propagation)时。提供一个bootloader,连同(along with) 在前面阶段识别的seed 和sink, 分析遵照如下进行(proceed):

  • 定位一组入口点,定义为任何直接调用任何一个识别的seed的函数。

  • 在每一个入口点的开始,开始符号执行。注意,在开始符号执行一个入口点时,BOOTSTOMP 通过寻找著名的数据头如ELF, 尝试推断全局数据存放在哪里。如果没有找到,它就不能驱使在里面的没一个byte, 所以会打破任何在开始分析入口点前,关于内存内容的设定(assumption)。

  • 就下面讲的代码遍历(traversal)规则而言,当一个路径遭遇了一个函数,要么单步跨过,要么单步进入。

  • 因为符号执行的性质, 污点被暗中传播。这包含处理污点数据的函数的返回值。

代码穿过(code traversal)。 为了避免状态激增(state explosion), 我们用合适的内部函数水平(adaptive inter-function level)限制(constrain)一个路径会穿过的函数。一般,内部函数水平指定一个路径会越过函数的深度。然而,在我们的分析中,处理被污染的数据,意味着,我们隐含地更多关注那些消费(consume)污点数据的函数。因此,我们只进入那些由内部函数水平决定的消费污点数据的函数。对我们的实验,我们固定内部函数水平为1。更详细点(more in detail),我们的分析根据以下规则穿过代码:

  • 当没有数据污染,函数不继续跟进,比如,在一个入口点开始,seed被送触及之前。尤其是,这种路径选择标准(criteria),还允许我们有一个快速的污点分析,它以可能的错误负面结果(false negative result)为代价(at the expense of ),如一些污点路径可能因为一些丢失的数据别名而未被发现。

  • 如果函数的参数未被污染,函数就不进行下去。

  • 当在入口点和他的结束点,所有的可能路径都被分析,或者触发一个超时,分析中止。注意,我们为每一个入口点设置一个十分钟的超时。正如7.2章节,我们的结果指出的,这是一个非常合理的时间限制。

  • 除了以上任何条件被满足外,我们用内部函数水平1来进入函数。换句话说,分析至少会探索(explore)远离入口点的一个函数。

  • 我们精确地探索一个循环体(展开循环)一次,然后假定退出循环的路径。

(未限定的)符号执行。我们的方法需要被设计来从任意函数开始分析,并且没有必要从bootloader的入口,这个入口我们也甚至不能确定。这意味着,初始状态可能包含极少的约束,相比他在那个特定代码位置上应该有的约束来将。由于这个原因,我们使用未限定的符号执行,它第一次被Ramos et al. [23] 所提提议,这已经被证明(prove)在这种环境能达到好的精度(precision)。

多标签污染分析。 为了达到一个好的精度,我们的系统实现了一种多标签污染方法[18]。这意味着,替代有一个污点的概念,每个污点 seed产生污点数据,这些数据能被

唯一地追踪到它从哪里产生。而且,我们为程序中每一个seed的调用创建一个唯一的污点标签。这意味着,例如,如果一个污点 seed被反复调用,它会产生许多不同的污点标签。当推理污点流动,这会提高精度。        

污点传播和污点移动(taint propagation and taint removal)。使用符号执行时,污点是暗中传播的,因为没有约束减少。这意味着,如果一个变量 x 依赖于一个污染的变量ty,后者会出现在前者的符号表示中。以图3为例。假设,ty指向的一个数组的位置,被间接引用并且分配给x。现在,假设 ty 是污染的,因为指向的数据从不信任的存储中读到,它指向的内存页也将会被污染,这意味着,在那个页之内的任何内存位置将包含一个 TAINT_ty_loc_i 形式(in the form TAINT_ty_loc_i)的符号变量。在指令 x = ty[5] 之后,符号变量x将会成这种形式 deref(TAINT_ty_loc_5)。

        另一方面,污点在两种情况(in two cases)被移动。隐含地,当一个未污染的变量或值被写到一个污染的内存位置,或者当一个污染的变量被限制(constrain)在未污染的值内。作为一个例子(as an example),并且参考上面的污点变量x,如果存在一个检查比如 if(x < N),这里N是未污染的值,那么x将会被污染。

具体化策略(concretization strategy)。 当处理写在符号位置的内存时,目的地址需要被具体化。不像已经存在的工作[5],我们的分析选择偏向于合理范围的小的数值来具体化数值(而不是偏向于大的值)。这意味着,当一个符号变量能被具体化为超过1个值,更小的数值是首选的(preferred)。在以前的工作中,更大的数值被选择用来帮助发现这些情况,内存在哪里越过一个分配的内存区域,将会导致漏洞。然而,这些数据可能不满足程序中的条件语句,它们期待值是合理的(比如在用于一个矢量的索引项的值的例子),并且具体化为更小的值允许路径进入程序更深层。换句话说,我们选择这种策略来最大化(maximize)探索的路径数量。并且,当BOOTSTOMP 必须去具体化一些表达式,他尝试具体化不同的不受约束的变量为不同的(小的)值。这中策略旨在保持误报率尽可能的低。更深入关于漏报率和误报率(false negatives and positive)的讨论可能即将出现,请参考7.4章节。



        最后,我们的分析严重依赖于angr [28](污点引擎) 和 IDA Pro [11] (sink 和 seed 的发现)。


----------------------------------------------------------------------------------------------------------------------------------------------------------------

PS:

水平有限,欢迎指正。期待大家提供的翻译建议。

seed:

sink: 还不知道怎么翻译更好

taint: 污点