深入研究Samsung系统的安全特性,Part 1: TEE、TrustZone与TEEGRIS

匿名者  41天前

经过多年的发展,可信执行环境(TEE)已经在Android生态系统中逐渐普及开来。在本系列文章中,我们将分析三星的TEEGRIS TEE操作系统在其Galaxy S10手机上的实现方面的安全性,并展示如何识别其中的安全漏洞并对其加以利用。当然,本文中介绍的漏洞都已向三星提交了报告,并于2019年底得到了相应的修复。

我们进行本调查的目的,是评估三星的TEE OS的安全程度,以及攻击者是否可以入侵该系统,以获得运行时控制权并获取所有受保护的资产,例如解密用户数据。不过,这里并没有考虑完整的漏洞利用链,而是只关注TEE,并假设攻击者已经获得了Android环境的控制权。

实际上,本系列文章是我们在2020年9月Riscure研讨会上的演讲的加强版:

  • 在第一篇文章中,我们将介绍TEE、TrustZone和TEEGRIS。
  • 在第二篇文章中,我们将考察在TEEGRIS中运行的TA的安全漏洞,并利用一个TA(可信应用程序,TrustedApplications)来获得运行时控制权。
  • 在最后一篇文章中,我们将展示如何进一步提升权限并获得对TEE所有内存的访问权限。

可信执行环境(Trusted Execution Environment,TEE)

可信执行环境旨在为支付、用户认证和用户数据保护等安全关键任务的执行提供安全环境。

请注意,安全环境通常是与不安全或不可信的环境(通常成为Rich Execution Environment,即REE)隔离开来的——在我们的例子中,这里的REE就是Android操作系统(注意,在其余的文章中,REE和Android这两个术语是可以互换使用的)。一个TEE操作系统通常由一个运行在高权限级别的内核和多个被称为可信应用程序(TrustedApplications,TA)的低权限应用程序组成。并且,TA不仅应该与TA相互隔离,同时,也应该与TEE内核保持隔离,这样的话,被入侵的应用程序就无法入侵其他应用程序或TEE内核了。简而言之,一个强大的TEE环境应该实现三种类型的隔离机制:

  •  TEE与REE之间的隔离;
  •  TA与TEE内核之间的隔离;
  •  TA之间的隔离。

为了实现这些安全要求,TEE需要借助于硬件原语的支持来实现隔离机制。实际上,硬件(HW)和软件(SW)之间的合作是至关重要的,并且这种需要还将一直持续下去。

广义上讲,一个TEE是由各种组件组成的,例如:

  •   TEE硬件;
  •   一个健壮的安全引导链,用于初始化TEE软件;
  •   一个TEE操作系统内核,用于管理安全世界与可信应用程序;
  •   可信应用程序,为REE提供各种功能。

在我们的文章中,我们将主要关注第1、3和4项——并假设安全引导过程是由平台正确实现的。我们还将假设有一个已经获得REE控制权的攻击者(也就是说,他可以与TEE进行通信),并企图控制整个TEE。

通常情况下,TEE内核对Android操作系统暴露的接口是非常有限的,而大部分功能都是由TA实现的。因此,我们的计划是先在TA中找到一个可利用的漏洞,然后进行提权。不过在进入反汇编器之前,我们首先需要了解一下用于实现TEE的ARM扩展:TrustZone。

ARM的TrustZone技术

TrustZone技术是ARM公司开发的一种硬件架构,它允许软件在两个域中执行:安全域和非安全域。这是通过使用一个“NS”位来实现的,该位指示主设备(master)是运行在安全模式还是非安全模式下。其中,主设备既可以是CPU核,也可以是硬件外设,如DMA或加密引擎。需要注意的是,主设备的安全状态既可以通过物理连接,也可以通过软件配置进行切换;例如,CPU核的安全状态可以通过调用SMC指令(后面会有更多介绍)或通过切换“SCR”寄存器中的“NS”位来进行切换。

为了定义从设备(slave,如外设和内存)的访问限制,TrustZone通常会提供分别名为TrustZone地址空间控制器(TZASC)和TrustZone保护控制器(TZPC)的两种组件。

TZASC可用于定义DRAM中的安全范围。ARM提供了多种不同的实现;最近的一种实现被称为“TZC-400”,下面给出了基于SoC的实现概述:

 1.png

图1  TZASC概述

可以看出,任何DRAM存储器访问在转发到存储器控制器之前都要先经过TZASC。TZASC可以根据一组内部规则来决定是否允许该内存访问操作。

TZASC包含一个始终处于可用状态的基区(区域0),并且该区域覆盖整个DRAM存储范围。然后,定义可以限制其访问的多个其他安全区域。更具体地说,对于其他地区,可以设置如下内容:

  •   起始和结束地址。
  •   保护性读写权限。这些权限将应用于任何试图访问该内存范围的安全主设备。注意,TZASC并没有委托给MMU执行权限和强制执行等概念。
  •   非安全ID筛选:可以将该区域配置为允许非安全主设备进行访问。在TZC-400的情况下,可以为读和写权限指定位掩码,以便对允许哪个非安全主设备访问哪些内存范围进行细粒度控制。

实际上,TZPC也实现了一个类似的概念,但它被应用于内部外设和SRAM,非外部DRAM。它包含一个寄存器(R0size),用于以4KB为单位指定安全性片上SRAM的大小。与TZASC相比,它的灵活性较差,因为它只允许定义一个安全区域和一个非安全区域:安全区域是从0开始直到指定的大小为止,其余的SRAM将被视为非安全区域。

此外,还存在一些额外的寄存器来指定每个外设是否是安全的(即它们只能被安全主设备访问)。不过,至于TZPC寄存器中哪些位对应哪些外设的映射并没有进行定义,这完全取决于特定的SoC。

通常情况下,TZASC和TZPC的大部分设置都是在初始化时进行配置的,并且永远不会改变。然而,其中一些设置需要在运行时进行动态的修改,比如用于执行安全支付的可信用户界面(TUI)。当用户需要输入PIN码来授权支付时(例如在S10中使用Samsung Pay时),TEE会接管并直接控制显示屏和触摸传感器。这里的思路是,由于PIN码是敏感资产,因此需要由TEE来完成整个处理过程,而不是交由不可信的Android操作系统进行相应的处理。因此,它必须使用TZPC将显示屏和触摸控制器都重新配置为安全的设备,这样即使是在Android中执行内核级代码的攻击者也无法读出PIN码。此外,由于在屏幕上显示图像需要一个存储在DRAM中的安全帧缓冲区,因此TEE也需要使用TZASC将DRAM中的一部分重新配置为安全区域,并将其作为帧缓冲区。一旦用户完成输入PIN码的操作,TZASC和TZPC就会恢复到之前的值,并由Android再次接管。

安全模式与非安全模式之间的转换是由一个名为“安全监视程序”的组件来管理的。实际上,这个监视程序是TEE和REE之间的主要接口,也是唯一可以改变内核安全状态的一个组件。

与REE一样,TEE在内核和TA之间实现了用户模式/内核模式的隔离。同时,TEE操作系统还负责加载TA,在REE和TA之间以及TA之间传递参数。而TA则在安全世界的用户空间中运行,为REE提供服务。

ARMv8-A CPU对每个“世界”提供了四种权限级别,也称为异常级别,具体如下所示:

  •   (S-)EL0级别:用户模式/应用
  •   (S-)EL1级别:内核
  •   EL2级别:管理程序
  •   EL3级别:安全监视程序

 1.png

图2

在REE中,我们的Android应用运行在EL0级别,而Linux内核则运行在EL1级别。

需要注意的是,EL2级别仅存在于非安全模式,即管理程序。它最初被设计为处理以较低特权级别并行运行的多个虚拟环境的方法,但是在Android环境中,通常将其用作内核的安全加固机制。在三星手机中,情况也是如此。管理程序组件被称为实时内核保护(RKP),除此之外,它还限制了内核可访问的内存,并使一些内核结构体成为只读的,以提高利用内核漏洞的难度。在我们系列的第三部分,我们将提供更多关于RKP的细节。

最后,它还提供了许多安全组件,这些也是我们的研究目标:EL3(总是在安全模式下运行), S-EL0和S-EL1。关于TEE的实现方法,实际上存在多种范式,但到目前为止,最常见的是在EL3上运行一个非常小的组件,负责在两个世界之间切换,在EL1上运行一个成熟的内核,在EL0上运行多个TA。三星的TEE OSTEEGRIS也不例外,它也是采用了这种设计。

虽然一个完全隔离的环境会非常安全,但要想让它发挥实际作用,就免不了与Android中运行的其他不可信的组件进行通信。实际上,REE和TEE之间的通信是通过一个名为“安全监控调用”(SMC)的专用指令来触发的。当这两个世界的运行级别EL>0时,都可以调用这条指令,这意味着Android应用程序不能直接启动与TEE的通信。通常出现的情况是,Linux内核作为代理,并暴露出一个驱动程序,供应用程序与TEE进行交互。这种设计的好处是,访问限制策略(例如使用SELinux)可以应用于驱动程序,这样的话,可以只允许部分应用程序与TEE通信,从而限制了系统面临的攻击面。对于S10,情况也是如此,其中只有有限数量的应用程序和服务被允许与TEE进行通信。

请注意,在我们后面的调查中,将假设攻击者具有与TEE进行通信的能力。例如,对于已经使用Magisk等工具获取root权限的手机来说,就是属于这种情况;此外,攻击者也可以通过获取Linux内核或任何允许与TEE通信的Android应用和服务的运行时控制权来获取与TEE进行通信的能力。

一旦SMC指令被执行,在EL3级别运行的安全监视程序就会生成一个中断。SMC处理机制会将SMC路由到相应的组件来进行相应的处理。如果监控程序可以直接处理SMC,它就会直接进行处理,并立即返回。否则,它会将请求转发到TEE内核(运行在S-EL1级别),然后在该内核内部进行处理,或者进一步转发给运行在S-EL0级别的TA。

现在,我们已经掌握了TrustZone的工作原理,接下来,让我们来分析一下三星的具体实现。

TEEGRIS

TEEGRIS是三星公司在Galaxy S10上推出的一个比较新的TEE操作系统。对于近期推出的(自2019年之后)、采用了Exynos芯片组的大多数三星手机来说,它们通常都会在TEE中运行TEEGRIS。

在2019年3月推出S10之前,Exynos芯片组使用不同的TEE操作系统,名为Kinibi,它由Trustonic开发。对于这个操作系统,许多安全研究人员已经对其进行了深入的研究,具体请参见参考资料中的[2][3][4]。然而,由于TEEGRIS是一个相对较新的操作系统,因此,目前公开的信息并不多。事实上,我们唯一能找到的信息都位于这篇博文中,它对TEEGRIS及其内核进行了相应的介绍,并且该文主要解释了如何设置QEMU,以便对其进行模糊测试。尽管在我们的调查中,我们决定只关注逆向分析方面,但这篇文章仍然包含了一些有用的信息,比如引导映像的布局(存储TEEGRIS的地方),以及如何识别由内核处理的系统调用。

下面,我们来看看TEEGRIS的主要组成部分:内核、TA和驱动程序。需要注意的是,正如前面所解释的那样,监视程序代码在TrustZone中有着非常重要的作用,然而,对于三星的系统来说,它被加密存储在闪存中。因此,我们没有对其进行分析,而是将重点放在其他组件上,因为这些组件是以明文形式存储的。

TEEGRIS内核

TEEGRIS内核是一个运行在安全EL1级别的小型组件。尽管它的体积很小,但它并不是一个微内核,例如,它集成了许多供TA使用的驱动程序。它以64位模式运行,同时支持64位和32位的TA以及在用户空间中运行的驱动程序。由于该内核是以明文的形式存储在引导分区中,因此,它可以很容易提取出来并进行反汇编处理。

该内核实现了许多POSIX兼容的系统调用,并添加了一些TEEGRIS的专用调用。另外,Alexander Tarasikov在他的文章中注意到,可以通过两个共享库来实现这些系统调用的封装(关于这些共享库的处理方法,请参见下面的TA部分):libtzsl.so和libteesl.so。这使我们能够快速地在内核中识别出两个表,其中包含64位和32位TA的系统调用处理程序。

 1.png

图3  Aarch64和Aarch32的系统调用处理程序

对这些系统调用的实现进行分析后发现,三星充分利用了两个Linux内核例程,并且,熟悉Linux内核的人可能都知道这些例程,即copy_to/from_user。这些例程用于访问来自用户空间的数据,以确保TA不能引用内核内部的结构。

 1.png

图4  copy_from_user的反编译结果

图4中的代码首先验证是否设置了一个标志来忽略其他检查。当内核用已知的安全参数直接调用系统调用处理程序时,就会用到这个标志。如果设置了,这个函数的作用,就只是memcpy的一个包装器。

在所有其他情况下,这里的代码会调用check_address,具体如下图所示:

 

 1.png

图5  地址检查例程

图5所示的一段代码为我们提供了一些重要的信息:TA的第一页永远不应该被映射,正如第10行所检查的那样(可能是为了防止NULL指针解引用漏洞),有效的TA地址应该小于0x40000000(详见第12行)。大于此值的地址将被视为无效并被丢弃。另外,如图4所示,复制操作是使用LDTR*指令进行的。实际上,虽然LDTR*指令的行为与普通的LDR*指令相同,但会导致以EL0权限执行内存访问操作。这是因为启用了PAN,即使check_address函数也可能会遗漏一些边缘情况,但对内核内存的任何非特权访问都将导致访问违规。

此外,由于TA地址空间的上限0x40000000,这就意味着ASLR机制引入随机性将会受限——特别是考虑到它支持64位TA。为了验证这个假设是否属实,我们研究了TA映像的加载方式。请注意,在TEEGRIS中,TA实际上就是(略作修改的)ELF文件,因此我们可以很容易地通过跟踪代码来找出解析标准ELF格式的函数。我们最终找到了函数“map_elf32_image”(对于64位的TA,也有一个对应的函数)。

 1.png

图6  编码段和数据段的随机化

请注意,这段代码强制要求只能加载PIE可执行文件(见第120行)。之后(第132行),它随机生成了长度为2字节的随机变量,并用0x7FFF进行掩码处理(第134行),然后将其作为页偏移量,与入口点(和基地址,稍后在同一个函数中完成)进行相加。这意味着ASLR偏移量最多只能有32768个值,而且它应用于ELF中指定的所有段。

动态内存(例如,用于与REE共享的堆和映射内存)则在“spwn”系统调用中使用不同的值进行随机化,但使用的方法与上面的类似。

 

 1.png

图7  动态内存的随机化

请注意,ASLR机制不仅在TA中使用,而且在内核本身也会使用(通常称为KASLR)。虽然我们不会去讨论其实现的细节,然而,如果我们最终想利用内核漏洞的话,这一点必须要牢记。在entry函数中,内核会生成另一个随机值,并相应地修改页表和重定位表。

如前所述,内核内置了许多驱动程序。这些驱动程序主要用于与外设(如SPI和I2C)通信或执行加密操作。

 

 1.png

图8  内核中实现的部分驱动程序清单

由于三星按照POSIX规范实现了TEEGRIS,所以它们与驱动程序的交互方式也使用了相应的方式。例如,驱动程序的名称通常以“dev://”开头,可以通过打开TA的相应文件进行访问。之后,TA可以通过一些系统调用(如read/write/ioctl/mmap)与驱动程序进行交互。在内核内部,有一个结构体专门用来存储每个驱动程序的系统调用实现。

当然,并不是所有的TA都具有访问驱动程序和系统调用的权限,这是因为,每个TA都属于不同的组,而每个组则具有不同的权限级别。内核会跟踪哪些权限被授予给哪个TA,并进行相应的检查,以确保只有被授权的TA才能访问受限的功能。下面两个图显示了授予两个不同组的权限的例子:samsung_ta和samsung_drv组。

 1.png

图9   samsung_ta组的访问权限

 

 1.png

图10  samsung_drv组的访问权限

从图中可以看出,与每个TA相关联的权限共有19个。并且,如果值为0,则意味着没有授予相应的权限,而其他值则至少授予部分权限。其中大部分权限要么授予,要么不授予,但对于其中的几个(MMAP的)权限,有一个特定的掩码来决定内存是否可以用读/写/执行权限进行映射。在这两个例子中,samsung_ta组的权限相当有限,而samsung_drv组的权限则比较多。当然,系统中还有其他的组,它们也具有不同的权限设置,但这两个组是目前最常见的组。

TA与用户空间驱动程序

现在我们对内核的运行机制以及如何与内核进行交互已经有了一个大概的了解,接下来,让我们来考察TA。通常情况下,TEE中的TA有两种模式:一种模式是,TA是一个不可改变的blob,与TEE操作系统捆绑在一起,并且总是在初始化时加载;另一种模式是,TA可以在运行时被Android加载。三星在TEEGRIS中采取了混合方式,也就是说,这两种模式都得到了支持和应用。

在引导分区中,有一个特殊的存档(称为startup.tzar),其中含有TA所需要的所有共享库,以及一些特殊的TA和驱动程序,这些都是系统在早期阶段,即Android完全启动之前所需要的东西:TSS(用于管理共享内存)、ACSD(支持TA认证的驱动)和root_task(用于加载TA并结合ACSD进行验证)。tzar存档中的二进制文件是标准的ELF文件,可以直接加载到反汇编器中进行分析。由于存档是引导映像的一部分,它的签名将在引导时进行验证。

此外,TA也可以在运行时从Android中进行加载。一般来说,可加载的TA通常存储在/vendor/tee和/system/tee分区中。在S10中,大约有30种不同的可加载TA,其格式如下所示:

 fig_11.png

图11

  •   头部长度为8个字节,包含4个字节的版本(可以是“SEC2”、“SEC3”或“SEC4”)和内容部分的长度。
  •   内容部分是一个常规的ELF文件,包含实际的TA内容。如果TA的类型是“SEC4”,则内容是加密的,否则,它们就是明文形式的。
  •   元数据部分包含TA组。从版本“SEC3”开始,有一个额外的字段专门存放版本号。这个版本由root_task结合ACSD来管理TA的防回滚保护。每当加载一个SEC3或SEC4的TA时,版本号都会被提取出来,并与存储在RPMB存储中的版本进行比较。如果版本号较低,则不能加载TA,并返回一个错误。如果较高,则提高RPMB中的版本号,使之与TA的版本相匹配,这样就不能再加载同一TA的旧副本。这也意味着,从这时开始,TA的SEC2版本将无法使用。这个功能对于撤销具有已知漏洞的旧TA至关重要,我们将在本系列的第二篇文章中更详细地研究这个问题。
  •   签名部分包含在映像的其余部分上的RSA签名。它遵循X.509格式,并由ACSD进行解析。

从上面简短的描述中可以看出,通过删除初始标头并将它们作为ELF文件加载到反汇编程序中,可以轻松地反汇编可加载的TA。唯一的复杂情况是当使用SEC4格式时(因为ELF是加密的),然而,在实践中,Galaxy S10和S20只使用SEC2和SEC3。然后,TA就可以从tzar存档中导入相应的程序库。实际上,这些库也是常规的ELF文件,其中实现了C库函数、GlobalPlatform(GP)API和TEEGRIS的特定功能。

TEEGRIS中的TA实现了GP API(其规范可以在这里找到),它规定了TA需要实现的5个接口,以便能够与REE进行交互:

  •   TA_CreateEntryPoint,在TA被加载时被调用。
  •   TA_OpenSessionEntryPoint,当运行在REE中的客户端应用(CA),在我们的例子中是Android应用,首次建立与TA的连接时调用。
  •   TA_InvokeCommandEntryPoint,它包含将为CA发送的每个命令调用的主命令处理程序。TA的大部分功能将在这里实现。
  •   TA_CloseSessionEntryPoint,当CA关闭与TA的会话时调用。
  •   TA_DestroyEntryPoint,当TA从内存中卸载时执行。

尽管TA是一个可执行文件,但由于实际的main()函数是在libteesl.so库中实现的,所以它的执行稍微有点复杂。当TA被启动时,实际上将发生以下情况:

  •   TA内部的start()函数被执行。这个函数通常只是main()的一个包装器。

 

 1.png

图12  start()函数示例

其实main函数并不是在TA内部实现的,而是在libteesl.so库中实现的。通过TEEGRIS内核与REE通信的大多数逻辑都是在这里配置的。它为与根TA的通信建立了标准的基于POSIX ePoll的机制。在下面的代码片段中,main函数首先调用TA_CreateEntryPoint(),然后跳转到start_event_loop()函数。

 1.png

图13  libteesl中main函数的代码片段

  •   在start_event_loop()函数中,库将接收代表来自CA的请求的事件。然后,这些事件将被转发到相应的GP API入口点。

虽然本节叫做“TA与用户空间驱动程序”,但直到现在,我们主要讨论的只是TA。那么,驱动程序在哪里呢?嗯,驱动程序与TA一样。它们具有相同的格式,实现相同的GP API,但它们调用了一个名为TEES_InitDriver的TEEGRIS特定API。这个函数允许驱动程序指定驱动程序名称和一个结构体,它可以用来与用户空间的驱动程序进行交互,其方式与内核空间的驱动程序类似。用户空间驱动程序在默认情况下是没有任何特权的,但它们通常属于高权限的组。

针对漏洞利用的缓解措施

现在,我们来总结一下在内核和TA中实现的漏洞利用缓解措施:

  •   同时应用于内核和TA的XN(eXecute Never,XN)机制。这意味着存放数据的内存都是不可执行的,而存放代码的内存总是不可写的。
  •   同时应用于内核和TA的堆栈金丝雀机制,以防止基于堆栈的缓冲区溢出攻击。
  •   ASLR和KASLR机制,用于随机化TA和内核的地址空间。
  •   PAN和PXN机制,防止内核访问或执行用户模式内存。

从历史上看,与其他现代操作系统相比,TEE操作系统中的漏洞利用缓解措施一直是乏善可陈的。之前三星TEE上的漏洞攻击,针对的是只通过XN实现防御的老手机,因此,需要绕过的缓解措施较少。因此,S10是在正确的方向上迈出了一步,而志在危及整个TEE的全面攻击则需要组合利用多个漏洞。

如何与TA进行通信

现在我们对TA有了一定的了解,接下来,我们需要弄清楚如何从Android环境中与TA进行通信。幸运的是,GP标准不仅为TA定义了一套API,同时,也为想要与TA通信的CA定义了一套API。上面介绍的每一个入口点都有一个对应的调用,以供CA使用(比如TEEC_OpenSession可以用来打开一个会话,TEEC_InvokeCommand可以用来发送一个命令等等)。

对于TEEGRIS来说,因为已经有一个名为“libteecl.so”的库实现了GP API,因此,与TAs的通信就像使用dlopen/dlsym来解析GP API所需的符号一样简单。要打开一个会话,我们需要指定目标TA的UUID。然后,该库将在/vendor/tee或/system/tee中寻找具有该UUID的TA(UUID是文件名),并将整个TA映像传递给TEE,然后,TEE将对其进行认证和加载。需要说明的是,所有的这一切对CA来说都是透明的,也就是说,CA并不知道实际的通信是如何发生的。

需要注意的是,正如我们前面提到的,并不是每一个Android应用都可以和TEE进行通信。值得重申的是,由于存在某些限制,因此,要想实现组合多个漏洞发动攻击,攻击者必须首先获得与TEE交互的应用程序的运行时控制权。

小结

本系列文章的第一篇到此结束。在下一篇文章中,将为大家展示我们是如何识别并利用TA中的漏洞的,以便在TA的上下文中获得运行时控制权。在最后一篇文章中,我们将展示如何提升权限,以便攻陷整个TEE。

参考文献

[1] https://developer.arm.com/documentation/ddi0504/c/

[2] https://medium.com/taszksec/unbox-your-phone-part-i-331bbf44c30c

[3] https://labs.bluefrostsecurity.de/blog/2019/05/27/tee-exploitation-on-samsung-exynos-devices-introduction/

[4] https://blog.quarkslab.com/a-deep-dive-into-samsungs-TrustZone-part-1.html

[5] http://allsoftwaresucks.blogspot.com/2019/05/reverse-engineering-samsung-exynos-9820.html

[6] https://globalplatform.org/wp-content/uploads/2018/06/GPD_TEE_Internal_Core_API_Specification_v1.1.2.50_PublicReview.pdf


本文由secM整理并翻译,不代表白帽汇任何观点和立场
来源:https://www.riscure.com/blog/samsung-investigation-part1

最新评论

昵称
邮箱
提交评论