CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出

@tamir_zb最近披露了一个缓冲区溢出漏洞,影响到谷歌的多种Android DRM服务。 Google将其分类为高严重性,将其指定为CVE-2017-13253,并在3月份的安全更新中对其进行了修补。

在这篇博文中,我们将介绍该漏洞的详细信息。 首先,我们将介绍相关的背景信息,从一般的Android机制到与漏洞相关的特定机制。 我们将重点介绍最近推出的Project Treble,以及它的变化究竟意味着什么。 然后,我们将分析这个漏洞及其影响。 我们将研究如何利用某些设备上的其他故障来解决此漏洞,以实现root权限。 最后,我们将讨论该漏洞的起源以及它如何被阻止。 尽管Google声称Project Treble有利于安全性,但我们仍将看到它的反面。

Android的Binder和安全

在许多操作系统(包括Android)中使用的一种非常常见的安全模型围绕进程间通信(IPC)展开。 在非特权进程中运行的不可信代码可以与特权进程(服务)进行通信,并要求它们执行操作系统允许的特定操作。 该模型依赖于服务(和IPC机制本身)来正确验证从非特权进程发送的每个输入。 反过来,这意味着这些服务中的错误,尤其是输入验证部分中的错误很容易导致漏洞。

例如,Rani Idan在Zimperium发现的最新iOS漏洞依赖于这种方法。 IPC输入验证中的错误允许攻击者从非特权应用程序执行具有更高权限的代码。

在Android的情况下,IPC机制被称为Binder。 Android Binder服务的安全性对于漏洞研究来说当然非常有趣。 Binder具有许多有用的功能,例如,它允许进程在彼此之间传输复杂对象,例如文件描述符或对其他Binder服务的引用。 为了保持简单性和良好性能,Binder将每个事务限制为1MB的最大大小。 在进程需要传输大量数据的情况下,他们可以使用共享内存来快速共享数据。

Binder’s C++ 目录

Android的Binder库(libbinder)为依赖于Binder的C ++代码提供了许多抽象。 它允许你调用C ++类的远程实例的方法,就好像它们不驻留在另一个进程中一样。

每个使用这种机制的对象都在预定义的结构中实现了几个类:

  • 一个接口类,它定义了可以通过Binder调用的对象的方法。 以“I”为前缀。
  • 负责序列化输入和反序列化输出的“客户端”类。 以“Bp”为前缀。
  • 负责反序列化输入和序列化输出的“服务器端”类。 前缀为“Bn”。

最终,在使用该对象时,几乎总是使用接口类型。 这允许您以相同的方式处理对象,无论它处于同一个进程还是处于不同的进程中。

CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出插图

在ICrypto接口中使用libbinder的示例

代码中的“服务器端”部分传统上位于特权服务内部(尽管在某些情况下角色是相反的),所以它通常负责验证输入。 验证代码可以从Bn *类开始,并沿着随后调用的方法继续。 这显然是脆弱性研究中最有趣的部分。

ICrypto接口和解密方法

一般来说,在介绍Binder之后,我们来看看与漏洞相关的具体实现。 mediadrmserver服务(毫无疑问,负责DRM媒体)提供了一个加密对象的接口,接口名为ICrypto。 请注意,该对象最近更改为CryptoHal,我们将在稍后讨论。 此接口的一般用途是允许非特权应用程序解密需要较高权限解密的DRM数据,如访问TEE。 加密本身的细节不在本篇博文的范围之内,再一次,我们对输入验证更感兴趣。

ICrypto有多种方法,但无疑最重要的方法是解密。

CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出插图1

解密的签名(来源)

解密签名中最引人注目的事情之一是输入的复杂程度。 从我们的角度来看,这非常有趣。 复杂的输入会导致复杂的验证代码(每个参数都通过Binder进行传输并且需要验证),这些代码可能易受漏洞影响。

我们来看看一些参数:

参数 描述
模式 一个控制加密模式的枚举。 其中一种模式是kMode_Unencrypted,它表示数据实际上未加密。 这种模式意味着数据只能从一个地方复制到另一个地方,而不涉及任何解密。 这使得这个过程更加简单,所以从现在开始我们将专注于这个模式。 这也是我们不考虑一些加密相关参数(如密钥或IV)的原因。
来源/目的地 输入和输出缓冲区。 由于数据的大小可能非常大(大于1MB),实际数据通过这些对象所代表的共享内存进行传输。
offset 偏移到数据开始的输入缓冲区。
附属样本 子样本数组,是有关输入的元数据。 每个子采样表示多个清零字节,后面跟着一些加密字节。 这使您可以在清除和加密的输入数据之间切换。 使用kMode_Unencrypted也简化了这一点,因为您只需使用一个代表所有清除数据的子样本。

(有关API的更高级别Java一些参数的更多信息,请参阅MediaCodec.CryptoInfo)

现在让我们仔细看看源和目标参数的类型:

CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出插图2

(源)

这里的相关结构成员是mHeapSeqNum和两个mSharedMemory成员(DestinationBuffer的其余部分是在目标未被存储为共享内存的情况下,这种情况与此漏洞无关)。 名称堆在这里用来指代实际的共享内存(这是你运行mmap的内容)。 mHeapSeqNum是一个像这样的内存标识符,它以前使用称为setHeap的ICrypto方法共享。 这两个mSharedMemory成员仅表示堆内缓冲区的偏移量和大小。 这意味着虽然mHeapSeqNum在源结构内部,但它实际上与两者都相关。

CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出插图3

清除数据解密运行的参数示例

值得注意的是,参数结构的某些部分有点奇怪。 mSharedMemory是一个IMemory,它实际上连接到它自己的堆,并且应该表示内部的一个缓冲区,但是这个堆被忽略,偏移量和大小被用于mHeapSeqNum堆。 源结构中还存在mHeapSeqNum,但它与源和目标都有关。 这是所有这些代码最近发生的变化的结果,这些代码是作为名为Project Treble的Android框架的重要架构的一部分而创建的。

Treble项目

Project Treble是作为Android 8.0的一部分引入的; 其主要目标是通过在AOSP和供应商之间建立明确的分离来使系统更新更容易。 谷歌还声称,Project Treble通过增加更多的隔离功能来使Android安全性受益。

对于像mediadrmserver这样的服务,Project Treble意味着分离成多个进程。 负责解密的代码属于供应商,因此它被分成多个供应商进程,称为HAL,每个供应商都负责其自己的DRM方案。 mediadrm服务器的作用现在减少到在相关DRM方案的应用程序和HAL进程之间传输数据。 mediadrmserver和HAL之间的通信也在Binder之上,但是在不同的域中并使用不同库的格式 – libhwbinder。 之前提到的从Crypto到CryptoHal的变化是因为现在它是一个不同的类,其唯一目的是将数据转换为libhwbinder的格式并将其传递给HAL。

CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出插图4

上图显示了Google为什么声称Project Treble受益于安全。 权限在不同的进程中分开(每个HAL只能与自己的驱动程序通信),不受信任的应用程序不再直接与高权限进程交互。

请注意,从Android 8.1开始,分离仍然是可选的,取决于供应商。 例如,在Nexus 5X中,HAL都位于mediadrmserver进程中。 数据仍然转换为HAL格式,但不会转移到其他进程。

加密插件

我之前提到了不同的DRM方案,在Android术语中,每种DRM方案的处理程序都称为插件,或者在我们的特定情况下称为加密插件。 供应商负责提供这些插件,但AOSP中有一些供销售商使用的有用代码。 例如,AOSP包含ClearKey DRM方案插件的完整开源实现。 通常,设备将具有开源的ClearKey插件和闭源的Widevine插件(例如Nexus / Pixel设备就是这种情况)。

上述Project Treble变化的问题是现在插件接收HAL格式的数据。 为了简化转换,无需更新每个插件以支持这种新格式,默认的Crypto Plugin实现已添加到AOSP供供应商使用。 该实现将数据从HAL格式转换为传统格式,并将其传递给原始插件代码。 理想情况下,这个解决方案应该只是暂时的,直到插件更新,否则我们会留下冗余格式转换(往返于HAL)。

CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出插图5

数据格式转换的流程

研究源代码

在介绍ICrypto的解密方法的一般过程之后,我们来仔细看看共享内存缓冲区的验证代码。 正如您可能已经猜到的(因为我们正在谈论缓冲区溢出),这是发现漏洞的地方。

如前所述,验证通常从Bn *类开始,在我们的例子中就是ICrypto接口的“服务器端”BnCrypto。

CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出插图6

BnCrypto验证代码的一部分(源)

  • 首先,代码检查子采样大小的总和是否有效并且不会溢出。 请记住,这是要复制的数据的大小。
  • 它还检查这个总和是否与totalSize匹配,通过Binder传递的另一个参数非常多余(您可以通过子样本的总和来告诉总大小,代码明确验证了这种情况)。
  • 接下来的检查是数据大小不超过源缓冲区的大小。
  • 最后,它检查数据大小加上偏移量仍然不超过源缓冲区。

Crypto Hal将数据转换为HAL格式并将其发送给相关插件; 这里没有有趣的验证代码。

接下来,默认的Crypto Plugin实现(可能会或可能不在不同的进程中)将数据转换回传统格式并继续验证它。

CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出插图7

部分默认加密插件验证码(源)

关于这个代码的一个附注:我觉得它有点混乱。 有多个“dest”和“source”变量,sourceBase和destBase实际上是完全相同的东西(堆),并且根本没有任何评论可以帮助你。 正如我之前提到的那样,这部分是全新的,并且仅在Android 8.0中添加,因此它是有道理的。 尽管如此,我还是怀疑这种混乱导致了这个漏洞,因为它使得查看整个验证代码和查看是否有缺失更加困难。

  • 这里的第一个检查是偏移量和缓冲区大小的总和不超过堆大小。 sourceBase是堆,而源是之前的source.mSharedMemory。 如果您对两个偏移量感到困惑,请记住mSharedMemory包含一个偏移量,并且解密方法也有一个不同的偏移量参数。
  • 其他检查类似,但在目标缓冲区上执行。 destBuffer是destination.mSharedMemory和destBase与sourceBase相同的堆。 这次不涉及偏移量参数。

最终,每个缓冲区都简化为一个指向内存的指针; 偏移量现在是指针的一部分,而缓冲区大小被省略。 为了确定数据大小,插件使用subSamples数组。

CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出插图8

数据未加密时的ClearKey插件代码(源代码)

上面的代码显示了最后一部分,以帮助理解流程。 如前所述,当数据未加密时,它只是从一个地方复制到另一个地方。

到目前为止,我已经提供了足够的信息,可以在理论上发现漏洞。 如果你想尝试做到这一点,欢迎回去继续阅读代码。 根据我的经验,在这类博客文章中很难提供足够的信息来发现它,同时仍然保持实际的挑战性(尤其是从已经找到它的人的角度来看),所以即使你 无法发现它(或者它可能太简单了?)。

该漏洞

问题是没有验证被复制的数据量没有超过目标缓冲区。 对源缓冲区只有一个类似的检查(BnCrypto的第三个检查检查并且下一个检查甚至将额外的偏移量考虑在内)。 与目标缓冲区相关的唯一检查是默认Crypto Plugin的第二次检查(它确保缓冲区位于堆内并且不超过它),但这仅仅是不够的。

我们来看一个例子。 假设要复制的数据的大小是0x1000。 由于这个大小是由subsamples数组表示的,所以我们将在该数组中有一个条目,其中包含0x1000个清晰字节(以及0个加密字节)。 堆也将有0x1000字节,并且源缓冲区将指向整个堆(偏移量= 0,大小= 0x1000)。 目标缓冲区是它变得有趣的地方。 假设偏移量是0x800,大小是0x800。 这仍然适合堆,所以它通过了默认加密插件的检查。 在这种情况下,会出现溢出; 0x800字节将在堆后写入。

CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出插图9

概念验证

CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出插图10

触发漏洞的示例的代码

注意:MemoryBase对象是IMemory libbinder接口的实现。 这是一个使用Binder将引用传递给其他Binder对象的例子。 这也是Binder角色颠倒的一个例子。 特权流程是“客户端”,因此它通过Binder请求信息并负责验证它。

漏洞的影响

此漏洞允许攻击者用任意数据覆盖目标进程中的内存。 由于这是内存页级别的溢出,因此目前没有任何缓解措施可以阻止它(例如堆栈溢出堆栈)。 由于缺省Crypto Plugin的检查,数据必须从共享内存开始,这仍然受到限制。 这意味着只有位于共享内存之后的内存才能被覆盖。 此外,内存中的许多区域通常是未分配或不可写入的,因此试图在其中写入将导致分段错误。

受影响的流程取决于供应商的实施。 如果供应商不将HAL分成不同的进程,则mediadrmserver会受到影响。 如果供应商将它们分开,那么Crypto Plugin的每个HAL服务都会受到影响。 由于默认的Crypto Plugin代码仅留下指向目标缓冲区的指针,并且大小仅由子采样确定,供应商代码无法确定它接收到格式错误的数据。 这意味着供应商部分编写得并不重要,它仍然是脆弱的(理论上,供应商可能会忽略AOSP的默认加密插件代码,并实现自己的代码来检测格式错误的数据,但我没有’ 没有看到供应商那么做)。

可能的影响

假设攻击者设法利用此漏洞将特权提升为易受攻击服务的特权,那么我们来看看他们可以实现的功能。 请注意,这部分大多是推测性的。 我没有编写漏洞利用表,但是我对这个漏洞理论上如何被用来达到完全的root权限有一些想法。

这就是Android的SELinux规则发挥作用的地方; 即使易受攻击的服务拥有更多权限,SELinux仍然会严重限制它们。 尽管如此,即使在限制之后,我们仍然留下了一个非常有趣的权限:完全访问TEE设备。

在这种情况下,Project Treble的额外隔离几乎没有帮助。 易受攻击的进程将是可以访问TEE设备的进程,无论是否存在分离到多个进程。 在分离的情况下,唯一受保护的过程是中间没有趣味的媒体服务器。

那么你可以通过完全访问TEE来做什么? Gal Beniamini的优秀研究表明,许多设备无法正确吊销旧的易受攻击的TEE信托。 这意味着,如果您攻击具有旧的易受攻击的trustlet的设备,则可以使用TEE设备的访问权限,加载trustlet并将其用于TEE上的代码执行。 更重要的是,Gal Benimaini过去也展示了基于Qualcomm的设备上的TEE代码执行如何导致root权限。

CVE-2017-13253: 多个Android DRM服务中的缓冲区溢出插图11

可能的攻击流向根特权

漏洞的来源

我已经多次提到Project Treble如何对代码的这个区域进行重大修改。 如果知道这些更改实际上引入了此漏洞(在更改之前,目标缓冲区甚至无法以此格式设置),那么您可能不会感到惊讶。

显然,你不能仅仅因为使代码易受攻击而对其进行重构,因为这意味着代码重构不应该发生,这是不正确的。 正如我已经指出的那样,这段代码的多个部分都是混乱的或冗余的。 虽然这本身并不一定会使代码易受攻击,但确实增加了这种可能性,因为它使代码更难以复审(代码的某些部分花了我相当长的时间才能理解,而相比之下它们实际上的复杂性 做)。 因此,虽然漏洞有时难以发现,但通常更容易发现杂乱或冗余的代码。 我知道从评论者的角度来看批评不好的代码设计比实际编写好的代码更容易,但我仍然认为应该改进一些部分。

结论

Google声称Project Treble对Android的安全性有好处,但在这个例子中,它却反其道而行之。 高音项目本身并不一定是坏的,这里的关键问题是实施处理得不好。

可以在GitHub(https://github.com/tamirzb/CVE-2017-13253)上找到触发漏洞的PoC的完整源代码以及一些额外信息。

原文:https://blog.zimperium.com/cve-2017-13253-buffer-overflow-multiple-android-drm-services/

发表评论

邮箱地址不会被公开。 必填项已用*标注