@TOC
只有一个re题,属于查文档现学现卖的类型,我发现国外题都喜欢这么考,不像平常做的硬在文件里搞个加密算法,当然这种题的难度还是很大的,比如这次在前期做题的时候就出现目标不明确的情况,不知道要干什么,ida都派不上用场,到后来hint出来的时候,才开始去搞tee,虽然说前面也关注到这个了,但方向还是走错了,浪费了大部分的时间

TEE

背景

随着移动设备的发展,移动设备的功能越来越强大,移动设备会存储用户的资产,处理支付等操作。目前移动端的系统运行环境叫做REE(Rich Execution Environment),在其中运行的系统叫做Rich OS(Operating System),包含Android、IOS和Linux,Rich OS的特点是功能强大,开放和扩展性好,可以给上层应用提供设备的所有功能,比如摄像头,触摸屏等,这样才有了我们现在看到的各种各样的应用。
但Rich OS存在很多的安全隐患,比如经常会发现bug,OS可以获得应用所有的数据,很难验证OS是否被篡改,这些对于用户资产有很大的安全隐患,那如何解决这些问题呢?
这时候就需要TEE帮忙了。
TEE的全称trusted execution environment,它是移动设备(智能手机、平板电脑、智能电视)CPU上的一块区域。这块区域的作用是给数据和代码的执行提供一个更安全的空间,并保证它们的机密性和完整性。

为啥说TEE更安全呢?

因为TEE提供了一个与REE隔离的环境保存用户的敏感信息,TEE可以直接获取REE的信息,而REE不能获取TEE的信息。当用户付款时,通过TEE提供的接口来进行验证,以保证支付信息不会被篡改,密码不会被劫持,指纹信息不会被盗用。

在TEE上运行的应用叫做可信应用(Trusted Application,简称TA),可信应用之间通过密码学技术保证它们之间是隔离开的,不会随意读取和操作其它可信应用的数据。另外,可信应用在执行前需要做完整性验证,保证应用没有被篡改。

可信应用可以和触摸屏,摄像头还有指纹传感器等外设进行直接交互,不需要通过REE提供的接口,所以更加保证了安全。
在这里插入图片描述

介绍

总的来说,TEE就是移动端的一个可信执行环境,用来抵御硬件攻击,通信速度快。
流程为 打开 TEE 环境 > 开启一个会话 > 发送命令 > 获取信息 > 结束会话 > 关闭 TEE 环境

加密方式

大多数的加密代码在TEE的内核中运行
实现方式是通过各种api来实现,包括跨越各种加密需求的加密功能:消息摘要、对称密码、消息身份验证代码 (MAC)、经过身份验证的加密、非对称操作(加密/解密或签名/验证)、密钥派生和随机数据生成。
内部 API 在tee_api_operations.c中实现,该数据库被编译成一个静态库
所有与加密相关的系统调用都在tee_svc_cryp.h中声明,并在tee_svc_cryp.c中实现
在这里插入图片描述

安全存储

默认情况下,OP-TEE 使用 /data/tee/ 作为 Linux 文件系统中的安全存储空间
在这里插入图片描述密钥的管理是安全存储的重要组成部分,有硬件唯一密钥(Hardware Unique Key, HUK),安全存储密钥(Secure Storage Key, SSK)、TA存储密钥(Trusted Application Storage Key, TSK)和文件加密密钥(File Encryption Key, FEK)。

1
HUK 硬件的唯一密钥 用于派生其他密钥
1
2
3
SSK 每个设备的密钥 
Chip ID 芯片ID
SSK = HMACSHA256 (HUK, Chip ID || “static string”)
1
2
3
TSK 每个受信任的应用程序密钥
TA_UUID TA的标识符(UUID)
TSK = HMACSHA256 (SSK, TA_UUID)
1
2
FEK 文件加密密钥
当一个新的TEE文件被创建时,密钥管理器将通过 PRNG(pesudo随机数生成器)为TEE文件生成一个新的 FEK,并将加密的 FEK 存储在 meta 文件中。FEK 用于对存储在 meta 文件中的TEE文件信息或块文件中的数据进行加密/解密。

在这里插入图片描述文档里还有两种加密
在这里插入图片描述

在这里插入图片描述

RWCTF2022 Trust or Not

分析文件

题目给了一个qemu命令 一大堆东西 和一个固件,先照搬命令运行一下,两个用户,看了一下没有什么东西,解压一下固件 发现/data/tee/2 这里有东西,这下就是查文档,学习TEE 看源码(放在文章最后了)

一大堆东西里有bl1.bin,bl2.bin,bl31.bin,bl32.bin,bl32_extra1.bin
-bios bl1.bin <– Specify the custom bios.;bl1, bl2 are just vendor firmware shit from arm, safe to ignore it lol;bl32_extra1 is optee kernel that run in tRuStzone.
所以我门真正要分析的就是bl32_extra.bin(optee的内核)

在固件里还有个东西我门需要用到
/lib/optee_armtz/f4e750bb-1437-4fbf-8785-8d3580c34994.ta
这个ta文件里含有ta_uuid
根据阅读源码可以找到

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Copyright (c) 2015, Linaro Limited
*/
#ifndef SIGNED_HDR_H
#define SIGNED_HDR_H

#include <inttypes.h>
#include <stdlib.h>
#include <tee_api_types.h>
#include <util.h>

enum shdr_img_type {
SHDR_TA = 0,
SHDR_BOOTSTRAP_TA = 1,
SHDR_ENCRYPTED_TA = 2,
};

#define SHDR_MAGIC 0x4f545348

/**
* struct shdr - signed header
* @magic: magic number must match SHDR_MAGIC
* @img_type: image type, values defined by enum shdr_img_type
* @img_size: image size in bytes
* @algo: algorithm, defined by public key algorithms TEE_ALG_*
* from TEE Internal API specification
* @hash_size: size of the signed hash
* @sig_size: size of the signature
* @hash: hash of an image
* @sig: signature of @hash
*/
struct shdr {
uint32_t magic;
uint32_t img_type;
uint32_t img_size;
uint32_t algo;
uint16_t hash_size;
uint16_t sig_size;
/*
* Commented out element used to visualize the layout dynamic part
* of the struct.
*
* hash is accessed through the macro SHDR_GET_HASH and
* signature is accessed through the macro SHDR_GET_SIG
*
* uint8_t hash[hash_size];
* uint8_t sig[sig_size];
*/
};

static inline size_t shdr_get_size(const struct shdr *shdr)
{
size_t s = sizeof(*shdr);

if (ADD_OVERFLOW(s, shdr->hash_size, &s) ||
ADD_OVERFLOW(s, shdr->sig_size, &s))
return 0;

return s;
}

#define SHDR_GET_SIZE(x) shdr_get_size((x))
#define SHDR_GET_HASH(x) (uint8_t *)(((struct shdr *)(x)) + 1)
#define SHDR_GET_SIG(x) (SHDR_GET_HASH(x) + (x)->hash_size)

/**
* struct shdr_bootstrap_ta - bootstrap TA subheader
* @uuid: UUID of the TA
* @ta_version: Version of the TA
*/
struct shdr_bootstrap_ta {
uint8_t uuid[sizeof(TEE_UUID)];
uint32_t ta_version;
};

在这里插入图片描述

这里可以发现uuid就是文件名

目标

这是整个加密的流程
在这里插入图片描述获取FEK,找到加密的文件,用FEK去解密文件

分析2文件

先明白什么是哈希树?

哈希树负责处理安全存储文件的数据加密和解密。哈希树被实现为二叉树,树中的每个节点(struct tee_fs_htree_node_image)保护其两个子节点和一个数据块。元数据(meta data)存储在一个头(struct tee_fs_htree_image)中,它也保护顶层节点。

简单来说哈希树包含两个文件
1 一些列的加密模块 就是那个2文件
2 一个加密的目录节点 dirf.db (这里没有)

2文件到底是什么文件?
More importantly, the only file with newer modified timestamp (very important CTF cheesing strategy) is data/tee/2. that’s where optee store encrypted blobs for its “secure object” storage api. it’s some hash tree shit. The way the hash tree is setup is there’s a bunch of encrypted blobs in data/tee and a encrypted directory node dirf.db as well. But we’re only give the blob 2, but with no directory node dirf.db.

The flag is stored in this encrypted blob. The problem is how do we decrypt it, or get optee to read it out.
文件格式如下:

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
/*
* File layout
* [demo with input:
* BLOCK_SIZE = 4096,
* node_size = 66,
* block_nodes = 4096/(66*2) = 31 ]
*
* phys block 0:
* tee_fs_htree_image vers 0 @ offs = 0
* tee_fs_htree_image vers 1 @ offs = sizeof(tee_fs_htree_image)
*
* phys block 1:
* tee_fs_htree_node_image 0 vers 0 @ offs = 0
* tee_fs_htree_node_image 0 vers 1 @ offs = node_size
* tee_fs_htree_node_image 1 vers 0 @ offs = node_size * 2
* tee_fs_htree_node_image 1 vers 1 @ offs = node_size * 3
* ...
* tee_fs_htree_node_image 30 vers 0 @ offs = node_size * 60
* tee_fs_htree_node_image 30 vers 1 @ offs = node_size * 61
*
* phys block 2:
* data block 0 vers 0
*
* phys block 3:
* data block 0 vers 1
*/

/*
* htree_image is the header of the file, there's two instances of it. One
* which is committed and the other is used when updating the file. Which
* is committed is indicated by the "counter" field, the one with the
* largest value is selected.
*
* htree_node_image is a node in the hash tree, each node has two instances
* which is committed is decided by the parent node .flag bit
* HTREE_NODE_COMMITTED_CHILD. Which version is the committed version of
* node 1 is determined by the by the lowest bit of the counter field in
* the header.
*
* Note that nodes start counting at 1 while blocks at 0, this means that
* block 0 is represented by node 1.
*/

那么这个2文件里就有着我们所需要的被加密数据和被加密的FEK
2文件大概分为3部分,填充就可以看出来,前两部分是一些列的哈希树模块,最后一部分是数据

解密FEK

TSK作为FEK的加解密密钥,想要获取TSK就要获取SSK,ta_uuid;
ta_uuid我门已经在ta文件里找到了,SSK还需要HUK,chipID 和“static string”

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

HUK如果在缺少硬件设备的支持下,默认为0

1
2
3
4
5
6
// otp_stubs.c
__weak TEE_Result tee_otp_get_hw_unique_key(struct tee_hw_unique_key *hwkey)
{
memset(&hwkey->data[0], 0, sizeof(hwkey->data));
return TEE_SUCCESS;
}

chip_id static_steing

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
static TEE_Result get_otp_die_id(uint8_t *buffer, size_t len)
{
static const char pattern[4] = { 'B', 'E', 'E', 'F' };
size_t i;

if (IS_ENABLED(CFG_CORE_HUK_SUBKEY_COMPAT_USE_OTP_DIE_ID))
return tee_otp_get_die_id(buffer, len);

for (i = 0; i < len; i++)
buffer[i] = pattern[i % 4];

return TEE_SUCCESS;
}

/*
* This does special treatment for RPMB and SSK key derivations to give
* the same result as when huk_subkey_derive() wasn't used.
*/
static TEE_Result huk_compat(void *ctx, enum huk_subkey_usage usage)
{
TEE_Result res = TEE_SUCCESS;
uint8_t chip_id[TEE_FS_KM_CHIP_ID_LENGTH] = { 0 };
static uint8_t ssk_str[] = "ONLY_FOR_tee_fs_ssk";

switch (usage) {
case HUK_SUBKEY_RPMB:
return TEE_SUCCESS;
case HUK_SUBKEY_SSK:
res = get_otp_die_id(chip_id, sizeof(chip_id));
if (res)
return res;
res = crypto_mac_update(ctx, chip_id, sizeof(chip_id));
if (res)
return res;
return crypto_mac_update(ctx, ssk_str, sizeof(ssk_str));
default:
return mac_usage(ctx, usage);
}

}

上面都是翻阅源码找到的,文件有可能被修改,还需要逆向bl32_extra.bin.那么现在所有参数都被找见了,解密模式是AES_ECB,就可以拿到FEK了。
这里需要注意的是求出来的ta_uuid是小端序,以及填充message到16个字节

解密数据

AES的GCM模式
在这里插入图片描述
大哥的代码

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import os
import struct
from hashlib import sha256
# from Crypto.Hash import HMAC, SHA256
from hmac import HMAC
from Crypto.Cipher import AES
import binascii


def AES_Encrypt_CBC(key, iv, data):
# vi = '0102030405060708'
pad = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
data = pad(data)
cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, iv.encode('utf8'))

encryptedbytes = cipher.encrypt(data.encode('utf8'))

# encodestrs = base64.b64encode(encryptedbytes)
# enctext = encodestrs.decode('utf8')
# return enctext
return encryptedbytes


def AES_Decrypt_CBC(key, iv, data):
# vi = '0102030405060708'
# data = data.encode('utf8')
# encodebytes = base64.decodebytes(data)

cipher = AES.new(key, AES.MODE_CBC, iv)
text_decrypted = cipher.decrypt(data)

unpad = lambda s: s[0:-s[-1]]
text_decrypted = unpad(text_decrypted)
# text_decrypted = text_decrypted.decode('utf8')
return text_decrypted


def AES_Decrypt_ECB(key, data):
cipher = AES.new(key, AES.MODE_ECB)
text_decrypted = cipher.decrypt(data)
return text_decrypted


def bytesToHexString(bs):
return ' '.join(['%02X' % b for b in bs])


fp = open("C:\\Users\\86178\\Desktop\\2", "rb")
data = fp.read()
fp.close()

print(".......... Tee_fs_htree_image ver0 ..............")
offset = 0
Tee_fs_htree_image_0_iv = data[offset + 0x00: offset + 0x10]
Tee_fs_htree_image_0_tag = data[offset + 0x10: offset + 0x20]
Tee_fs_htree_image_0_enc_fek = data[offset + 0x20: offset + 0x30]
Tee_fs_htree_image_0_imeta = data[offset + 0x30: offset + 0x40]
Tee_fs_htree_image_0_counter = struct.unpack("I", data[offset + 0x40: offset + 0x44])[0]
print(bytesToHexString(Tee_fs_htree_image_0_iv))
print(bytesToHexString(Tee_fs_htree_image_0_tag))
print(bytesToHexString(Tee_fs_htree_image_0_enc_fek))
print(bytesToHexString(Tee_fs_htree_image_0_imeta))
print(Tee_fs_htree_image_0_counter)

print(".......... Tee_fs_htree_image ver1 ..............")
offset = 0x44
Tee_fs_htree_image_1_iv = data[offset: offset + 0x10]
Tee_fs_htree_image_1_tag = data[offset + 0x10: offset + 0x20]
Tee_fs_htree_image_1_enc_fek = data[offset + 0x20: offset + 0x30]
Tee_fs_htree_image_1_imeta = data[offset + 0x30: offset + 0x40]
Tee_fs_htree_image_1_counter = struct.unpack("I", data[offset + 0x40: offset + 0x44])[0]
print(bytesToHexString(Tee_fs_htree_image_1_iv))
print(bytesToHexString(Tee_fs_htree_image_1_tag))
print(bytesToHexString(Tee_fs_htree_image_1_enc_fek))
print(bytesToHexString(Tee_fs_htree_image_1_imeta))
print(Tee_fs_htree_image_1_counter)

print(".......... Tee_fs_htree_node_image ver0 ..............")
offset = 0x1000
Tee_fs_htree_node_image_0_hash = data[offset + 0x00: offset + 0x20]
Tee_fs_htree_node_image_0_iv = data[offset + 0x20: offset + 0x30]
Tee_fs_htree_node_image_0_tag = data[offset + 0x30: offset + 0x40]
Tee_fs_htree_node_image_0_flags = struct.unpack("H", data[offset + 0x40: offset + 0x42])[0]
print(bytesToHexString(Tee_fs_htree_node_image_0_hash))
print(bytesToHexString(Tee_fs_htree_node_image_0_iv))
print(bytesToHexString(Tee_fs_htree_node_image_0_tag))
print(Tee_fs_htree_node_image_0_flags)

print(".......... Tee_fs_htree_node_image ver1 ..............")
offset = 0x1042
Tee_fs_htree_node_image_1_hash = data[offset + 0x00: offset + 0x20]
Tee_fs_htree_node_image_1_iv = data[offset + 0x20: offset + 0x30]
Tee_fs_htree_node_image_1_tag = data[offset + 0x30: offset + 0x40]
Tee_fs_htree_node_image_1_flags = struct.unpack("H", data[offset + 0x40: offset + 0x42])[0]
print(bytesToHexString(Tee_fs_htree_node_image_1_hash))
print(bytesToHexString(Tee_fs_htree_node_image_1_iv))
print(bytesToHexString(Tee_fs_htree_node_image_1_tag))
print(Tee_fs_htree_node_image_1_flags)

HUK = b'\x00' * 0x10
chip_id = b'BEEF' * 8
static_string = b'ONLY_FOR_tee_fs_ssk'
message = chip_id + static_string + b'\x00'
print(message)
# message = b'\x01\x00\x00\x00'
# message = static_string

# SSK = HMAC(HUK, chip_id, digestmod=sha256)
# SSK.update(static_string)
# SSK = SSK.digest()

SSK = HMAC(HUK, message, digestmod=sha256).digest()
print("SSK: " + bytesToHexString(SSK))

ta_uuid = b'\xbb\x50\xe7\xf4\x37\x14\xbf\x4f\x87\x85\x8d\x35\x80\xc3\x49\x94'
# ta_uuid = b"\xF4\xE7\x50\xBB\x14\x37\x4F\xBF\x87\x85\x8D\x35\x80\xC3\x49\x94"
# ta_uuid = b"\xb6\x89\xf2\xa7\x8a\xdf\x47\x7a\x9f\x99\x32\xe9\x0c\x0a\xd0\xa2"
# ta_uuid = b"\xb6\x89\xf2\xa7"[::-1] + b"\x8a\xdf"[::-1] + b"\x47\x7a"[::-1] + b"\x9f\x99\x32\xe9\x0c\x0a\xd0\xa2"
TSK = HMAC(SSK, ta_uuid, digestmod=sha256).digest()
print("TSK: " + bytesToHexString(TSK))

Enc_FEK = Tee_fs_htree_image_1_enc_fek
# Enc_FEK = b"\xf8\x83\x1a\xf3\x80\x0b\x72\xeb\xdb\xc7\x50\x27\xcb\xf2\xf4\xe7"
FEK = AES_Decrypt_ECB(TSK, Enc_FEK)
print("FEK: " + bytesToHexString(FEK))

# test()

"""
print ("........ decrypt meta data ...........")
cipher = AES.new(FEK, AES.MODE_GCM, nonce = Tee_fs_htree_image_1_iv)
cipher.update(Tee_fs_htree_node_image_1_hash)
cipher.update(Tee_fs_htree_image_1_counter.to_bytes(4, "little"))
cipher.update(Enc_FEK)
cipher.update(Tee_fs_htree_image_1_iv)
plaintext = cipher.decrypt_and_verify(Tee_fs_htree_image_1_imeta, Tee_fs_htree_image_1_tag)
print (plaintext)
"""

print("........ decrypt block data ...........")
block_0 = data[0x2000:0x3000]
# block_1 = data[0x3000:0x4000]

cipher = AES.new(FEK, AES.MODE_GCM, nonce=Tee_fs_htree_node_image_1_iv)

cipher.update(Enc_FEK)
cipher.update(Tee_fs_htree_node_image_1_iv)

plaintext = cipher.decrypt_and_verify(block_0, Tee_fs_htree_node_image_1_tag)
print(plaintext)

参考文档

简单介绍
简单介绍2
官方文档
源码
wp