深入考察Samsung系统的安全特性,Part 2: TA安全漏洞的挖掘与利用技术
在本文中,我们将介绍如何通过逆向分析技术来识别和利用可信应用程序(TA)中的安全漏洞,以继续探索TEEGRIS系统的安全特性。
与TA相关的安全漏洞
GP(Global Platform)接口
如前文所述,TA实现了GP接口。对于该接口来说,我们最感兴趣的入口点是TA_InvokeCommandEntryPoint,因为每次客户端应用(CA)向TA发送命令时,都会执行这个函数。
GP规范给出了该函数的原型:
图1 关于TA_InvokeCommandEntryPoint的相关描述
该API允许CA最多指定4个参数。并且,每个参数都有一个相关的参数类型。在同一份GP文档中,还列出了所有的参数类型,具体如下所示:
图2 GP的参数类型
假设我们使用了一个参数(因此它的类型将不是TEE_PARAM_TYPE_NONE),那么,它实际上主要有两种类型:value和memref。每种参数类型的值都被编码为半字节(nibble),并将它们合并在一起,从而形成paramTypes参数。
其中,TEE_Param是一个共用体(union),其定义如下所示:
图3 TEE_Param共用体
当然,这个共用体的内容,是由参数的值为类型value还是类型memref而定的,具体如下所示:
- value:成员a和b等于CA传递的值。
- memref:这时,CA传递的是一个引用,该引用指向其私有内存中的缓冲区。TEE OS将在TA的地址空间中映射相同的内存空间,并相应地填充共用体成员buffer与size。具体来说,buffer将指向TA内存空间中的一个有效的虚拟地址。这样,TA和CA都将拥有相同物理内存的视图。
从上面的描述中可以看出,检查参数类型是至关重要的。如果TA期望的参数类型为memref,但CA却传递了一个value类型,就无法保证buffer或a会指向一个有效的共享内存位置。这可能会导致类型混淆漏洞,这时TA认为有效的缓冲区指针及其大小,可以被CA完全控制。
可信应用程序中的GP检查
由于该项检查非常重要,所以,很有必要验证一下是否所有的TA都正确地实施了这项检查。出乎我们意料的是,貌似并非所有的TA都是如此。
以下内容摘自TIMA TA:
图4 TIMA TA的参数类型验证
如上所示,该TA首先会检查paramTypes参数,如果它们不是预期的值,则返回一个错误。这是一个符合预期且无法加以利用的行为。
现在,让我们切换到另一个TA,HDCP TA(UUID00000000-0000-0000-0000-000048444350)。
图5 HDCP TA的参数类型验证
我们可以看到,这个TA根本就没有对参数类型进行相应的检查。实际上,这里的参数是从输入中获取并直接传递给主函数的。这就意味着,攻击者可以对这个TA进行类型混淆攻击。
假设我们现在可以将输入参数设置为我们想要的任何值,那么,我们如何才能利用这一点呢?在理想情况下,我们希望利用它来实现任意读写原语,之后,再利用它们来获得TA的运行时控制权。
在分析了TA实现的所有命令后,我们发现,通过串联三个命令,就可以同时获得读原语和写原语。需要注意的是,我们并不知道TA中实现的命令的具体用途,因为GP API根本就没有定义任何标准命令。也就是说,这些命令是该TA所特有的,因此,同样的命令值在不同的TA中可能会有完全不同的功能。在这里,我们要考察的命令是FB、FC和CB。
- 命令FB:
这个命令用于从Android应用中获取一个输入缓冲区,并将其打包成一个安全对象(这就意味着缓冲区是经过加密处理的,并用TA特定的密钥进行了签名),并将安全对象返回给应用程序。由于存在类型混淆漏洞,因此,输入和输出指针都可以被攻击者完全控制。但是,由于输出内容不可预知,所以,该命令本身并不能提供一个非常强大的原语。
- 命令FC:
命令FC的用途,与命令FB的用途正好相反:它把一个安全的对象进行解包,从而恢复为原始内容。但是,解包后的输出并不会返回给REE,而是存储在TA内部的固定内存位置。
- 命令CB:
最后,命令CB用于获取存放解包后的数据的缓冲区,并将其中的一部分返回给REE。需要注意的是,由于存在类型混淆漏洞,目标地址也可以设置为指向TA内部内存。
通常情况下,这三个命令串联方式如下所示:
图6 FB/FC/CB命令的常规串联方式
首先,让命令FB接收来自REE的输入,对其进行加密并返回给REE。然后,REE通过命令FC将其传给TA,而TA则会对其进行解密并保存到该TA的内部存储区域中。最后,通过命令CB将缓冲区的一部分返回给REE。最终的结果是,命令CB的输出恰好就是命令FB的输入的部分拷贝。
通过让命令FB的输入指向TA内部存储区域,让CB的输出指向REE的存储区域,就可以实现任意读取原语。
图7 对FB命令的输入展开类型混淆攻击,以读取TA的内存
同样,通过将命令FB的输入设置为REE内存区域,将命令CB的输出设置为TA内存区域,这可以实现任意写入原语。
图8 对CB命令的输出实施类型混淆攻击,以覆盖TA的内存
这样的话,我们就可以在HDCP TA内进行任意读写了。这已经算是一个重大的成就了,因为这个TA应该是安全的,并且与不受信任的Android操作系统完全隔离。然而,由于三星实施的漏洞缓解措施,仍然存在两大障碍:
- 由于ASLR机制的原因,我们还不知道TA确切的内存映射偏移量。
- 由于我们想利用TA进行提权,从而获得对整个TEE内存空间的完全访问权限,因此,我们还希望进一步获得代码执行权限。
绕过ASLR机制
正如上一篇文章中提到的,ASLR是通过对程序的代码段和数据段进行随机分片的形式来实现的。这些分片的大小,是由一个介于0到32767之间的随机数乘以4096(页面大小)来确定的。
本来,我们希望能够找到这样一种方法:以某种方式泄露信息(例如指针值),从而恢复随机偏移量,不过,最终还是失败了。但是,如果我们只是将上面所示的三个命令串联起来,试图从一个随机位置读取内存,会发生什么情况呢?很明显,该TA将会崩溃,而在Android端,我们会在dmesg中得到一个错误日志:
[ 119.608950] SW> [TEEgris:SCrypto] SCrypto 2.4 isin FIPS approved mode
[ 119.608979] SW> HDCP : TA_CreateEntryPoint
[ 119.608992] SW> HDCP : TA_OpenSessionEntryPoint
[ 119.612625] SW> SECUREOS VERSION: Samsung SecureOS Release Version 3.1.0.0 (15415496 15403694) built on: 2019-02-14 16:37:50,binary version: 9e91f07b
[ 119.612651] SW> ERR: UNKNOWN TEE_MemMove()pid=114: Panic Reason: check of [inbuf] parameter is failed in ID_TEE_MemMove
[ 119.612665] SW> SECUREOS VERSION: Samsung SecureOS Release Version 3.1.0.0 (15415496 15403694) built on: 2019-02-14 16:37:50,binary version: 9e91f07b
[ 119.612678] SW> ERR: UNKNOWN _TEE_Panic()pid=114: Function No: 0x607, Specification No: 0xa
[ 119.612688] SW> SECUREOS VERSION: Samsung SecureOS Release Version 3.1.0.0 (15415496 15403694) built on: 2019-02-14 16:37:50,binary version: 9e91f07b
[ 119.612699] SW> ERR: UNKNOWN _TEE_Panic()pid=114: Panic thrown out from file:
[ 119.612706] SW> src/teesl/tee_string.c,
[ 119.612712] SW> Line - 20,
[ 119.612719] SW> Code - 0x607
[ 119.612727] SW> Samsung Secure OS Release Version3.1.0.0 (15415496 15403694) built on: 2019-02-14 16:37:50, binary version:9e91f07b
[ 119.612734] SW> Th#222 of Pid=114 panicked withcode: 0x00000607
然而,我们其实可以再次与同一个TA进行交互,并访问同一个随机地址,来看看我们这次的运气是否足够好,从而“命中”映射的内存。实际上,我们可以多试几次,直到我们试图访问的地址被正确映射为止。由于可能的随机数只有32k,所以找到那个“幸运的”随机数并没有那么难,通常情况下,我们可以在一分钟之内搞定。在利用漏洞的过程中,虽然这种方法并不是最理想的,但也是可以接受的。另外,需要注意的是,实际命中有效随机数的几率要高于32k分之一,因为我们可以访问一个任意读取原语,并且我们只需要从被映射的二进制代码中找到并读取一页;提取内存内容后,我们可以将它们与已知的二进制内容进行匹配,以检索实际的随机偏移量。
实现代码执行
现在,我们已经获取了任意读写原语,并成功绕过了ASLR旁路。实际上,只要获得了如此强大的原语,利用它来获取代码执行就不是什么难事了。对于我们来说,实现代码执行的方案有多种,但我们最后决定,将读写原语与我们在TA中发现的另一个漏洞结合起来使用,这个漏洞就是:基于栈的缓冲区溢出漏洞。
对于命令TZ_RepeaterAuth_Send_ReceiverId_List_T()来说,它能够接收request缓冲区内的某些信息,并对传递的参数进行相应的验证,然后,根据设定的HDCP版本调用函数TZ_RepeaterAuth_Send_ReceiverId_List20_T()或TZ_RepeaterAuth_Send_ReceiverId_List21_T()。然而,从下图可以看出,参数的验证过程,是通过比较攻击者提供的两个值(即request[3]和req_size)来完成的。这使得攻击者可以精心构造一个请求,使检查总是成功通过。
图9 反编译后的TZ_RepeaterAuth_Send_ReceiverId_List_T函数
仔细观察下图中的TZ_RepeaterAuth_Send_ReceiverId_List20_T()函数,我们可以发现,我们的request缓冲区的内容将被复制到一个160字节的数组中,这个数组位于堆栈上,并且其长度是受控的,即5 *request[3]——这就导致了基于栈的缓冲区溢出漏洞。
图10 反编译后的TZ_RepeaterAuth_Send_ReceiverId_List20_T函数
这是一个教科书式的基于堆栈的缓冲区溢出漏洞:我们不仅可以控制复制的内容的长度,还能控制缓冲区的内容。在这里,我们最多可以复制1275个字节,这些空间已经足以用来存储shellcode了。然而,由于这个TA使用了堆栈金丝雀,具体如下图所示,因此,要想利用这个漏洞的话,也不是件非常容易的事情。
图11 反编译后的TZ_RepeaterAuth_Send_ReceiverId_List20_T函数的尾声(epilogue)
由于我们已经获得任意读取原语,并能绕过ASLR机制,所以,我们可以直接读取__stack_chk_guard的值,并将其填入我们的shellcode中,这样就能顺利通过金丝雀验证(canary verification)。
我们可以将所有的步骤结合起来,这样就能控制HDCPTA中的程序计数器(PC)了。当堆栈金丝雀被设置为错误的值时,我们会在dmesg中得到以下调试信息:
[38142.232347] SW> [TEEgris:SCrypto] SCrypto 2.4 isin FIPS approved mode
[38142.232374] SW> HDCP : TA_CreateEntryPoint
[38142.232381] SW> HDCP : TA_OpenSessionEntryPoint
[38142.240917] SW> TZ_SET_HDCP_VERSION_T : HDCP 2.0version is setuped
[38142.243372] SW> *** tzsl detected *** Stacksmashing
[38142.243387] SW> rettadr: 0x286f9c
当金丝雀被正确读取和设置,并且我们将堆栈上的返回地址替换为0xAAAAAAA时,将打印以下内容:
[37276.997902] SW> [TEEgris:SCrypto] SCrypto 2.4 isin FIPS approved mode
[37276.997921] SW> HDCP : TA_CreateEntryPoint
[37276.997928] SW> HDCP : TA_OpenSessionEntryPoint
[37277.005590] SW> TZ_SET_HDCP_VERSION_T : HDCP 2.0version is setuped
[37277.007939] SW> Samsung Secure OS ReleaseVersion 3.1.0.0 (15415496 15403694) built on: 2019-02-14 16:37:50, binaryversion: 9e91f07b
[37277.007952] SW> Th#2528 of Pid=5344 panickedwith signal: 5 (SIGTRAP)
[37277.007960] SW> Fault addr 0xaaaaaaaa in moduleN/A
至此,关于TA漏洞利用的第一部分就告一段落了。现在,我们已经在HDCP TA内获得了任意的读、写和代码执行权限;但是,由于XN机制的原因,我们只能重用现有的代码。更多TA漏洞利用方面的内容,我们将在后面的文章中加以介绍。
TA的防回滚功能
就在我们调查到这个阶段的时候,我们在三星最新(当时)的安全补丁说明上看到了以下内容:
图12 三星发布说明,已经包括HDCP TA参数的类型检查
由此看来,三星好像收到了其他研究人员提交的缺乏参数检查所带来的安全隐患方面的报告,并对这个漏洞进行了修复。但是,修复效果咋样呢?让我们来看看修复后的TA_InvokeCommandEntryPoint函数:
图13 HDCP TA中经过修复的TA_InvokeCommandEntryPoint函数
乍一看,该函数的确实现了对参数类型的正确检查,但是基于堆栈的缓冲区溢出问题依然存在;然而,如果不能设法泄露金丝雀的话,攻击者就无法利用该漏洞。那么,这是否意味着我们所有的工作都泡汤了呢?
是的,但也不尽然。归根结底,TA只是一个由REE传递给TEE执行的、经过签名的blob代码。如果REE要求TEE执行原来发行的TA,那么将出现什么情况呢?在上一篇文章中,我们看到根据TA的版本的不同,有时是可以启用防回滚功能的。具体来说,版本SEC2并不支持该功能,但是版本SEC3和SEC4却支持该功能。那么,新发行的HDCP TA的头部中指定的版本到底是什么呢?很简单,我们可以用十六进制编辑器来找到答案。
图14 新发行的TA的头部信息
看来新发行的TA的版本还是SEC2!这意味着应该没有强制执行防回滚。那么,让我们继续进行验证:将手机的固件升级到较新的版本,并创建了一个经过修改的 “libteecl.so”副本,让它在“/data/local/tmp”目录中寻找相关的TA,而不是在“/vendor/tee”目录下寻找。这样,我们就可以把原来发行的TA放到“/data/local/tmp”目录中,并让TEE执行这个TA。
事实上,TEEGRIS操作系统是允许加载原来发行的TA的,而原来发行的TA仍然包含可利用的安全漏洞。这就意味着,即使新发行的TA版本正确修复了参数检查漏洞,但是,攻击者仍然可以强迫TEE加载原来发行的TA,从而使得系统重新暴露在危险之中。
小结
在本文中,我们不仅为读者演示了如何挖掘和利用HDCPTA中的安全漏洞,同时,还展示了TEE中防回滚功能的重要性。
在下一篇文章中,我们将为读者介绍如何进行提权,并获得对整个TEE内存的完全访问权限。
本文由secM整理并翻译,不代表白帽汇任何观点和立场
来源:https://www.riscure.com/blog/samsung-investigation-part2
最新评论