Android可信执行环境安全研究(二):可信应用漏洞利用

 

0x00 前言

在第二篇系列文章中,我们将继续通过逆向工程技术来探索TEEGRIS,从而发现并利用其中存在的安全漏洞。

 

0x01 TA漏洞分析

1.1 Global Platform(GP)接口

正如第一篇文章所述,TA实现了GP接口。其中我们最关注的入口点是TA_InvokeCommandEntryPoint,它在每次客户端应用程序(CA)向TA发送命令时执行。
在GP规范中,记录了该函数的原型。
TA_InvokeCommandEntryPoint的描述:

该API允许CA最多指定4个参数。每个参数都有一个关联的参数类型。在这份GP文档中同时还列出了可选的参数类型。
GP参数类型:

假设这里使用了一个参数(因此其类型不是TEE_PARAM_TYPE_NONE),这里主要涉及到两种类型,分别是valuememref。每种参数类型的值被编码为半字节,合并在一起组成了paramTypes参数。
TEE_Param是一个union类型,其定义如下。
TEE_Param Union定义:

根据参数是value类型还是memref类型,其填充内容也有所不同:
(1)value:成员ab被设置为与CA传递的值完全相同的值。
(2)memref:CA将引用传递给其私有内存中的缓冲区。TEE OS将在TA的地址空间中映射相同的内存,并相应地填充union的buffersize成员。具体而言,buffer将指向TA内存空间中保证有效的虚拟地址。这样,TA和CA都会有相同物理内存的视图。
从描述中可以明显看出,检查参数类型这一点至关重要。如果TA期望使用memref,但CA却传递了一个值,这样则不能保证buffera指向有效的共享内存位置。这可能会导致类型混乱,其中CA可以完全控制TA认为有效的缓冲区指针及大小。

1.2 在TA中的GP检查

考虑到这个检查的重要性,我们确实有必要验证所有TA是否正确实施了这一检查。出乎意料的是,似乎并不是所有TA都有完善的检查。
TIMA TA参数类型验证:

TA首先正确检查paramTypes参数,如果它们并非预期值,则返回错误。这是符合预期的行为,不存在风险。
现在,我们切换到另一个TA,也就是HDCP TA(UUID 00000000-0000-0000-0000-000048444350)。
HDCP TA参数类型验证:

这个TA完全不包含对参数类型的检查。这些参数可以从输入中获取,并直接传递给main函数。这意味着,可以对这个TA进行类型混淆攻击。
假设我们现在可以将输入参数设置为所需的任何值,我们可以尝试找到一种利用该参数的方法。理想情况下,我们希望利用它来实现任意读写,然后在利用它获得对TA的运行时控制。
在分析了TA中实现的所有命令之后,我们发现可以组合使用其中的三个命令,从而获得读写原语。由于GP API没有定义任何标准命令,因此我们不知道在TA中实现命令的确切目的。它们是特定于TA的,并且不同TA中的相同命令值可能具有完全不同的功能。我们的目标命令是FB、FC和CB。

(1)FB命令:

这个命令从Android应用程序获取输入缓冲区,将其包装到安全对象中(意味着该缓冲区已经使用TA专用密钥进行加密和签名),然后将安全对象返回给应用程序。由于类型混淆漏洞,攻击者可以完全控制输入和输出指针。但是,由于输出内容是不可预测的,因此命令本身不能提供非常强大的原语。
(2)FC命令:

FC命令与FB命令执行相反的操作:它会获取一个安全对象,并将其解包回原始内容。但是,解包的输出不会返回到REE,而是存储在TA内的固定内存位置。
(3)CB命令:

最后,CB命令获取包含未包装数据的缓冲区,并将其一部分返回给REE。请注意,由于类型混淆漏洞,目标地址也可以设置为指向TA内部内存。
通常,三个命令组合使用的场景如下:

FB命令接收来自REE的输入,对其进行加密,然后将其返回给REE。REE使用FC命令将其传递给TA,后者将其揭秘并存储在TA内部内存位置中。最后,使用CB命令将缓冲区的一部分返回到REE。最终结果是CB命令的输出是FB命令输入的部分副本。
可以通过指定FB命令的输入指向TA内部内存,并指定CB命令的输出以指向REE内存的方式,实现任意读取。
对FB命令输入进行类型混淆攻击以提取TA内存:

类似地,还可以通过将FB命令的输入设置为REE内存,并将CB命令的输出设置为TA内存的方式,实现任意写入。
对CB命令输出进行类型混淆攻击以覆盖TA内存:

现在,我们就可以在HDCP TA中实现任意读写。由于TA被认为是安全的,并且与不受信任的Android OS完全隔离,因此这已经是我们的一大突破。不过,三星实施了漏洞利用缓解措施,所以这里还有两个问题:
(1)由于ASLR,我们不知道TA的确切内存映射位置。
(2)因为我们想利用TA提升特权,获得对整个TEE内存的完整访问权限,所以我们可能也希望获得代码执行。

1.3 绕过ASLR

如上篇文章所说,ASLR的原理是对代码和数据段进行随机化。这个随机的范围是0到32767之间的随机数乘以4096(页大小)。
我们没有找到能够以某种方式泄露信息(例如指针值)的方法,如果能有这样的信息,我们就可以恢复随机偏移量。如果我们仅仅是将上述三个命令组合起来,尝试从随机位置读取,会发生什么情况?实际上,TA将会崩溃,在Android中我们将可以在dmesg中看到错误日志。

[ 119.608950] SW> [TEEgris:SCrypto] SCrypto 2.4 is in FIPS approved mode
[ 119.608979] SW> HDCP : TA_CreateEntryPoint
[ 119.608992] SW> HDCP : TA_OpenSessionEntryPoint
[ 119.612625] SW> SECUREOS VERSION: Samsung Secure OS 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 Secure OS 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 Secure OS 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 Version 3.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 with code: 0x00000607

但是,它不会阻止我们再次尝试与同一个TA进行交互,也不会阻止访问同一个随机地址。这一次,可以看看是否足够幸运,可以命中真实的映射内存。这个过程可以重复多次,直到映射了我们尝试访问的地址。由于可能的随机数只有32k,因此找到真实的随机数并不是那么困难,通常可以在不到一分钟的时间内完成。尽管这样的效率在漏洞利用过程中并不理想,但还是可以接受的。另外,实际上我们碰到有效随机数的机会是大于1/320000的,因为我们已经有了任意读取原语,只需要从映射的二进制文件中查找和读取一页即可。在提取内存的内容后,我们可以将它们与已知的二进制内容进行匹配,以检索实际的随机偏移量。

1.4 实现代码执行

在这个阶段,我们已经有了任意读写权限和ASLR绕过方式。在拥有了如此强大的原语之后,利用它们来获取代码执行就不是一个困难的问题。可能会有几种不同的思路,最终我们决定将其与在TA中发现的另一个栈缓冲区溢出漏洞结合起来。
命令TZ_RepeaterAuth_Send_ReceiverId_List_T()在请求缓冲区中接收某些信息,对传递的参数执行一些验证,然后根据设置的HDCP版本调用函数TZ_RepeaterAuth_Send_ReceiverId_List20_T()TZ_RepeaterAuth_Send_ReceiverId_List21_T()。但是,如下图所示,参数验证的过程仅仅是比较两个攻击者提供的值,也就是request[3]req_size。这样一来,攻击者就可以构造请求,确保检查通过。
反编译的TZ_RepeaterAuth_Send_ReceiverId_List_T函数:

在下图中跳转到函数TZ_RepeaterAuth_Send_ReceiverId_List20_T(),我们可以看到将请求缓冲区的内容复制到一个160字节的数组中,该数组驻留在栈中,大小控制为5 * request[3]。这会导致in缓冲区存在栈缓冲区溢出。
反编译的TZ_RepeaterAuth_Send_ReceiverId_List20_T函数:

这是非常经典的栈缓冲区溢出,我们在其中控制复制的大小和缓冲区内容。最多可以复制1275个字节,这样就有足够的空间来存储Shellcode。但是,TA还使用了栈金丝雀(Stack Canaries)防护,如下图所示,所以漏洞利用就变得困难起来。
反编译的TZ_RepeaterAuth_Send_ReceiverId_List20_T末尾部分:

由于我们现在有任意读取和ASLR绕过,所以可以轻松读取__stack_chk_guard的值并将其填充到我们的Shellcode中,以便使金丝雀验证通过。
我们尝试将所有步骤结合起来,以证明最终可以控制HDCP TA中的程序计数器(PC)。当栈金丝雀设置为错误的值时,我们在dmesg中可以看到如下调试信息:

[38142.232347] SW> [TEEgris:SCrypto] SCrypto 2.4 is in 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.0 version is setuped
[38142.243372] SW> *** tzsl detected *** Stack smashing
[38142.243387] SW> rettadr: 0x286f9c

在读取并正确设置金丝雀后,我们将栈上的返回地址替换为0xAAAAAAAA,则会打印如下内容:

[37276.997902] SW> [TEEgris:SCrypto] SCrypto 2.4 is in 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.0 version is setuped
[37277.007939] SW> Samsung Secure OS Release Version 3.1.0.0 (15415496 15403694) built on: 2019-02-14 16:37:50, binary version: 9e91f07b
[37277.007952] SW> Th#2528 of Pid=5344 panicked with signal: 5 (SIGTRAP)
[37277.007960] SW> Fault addr 0xaaaaaaaa in module N/A

至此,TA漏洞利用的第一部分就告一段落。我们在HDCP TA中获得了任意读取、写入和代码执行。但是,由于XN,我们只能够复用现有的代码,这一点会在下一篇文章中进行更多分析。

 

0x02 TA防回滚机制

在研究过程中,我们发现三星当时最新的安全补丁公告中包含了如下内容。
三星版本发布公告,涉及HDCP TA参数类型检查:

似乎已经有研究人员将缺失的参数检查报告给三星,并且得到了修复。但是,修复方法真的是正确的吗?让我们看一下新的TA_InvokeCommandEntryPoint
新HDCP TA中的TA_InvokeCommandEntryPoint

该函数似乎对参数类型实施了适当的检查。基于栈的缓冲区溢出漏洞仍然存在,但是如果没有泄露金丝雀的方法,我们就无法对其进行利用。这样一来,我们之前的成果就付之东流了。
但事实并非如此,我们发现,TA只是REE传递给药执行的TEE的代码签名blob。如果REE要求TEE执行旧版本的TA,会发生什么情况?在上篇文章中,我们看到根据TA版本的不同,可以启用防回滚功能。SEC2版本不支持这个功能,而SEC3和SEC4支持。新的HDCP TA标头中指定的版本是什么?我们用十六进制编辑器打开它。
新的TA标头:

看来新的TA版本仍然是SEC2!这意味着,不会有任何防回滚的机制。随后,我们可以进行验证,将手机的固件升级到较新版本,创建修改后的libteecl.so副本,该副本将会在/data/local/tmp查找TA,而不是/vendor/tee。这样一来,我们可以将旧的TA放在/data/local/tmp中,并让TEE执行旧版本的TA。
实际上,TEEGRIS OS允许加载旧版本的TA,该TA仍然包含可以利用的漏洞。这意味着,即使新版本TA正确修复了参数检查问题,攻击者也可以强制TEE加载旧版本TA,以回到易受攻击的状态。

 

0x03 总结

在这篇文章中,我们演示了发现并利用HDCP TA中两个漏洞的详细过程,同时还说明了TEE中防回滚机制的重要性。
欢迎大家继续关注下一篇文章,我们将详细分析如何提升特权,并获得对整个TEE内存的完整访问权限。

(完)