看雪议题分享 | 深入 Android 可信应用漏洞挖掘
2023-10-27 10:16:0 Author: paper.seebug.org(查看原文) 阅读量:38 收藏

作者:启明星辰ADLab
原文链接:https://mp.weixin.qq.com/s/ZheWK5WB5Qa7I9deY9uH7A

写在前面

启明星辰ADLab高级安全研究员、移动安全专家李中权在10月23日第七届看雪安全峰会上完成了关于深入Android 可信应用漏洞挖掘的议题演讲:https://meet.kanxue.com/kxmeet-11.htm

在过去的几年中,可信执行环境(TEE,Trusted Execution Environment)在Android生态系统(智能手机、智能汽车、智能电视等)中实现了普及。TEE 运行独立、隔离的 TrustZone 操作系统,与 Android 并行,保证在Android系统沦陷的情况下用户的核心敏感数据或者手机的核心安全策略仍然安全。

与Android系统中预置的系统级App一样,TEE系统中也存在必要的应用(Trusted Application, 即TA)以承担数据加密等安全策略的实现。

2022年下半年演讲者对部分主流厂商的TA实现做了安全研究,目前已有60处漏洞被确认,包括但不限于指纹图片提取、指纹锁屏绕过、支付密钥提取、提取用户的明文密码等严重漏洞。

在本次议题中,重点介绍了主流厂商的TEE环境中的TA实现以及常见的攻击面并分享了一些针对TA做安全研究的技巧与方法,比如如何尽可能快速的拥有一台具备Root权限的手机用于研究与测试。同时还介绍了如何实现对TA进行模拟以及使用到的Fuzzing技术和部分调优策略。

1. TEE的介绍和基本概念

1. 什么是TEE?

全称为“可信执行环境”(Trusted Execution Environment),是位于主处理器中的一个独立的安全区域。

2. TEE的角色?

TEE 为运行在其中的应用程序提供了一个隔离的环境以保护应用程序和数据免受其他软件的攻击。TEE 常用于处理敏感的数据,如密码、密钥、生物识别数据等。

即使设备的主操作系统被攻击,TEE 中保存的敏感数据和安全策略也不会受到影响。

3. 什么是Normal World?

指设备的主操作系统环境,包括运行的应用程序和操作系统本身。这个环境通常包含用户的各种应用程序和服务,但是它并不被认为是安全的,因为它可能受到各种各样的攻击。

在本次演讲中,Normal World 和 REE 都指的是 Android 操作系统。

4. 什么是CA?

在Android系统中,那些会通过特定的API与TEE通信的应用程序,就被称为Client Application,简称为CA。

5. 通用的TEE架构图

图片

不同的TEE可能存在不同的架构实现。比如,Kinibi TEE在S-EL0级别还存在可信驱动(Trusted Driver,简称为TD)的概念。本次演讲主要聚焦于S-EL0级别的漏洞挖掘。

6. 可信应用TA承担的作用

TEE用于敏感数据的安全存储、安全通信、可信UI的绘制、安全的加密策略等,而这些策略的承载者,就是可信应用(Trusted Application,简称为TA)。

图片

2、主流TA的架构实现和逆向分析

在对TA做安全研究之前,首先需要研究CA如何与TA进行通信。

图片

在介绍TA的生命流程之前,需要了解Global Platform TEE Client API 规范。

图片

简单理解:GP 规范是一套标准化的开发流程,只要开发者按照GP规范,便可以快速地实现自己的TA和CA。

目前市面上也存在一些不遵循GP规范的TA,但从逆向结果来看,在它们自己实现的开发规范中也能发现GP规范的影子,或者只是对GP规范做了二次封装。

下图是GP规范当中的TA的生命周期:

图片

主要关注前面的3个函数:

  • TA_CreateEntryPoint

  • TA_OpenSessionEntryPoint

  • TA_InvokeCommandEntryPoint

TA_CreateEntryPoint和TA_OpenSessionEntryPoint,为TA初始化相关的函数。其中,TA_OpenSessionEntryPoint具备接受外部输入数据的能力,故该函数也可能存在漏洞。

TA_InvokeCommandEntryPoint是研究的核心目标,因为开发者会在这个函数中对外部传入的数据做处理,并根据command_id做请求的分发。

下图为GP规范的一些基本定义,可以清晰地看到TA依靠UUID来做TA的识别与区分。

图片

在一些高通芯片的手机上,或者厂商自研TEE的手机上, TA可能会是下图中的英文短字符的形式,而非UUID。

图片

只要在Android的文件系统上做一下简单的逆向和分析,可发现一个UUID和英文短字符的映射表,所以它们的本质仍然是UUID。

图片

在去年的研究过程中,我们的研究目标是几个主流厂商的TA实现,而每个厂商的TA的文件格式、接受外部数据的方式都有差别,为方便归类以模拟和Fuzzing,我们根据TA处理外部输入数据的形式,将TA分为两类。

图片

同时,不同的TEE实现有不同的调用流程,出于厂商隐私保护的需要,我们省略了具体的Case,这里分享一些通用分析思路。

图片

本质还是使用lsof命令去查看Android系统上这些TA对应的CA加载了哪些依赖库(.so文件),很多时候我们根据so的文件名,即可快速定位用于处理TEE的API的so文件。

下图为基于GP规范的TA的方法调用。

图片

它只是简单的加载了/vendor/lib64/下的libTEECommon.so,并载入了一些基本的TEE API函数,之后再按照TA的生命周期执行请求给指定TA发送数据。

下图为高通TA的方法调用。

图片

可以发现,它的调用方式跟GP格式的TA的调用方式完全不同。由于高通的CA的代码在AOSP中是完全开源的,只需对源码进行简单的代码分析,即可知道在高通芯片上如何给高通的TA发送请求。

此外,在分析CA如何给TA发送请求的时候,我们需要额外关注:TA的实例状态可能会影响我们测试TA的方式。

图片

1. TA的提取和逆向分析

图片

图片

从去年研究的结果来看,目前只有高通和Kinibi的TA采用了自定义的格式(即上图的MDT和MCLF格式)。针对这两种格式,Github上已经有非常成熟的解决方案了。而对于其他厂商的TEETA,目前都基于OP-TEE格式。对于这类型的TA,只需要很简单地移除掉它们的TA Header就可以拖进IDA Pro做安全分析了。

下图是两款TA的二进制内容,它们都包含.ELF文件头,所以只需要很简单的移除掉.ELF文件头前面的数据,便可使用IDA Pro正常解析。

图片

如果我们想要对TA做模拟,比如使用Qemu或者Unicorn对TEEOS做了全系统模拟,并在尝试使用这个模拟的系统加载TA,不能简单的移除Header。因为TA的Header中包含了很多必要信息,比如UUID、Section等。

3、如何对TA做安全研究

对TA做安全研究存在下面的3个难点:

图片

所以,目前对TA做安全研究主要采用下面的两种策略:

图片

在真实的Android设备上针对TA做安全研究,又有下面的3种策略:

图片

1.本地System / Root提权从而构建我们的安全研究设备

图片

文章链接:Android App半自动化静态漏洞挖掘技术分析

在我们漏洞模板足够丰富的情况下,这一套半自动化静态漏洞扫描工具可以发现几乎所有的System / Root提权的攻击点,后续只需做简单的人工二次确认就能发现可能的提权漏洞了。

此外,我们将漏洞划分为两类:

图片

此时挖掘本地System或Root提权的目的,是构建一个自己的安全研究设备,而非数据取证或者单纯的攻击。因此,我们可以额外关注那些仅可用于安全研究的漏洞。

图片

最典型的例子就是通知栏当中的PendingIntent劫持漏洞。

如果一个恶意应用想要劫持通知栏的PendingIntent漏洞,需要用户预先在设置当中手动的为恶意应用授予Notification Manager权限。而这一次的授予操作,也被称为强交互。即使最终能够做到System级别的Intent转发,厂商也只可能为这种漏洞授予低危或者中危的漏洞等级。

但这种漏洞,在我们安全研究员的手中,就能变成构建研究设备的大杀器。

2.解锁Bootloader从而构建我们的安全研究设备

图片

在去年的研究内容中,我们采用的是解锁Bootloader获得持久性Root的方法,来对主流厂商的TA做安全研究。在去年还未对Bootloader做安全研究之前,我们认为这部分的工作可能会花费大量的时间。因为当时的研究目标是几个主流厂商的手机,也就意味着需要都解锁这些厂商的Bootloader。但当我们真正做了之后,发现这里的难度其实并没有想象的那么高。最终,我们解锁了5款手机的Bootloader,其中3款用到了N Day,2款用到了0 Day。

图片

由于不同型号的手机获得补丁的时间不一样,有的1个月更新,有的3个月、半年,甚至有的一个大版本才更新。所以只要耐心去找,大概率是能够找到合适的攻击目标的。

但如果我们搜寻了所有的机型,确实没有一个可用的N Day,可以考虑0 Day + N Day的形式。

图片

图片

4、TA的模拟

谈到TA的模拟,Unicorn和Qemu就是绕不开的话题。我们选择了Unicorn,因为它的颗粒度更细。

图片

同时,在Qiling Framework和自己基于Unicorn做二次开发的考量中,选择了后者。不过研究员想要针对指定的TA或者TEEOS做安全研究,建议直接选择Qiling Framework,因为确实可以省很多工作量。

图片

下图是我们去年构建的模拟和Fuzzing系统的功能图:

图片

需要额外介绍到的是Hook模块当中的Crash Patch Handler。

在最初的架构设计中,并没有设计这个模块,但当Fuzzing开始的时候,这个模块是必不可少的。因为如果一个TA的漏洞太多,那么前序代码造成的Crash会导致后续代码无法得到有效的测试。

所以我们构建了一个Crash Patch Handler模块, 手动给TA打补丁。例如:当TA执行memcpy时会发生越界或者溢出,此时,Crash Patch Handler模块会自动调整memcpy的size对应的寄存器的值,从而防止crash

5、TA的Fuzzing

对于TA的Fuzzing,我们采用了AFL-Unicorn。不过AFL-Unicorn存在下面的几个已知的问题:

图片

现在AFL-Unicorn的git库里,能发现它使用Python实现了一套类似ASAN的校验逻辑:

图片

它的校验逻辑跟ASAN一样。进行malloc堆分配时,比如malloc(8),unicorn会在分配的堆块的前方跟后方各插入一个red zone。一旦TA触碰到red zone,就被认为发生了越界访问。

但这部分的代码,建议大家只是参考,而不要直接使用。因为它在某些极端的情况下可能出现问题。

对于malloc(8)的操作,Unicorn会先对8的堆块大小做页对齐,变为0x1000,两个red zone各占0x1000。即,malloc(8)最终会导致0x3000的内存分配。

之所以会出现这种情况,是因为这套malloc策略本质使用的是实时Unicorn mmap的方式。而Unicorn mmap的最小单位就必须是0x1000或者0x1000的整数倍。

解决方案也很简单,我们只需在初始化Unicorn的时候,提前把堆的内存区域mmap出来,如0x10000000,之后malloc时,从这个0x10000000堆区域中划分出指定大小的堆块便可以了。

既然实现了malloc,肯定也会有free,不过当时的free存在一点小BUG。当free一个对象失败后,这个free函数会简单地返回 false。但这里实际应该是double-free或者invalid-free漏洞。这个Patch现在已经合进去了,可以直接使用。

图片

下图是一个简易版整数溢出检查,核心思想还是针对指令集做判断:

图片

这里给的示例是ARM64而非ARM,因为ARM TA的指令需要同时关注ARM和Thumb指令,代码量翻倍,内容过多便没有展示。

图片

1.AFL-Unicorn核心问题(无状态Fuzzing)的解决

之所以AFL-Unicorn存在无状态Fuzzing,是因为它在做Fuzzing之前,会保存Fuzzing开始前的内存状态以及所有寄存器的值。当它Fuzzing完cmd 1后,便会还原内存状态以及所有寄存器的值为初始状态,接着再Fuzzingcmd 2。

那如果cmd 2的漏洞触发依赖于cmd 1的执行结果,此时就会发生漏报。因为cmd 1的执行结果已经被清空了。

在遇到这个问题后,我们先对部分TA做了调研和分析。发现很多TA支持的cmd并不多,所以也就快速的推出了第一个版本的解决方案:我们只需提取出TA中的所有cmd_id,并对cmd分支做排列组合,比如1324和2341,当一条完整的cmd(如2341)执行完后,才还原AFL-Unicorn的内存状态便可解决这个问题。

这套方案非常的暴力,但确实是有效的。不过现在并不建议大家继续使用,因为它存在下面的3个问题:

  • 会显著降低Fuzzing的效率

  • 不容易对Fuzzing结果去重

  • 发生Crash后,有时会很难定位根本原因

所以,后续采用了第二套方案:预先执行并保存上下文,即基于快照。去年做这套方案的时,并无AI介入,会按照一定标准人工选择快照。现在建议大家采用第二种方法,同时加入AI做快照的选择判断,效果会更好。

下图所示的“后续漏洞的触发依赖于前序请求的执行”漏洞是一个很好的例子。这个漏洞是手动挖掘到的,因为在最初使用AFL-Unicorn Fuzzing TA的时候并没有发现这个问题,而后续在对Fuzzing策略做微调之后已经可以发现这个漏洞了。

这个漏洞的根本成因是,攻击者需要先调用cmd 13,把一个攻击者完全可控的keyblock结构体放入RPMB中,之后再调用cmd 12,此时TA会主动从RPMB当中读取用户刚刚保存的keyblock结构体,并根据keyblock的成员变量的值判断循环拷贝的下一次步长。因为此时TA并没有做好下一次步长的值的安全校验,就导致了循环拷贝导致的越界漏洞。

图片

图片

6、TA的攻击面分析

图片

1.类型混淆

图片

遵循GP规范的TA使用TEE_Param结构体封装请求数据,而TEE_Param结构体支持两种参数,即Value和Buffer。这也意味着,开发者在TA中使用外部传入的数据时,一定需要预先对外部输入的参数类型做类型校验。一旦遗漏,把Value当成Buffer,把Buffer当成Value,那就会造成类型混淆漏洞。

图片

造成这类漏洞的根本原因是:TEE授予了开发者过大的权限,把TA接受和处理外部参数类型的危险能力授予了开发者。

当然,站在OP-TEE或者制定一套通用的、便于开发的规范的角度来看,这套策略没有任何问题。因为它确实能帮助开发者快速地开发TA。可是,对于很多厂商自研或者私有的TEE来说,一旦开发者没有很好地理解参数类型或者单纯的疏忽,就会造成很严重的安全问题。

下图所示是一个非常典型的类型混淆导致的任意位置读取漏洞。

图片

首先,我们关注最下面的两个箭头,能发现开发者非常有安全意识地调用了TEE_CheckMemoryAccess函数用于判断外部的输入是否是buffer,只有校验通过才会执行后面的业务代码。

但回到前面的两个箭头,能发现开发者在还没有校验参数类型的情况下就已经使用了外部的输入数据,从而导致类型混淆漏洞。

这个漏洞,很可能是二次开发造成的问题。第一次的开发者有安全意识,但第二次的开发者没有或者单纯的疏忽了。

接着,就是如何防御TA的类型混淆漏洞,并介绍一套我们认为的最有效的解决方案:

图片

2.内存破坏

图片

这是一个教科书级别的栈溢出漏洞,该TA并不存在ASLR和Stack Cookie,所以很简单的构建ROP就能实现任意代码执行。

但后续在TA的内存当中漫游的时候,我们发现了一个特殊的攻击面,即攻击者可以尝试攻击指纹TA的匹配度从而绕过指纹校验并解锁手机。

图片

指纹校验的逻辑是:当用户的手指按压手机的指纹传感器时,指纹传感器本质会把指纹的基本信息发送给指纹TA,指纹TA会把外部输入和本地保存的指纹模板做相似度的匹配。一旦匹配度达到某个阈值,就会认为是一个合法的指纹从而解锁手机。

可在上面的第二张图中,这款指纹TA的校验阈值是37,即外部的指纹跟指纹模板有37%的匹配度就会被认为是一个合法的指纹从而解锁手机。看起来好像这个厂商的指纹很不安全容易误报?但实际上这是能接受的。因为当时这款手机的指纹是侧边指纹,校验指纹的位置是电源键,本身就没有太多的可触碰范围。所以37%是能够接受的。

站在安全研究员的角度,思考一下这个问题,我们就会发现指纹TA或者类似TA的特殊攻击面。如果我们现在利用类型混淆或者其他漏洞实现了任意位置写0、任意位置写1的效果,很多时候很难构建ROP,甚至都没有办法完成漏洞利用。但我们可以尝试利用这些漏洞攻击这些状态变量从而绕过逻辑校验以解锁手机。当然前提是这些状态变量的内存页是可写状态。

3.TA上的信息泄露

图片

从去年的研究内容来看,目前并没有太多厂商为TA开启了Log,基本都加密或者关闭了Log从而提高攻击者的攻击难度。但实际上很多厂商的开发者,为了方便自己开发和测试,在相关策略上留了后门。由于厂商漏洞披露策略的限制,这里无法展示具体的案例。建议检查proc, vendor, system, products, oem, odm这些系统目录下的二进制文件或者shell脚本,有时存在可以直接使用的彩蛋。

4.逻辑漏洞:从Android手机中提取用户的明文密码

Android中的密钥库,被称为Keystore。https://developer.android.com/training/articles/keystore

图片

图片

Android系统为上层的App提供了一套安全加密的策略。使用Keystore加密的密钥材料,会被Android保存在TEE中,提升密钥材料的防护等级从而提升整体的安全性。

下图为简单的使用示例。

图片

在这种加密策略下,一旦攻击者提权到这个应用的权限,便能以这个应用的身份请求Keystore解密这个应用的所有加密数据。

所以,Keystore还存在防护等级更高的加密API,也就是基于用户身份认证的加密。

图片

图片

在这种模式下,如果想要解密密文,需要先校验指纹或者Pin码,通过后才会执行解密操作。而指纹校验和Pin码,本身就是TEE的功能,能从源头上保证校验逻辑的安全。

图片

所以,只要应用使用了基于用户身份认证的加密策略,那么在REE沦陷的情况下用户的数据仍然是安全的。即使攻击者获得了Android手机的Root,也无法解密被Keystore保护的密文。

这是一个很好的代码实例:https://github.com/stevenocean/UnpasswdDecrypt

图片

图片

下面,我们再介绍一下Keystore的实际应用场景,即手机中的自动填充服务:https://developer.android.com/guide/topics/text/autofill-services

当用户登录某个App时,系统可能会弹窗,只要用户校验完指纹,那么系统会自动把用户之前保存的账号密码填充进App,而无需手动输入。

每个OEM厂商都可以实现自己的自动填充服务策略。

在本次的演讲中,使用了Pixel 5a做演示。在Pixel 5a上,自动填充服务的实现者是GMS。

图片

图片

故,最简单的攻击代码如下:

图片

攻击者只要获得了System权限,便可提取本应由TEE保护的用户数据,对于用户密码的防护等级明显不足。

不过这个漏洞,对于GMS来说,可修可不修。因为GMS只是一个三方应用,没有表明会使用最高的防护等级来保存用户的明文密码。

但如果厂商或者某些安全系数特别高的App,在自己的安全白皮书中明确提到了会使用手机的最高防护等级来保护用户的密码,那这个漏洞就是必修的。

5.跳板

图片

在研究TA时,经常会发现虽然Android侧的CA或者普通App可以调用TEE API,但是并没有办法跟TA实际通信。因为TEE API会让CA跟Android侧的TEE Driver通信,而TEE Driver一般都具备System权限,这也意味着我们并没有办法直接调用TA。

部分TA从设计上讲,并没有任何权限限制,比如IFAA。IFAA可以简单理解为一套支付相关的规范,它也具备一套通用的组件,各个OEM厂商可以在这个通用组件上自行实现自己的支付逻辑:

图片

下图代码中,厂商实现了自己的IFAA策略,但它的实现存在漏洞,导致攻击者可以以IFAA TA的身份读取或者写入任意文件。这个攻击,普通App就能完成,它的漏洞等级就是严重。

图片

6.TEE文件操作

图片

TEE的数据存储,分为RPMB和SFS。RPMB本身的存储大小有限,所以对于大数据,比如指纹模板,TEE会对数据做AES加密后保存在Android的文件系统上。这套机制,就被称为SFS。

对应的攻击点也非常明显:

  • 这个文件是保存在Android的文件系统上的,所以虽然Android侧的攻击者并不知道这个文件的解密key,但Android侧的攻击者可以直接把这个文件删除、篡改内容或者是创建一个同名的空文件。如果一个TA的安全策略的生效标准是判断文件系统上是否存在某个文件,那么这套安全机制就能被直接绕过。对于被篡改文件的读取和解析也可能会产生内存破坏漏洞。

  • 默认的OP-TEE的API并不存在路径穿越漏洞。但部分厂商自己实现的API实现了自己的文件交互API,故导致路径穿越漏洞的重现。

  • 多实例TA对于同一个文件的交互可能存在条件竞争。

演讲的PDF放于

  1. https://pan.baidu.com/s/1xHMgeRqBFiAnxY9DI8c-KA(提取码: a1b5)

  2. https://drive.google.com/file/d/1dj6nsPPaHYJpkNAjaxN56SeJz-F9UbRO/view?usp=sharing


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/3060/


文章来源: https://paper.seebug.org/3060/
如有侵权请联系:admin#unsafe.sh