OP-TEE,open source project Trusted Execution Environment (TEE), 开源可信执行环境。Rich Execution Environment (REE)相对应。REE中运行的是non-secure OS,例如安卓,Linux系统等。TEE中运行的是secure OS,需要Arm TrustZone技术的支持,依赖硬件设计...

OP-TEE

 SCTF2023的一道题是关于OP-TEE的,学习一波。

 OP-TEE,open source project Trusted Execution Environment (TEE), 开源可信执行环境。Rich Execution Environment (REE)相对应。REE中运行的是non-secure OS,例如安卓,Linux系统等。TEE中运行的是secure OS,需要Arm TrustZone技术的支持,依赖硬件设计。

 TrustZone是操作系统间实现了相互独立。可以在同一个CPU上运行两个OS,其中一个OS只负责安全存储或者计算的操作。另一个OS就是平时用的OS,比如安卓系统,对于这个OS来说,安全OS是不可见的,其没有权限获取到安全OS中的隐私数据。 两个OS之间需要交互,也就是CA/TA这种调用机制。

0x00 OP-TEE组件功能

 其执行流程如下:

image-20230713182350737

(1)CA(代理应用)发起函数调用。完成一次完整的CA请求时在linux userspace端(图的左上部分)需要执行的操作依次如下 :

  a. 调用TEEC_InitializeContext函数打开op-tee驱动文件,获取到操作句柄并存放到TEE_Context类型的变量中。

  b. 调用TEEC_OpenSession函数,通过获取到的TEE_Context类型的变量创建一个CA与TA之间通信的通道,如果TA image被存放在file system中,那么OP-TEE OS端还会将TA image从file system中加载到OP-TEE。

  c. 初始化TEEC_Operation类型的变量,并根据实际需要借助TEEC_PARAM_TYPES宏来设定TEEC_Operation类型变量中paramTypes成员的值,该值规定传递到OP-TEE中的最多4个变量的作用(作为输入还是输出)。

  d. 使用session,TA与CA端规定的command ID以及TEEC_Operation作为参数,调用TEEC_InvokeCommand函数来发起请求。 调用TEEC_InvokeCommand成功之后,剩下的事情就由OP-TEE和TA进行处理并将结果和相关的数据通过TEEC_Operation中的params返回给CA。

  e. 调用成功之后,如果不需要调用该TA则需要注销session和context,通过调用TEEC_CloseSession函数和TEEC_FinalizeContext函数来实现。

注:libteec库是OP-TEE提供给用户在linux userspace层面调用的接口实现,上述函数都是libteec库中的函数。

(2)tee_supplicant可以使OP-TEE能够通过它来访问REE端文件系统中的资源,例如加载存放在文件系统中的TA镜像到TEE中。

  • tee_supplicant在REE启动的时候会作为一个后台程序被自启动,而且常驻于系统中。
  • tee_supplicant的启动代码存放在build/init.d.optee文件中,在编译的时候init.d.optee文件将会被打包到根文件系统(rootfs)中。这些操作是在编译生成rootfs的时候所做的。
  • 当tee_supplicant接收到来自TA的请求并解析出对应的请求func ID之后,tee_supplicant将会根据func ID来执行具体的请求操作,例如:RPC_CMD_LOAD_TA指tee_supplicant将会到文件系统中将TA镜像的内容读取到共享内存中,RPC_CMD_FS指TA对常规的文件和目录进行打开、关闭、读取等操作。

(3)OP-TEE驱动主要作用是在REE与TEE端进行数据交互。

  • tee_supplicant和libteec调用之后都会进入到kernel space,然后kernel根据传递的参数找到OP-TEE驱动,并命中驱动的operation结构体中的具体处理函数来完成实际的操作。对于OP-TEE驱动,会触发SMC调用(非安全模式向安全模式的调用),并带参数进入到ARM cortex的monitor模式,执行normal world和secure world的切换,切换完成之后,会将驱动端带入的参数传递给OP-TEE中的thread进行进一步的处理。

  • Step1:OP-TEE驱动挂载。

    • linux kernel中加载驱动的函数有两个:subsys_initcall和module_init。

    • OP-TEE驱动的加载过程分为两步,第一步是初始化class和设备号(由subsys_initcall完成),第二步是probe过程(由module_init完成)。在OP-TEE驱动源代码中使用subsys_init定义的函数为tee_init,使用module_init定义的函数为optee_driver_init。

    • OP-TEE驱动会分别针对libteec和tee_supplicant建立不同的设备/dev/tee0和/dev/teepriv0。并且为两个设备建立消息队列,来存放normal world与secure world之间的请求,这样libteec和tee_supplicant使用OP-TEE驱动的时就能做到相对独立。secure world与OP-TEE驱动之间使用共享内存进行数据交互。

  • Step2:驱动挂载完成之后,CA程序通过调用libteec中的接口调用OP-TEE驱动,来切换到secure world中,从而调用对应的TA程序。其中,有4个比较重要的结构体:

    • tee_fops。当在userspace层面调用open/release/ioctl进行文件操作时,就会调用到tee_fops中的函数。

    • optee_ops。存放针对/dev/tee0设备的具体操作函数的指针。当用户调用libteec中的接口时(即操作/dev/tee0设备),首先会调用到tee_fops中的函数,tee_fops中的函数再会调用optee_ops中的函数,来完成对/dev/tee0设备的实际操作。

    • optee_supp_ops。与optee_ops类似,只不过存放的是针对/dev/teepriv0设备的函数的指针。

    • tee_shm_dma_buf_ops。略。

 libteec提供给上层使用的接口总共有10个,这10个接口通过系统调用最终会调用到驱动中,在接口libteec中调用Open函数的时候,在驱动中就会调用到file_operations结构体变量tee_fops中的Open成员。同理在libteec接口中调用ioctl函数,则在驱动中最终会调用tee_fops中的ioctl函数。tee_supplicant与secure world之间的交互则是分为3步:1. 驱动获取来自TEE侧的请求。2.tee_supplicant从驱动中获取TEE侧的请求。3.驱动返回请求操作结果给TEE侧。

(4)Monitor如何处理SMC请求。

  • libteec和tee_supplicant调用接口之后,最终会调用到OP-TEE驱动来触发对应的SMC操作。具体做法是:调用arm_smccc_smc之后,CPU中的cortex就会切换到monitor模式,其后会去获取MVBAR寄存器中存放的monitor模式的中断向量表地址,然后查找smc的处理函数。进入到处理函数之后,再根据从REE侧带入的参数判定是进行快速smc处理还是标准的smc处理。
  • fast smc一般会在驱动挂载过程中被调用。fast smc的特点就是在OP-TEE不会使用一个thread来对fast smc进行处理,而是在OP-TEE的kernel space层面直接对smc进行请求,并返回处理结果。

0x01 demo

 下面是一个optee的官方示例hello_world。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
hello_world
│ Android.mk
│ CMakeLists.txt
│ Makefile
│ tree

├─host
│ main.c
│ Makefile

└─ta
│ Android.mk
│ hello_world_ta.c
│ Makefile
│ sub.mk
│ user_ta_header_defines.h

└─include
hello_world_ta.h

 上面的结构中,ta负责可信应用TA部分,而host(代理应用CA)则是调用TA的提供的功能。

 以此为基础,写一个数据的加密保存和读取恢复的功能。

Step1:修改主目录下Cmakelist的文件名,修改host文件夹下Makefile的二进制文件名。

Step2:修改host文件夹下的main.c:

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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#include <err.h>
#include <stdio.h>
#include <string.h>

/* OP-TEE TEE client API (built by optee_client) */
#include <tee_client_api.h>

/* TA API: UUID and command IDs */
#include <read_write_ta.h>

/* TEE resources */
struct test_ctx {
TEEC_Context ctx;
TEEC_Session sess;
};

void prepare_tee_session(struct test_ctx *ctx)
{
TEEC_UUID uuid = TA_READ_WRITE_UUID;
uint32_t origin;
TEEC_Result res;

/* Initialize a context connecting us to the TEE */
res = TEEC_InitializeContext(NULL, &ctx->ctx);
if (res != TEEC_SUCCESS)
errx(1, "TEEC_InitializeContext failed with code 0x%x", res);

/* Open a session with the TA */
res = TEEC_OpenSession(&ctx->ctx, &ctx->sess, &uuid,
TEEC_LOGIN_PUBLIC, NULL, NULL, &origin);
if (res != TEEC_SUCCESS)
errx(1, "TEEC_Opensession failed with code 0x%x origin 0x%x",
res, origin);
}

void terminate_tee_session(struct test_ctx *ctx)
{
TEEC_CloseSession(&ctx->sess);
TEEC_FinalizeContext(&ctx->ctx);
}

TEEC_Result read_secure_object(struct test_ctx *ctx, char *id,
char *data, size_t data_len)
{
TEEC_Operation op;
uint32_t origin;
TEEC_Result res;
size_t id_len = strlen(id);

memset(&op, 0, sizeof(op));
op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT,
TEEC_MEMREF_TEMP_OUTPUT,
TEEC_NONE, TEEC_NONE);

op.params[0].tmpref.buffer = id;
op.params[0].tmpref.size = id_len;

op.params[1].tmpref.buffer = data;
op.params[1].tmpref.size = data_len;

res = TEEC_InvokeCommand(&ctx->sess,
TA_READ_WRITE_CMD_READ_RAW,
&op, &origin);
switch (res) {
case TEEC_SUCCESS:
case TEEC_ERROR_SHORT_BUFFER:
case TEEC_ERROR_ITEM_NOT_FOUND:
break;
default:
printf("Command READ_RAW failed: 0x%x / %u\n", res, origin);
}

return res;
}

TEEC_Result write_secure_object(struct test_ctx *ctx, char *id, char *data, size_t data_len)
{
TEEC_Operation op;
uint32_t origin;
TEEC_Result res;
size_t id_len = strlen(id);

memset(&op, 0, sizeof(op));
op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT,
TEEC_MEMREF_TEMP_INPUT,
TEEC_NONE, TEEC_NONE);

op.params[0].tmpref.buffer = id;
op.params[0].tmpref.size = id_len;

op.params[1].tmpref.buffer = data;
op.params[1].tmpref.size = data_len;

res = TEEC_InvokeCommand(&ctx->sess,
TA_READ_WRITE_CMD_WRITE_RAW,
&op, &origin);
if (res != TEEC_SUCCESS)
printf("Command WRITE_RAW failed: 0x%x / %u\n", res, origin);

switch (res) {
case TEEC_SUCCESS:
break;
default:
printf("Command WRITE_RAW failed: 0x%x / %u\n", res, origin);
}

return res;
}

TEEC_Result delete_secure_object(struct test_ctx *ctx, char *id)
{
TEEC_Operation op;
uint32_t origin;
TEEC_Result res;
size_t id_len = strlen(id);

memset(&op, 0, sizeof(op));
op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT,
TEEC_NONE, TEEC_NONE, TEEC_NONE);

op.params[0].tmpref.buffer = id;
op.params[0].tmpref.size = id_len;

res = TEEC_InvokeCommand(&ctx->sess,
TA_READ_WRITE_CMD_DELETE,
&op, &origin);

switch (res) {
case TEEC_SUCCESS:
case TEEC_ERROR_ITEM_NOT_FOUND:
break;
default:
printf("Command DELETE failed: 0x%x / %u\n", res, origin);
}

return res;
}

#define TEST_OBJECT_SIZE 7000

int main(int argc, char *argv[])
{
struct test_ctx ctx;
char obj1_id[50]; /* object的字符串标识 */
char obj2_id[50]; /* object的字符串标识 */
char obj1_data[TEST_OBJECT_SIZE];
char read_data[TEST_OBJECT_SIZE];
TEEC_Result res;

if(argc == 3){
strcpy(obj1_id, argv[1]); /* 参数1为第1个object */
strcpy(obj2_id, argv[2]); /* 参数2为第2个object */
}
else{
strcpy(obj1_id, "#object1");
strcpy(obj2_id, "#object2");
}

printf("Prepare session with the TA\n");
prepare_tee_session(&ctx);

/*
* 创建/读取/删除object
*/
printf("\nTest on object \"%s\"\n", obj1_id);

printf("- Create and load object in the TA secure storage\n");

memset(obj1_data, 0xA1, sizeof(obj1_data));

// 向TEE写入obj1_data
res = write_secure_object(&ctx, obj1_id, obj1_data, sizeof(obj1_data));
if (res != TEEC_SUCCESS)
errx(1, "Failed to create an object in the secure storage");

printf("- Read back the object\n");
// 从TEE读取obj1_data
res = read_secure_object(&ctx, obj1_id, read_data, sizeof(read_data));
if (res != TEEC_SUCCESS)
errx(1, "Failed to read an object from the secure storage");
// 比较写入与读取的是否相同
if (memcmp(obj1_data, read_data, sizeof(obj1_data)))
errx(1, "Unexpected content found in secure storage");

printf("- Delete the object\n");

// 删除object1
res = delete_secure_object(&ctx, obj1_id);
if (res != TEEC_SUCCESS)
errx(1, "Failed to delete the object: 0x%x", res);

/*
* 非易失性存储:如果没有找到则创建object2,如果找到则删除它
*/
printf("\nTest on object \"%s\"\n", obj2_id);

res = read_secure_object(&ctx, obj2_id, read_data, sizeof(read_data));
if (res != TEEC_SUCCESS && res != TEEC_ERROR_ITEM_NOT_FOUND)
errx(1, "Unexpected status when reading an object : 0x%x", res);

if (res == TEEC_ERROR_ITEM_NOT_FOUND) {
char data[] = "This is data stored in the secure storage.\n";

printf("- Object not found in TA secure storage, create it.\n");

res = write_secure_object(&ctx, obj2_id, data, sizeof(data));
if (res != TEEC_SUCCESS)
errx(1, "Failed to create/load an object");

} else if (res == TEEC_SUCCESS) {
printf("- Object found in TA secure storage, delete it.\n");

res = delete_secure_object(&ctx, obj2_id);
if (res != TEEC_SUCCESS)
errx(1, "Failed to delete an object");
}

printf("\nWe're done, close and release TEE resources\n");
terminate_tee_session(&ctx);
return 0;
}

Step3:清空ta文件夹下Android.mk的内容,修改sub.mk中的文件名,修改Makefile中的uuid值(任意更改都可)。

Step4:修改user-ta-header-defines.h为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef USER_TA_HEADER_DEFINES_H
#define USER_TA_HEADER_DEFINES_H

#include <hello_world_change_ta.h>

#define TA_UUID TA_READ_WRITE_UUID

#define TA_FLAGS (TA_FLAG_EXEC_DDR | TA_FLAG_SINGLE_INSTANCE)
#define TA_STACK_SIZE (2 * 1024)
#define TA_DATA_SIZE (32 * 1024)

#define TA_CURRENT_TA_EXT_PROPERTIES \
{ "gp.ta.description", USER_TA_PROP_TYPE_STRING, \
"Example of TA writing/reading data from its secure storage" }, \
{ "gp.ta.version", USER_TA_PROP_TYPE_U32, &(const uint32_t){ 0x0010 } }

#endif /*USER_TA_HEADER_DEFINES_H*/

Step5:修改include/hello_world_change_ta.h,hello_world_change_ta.c的内容,具体看链接

0x02 运行hello_world以及0x01中的demo

 没看出来。不知道咋运行啊?

To be continue..

留言

2023-07-13

© 2024 wd-z711

⬆︎TOP